WebView与js交互注意事项

一.分析

通常我们在使用webview与js进行交互的时候,会看到这样一个提示

WebView与js交互注意事项_第1张图片

Android Stduio提示给我们的意思是,我们添加的接口中的方法没有使
用 @JavascriptInterface进行注解,这些方法在API17是不可见的,如果使用的是API17,必须使用@JavascriptInterface进行注解。
那么为什么会出现上面的提示呢,我们去看看addJavascriptInterface这个方法的源码:

 /**
     * Injects the supplied Java object into this WebView. The object is
     * injected into the JavaScript context of the main frame, using the
     * supplied name. This allows the Java object's methods to be
     * accessed from JavaScript. For applications targeted to API
     * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
     * and above, only public methods that are annotated with
     * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
     * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
     * all public methods (including the inherited ones) can be accessed, see the
     * important security note below for implications.
     * 

Note that injected objects will not * appear in JavaScript until the page is next (re)loaded. For example: *

     * class JsObject {
     *    {@literal @}JavascriptInterface
     *    public String toString() { return "injectedObject"; }
     * }
     * webView.addJavascriptInterface(new JsObject(), "injectedObject");
     * webView.loadData("", "text/html", null);
     * webView.loadUrl("javascript:alert(injectedObject.toString())");
*

* IMPORTANT: *

    *
  • This method can be used to allow JavaScript to control the host * application. This is a powerful feature, but also presents a security * risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier. * Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN} * are still vulnerable if the app runs on a device running Android earlier than 4.2. * The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} * and to ensure the method is called only when running on Android 4.2 or later. * With these older versions, JavaScript could use reflection to access an * injected object's public fields. Use of this method in a WebView * containing untrusted content could allow an attacker to manipulate the * host application in unintended ways, executing Java code with the * permissions of the host application. Use extreme care when using this * method in a WebView which could contain untrusted content.
  • *
  • JavaScript interacts with Java object on a private, background * thread of this WebView. Care is therefore required to maintain thread * safety. *
  • *
  • The Java object's fields are not accessible.
  • *
  • For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP} * and above, methods of injected Java objects are enumerable from * JavaScript.
  • *
* * @param object the Java object to inject into this WebView's JavaScript * context. Null values are ignored. * @param name the name used to expose the object in JavaScript */ public void addJavascriptInterface(Object object, String name) { checkThread(); mProvider.addJavascriptInterface(object, name); }

源码怎么实现的不是重点,重点我们看上面一大堆注释,我大概总结一下人家说了什么东西。

addJavascriptInterface这个方法接收两个参数,第一个参数是我们要调用方法所在的对象,第二个方法是对象的别名,这个是任意取的,
这个方法的作用就是将我们提供的Java对象注入到Webview中,同时会使用这个别名将这个对象注入到JavaScript的主框架中。这样会允许JavaScript访问Java对象的方法,当我们应用的API在17及其以上时,只有带有@JavascriptInterface注解的公共方法才能被JavaScript访问,在API16及其以下,所有的公共方法都可以被访问。

addJavascriptInterface这个方法可以用来使JavaScript控制我们的应用,这是一个鲜明的特点,但是在API16及其以下提供了安全隐患。即使APP的版本在16以后,如果此时运行在16以下的设备上,还是容易收到攻击,最安全的方式是我们的APP版本大于16并且运行在16以上的设备上。在老的版本上,JavaScript可以利用反射访问注入对象的公共方法,在WebView中使用这个包含不可信任内容的公共方法会允许以意料之外的方式操作我们的应用程序,使用应用的权限来执行Java代码。如果方法中包含不可信任的方法,当我们在WebView中使用时要格外小心。
JavaScrit与Java对象的交互是在Webview的后台子线程中进行的,所以注意线程安全。
注入的Java对象的属性是不能被JavaScript使用的。
在API Level L系统中使用JS交互,Java对象是要被声明在枚举中

常用功能

WebView cookies清理

CookieSyncManager.createInstance(this);   
CookieSyncManager.getInstance().startSync();   
CookieManager.getInstance().removeSessionCookie(); 

清理cache 和历史记录

webView.clearCache(true);   
webView.clearHistory(); 

判断WebView是否已经滚动到页面底端

