webview之复习

 三.WebView面试

1.WebView的漏洞
 JsBridge原理

 如何提高WebView加载速度

1.WebView的漏洞:
API16之前存在远程代码执行安全漏洞,该漏洞源于程序没有正确限制使用WebView.addJavascriptInterface方法,远程攻击者可通过使用Java反射机制利用该漏洞执行任意Java对象的方法。

2. WebView销毁步骤

  • WebView在其他容器上时(如LinearLayout),当销毁Activity时,需要在onDestroy()中先移除容器上的WebView,然后再将WebView.destroy(),这样就不会导致内存泄漏

3.webview的JsBridge原理:

  • 客户端和服务端之间通过Javascript来互相调用各自的方法

3.如何提高WebView加载速度

1)、WebView硬件加速
Android3.0引入硬件加速,默认会开启,WebView在硬件加速的情况下滑动更加平滑,性能更加好,但是会出现白块或者页面闪烁的副作用,建议WebView暂时关闭硬件加速
2)、WebViewClient的onPageFinished
   onPageFinished在每次完成页面的时候调用,但是遇到未加载完成的页面跳转其他页面时,就会一直调用,使用WebChromeClient.onProgressChanged可以替代

3)、WebView后台耗电
在WebView加载页面的时候,会自动开启线程去加载,如果不很好的关闭这些线程,就会导致电量消耗加大,可以采用暴力的方法,直接在onDestroy方法中System.exit(0)结束当前正在运行中的java虚拟机

4、WebView内存泄漏
由于WebView是依附于Activity的,Activity的生命周期和WebView启动的线程的生命周期是不一致的,这会导致WebView一直持有对这个Activity的引用而无法释放,
解决方案:
 1) 独立进程,简单暴力,不过可能涉及到进程间通信(推荐);
 2) 动态添加WebView,对传入WebView中使用的Context使用弱引用;
WebView销毁步骤:
WebView在其他容器上时(如:LinearLayout),当销毁Activity时,需要在onDestroy()中先移除容器上的WebView,然后再将WebView.destroy(),这样就不会导致内存泄漏

WebView的内存泄漏问题

原因:WebView会关联一个Activity,WebView执行的操作是在新线程当中回收,时间Activity没有办法确认,Activity的生命周期和WebView线程生命周期不一致导致WebView一直执行,因为WebView内部持有Activity的引用,导致Activity一直不能被回收,原理类似于匿名内部类持有外部类的引用一样。那么如何解决呢?解决方案如下:

  1. 独立进程,简单暴力,涉及到进程间通信。(开发过程中常用)

  2. 动态添加WebView,对传入WebView中使用Context弱引用,动态添加WebView意思在布局中创建一个ViewGroup用来放置WebView,Activity创建add进来,Activity停止时remove掉

5. WebView常见的坑

    1. API 16之前版本存在远程代码执行漏洞,该漏洞源自于程序没有正确限制使用WebView.addJavascriptInterface方法,攻击者可以使用Java Reflection API利用该漏洞执行任意Java对象和方法。

    2. WebView的销毁和内存泄漏问题。WebView的完全销毁是件麻烦事,一旦销毁流程不正确,极易容易导致内存泄漏。

    3. jsbridge
       通过javascript构建一个桥,桥的两端一个端是客户端,一端是服务端,它可以让本地端调用远端的web代码,也可以让远端调用本地的代码。

    4. WebViewClient.onPageFinished(不靠谱)  – > WebChromeClient.onProgressChanged(靠谱)

    5. 后台耗电(性能优化)
      WebView开启网页时会自己开启线程,如果没有合理的销毁,那么残余线程就会一直运行,so这会非常耗电的,解决方案:有一种暴力方式就是Activity的onDestroy中调用System.exit()方法把虚拟机关闭,也可结合自己应用的WebView的情况设计出一个温柔的方案。

    6. WebView硬件加速导致的页面渲染问题
        WebView硬件加速偶尔导致页面白块,页面闪烁,但是加载速度比较快,解决方案:关闭硬件加速。

二.webview常见问题

1. webview 运行时不调用系统自带浏览器

mobView.loadUrl("http://www.csdn.net");

WebSettings wSet = mobView.getSettings();    
wSet.setJavaScriptEnabled(true);
mobView.setWebViewClient(new WebViewClient() {
        public boolean shouldOverrideUrlLoading(WebView view, String url){
            //  重写此方法表明点击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边
             view.loadUrl(url);
             return true; }

        });

最后把权限加上:<uses-permission android:name="android.permission.INTERNET" />

