Webview
Webview是用于展示网页的控件,相当于一个浏览器
WebView 调用 loadUrl 后,会首先根据传入的URL获取响应,然后再将响应显示到页面上,这就是 WebView 的原理。那么我们可以在获取响应过程中重新改变请求URL或者直接将响应替换。
WebView 的方法:
Void goBack();//后退
Void goForward();//前进
Boolean zoomln();//放大网页
Boolean zoomOut();//缩小网页
可实现的功能:
1. WebView加载网页的实现方法:
Void loadUrl(Stirng url);//加载指定url的网页,即给出网页的网址进行加载。
LoadData(String data,String mimeType,String encoding);//用于加载和显示html代码,可能会产生乱码,第一个参数是需要加载的html代码,第二个参数是指定html的MIME类型html代码可以指定为text/html,第三个参数是html代码所使用的字符集,utf-8
loadDataWithBaseURl(String baseurl,Stirng data,String mimetype,String encoding,String historyurl);//该方法是上面方法的加强不会产生乱码
加载网页代码
data指定需要加载的HTML代码。
mimeType指定HTML代码的MIME类型,对于HTML代码可指定为text/html。
encoding指定HTML代码编码所用的字符集。比如指定为GBK
webview.loadDataWithBaseURl(null,“<html><body><h1>欢迎<h1></body></html>“,”text/html“,”utf-8“,null);
String html="<img src=\"http://img.wanyx.com/softImg/soft/1650_s.jpg\"><a href=\"http://img.wanyx.com/softImg/soft/1650_s.jpg\"><br></img>\n风景展示</a>";
showonwebview.loadDataWithBaseURL(null, html, "text/html", "utf-8", null);
2. Webview上显示的javascripe或显示并调用Android的方法实现
WebView showview=(WebView)findViewById(R.id.showview);
WebSettings set=showview.getSettings();//webview的选项控制工具类
set.setJavaScriptEnabled(true);//必须设置,只有设置了这个webview才可以操作javascripe,同样javascripe也才可以操作java方法(若希望javascripe操作Android方法必须通过addjavascripeinterface(Objectm,String))
showview.loadUrl("http://m.kuaidi100.com/index_all.html?postid=19371949042");
webview 中的javaScript调用Android方法实现
(1)拿到WebView控件
(2)通过WebView对象拿到其对应的WebSeting
(3)调用webview关联的WebSeting的setJavaScriptEnabled()方法设置Android可以和JavaScripe进行交互
(2)调用webview的addJavaScripeInterface(Object object,String name);
(3)在javaScripe中通过name对象调用Android方法
代码
public classWebViewTestTwo extends Activity {
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview);
WebViewshowview=(WebView) findViewById(R.id.showview);
WebSettingsset=showview.getSettings();
set.setJavaScriptEnabled(true);
showview.addJavascriptInterface(new MyObject(WebViewTestTwo.this),"ObjectTest");//ObjectTest是指代MyObject对象,在javaScripe中通过ObjectTest来操作
}
class MyObject{
private Contextcontext;
public MyObject(Contextcontext){
this.context=context;
}
//那个方法可以被javascripe调用就要添加上下面这一行
@JavascriptInterface
public void show(String name){
Toast.makeText(context, name+"javascripe操作Android方法成功", Toast.LENGTH_LONG).show();
}}}
html代码
<html>
<body>
<inpute type="button"value="showToast"οnclick="ObjectTest.show("我");">//onclick中的代码就是一句java语句,只不过对象名称是java中规定好的对象名称
</inpute>
</body>
</html>
参数设置参考:http://blog.youkuaiyun.com/anqixing/article/details/42488057
WebView与js的交互:https://juejin.im/post/5924dbf58d6d810058fdde43?utm_source=wechat
WebView 拦截,对webview页面加载管理、如url重定向WebViewClient使用:
WebView 调用 loadUrl 后,会首先根据传入的URL获取响应,然后再将响应显示到页面上,这就是 WebView 的原理。那么我们可以在获取响应过程中重新改变请求URL或者直接将响应替换, 而具体的替换在 WebViewClient 的shouldInterceptRequest方法中,该方法用于根据请求去获取响应,重写了该方法并返回了响应,那么 WebView 就会使用你的响应数据。其中 WebResourceRequet 封装了请求,WebResourceResponse 封装了响应,该方法是在非UI线程中,WebView 中调用的每个请求都会经过shouldInterceptRequest拦截器
布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/btn_show"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="展示" />
<ProgressBar
android:id="@+id/prog"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="10dp"
android:max="100" />
<WebView
android:id="@+id/barchart2d_webview"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</LinearLayout>
public class MainActivity extends Activity {
private WebView webView;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar=(ProgressBar) findViewById(R.id.prog);
webView = (WebView) findViewById(R.id.barchart2d_webview);
findViewById(R.id.btn_show).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
webViewShow();
}
});
}
private void webViewShow() {
webView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
WebSettings webSettings = webView.getSettings();
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//不缓存
webSettings.setJavaScriptEnabled(true);// 置是否允许 WebView 使用 JavaScript(默认是不允许)
webSettings.setBuiltInZoomControls(true);//允许缩放
webSettings.setDisplayZoomControls(false);//
//是否允许访问文件,默认允许。注意,这里只是允许或禁止对文件系统的访问,Assets 和 resources 文件使用file:///android_asset和file:///android_res仍是可访问的
webSettings.setAllowFileAccess(false);
//设置是否允许通过 file url 加载的 Js代码读取其他的本地文件
webSettings.setAllowFileAccessFromFileURLs(false);
// // 设置是否允许通过 file url 加载的 Javascript 可以访问其他的源(包括http、https等源)
webSettings.setAllowUniversalAccessFromFileURLs(false);
webSettings.setDefaultTextEncodingName("gbk");
webView.removeJavascriptInterface("searchBoxJavaBridge_");
webView.removeJavascriptInterface("accessibility");
webView.removeJavascriptInterface("accessibilityTraversal");
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
webView.addJavascriptInterface(new MyHtmlObject(), "jsObj");
webView.setWebChromeClient(new MyWebChromeClient());
webView.setWebViewClient(new MyWebViewClient());
webView.loadUrl("https://www.baidu.com/");
}
private class MyWebViewClient extends WebViewClient {
/**
* shouldInterceptRequest WebView
* 中调用的每个请求都会经过该拦截器,如果一个页面有超链接,那么依然会经过该拦截器
* 参数说明:
* @param view
* 接收WebViewClient的那个实例,前面看到webView.setWebViewClient(new
* MyAndroidWebViewClient()),即是这个webview。
* @param url
* raw url 制定的资源
* @return 返回WebResourceResponse包含数据对象,或者返回null
*/
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,
String url) {
if (url.contains("http") || url.contains("www")
|| url.contains("https")) {
String response = "<html><body>该数据不存在</body></html>";
WebResourceResponse weResourceResponse = new WebResourceResponse(
"text/html", "utf-8", new ByteArrayInputStream(
response.getBytes()));
return weResourceResponse;
} else {
return super.shouldInterceptRequest(view, url);
}
}
/**
* WebView开始加载网页时调用
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
/*
* url重定向会执行此方法以及点击页面某些链接也会执行此方法
* 当加载的网页需要重定向的时候就会回调这个函数告知我们应用程序是否需要接管控制网页加载,如果应用程序接管,并且return
* true意味着主程序接管网页加载,如果返回false让webview自己处理。
*
* @param view 当前webview
*
* @param url 即将重定向的url
*
* @return true:表示当前url已经加载完成,即使url还会重定向都不会再进行加载, false
* 表示此url默认由系统处理,该重定向还是重定向,直到加载完成
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.contains("http") || url.contains("www")
|| url.contains("https")) {
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
/**
* 加载网页结束时调用
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
}
private class MyWebChromeClient extends WebChromeClient {
/**
* 网页显示进度条
*/
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
if (newProgress == 100) {
// 网页加载完成
progressBar.setVisibility(View.GONE);// 加载完网页进度条消失
} else {
// 加载中
progressBar.setVisibility(View.VISIBLE);// 开始加载网页时显示进度条
progressBar.setProgress(newProgress);// 设置进度值
}
}
}
/**
* 与javascript进行交互的接口
*
* @return
*/
private class MyHtmlObject {
@JavascriptInterface
public void load() {
runOnUiThread(new Runnable() {
@Override
public void run() {
webView.loadUrl("javascript:showPieChart1(" + chartdata
+ ")");
}
});
}
}
}
参考:
https://blog.youkuaiyun.com/niuba123456/article/details/81177567
http://www.360doc.com/content/17/0510/16/41302481_652733039.shtml
https://blog.youkuaiyun.com/zhongwn/article/details/48732787
https://www.jianshu.com/p/c6ea158dea68
WebViewClient参考:https://blog.youkuaiyun.com/harvic880925/article/details/51523983
进度条展示:https://blog.youkuaiyun.com/cn_jack_chen/article/details/74011704
WebView漏洞:
http://blog.youkuaiyun.com/leehong2005/article/details/11808557
https://blog.youkuaiyun.com/self_study/article/details/55046348
下面这段记录是摘抄自上面的博客,自己还不太理解,先记录下,慢慢研究
addJavascriptInterface造成漏洞的问题:
使用这个方法是不安全的,网页中的JS脚本可以利用接口 “testjs” 调用 App 中的 Java 代码,而 Java 对象继承关系会导致很多 Public 的函数及 getClass 函数都可以在JS中被访问,结合 Java 的反射机制,攻击者还可以获得系统类的函数,进而可以进行任意代码执行,
首先第一步 WebView 添加 Javascript 对象,并且添加一些权限,比如想要获取 SD 卡上面的信息就需要 `android.permission.WRITE_EXTERNAL_STORAGE` ;
第二步 JS 中可以遍历 window 对象,找到存在 getClass 方法的对象,再通过反射的机制,得到 Runtime 对象,然后就可以调用静态方法来执行一些命令,比如访问文件的命令;
第三步就是从执行命令后返回的输入流中得到字符串,比如执行完访问文件的命令之后,就可以得到文件名的信息了,有很严重暴露隐私的危险,核心 JS 代码:
function execute(cmdArgs)
{
for (var obj in window) {
if ("getClass" in window[obj]) {
alert(obj);
return window[obj].getClass().forName("java.lang.Runtime")
.getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
}
}
那么这个漏洞怎么去解决呢?这就需要用到 onJsPrompt 这个方法了
继承 WebView ,重写 addJavascriptInterface 方法,然后在内部自己维护一个对象映射关系的 Map,当调用 addJavascriptInterface 方法,将需要添加的 JS 接口放入这个 Map 中;
每次当 WebView 加载页面的时候加载一段本地的 JS 代码:
javascript:(function JsAddJavascriptInterface_(){
if(typeof(window.XXX_js_interface_name)!='undefined'){
console.log('window.XXX_js_interface_name is exist!!');
}else{
window.XXX_js_interface_name={
XXX:function(arg0,arg1){
return prompt('MyApp:'+JSON.stringify({obj:'XXX_js_interface_name',func:'XXX_',args:[arg0,arg1]}));
},
};
}
})()
这段 JS 代码定义了注入的格式,其中的 XXX 为注入对象的方法名字,终端和 web 端只要按照定义的格式去互相调用即可,如果这个对象有多个方法,则会注册多个 window.XXX_js_interface_name 块;
然后在 prompt 中返回我们约定的字符串,当然这个字符串也可以自己重新定义,它包含了特定的标识符 MyApp,后面包含了一串 JSON 字符串,它包含了方法名,参数,对象名等;
当 JS 调用 XXX 方法的时候,就会调用到终端 Native 层的 OnJsPrompt 方法中,我们再解析出方法名,参数,对象名等,解析出来之后进行相应的处理,同时返回值也可以通过 prompt 返回回去;
window.XXX_js_interface_name 代表在 window 上声明了一个对象,声明的方式是:方法名:function(参数1,参数2)。
还有一个问题是什么时候加载这段 JS 呢,在 WebView 正常加载 URL 的时候去加载它,但是会发现当 WebView 跳转到下一个页面时,之前加载的 JS 可能就已经无效了,需要再次加载,所以通常需要在一下几个方法中加载 JS,这几个方法分别是 onLoadResource,doUpdateVisitedHistory,onPageStarted,onPageFinished,onReceivedTitle,onProgressChanged。
通过这几步,就可以简单的修复漏洞问题,但是还需要注意几个问题,需要过滤掉 Object 类的方法,由于通过反射的形式来得到指定对象的方法,所以基类的方法也可以得到,最顶层的基类就是 Object,为了不把 getClass 等方法注入到 JS 中,我们需要把 Object 的共有方法过滤掉,需要过滤的方法列表如下:“getClass”,“hashCode”,“notify”,“notifyAll”,“equals”,“toString”,“wait”,具体的代码实现可以看看下面的源码。