getScrollY()方法返回的是当前可见区域的顶端距整个页面顶端的距离,也就是当前内容滚动的距离.   
getHeight()或者getBottom()方法都返回当前WebView 这个容器的高度   
getContentHeight 返回的是整个html 的高度,但并不等同于当前整个页面的高度,因为WebView 有缩放功能, 所以当前整个页面的高度实际上应该是原始html 的高度再乘上缩放比例. 因此,更正后的结果,准确的判断方法应该是:   
if(WebView.getContentHeight*WebView.getScale() == (webview.getHeight()+WebView.getScrollY())){ //已经处于底端 }

URL拦截
Android WebView是拦截不到页面内的fragment跳转的。但是url跳转的话,又会引起页面刷新,H5页面的体验又下降了。只能给WebView注入JS方法了

处理WebView中的非超链接请求(如Ajax请求)
有时候需要加上请求头,但是非超链接的请求,没有办法再shouldOverrinding中拦截并用webView.loadUrl(String url,HashMap headers)方法添加请求头

目前用了一个临时的办法解决:

首先需要在url中加特殊标记/协议, 如在onWebViewResource方法中拦截对应的请求,然后将要添加的请求头,以get形式拼接到url末尾

在shouldInterceptRequest()方法中,可以拦截到所有的网页中资源请求,比如加载JS,图片以及Ajax请求等等

@SuppressLint("NewApi")  
@Override  
public WebResourceResponse shouldInterceptRequest(WebView view,String url) {  
    // 非超链接(如Ajax)请求无法直接添加请求头,现拼接到url末尾,这里拼接一个imei作为示例  

    String ajaxUrl = url;  
    // 如标识:req=ajax  
    if (url.contains("req=ajax")) {  
       ajaxUrl += "&imei=" + imei;  
    }  
    return super.shouldInterceptRequest(view, ajaxUrl);  
}  

在页面中先显示图片

public void onLoadResource(WebView view, String url) {  
  mEventListener.onWebViewEvent(CustomWebView.this, OnWebViewEventListener.EVENT_ON_LOAD_RESOURCE, url);  
    if (url.indexOf(".jpg") > 0) {  
     hideProgress(); //请求图片时即显示页面  
     mEventListener.onWebViewEvent(CustomWebView.this, OnWebViewEventListener.EVENT_ON_HIDE_PROGRESS, view.getUrl());  
     }  
    super.onLoadResource(view, url);  
}  

屏蔽掉长按事件 因为webview长按时将会调用系统的复制控件:

mWebView.setOnLongClickListener(new OnLongClickListener() {  

          @Override  
          public boolean onLongClick(View v) {  
              return true;  
          }  
      });  

在WebView加入 flash支持:

String temp = """ + "black"  
                + "\"> 
"" + url + "\" width=\"" + "100%" + "\" height=\"" + "90%" + "\" scale=\"" + "noscale" + "\" type=\"" + "application/x-shockwave-flash" + "\"> "; String mimeType = "text/html"; String encoding = "utf-8"; web.loadDataWithBaseURL("null", temp, mimeType, encoding, "");

WebView保留缩放功能但隐藏缩放控件:

mWebView.getSettings().setSupportZoom(true);  
        mWebView.getSettings().setBuiltInZoomControls(true);  
        if (DeviceUtils.hasHoneycomb())  
              mWebView.getSettings().setDisplayZoomControls(false);  

注意:setDisplayZoomControls是在Android 3.0中新增的API.

WebView 在Android4.4的手机上onPageFinished()回调会多调用一次
需要尽量避免在onPageFinished()中做业务操作,否则会导致重复调用,还有可能会引起逻辑上的错误

需要通过获取Web页中的title用来设置自己界面中的title及相关问题:

WebChromeClient webChromeClient = new WebChromeClient() {    
            @Override    
            public void onReceivedTitle(WebView view, String title) {    
                super.onReceivedTitle(view, title);    

                txtTitle.setText(title);    
            }    

        };   

但是发现在小米3的手机上,当通过webview.goBack()回退的时候,并没有触发onReceiveTitle(),这样会导致标题仍然是之前子页面的标题,没有切换回来.

这里可以分两种情况去处理:

(1) 可以确定webview中子页面只有二级页面,没有更深的层次,这里只需要判断当前页面是否为初始的主页面,可以goBack的话,只要将标题设置回来即可.

(2)webview中可能有多级页面或者以后可能增加多级页面,这种情况处理起来要复杂一些:

因为正常顺序加载的情况onReceiveTitle是一定会触发的,所以就需要自己来维护webview  loading的一个url栈及url与title的映射关系

那么就需要一个ArrayList来保持加载过的url,一个HashMap保存url及对应的title.
正常顺序加载时,将url和对应的title保存起来,webview回退时,移除当前url并取出将要回退到的web 页的url,找到对应的title进行设置即可.
这里还要说一点,当加载出错的时候,比如无网络,这时onReceiveTitle中获取的标题为 找不到该网页,因此建议当触发onReceiveError时,不要使用获取到的title.

WebView页面中播放了音频,退出Activity后音频仍然在播放
需要在Activity的onDestory()中调用

webView.destroy();

但是直接调用可能会引起如下错误:

Error: WebView.destroy() called while still attached!

如上所示,webview调用destory时,webview仍绑定在Activity上.这是由于自定义webview构建时传入了该Activity的context对象,因此需要先从父容器中移除webview,然后再销毁webview

rootLayout.removeView(webView);  

给大家推荐一个博客
Android WebView的Js对象注入漏洞解决方案

最后给大家推荐一个开源项目,解决了webview的安全问题,
https://github.com/pedant/safe-java-js-webview-bridge

你可能感兴趣的:(WebView与js交互注意事项)