2. WebViewClient 和 WebChromeClient 区别?
WebView主要负责解析渲染,WebViewClient 和WebChromeClient是用来辅助WebView。
WebViewClient主要帮助WebView处理各种通知、请求事件的,比如:
onLoadResource
onPageStart
onPageFinish
onReceiveError
onReceivedHttpAuthRequest
WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等比如
onCloseWindow(关闭WebView)
onCreateWindow()
onJsAlert (WebView上alert无效,需要定制WebChromeClient处理弹出)
onJsPrompt
onJsConfirm
onProgressChanged
onReceivedIcon
onReceivedTitle
看上去他们有很多不同,多数只用WebViewClient就行。如果需要更丰富的处理效果,比如JS、进度条等,就要用到WebChromeClient。更多时候,可以这样

webView.setWebChromeClient(new WebChromeClient());
webView.setWebViewClient(new WebViewClient());//希望点击链接继续在当前browser中响应,必须覆盖 WebViewClient对象。
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl(url); 

webview.setWebChromeClient(new MyWebChromeClient());
webview.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// return super.shouldOverrideUrlLoading(view, url);
  view.loadUrl(url);
  return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
    mProgressDialog.show();
}
@Override
public void onPageFinished(WebView view, String url) {
   super.onPageFinished(view, url);
   mProgressDialog.hide();
 }
});

private class MyWebChromeClient extends WebChromeClient{
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
  textview.setText(title); }
}

3. JS调用android方法

webview.addJavascriptInterface(new JavaScriptInterfaceDemo(this), "JSInterfaceDemo");
//JSInterfaceDemo其实就是JavaScriptInterfaceDemo的别名,供HTML调用时使用
addJavascriptInterface(Object obj, String interfaceName)该方法将一个java对象绑定到一个javascript对象中,javascript对象名就是interfaceName,

如JSInterfaceDemo,作用域是Global。

这样初始化webview后,在webview加载的页面中就可以直接通过 javascript:window.JSInterfaceDemo访问到绑定的java对象了。
在HTML中如何调用呢,"window.JSInterfaceDemo.getResposeCode() " 其中getResposeCode需要在JavaScriptInterfaceDemo中实现

注意:addJavascriptInterface方法中要绑定的Java对象及方法要运行另外的线程中,不能运行在构造他的线程中,这也是使用Handler的目的。

public class JavaScriptInterfaceDemo{

@JavaScriptInterface
public void getResposeCode(final String msg){
mHandler.post(new Runnable(){
 @Override
 public void run() { }}
 }

webview.addJavascriptInterface(new JavaScriptInterfaceDemo( this ), "JSInterfaceDemo");

一.WebView与 js的交互

11. Js通过WebView调用Java代码
从API19开始,Android提供@JavascriptInterface对象注解方式建立Javascript对象和Android原生对象绑定,提供给JavaScript调用函数必须有@JavascriptInterface。

方式1. 通过WebView的addJavascriptInterface() 进行对象映射
优点:使用简单, 仅将Android对象和JS对象映射即可;
缺点:存在严重的漏洞问题


步骤1:定义与JS对象映射关系的Android类:AndroidObj继承自Object类


public class AndroidObj extends Object {


// 定义JS调用的方法且必须加入@JavascriptInterface注解


  @JavascriptInterface


  public void hello(String msg) {


   System.out.println("JS调用了Android的hello方法");
}


}


步骤2:将需JS代码以.html格式放到src/main/assets文件夹里:

javascript.html


<script> 
function callAndroid(){


// 由于对象映射,所以调用test对象等于调用Android映射的对象


   jsObj.hello("js调用了android中的hello方法");
}


</script>
步骤3:在Android里通过WebView设置Android类与JS代码的映射

//通过addJavascriptInterface()将Java对象映射到JS对象. 参数1:Java对象名, 参数2:Javascript对象名


//AndroidtoJS类对象映射到js的test对象


webSettings.setJavaScriptEnabled(true);


mWebView.addJavascriptInterface(new AndroidObj(), "jsObj");

mWebView.loadUrl("file:///android_asset/javascript.html");

方式2. 通过WebViewClient的shouldOverrideUrlLoading (WebView view,String url)回调拦截url
原理:解析该 url 的协议;如果检测到是预先约定好的协议,就调用相应方法;

优点:不存在方式1的漏洞;
缺点:JS获取Android方法的返回值复杂。
如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl()去执行JS 方法把返回值传递回去代码如下:
mWebView.loadUrl("javascript:returnResult(" + result + ")");
JS:javascript.html
  function returnResult(result){
  alert("result is" + result);
}

3个步骤:

1.Java被Js调用的方法上加@JavascriptInterface注解;
2.WebView注册JavaScriptInterface;
3.js调用,如window.android.show("JavaScript called~!");
Js调用Android Toast方法

  注册JavaScriptInterface:

    webView.addJavascriptInterface(this, "jsBridge");
   // addJavascriptInterface的作用是把this所代表的类映射为JavaScript中的android对象。

2) Webview调用Js

需要在主线程中调用

  • 基本格式:webView.loadUrl("javascript:methodName(parameterValues)");
  • 调用Js无参无返回值函数:
   String call = "javascript:readyToGo()";
   webView.loadUrl(call);
  • 调用Js有参无返回值函数
  String call = "javascript:alertMessage(\"" + "content" + "\")";
  webView.loadUrl(call);
  • 调用Js有参有返回值函数
 String call = "javascript:alertMessage(\"" + "content" + "\")";
 webView.evaluateJavascript(call, new ValueCallback<String>() {
          @Override
          public void onReceiveValue(String s) {} });

3) 通过H5打开App的某个页面(类似deeplink方式 )?

在manifest文件中最开始启动的activity中添加:
<activity android:name=".activitys.MainActivity">
 <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:host="host"
            android:pathPrefix="/pathPrefix"
            android:scheme="scheme" />
    </intent-filter>
</activity>
// 注意host,pathPrefix,scheme都是自己自定义的,只要与h5页面调用的一致即可,如下所示

如果要跳转到指定的页面,在MainActivity的onCreate()中添加:

Intent intent = getIntent();
Uri uri = intent.getData();
if (uri != null) {
        String routeId = uri.getQueryParameter("pid");
        Intent intent0 = new Intent(MainActivity.this, ZhidingActivity.class);
        startActivity(intent0);
}
uri.getQueryParameter("pid");获取h5页面传递的参数,如果没有的话可以忽略
注意一点,微信上对于app的唤醒有拦截,在浏览器中才可以起作用

其他知识点汇总参考

1.2 WebView与 js的交互

1. Android调用JS代码的方法有2种:
1)通过WebView的loadUrl()
webSettings.setJavaScriptEnabled(true); // 设置与Js交互的权限
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.post(new Runnable() {
public void run() {
// 注意调用的JS方法名要对应上
  mWebView.loadUrl("javascript:callJS()");
}
});

特别注意:JS代码调用一定要在 onPageFinished()回调之后才能调用,否则不会调用。onPageFinished()属于WebViewClient类的方法,主要在页面加载结束时调用。
特点:方便简洁。但效率低和获取返回值麻烦。
使用场景:不需要获取返回值,对性能要求较低时;

优点:该方法比第一种方法效率更高、使用更简洁。因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会;
2)Android4.4后才可使用(使用场景);
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
 @Override
 public void onReceiveValue(String value) {
   //此处为 js 返回的结果 }
});

2. 对于JS调用Android代码的方法有3种:

方式2:通过 WebViewClient 的方法shouldOverrideUrlLoading (webview,url)回调拦截 url
即JS需要调用Android的方法
步骤1:在JS约定所需要的Url协议
JS代码:javascript.html 以.html格式放到src/main/assets文件夹里
<script>
function callAndroid() { //约定url协议:
  document.location = "js://webview?arg1=111&arg2=222";
}
</script>
当该JS通过Android的mWebView.loadUrl("file:///android_asset/javascript.html")加载后,就会回调shouldOverrideUrlLoading():
步骤2:在Android通过WebViewClient复写shouldOverrideUrlLoading(webview,url)
webSettings.setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {

// 步骤2:根据协议的参数,判断是否是所需要的url
// 根据scheme(协议格式) &authority(协议名)判断(前两个参数)
//传入url = "js://webview?arg1=111&arg2=222"(约定好的需拦截的)
Uri uri = Uri.parse(url);
if (uri.getScheme().equals("js")) {
// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议
// 拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
//步骤3 执行JS所需要调用的逻辑
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
  HashMap<String, String> params = new HashMap<>();
  Set<String> collection = uri.getQueryParameterNames();
}
   return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}

法3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()输回调拦截JS对话框alert()、对话框confirm()、prompt()输入框消息
方式3的原理:onJsPrompt(webview, url, message, String defaultValue, JsPromptResult result)回调拦截JS对话框到他们的消息内容,然后解析即可。
常用的拦截是:拦截JS的输入框(即prompt()可以返回任意类型的值,操作最全面方便、更加灵活);

而alert()对话框没有返回值; confirm()对话框只能返回两种状态(确定 / 取消)两个值

步骤1:加载JS代码: javascript.html
<script>
function clickprompt(){
var result=prompt("js://demo?arg1=111&arg2=222");
alert("demo " + result);
}</script>
当使用mWebView.loadUrl("file:///android_asset/javascript.html")加载了上述JS代码后,就会触发回调onJsPrompt(),具体如下:
如果是拦截警告框(即alert()),则触发回调onJsAlert();
如果是拦截确认框(即confirm()),则触发回调onJsConfirm();

步骤2:在Android通过WebChromeClient复写onJsPrompt()
webSettings.setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {

// 拦截输入框(原理同方式2), 根据协议参数,判断是否是所需要的url
// 参数message: 代表promt()内容(不是url) result代表输入框的返回值
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
//假定传入进来的 url = "js://webview?arg1=111&arg2=222"(约定好的拦截)
Uri uri = Uri.parse(message);
if ( uri.getScheme().equals("js")) {
// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议
// 所以拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
 HashMap<String, String> params = new HashMap<>();
 Set<String> collection = uri.getQueryParameterNames();
 //参数result:代表消息框的返回值(输入值)
  result.confirm("js调用了Android的方法成功啦");
 }
  return true;
}
 return super.onJsPrompt(view, url, message, defaultValue, result);
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
   return super.onJsAlert(view, url, message, result);// 通过alert()和confirm()拦截的原理相同
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
     return super.onJsConfirm(view, url, message, result);
}
});

一. Android调用JS代码方法有2种:
1. 通过WebView的loadUrl()
webSettings.setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.post(new Runnable() {
public void run() {
// 注意调用的JS方法名要对应上
mWebView.loadUrl("javascript:callJS()");
}});
注意:JS代码调用一定要在onPageFinished()回调之后才能调用,否则不会调用。
特点:方便简洁。但效率低和获取返回值麻烦。
使用场景:不需要获取返回值,对性能要求较低时;
2. 通过WebView的evaluateJavascript()
优点:比第一种方法效率更高、使用更简洁。因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会;
Android4.4后才可使用(使用场景);
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
 @Override
 public void onReceiveValue(String value) {
   // 此处为js返回的结果
 }
});

1.2 对于JS调用Android代码的方法有3种:
1. 通过WebView的addJavascriptInterface() 进行对象映射
优点:使用简单, 仅将Android对象和JS对象映射即可;
缺点:存在严重的漏洞问题。
方式1:通过WebView的addJavascriptInterface())进行对象映射
步骤1:定义个与JS对象映射关系的Android类:AndroidObj继承自Object类
public class AndroidObj extends Object {
@JavascriptInterface
public void hello(String msg) {
// 定义JS调用的方法且必须加入@JavascriptInterface注解
System.out.println("JS调用了Android的hello方法");
}
}

步骤2:将需要调用的JS代码以.html格式放到src/main/assets文件夹里:javascript.html
<script>
function callAndroid(){
// 由于对象映射,所以调用test对象等于调用Android映射的对象
  jsObj.hello("js调用了android中的hello方法");
}
</script>
步骤3:在Android里通过WebView设置Android类与JS代码的映射
webSettings.setJavaScriptEnabled(true);
// 通过addJavascriptInterface()将Java对象映射到JS对象. 参数1:Java对象名, 参数2:Javascript对象名
mWebView.addJavascriptInterface(new AndroidObj(), "jsObj"); // AndroidtoJS类对象映射到js的test对象
mWebView.loadUrl("file:///android_asset/javascript.html");

2. 通过 WebViewClient的shouldOverrideUrlLoading ()方法回调拦截 url
按约定好的协议解析该 url;
优点:不存在方式1的漏洞;
缺点:JS获取Android方法的返回值复杂。
如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl()去执行JS 方法把返回值传递回去代码如下:
mWebView.loadUrl("javascript:returnResult(" + result + ")");
步骤2:在Android通过WebViewClient复写shouldOverrideUrlLoading ()
webSettings.setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//传入url = "js://webview?arg1=111&arg2=222"(约定好的需拦截的)
Uri uri = Uri.parse(url);
if (uri.getScheme().equals("js")) {
// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议
// 拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
//步骤3 执行JS所需要调用的逻辑
// 可以在协议上带有参数并传递到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}
3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()回调拦截JS对话框alert()、confirm()、prompt()消息
方式3的原理:onJsPrompt()方法回调分别拦截JS对话框 得到他们的消息内容,然后解析即可。
常用的拦截是:拦截JS的输入框(即prompt()可以返回任意类型的值,操作最全面方便、更加灵活);而alert()对话框没有返回值;confirm()对话框只能返回两种状态(确定 / 取消)两个值

posted on 2019-12-23 19:59  左手指月  阅读(329)  评论(0编辑  收藏  举报