WebView總結(jié)

鏈接:http://www.lxweimin.com/p/fd61e8f4049e

image.png

一、簡(jiǎn)介

這部分主要介紹下 WebView,WebView 是一個(gè)用來(lái)顯示 Web 網(wǎng)頁(yè)的控件,繼承自 AbsoluteLayout,和使用系統(tǒng)其他控件沒什么區(qū)別,只是 WeView 控件方法比較多比較豐富。因?yàn)樗褪且粋€(gè)微型瀏覽器,包含一個(gè)瀏覽器該有的基本功能,例如:滾動(dòng)、縮放、前進(jìn)、后退下一頁(yè)、搜索、執(zhí)行 Js等功能。

在 Android 4.4 之前使用 WebKit 作為渲染內(nèi)核,4.4 之后采用 chrome 內(nèi)核。Api 使用兼容低版本。

官方 WebView.html
A View that displays web pages. This class is the basis upon which you can roll your own web browser or simply display some online content within your Activity. It uses the WebKit rendering engine to display web pages and includes methods to navigate forward and backward through a history, zoom in and out, perform text searches and more.


二、基本使用

//配置網(wǎng)絡(luò)權(quán)限
<uses-permission android:name="android.permission.INTERNET"/>

 //布局
<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/main"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

<com.webview.SafeWebView
    android:id="@+id/web_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
</LinearLayout>

  // BaseWebView 是我自己封裝的 WebView 

  //實(shí)際使用時(shí)請(qǐng)采用 new 的方式
  mWebView = (SafeWebView) findViewById(R.id.web_view);
  mWebView.addJavascriptInterface(new NativeInterface(this), "AndroidNative");
  mWebView.loadUrl("http://www.lxweimin.com/u/fa272f63280a");


三、WebView 方法

主要包含 WebView 的使用方法。 我們基于這些方法能擴(kuò)展很多其他功能,例如:JsBridge、緩存等。

常用方法

  • void loadUrl(String url):加載網(wǎng)絡(luò)鏈接 url
  • boolean canGoBack():判斷 WebView 當(dāng)前是否可以返回上一頁(yè)
  • goBack():回退到上一頁(yè)
  • boolean canGoForward():判斷 WebView 當(dāng)前是否可以向前一頁(yè)
  • goForward():回退到前一頁(yè)
  • onPause():類似 Activity 生命周期,頁(yè)面進(jìn)入后臺(tái)不可見狀態(tài)
  • pauseTimers():該方法面向全局整個(gè)應(yīng)用程序的webview,它會(huì)暫停所有webview的layout,parsing,JavaScript Timer。當(dāng)程序進(jìn)入后臺(tái)時(shí),該方法的調(diào)用可以降低CPU功耗。
  • onResume():在調(diào)用 onPause()后,可以調(diào)用該方法來(lái)恢復(fù) WebView 的運(yùn)行。
  • resumeTimers():恢復(fù)pauseTimers時(shí)的所有操作。(注:pauseTimers和resumeTimers 方法必須一起使用,否則再使用其它場(chǎng)景下的 WebView 會(huì)有問題)
  • destroy():銷毀 WebView
  • clearHistory():清除當(dāng)前 WebView 訪問的歷史記錄。
  • clearCache(boolean includeDiskFiles):清空網(wǎng)頁(yè)訪問留下的緩存數(shù)據(jù)。需要注意的時(shí),由于緩存是全局的,所以只要是WebView用到的緩存都會(huì)被清空,即便其他地方也會(huì)使用到。該方法接受一個(gè)參數(shù),從命名即可看出作用。若設(shè)為false,則只清空內(nèi)存里的資源緩存,而不清空磁盤里的。
  • reload():重新加載當(dāng)前請(qǐng)求
  • setLayerType(int layerType, Paint paint):設(shè)置硬件加速、軟件加速
  • removeAllViews():清除子view。
  • clearSslPreferences():清除ssl信息。
  • clearMatches():清除網(wǎng)頁(yè)查找的高亮匹配字符。
  • removeJavascriptInterface(String interfaceName):刪除interfaceName 對(duì)應(yīng)的注入對(duì)象
  • addJavascriptInterface(Object object,String interfaceName):注入 java 對(duì)象。
  • setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled):設(shè)置垂直方向滾動(dòng)條。
  • setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled):設(shè)置橫向滾動(dòng)條。
  • loadUrl(String url, Map<String, String> additionalHttpHeaders):加載制定url并攜帶http header數(shù)據(jù)。
  • evaluateJavascript(String script, ValueCallback<String> resultCallback):Api 19 之后可以采用此方法之行 Js。
  • stopLoading():停止 WebView 當(dāng)前加載。
  • clearView():在Android 4.3及其以上系統(tǒng)這個(gè)api被丟棄了, 并且這個(gè)api大多數(shù)情況下會(huì)有bug,經(jīng)常不能清除掉之前的渲染數(shù)據(jù)。官方建議通過loadUrl("about:blank")來(lái)實(shí)現(xiàn)這個(gè)功能,陰雨需要重新加載一個(gè)頁(yè)面自然時(shí)間會(huì)收到影響。
  • freeMemory():釋放內(nèi)存,不過貌似不好用。
  • clearFormData():清除自動(dòng)完成填充的表單數(shù)據(jù)。需要注意的是,該方法僅僅清除當(dāng)前表單域自動(dòng)完成填充的表單數(shù)據(jù),并不會(huì)清除WebView存儲(chǔ)到本地的數(shù)據(jù)。

我這里在介紹下下面幾組方法,比較重要,項(xiàng)目當(dāng)中可能會(huì)遇到坑

  • onPause() 盡力嘗試暫停可以暫停的任何處理,如動(dòng)畫和地理位置。 不會(huì)暫停JavaScript。 要全局暫停JavaScript,可使用pauseTimers。

  • onResume() 恢復(fù)onPause() 停掉的操作;

  • pauseTimers() 暫停所有WebView的布局,解析和JavaScript定時(shí)器。 這個(gè)是一個(gè)全局請(qǐng)求,不僅限于這個(gè)WebView。

  • resumeTimers() 恢復(fù)所有WebView的所有布局,解析和JavaScript計(jì)時(shí)器,將恢復(fù)調(diào)度所有計(jì)時(shí)器.

另外注意 JS 端setTimeout()、setInterval() 方法使用,自測(cè)來(lái)看,當(dāng)不使用 pauseTimers() 和 pauseTimers() ,從 Activity 返回上一個(gè)包含WebView 的Activity時(shí),頁(yè)面里的 setTimeout() 是不執(zhí)行的,setInterval() 是可以恢復(fù)執(zhí)行的。

在適當(dāng)?shù)纳芷谑褂?pauseTimers() 和 pauseTimers() 既可以恢復(fù)setTimeout() 執(zhí)行。

一份 WebView 方法使用清單

    mWebView.loadUrl("http://www.lxweimin.com/u/fa272f63280a");// 加載url,也可以執(zhí)行js函數(shù)
    mWebView.setWebViewClient(new SafeWebViewClient());// 設(shè)置 WebViewClient 
    mWebView.setWebChromeClient(new SafeWebChromeClient());// 設(shè)置 WebChromeClient
    mWebView.onResume();// 生命周期onResume
    mWebView.resumeTimers();//生命周期resumeTimers
    mWebView.onPause();//生命周期onPause
    mWebView.pauseTimers();//生命周期pauseTimers (上數(shù)四個(gè)方法都是成對(duì)出現(xiàn))
    mWebView.stopLoading();// 停止當(dāng)前加載
    mWebView.clearMatches();// 清除網(wǎng)頁(yè)查找的高亮匹配字符。
    mWebView.clearHistory();// 清除當(dāng)前 WebView 訪問的歷史記錄
    mWebView.clearSslPreferences();//清除ssl信息
    mWebView.clearCache(true);//清空網(wǎng)頁(yè)訪問留下的緩存數(shù)據(jù)。需要注意的時(shí),由于緩存是全局的,所以只要是WebView用到的緩存都會(huì)被清空,即便其他地方也會(huì)使用到。該方法接受一個(gè)參數(shù),從命名即可看出作用。若設(shè)為false,則只清空內(nèi)存里的資源緩存,而不清空磁盤里的。
    mWebView.loadUrl("about:blank");// 清空當(dāng)前加載
    mWebView.removeAllViews();// 清空子 View
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
        mWebView.removeJavascriptInterface("AndroidNative");// 向 Web端注入 java 對(duì)象
    }
    mWebView.destroy();// 生命周期銷毀


四、常用屬性

主要包含三部分:WebSettings、WebViewClient、WebChromeClient。

WebSettings

常用方法
  • setJavaScriptEnabled(boolean flag):是否支持 Js 使用。
  • setCacheMode(int mode):設(shè)置 WebView 的緩存模式。
  • setAppCacheEnabled(boolean flag):是否啟用緩存模式。
  • setAppCachePath(String appCachePath):Android 私有緩存存儲(chǔ),如果你不調(diào)用setAppCachePath方法,WebView將不會(huì)產(chǎn)生這個(gè)目錄。
  • setSupportZoom(boolean support):是否支持縮放。
  • setTextZoom(int textZoom):Sets the text zoom of the page in percent. The default is 100。
  • setAllowFileAccess(boolean allow):是否允許加載本地 html 文件/false。
  • setDatabaseEnabled(boolean flag):是否開啟數(shù)據(jù)庫(kù)緩存
  • setDomStorageEnabled(boolean flag):是否開啟DOM緩存。
  • setUserAgentString(String ua):設(shè)置 UserAgent 屬性。
  • setLoadsImagesAutomatically(boolean flag):支持自動(dòng)加載圖片
  • setAllowFileAccessFromFileURLs(boolean flag::允許通過 file url 加載的 Javascript 讀取其他的本地文件,Android 4.1 之前默認(rèn)是true,在 Android 4.1 及以后默認(rèn)是false,也就是禁止。
  • setAllowUniversalAccessFromFileURLs(boolean flag):允許通過 file url 加載的 Javascript 可以訪問其他的源,包括其他的文件和 http,https 等其他的源,Android 4.1 之前默認(rèn)是true,在 Android 4.1 及以后默認(rèn)是false,也就是禁止如果此設(shè)置是允許,則 setAllowFileAccessFromFileURLs 不起做用。
  • boolean getLoadsImagesAutomatically():是否支持自動(dòng)加載圖片。
一份使用清單
    WebSettings webSettings = mWebView.getSettings();
    if (webSettings == null) return;
    // 支持 Js 使用
    webSettings.setJavaScriptEnabled(true);
    // 開啟DOM緩存
    webSettings.setDomStorageEnabled(true);
    // 開啟數(shù)據(jù)庫(kù)緩存
    webSettings.setDatabaseEnabled(true);
    // 支持自動(dòng)加載圖片
    webSettings.setLoadsImagesAutomatically(hasKitkat());
    // 設(shè)置 WebView 的緩存模式
    webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
    // 支持啟用緩存模式
    webSettings.setAppCacheEnabled(true);
    // 設(shè)置 AppCache 最大緩存值(現(xiàn)在官方已經(jīng)不提倡使用,已廢棄)
    webSettings.setAppCacheMaxSize(8 * 1024 * 1024);
    // Android 私有緩存存儲(chǔ),如果你不調(diào)用setAppCachePath方法,WebView將不會(huì)產(chǎn)生這個(gè)目錄
    webSettings.setAppCachePath(getCacheDir().getAbsolutePath());
    // 數(shù)據(jù)庫(kù)路徑
    if (!hasKitkat()) {
        webSettings.setDatabasePath(getDatabasePath("html").getPath());
    }
    // 關(guān)閉密碼保存提醒功能
    webSettings.setSavePassword(false);
    // 支持縮放
    webSettings.setSupportZoom(true);
    // 設(shè)置 UserAgent 屬性
    webSettings.setUserAgentString("");
    // 允許加載本地 html 文件/false
    webSettings.setAllowFileAccess(true);
    // 允許通過 file url 加載的 Javascript 讀取其他的本地文件,Android 4.1 之前默認(rèn)是true,在 Android 4.1 及以后默認(rèn)是false,也就是禁止
    webSettings.setAllowFileAccessFromFileURLs(false);
    // 允許通過 file url 加載的 Javascript 可以訪問其他的源,包括其他的文件和 http,https 等其他的源,
    // Android 4.1 之前默認(rèn)是true,在 Android 4.1 及以后默認(rèn)是false,也就是禁止
    // 如果此設(shè)置是允許,則 setAllowFileAccessFromFileURLs 不起做用
    webSettings.setAllowUniversalAccessFromFileURLs(false);

WebViewClient

1、常用方法
  • onPageStarted(WebView view, String url, Bitmap favicon):WebView 開始加載頁(yè)面時(shí)回調(diào),一次Frame加載對(duì)應(yīng)一次回調(diào)。
  • onLoadResource(WebView view, String url):WebView 加載頁(yè)面資源時(shí)會(huì)回調(diào),每一個(gè)資源產(chǎn)生的一次網(wǎng)絡(luò)加載,除非本地有當(dāng)前 url 對(duì)應(yīng)有緩存,否則就會(huì)加載。
  • shouldInterceptRequest(WebView view, String url):WebView 可以攔截某一次的 request 來(lái)返回我們自己加載的數(shù)據(jù),這個(gè)方法在后面緩存會(huì)有很大作用。
  • shouldInterceptRequest(WebView view, android.webkit.WebResourceRequest request):WebView 可以攔截某一次的 request 來(lái)返回我們自己加載的數(shù)據(jù),這個(gè)方法在后面緩存會(huì)有很大作用。
  • shouldOverrideUrlLoading(WebView view, String url):是否在 WebView 內(nèi)加載頁(yè)面。
  • onReceivedSslError(WebView view, SslErrorHandler handler, SslError error):WebView ssl 訪問證書出錯(cuò),handler.cancel()取消加載,handler.proceed()對(duì)然錯(cuò)誤也繼續(xù)加載。
  • onPageFinished(WebView view, String url):WebView 完成加載頁(yè)面時(shí)回調(diào),一次Frame加載對(duì)應(yīng)一次回調(diào)。
  • onReceivedError(WebView view, int errorCode, String description, String failingUrl):WebView 訪問 url 出錯(cuò)。
2、一份使用清單
public class SafeWebViewClient extends WebViewClient {

    /**
     * 當(dāng)WebView得頁(yè)面Scale值發(fā)生改變時(shí)回調(diào)
     */
    @Override
    public void onScaleChanged(WebView view, float oldScale, float newScale) {
        super.onScaleChanged(view, oldScale, newScale);
    }

    /**
     * 是否在 WebView 內(nèi)加載頁(yè)面
     *
     * @param view
     * @param url
     * @return
     */
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        view.loadUrl(url);
        return true;
    }

    /**
     * WebView 開始加載頁(yè)面時(shí)回調(diào),一次Frame加載對(duì)應(yīng)一次回調(diào)
     *
     * @param view
     * @param url
     * @param favicon
     */
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
    }

    /**
     * WebView 完成加載頁(yè)面時(shí)回調(diào),一次Frame加載對(duì)應(yīng)一次回調(diào)
     *
     * @param view
     * @param url
     */
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
    }

    /**
     * WebView 加載頁(yè)面資源時(shí)會(huì)回調(diào),每一個(gè)資源產(chǎn)生的一次網(wǎng)絡(luò)加載,除非本地有當(dāng)前 url 對(duì)應(yīng)有緩存,否則就會(huì)加載。
     *
     * @param view WebView
     * @param url  url
     */
    @Override
    public void onLoadResource(WebView view, String url) {
        super.onLoadResource(view, url);
    }

    /**
     * WebView 可以攔截某一次的 request 來(lái)返回我們自己加載的數(shù)據(jù),這個(gè)方法在后面緩存會(huì)有很大作用。
     *
     * @param view    WebView
     * @param request 當(dāng)前產(chǎn)生 request 請(qǐng)求
     * @return WebResourceResponse
     */
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        return super.shouldInterceptRequest(view, request);
    }

    /**
     * WebView 訪問 url 出錯(cuò)
     *
     * @param view
     * @param request
     * @param error
     */
    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
        super.onReceivedError(view, request, error);
    }

    /**
     * WebView ssl 訪問證書出錯(cuò),handler.cancel()取消加載,handler.proceed()對(duì)然錯(cuò)誤也繼續(xù)加載
     *
     * @param view
     * @param handler
     * @param error
     */
    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        super.onReceivedSslError(view, handler, error);
    }
}

WebChromeClient

1、常用方法
  • onConsoleMessage(String message, int lineNumber,String sourceID):輸出 Web 端日志。
  • onProgressChanged(WebView view, int newProgress):當(dāng)前 WebView 加載網(wǎng)頁(yè)進(jìn)度。
  • onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result):處理 JS 中的 Prompt對(duì)話框
  • onJsAlert(WebView view, String url, String message, JsResult result): Js 中調(diào)用 alert() 函數(shù),產(chǎn)生的對(duì)話框。
  • onReceivedTitle(WebView view, String title):接收web頁(yè)面的 Title。
  • onReceivedIcon(WebView view, Bitmap icon):接收web頁(yè)面的icon。
2、一份使用清單
public class SafeWebChromeClient extends WebChromeClient {

    @Override
    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
        return super.onConsoleMessage(consoleMessage);
    }

    /**
     * 當(dāng)前 WebView 加載網(wǎng)頁(yè)進(jìn)度
     *
     * @param view
     * @param newProgress
     */
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        super.onProgressChanged(view, newProgress);
    }

    /**
     * Js 中調(diào)用 alert() 函數(shù),產(chǎn)生的對(duì)話框
     *
     * @param view
     * @param url
     * @param message
     * @param result
     * @return
     */
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        return super.onJsAlert(view, url, message, result);
    }

    /**
     * 處理 Js 中的 Confirm 對(duì)話框
     *
     * @param view
     * @param url
     * @param message
     * @param result
     * @return
     */
    @Override
    public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
        return super.onJsConfirm(view, url, message, result);
    }

    /**
     * 處理 JS 中的 Prompt對(duì)話框
     *
     * @param view
     * @param url
     * @param message
     * @param defaultValue
     * @param result
     * @return
     */
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }

    /**
     * 接收web頁(yè)面的icon
     *
     * @param view
     * @param icon
     */
    @Override
    public void onReceivedIcon(WebView view, Bitmap icon) {
        super.onReceivedIcon(view, icon);
    }

    /**
     * 接收web頁(yè)面的 Title
     *
     * @param view
     * @param title
     */
    @Override
    public void onReceivedTitle(WebView view, String title) {
        super.onReceivedTitle(view, title);
    }

}


五、于 JavaScript 交互

介紹

注:這里著重介紹下第一種標(biāo)準(zhǔn)方式,后面會(huì)介紹其他兩種方式。

1、使用系統(tǒng)方法 addJavascriptInterface 注入 java 對(duì)象來(lái)實(shí)現(xiàn)。

2、利用 WebViewClient 中 shouldOverrideUrlLoading (WebView view, String url) 接口,攔截操作。這個(gè)就是很多公司在用的 scheme 方式,通過制定url協(xié)議,雙方各自解析,使用iframe來(lái)調(diào)用native代碼,實(shí)現(xiàn)互通。

3、利用 WebChromeClient 中的 onJsAlert、onJsConfirm、onJsPrompt 提示接口,同樣也是攔截操作。

使用清單:

//開啟Js可用
mWebView.getSettings().setJavaScriptEnabled(true);

// 創(chuàng)建要注入的 Java 類
public class NativeInterface {

    private Context mContext;

    public NativeInterface(Context context) {
        mContext = context;
    }

    @JavascriptInterface
    public void hello() {
        Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show();
    }

    @JavascriptInterface
    public void hello(String params) {
        Toast.makeText(mContext, params, Toast.LENGTH_SHORT).show();
    }

    @JavascriptInterface
    public String getAndroid() {
        Toast.makeText(mContext, "getAndroid", Toast.LENGTH_SHORT).show();
        return "Android data";
    }

}

// WebView 注入即可
mWebView.addJavascriptInterface(new NativeInterface(this), "AndroidNative");

//Js編寫
<script>
    function callHello(){
        AndroidNative.hello();
    }

    function callHello1(){
        AndroidNative.hello('hello Android');
    }

    function callAndroid(){
        var temp = AndroidNative.getAndroid();
        console.log(temp);
        alert(temp);
    }  

</script>

Native 調(diào)用 Js:mWebView.loadUrl(js);

Js 調(diào)用 Native :AndroidNative.getAndroid();

4.2版本以下會(huì)存在漏洞,4.2以上需要添加 @JavascriptInterface 注解才能被調(diào)用到,Js 調(diào)用方式不變。


六、Js 注入漏洞

雖然可以通過注入方式來(lái)實(shí)現(xiàn) WebView 和 JS 交互,但是實(shí)現(xiàn)功能的同時(shí)也帶了安全問題,通過注入的 Java 類作為橋梁,JS 就可以利用這個(gè)漏洞。

常見漏洞

目前已知的 WebView 漏洞有 4 個(gè),分別是:

1、CVE-2012-6636,揭露了 WebView 中 addJavascriptInterface 接口會(huì)引起遠(yuǎn)程代碼執(zhí)行漏洞;
2、CVE-2013-4710,針對(duì)某些特定機(jī)型會(huì)存在 addJavascriptInterface API 引起的遠(yuǎn)程代碼執(zhí)行漏洞;
3、CVE-2014-1939 爆出 WebView 中內(nèi)置導(dǎo)出的 “searchBoxJavaBridge_” Java Object 可能被利用,實(shí)現(xiàn)遠(yuǎn)程任意代碼;
4、CVE-2014-7224,類似于 CVE-2014-1939 ,WebView 內(nèi)置導(dǎo)出 “accessibility” 和 “accessibilityTraversal” 兩個(gè) Java Object 接口,可被利用實(shí)現(xiàn)遠(yuǎn)程任意代碼執(zhí)行。

如何解決漏洞

1、Android 4.2 以下不要在使用 JavascriptInterface方式,4.2 以上需要添加注解 @JavascriptInterface 才能調(diào)用。(這部分和JsBrige 有關(guān),更詳細(xì)的內(nèi)容后面會(huì)介紹)

2、同1解決;

3、在創(chuàng)建 WebView 時(shí),使用 removeJavascriptInterface 方法將系統(tǒng)注入的 searchBoxJavaBridge_ 對(duì)象刪除。

4、當(dāng)系統(tǒng)輔助功能服務(wù)被開啟時(shí),在 Android 4.4 以下的系統(tǒng)中,由系統(tǒng)提供的 WebView 組件都默認(rèn)導(dǎo)出 ”accessibility” 和 ”accessibilityTraversal” 這兩個(gè)接口,這兩個(gè)接口同樣存在遠(yuǎn)程任意代碼執(zhí)行的威脅,同樣的需要通過 removeJavascriptInterface 方法將這兩個(gè)對(duì)象刪除。

       super.removeJavascriptInterface("searchBoxJavaBridge_");
       super.removeJavascriptInterface("accessibility");
       super.removeJavascriptInterface("accessibilityTraversal");

以上都是系統(tǒng)機(jī)制層面上的漏洞,還有一些是使用 WebView 不擋產(chǎn)生的漏洞。

5、通過 WebSettings.setSavePassword(false) 關(guān)閉密碼保存提醒功能,防止明文密碼存在本地被盜用。

6、WebView 默認(rèn)是可以使用 File 協(xié)議的,也就是 setAllowFileAccess(true),我們應(yīng)該是主動(dòng)設(shè)置為 setAllowFileAccess(false),防止加載本地文件,移動(dòng)版的 Chrome 默認(rèn)禁止加載 file 協(xié)議的文件

setAllowFileAccess(true);//設(shè)置為 false 將不能加載本地 html 文件
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
if (url.startsWith("file://") {
    setJavaScriptEnabled(false);
} else {
    setJavaScriptEnabled(true);
}

安全修復(fù)案例

推薦 SafeWebView 這個(gè)庫(kù)中解決了 Android WebView 中 Js 注入漏洞問題,另外還包含了一些異常處理。可以自行下載閱讀源碼。


七、一些坑

主要總結(jié) WebView 相關(guān)的疑難 bug,由于 Android 版本嚴(yán)重碎片化,在使用 WebView 的時(shí)候也會(huì)遇到各種個(gè)樣的坑,特別是 4.4 之后更換了 WebView 內(nèi)核,4.2 以下有部分漏洞,所以想把經(jīng)歷過的 WebView 這些坑記錄下來(lái),僅供參考。

1、android.webkit.AccessibilityInjector$TextToSpeechWrapper

java.lang.NullPointerException
    at android.webkit.AccessibilityInjector$TextToSpeechWrapper$1.onInit(AccessibilityInjector.java:753)
    at android.speech.tts.TextToSpeech.dispatchOnInit(TextToSpeech.java:640)
    at android.speech.tts.TextToSpeech.initTts(TextToSpeech.java:619)
    at android.speech.tts.TextToSpeech.<init>(TextToSpeech.java:553)
    at android.webkit.AccessibilityInjector$TextToSpeechWrapper.<init>(AccessibilityInjector.java:676)
    at android.webkit.AccessibilityInjector.addTtsApis(AccessibilityInjector.java:480)
    at android.webkit.AccessibilityInjector.addAccessibilityApisIfNecessary(AccessibilityInjector.java:168)
    at android.webkit.AccessibilityInjector.onPageStarted(AccessibilityInjector.java:340)
    at android.webkit.WebViewClassic.onPageStarted(WebViewClassic.java:4480)
    at android.webkit.CallbackProxy.handleMessage(CallbackProxy.java:366)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loop(Looper.java:194)
    at android.app.ActivityThread.main(ActivityThread.java:5407)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:525)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
    at dalvik.system.NativeStart.main(Native Method)

此問題在4.2.1和4.2.2比較集中,關(guān)閉輔助功能,google 下很多結(jié)果都是一樣的。

修復(fù)方法:在初始化 WebView 時(shí)調(diào)用disableAccessibility方法即可。

public static void disableAccessibility(Context context) {
        if (Build.VERSION.SDK_INT == 17/*4.2 (Build.VERSION_CODES.JELLY_BEAN_MR1)*/) {
            if (context != null) {
                try {
                    AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
                    if (!am.isEnabled()) {
                        //Not need to disable accessibility
                        return;
                    }

                    Method setState = am.getClass().getDeclaredMethod("setState", int.class);
                    setState.setAccessible(true);
                    setState.invoke(am, 0);/**{@link AccessibilityManager#STATE_FLAG_ACCESSIBILITY_ENABLED}*/
                } catch (Exception ignored) {
                    ignored.printStackTrace();
                }
            }
        }
    }

2、android.content.pm.PackageManager$NameNotFoundException

AndroidRuntimeException: android.content.pm.PackageManager$NameNotFoundException: com.google.android.webview
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4604)
    at android.app.ActivityThread.access$1500(ActivityThread.java:154)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1389)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5302)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:916)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:711)
Caused by: android.util.AndroidRuntimeException: android.content.pm.PackageManager$NameNotFoundException: com.google.android.webview
    at android.webkit.WebViewFactory.getFactoryClass(WebViewFactory.java:174)
    at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:109)
    at android.webkit.WebView.getFactory(WebView.java:2194)
    at android.webkit.WebView.ensureProviderCreated(WebView.java:2189)
    at android.webkit.WebView.setOverScrollMode(WebView.java:2248)
    at android.view.View.<init>(View.java:3588)
    at android.view.View.<init>(View.java:3682)
    at android.view.ViewGroup.<init>(ViewGroup.java:497)
    at android.widget.AbsoluteLayout.<init>(AbsoluteLayout.java:55)
    at android.webkit.WebView.<init>(WebView.java:544)
    at android.webkit.WebView.<init>(WebView.java:489)
    at android.webkit.WebView.<init>(WebView.java:472)
    at android.webkit.WebView.<init>(WebView.java:459)
    at android.webkit.WebView.<init>(WebView.java:449)

現(xiàn)象:在創(chuàng)建 WebView 時(shí)崩潰,跟進(jìn)棧信息,我們需要在 setOverScrollMode 方法上加異常保護(hù)處理

修復(fù)方法:

try {
    super.setOverScrollMode(mode);
} catch (Throwable e) {
    e.printStackTrace();
}  

不過上面捕獲的異常范圍有點(diǎn)廣,在github上找到一個(gè)更全面的修復(fù)方法

try{
    super.setOverScrollMode(mode);
} catch(Throwable e){
        String messageCause = e.getCause() == null ? e.toString() : e.getCause().toString();
String trace = Log.getStackTraceString(e);
if (trace.contains("android.content.pm.PackageManager$NameNotFoundException")
  || trace.contains("java.lang.RuntimeException: Cannot load WebView")
    || trace.contains("android.webkit.WebViewFactory$MissingWebViewPackageException: Failed to load WebView provider: No WebView installed")) {
      e.printStackTrace();
    }else{
      throw e;
    }        
}

3、android.webkit.WebViewClassic.clearView

at android.webkit.BrowserFrame.nativeLoadUrl(Native Method)
System.err:     at android.webkit.BrowserFrame.loadUrl(BrowserFrame.java:279)
System.err:     at android.webkit.WebViewCore.loadUrl(WebViewCore.java:2011)
System.err:     at android.webkit.WebViewCore.access$1900(WebViewCore.java:57)
System.err:     at android.webkit.WebViewCore$EventHub$1.handleMessage(WebViewCore.java:1303)
System.err:     at android.os.Handler.dispatchMessage(Handler.java:99)
System.err:     at android.os.Looper.loop(Looper.java:137)
System.err:     at android.webkit.WebViewCore$WebCoreThread.run(WebViewCore.java:812)
System.err:     at java.lang.Thread.run(Thread.java:856)
webcoreglue: *** Uncaught exception returned from Java call!
System.err: java.lang.NullPointerException
System.err:     at android.webkit.WebViewClassic.clearView(WebViewClassic.java:2868)
System.err:     at android.webkit.WebViewCore.setupViewport(WebViewCore.java:2497)
System.err:     at android.webkit.WebViewCore.updateViewport(WebViewCore.java:2479)
System.err:     at android.webkit.BrowserFrame.nativeLoadUrl(Native Method)
System.err:     at android.webkit.BrowserFrame.loadUrl(BrowserFrame.java:279)
System.err:     at android.webkit.WebViewCore.loadUrl(WebViewCore.java:2011)
System.err:     at android.webkit.WebViewCore.access$1900(WebViewCore.java:57)
System.err:     at android.webkit.WebViewCore$EventHub$1.handleMessage(WebViewCore.java:1303)
System.err:     at android.os.Handler.dispatchMessage(Handler.java:99)
System.err:     at android.os.Looper.loop(Looper.java:137)

這個(gè)bug是在某些設(shè)備上發(fā)生的,是在調(diào)用webView.destroy() 之前調(diào)用了loadurl操作發(fā)生的,也不是畢現(xiàn)問題,所以只能跟進(jìn)源碼查看,
在清空 webview destroy 時(shí),調(diào)用清理方法,內(nèi)部可能時(shí)機(jī)有問題,會(huì)出現(xiàn),WebViewClassic 中 mWebViewCore 對(duì)象為null,其內(nèi)部為handler消息機(jī)制。

修復(fù)方法:

public void logdUrl(final String url) {
    try {
        super.loadUrl(url);
    } catch (NullPointerException e) {
        e.printStackTrace();
    }
}


八、JSBridge

相信很多都或多或少的了解 JsBridge,不管是 iOS 平臺(tái)還是 Android平臺(tái),特別是 Hybrid 應(yīng)用,肯定是要用的 JsBridge 這個(gè)機(jī)制來(lái)建立 Native 和 Web 端的通信。

本部分簡(jiǎn)單闡述下 JsBridge 原理,以及分析兩個(gè)實(shí)際案例。

JsBridge 介紹:

JSBridge 我們可以比喻成一座橋或者一根管道,一端是 Web一端是 Native。我們搭建這個(gè)通道的目的就是讓 Native 和 Web 之間互相調(diào)用更為方便統(tǒng)一和簡(jiǎn)潔。
JSBridge 做得好的一個(gè)典型就是微信,微信給開發(fā)者提供了 JSSDK,該SDK中暴露了很多微信native層的方法,比如支付,定位等。使用起來(lái)非常方便。

JsBridge 原理:

前面我們分析了 WebView 如何于 JavaScript 交互的,JSBridge 就是在這些基礎(chǔ)之上做擴(kuò)展使它支持更復(fù)雜的功能,三種形式兩種原理分析如下:

1、使用 addJavascriptInterface

原理:這是Android提供的Js與Native通信的官方解決方案,將 java 對(duì)象注入到 Js 中直接作為window的某一變量來(lái)使用。

2、WebViewClient 中 shouldOverrideUrlLoading (WebView view, String url)。

利用 scheme iframe 機(jī)制,只要有iframe 加載,shouldOverrideUrlLoading 方法就會(huì)有回調(diào)。可以構(gòu)造一個(gè)特殊格式的url,使用shouldOverrideUrlLoading 方法攔截url,根據(jù)解析url來(lái)之行native方法邏輯。

3、利用 WebChromeClient 中的 onJsAlert、onJsConfirm、onJsPrompt 提示接口,同樣也是攔截操作。

利用 js調(diào)用window對(duì)象的對(duì)應(yīng)的方法,即 window.alert,window.confirm,window.prompt,WebChromeClient 對(duì)象中的三個(gè)方法 onJsAlert、onJsConfirm、onJsPrompt 就會(huì)被觸發(fā),有了js到native的通道,那么我們就可以制定協(xié)議來(lái)約束對(duì)方。最終我們選擇使用 prompt 方法,onJsPrompt()方法的message參數(shù)的值正是Js的方法window.prompt()的message的值。

匯總:后面兩種雖然形式不同,但是原理是相同的,都是對(duì)url或者參數(shù)做文章,通過制定參數(shù)協(xié)議,不管是url還是message,到native攔截處理。native 調(diào)用 Js 只有一種方式,就是使用loadUrl(js),js 為在web端定義好的javascript 函數(shù)。

以上就是所有 JsBridge 的原理,自己可以寫給demo跑一下,下面看幾個(gè)問題:

1、如何避免 JS、Android、iOS 相互調(diào)用時(shí),需要事先“約定”方法名稱和參數(shù)?
2、原生調(diào)用 JS 方法,能否類似原生開發(fā)一樣,使用 Callback(block) 做為回調(diào)方式?
3、JS 調(diào)用原生能否使用 function 獲得返回值?

問題來(lái)源于網(wǎng)絡(luò),基本上都是這幾個(gè)疑問,下面我們帶著疑問去分析三個(gè)個(gè)方案。

JsBridge 案例:

一、 H5與Native交互之JSBridge技術(shù)

本案例為有贊技術(shù)團(tuán)隊(duì)博客分享的H5與Native交互之JSBridge技術(shù),并沒有最終完全的代碼,不過很清楚的分析了IOS和Android與Javascript的底層交互原理。

1、實(shí)現(xiàn)原理

通過schema方式,使用shouldOverrideUrlLoading方法對(duì)url協(xié)議進(jìn)行解析。

var url = 'jsbridge://doAction?title=分享標(biāo)題&desc=分享描述&link=http%3A%2F%2Fwww.baidu.com';  
var iframe = document.createElement('iframe');  
iframe.style.width = '1px';  
iframe.style.height = '1px';  
iframe.style.display = 'none';  
iframe.src = url;  
document.body.appendChild(iframe);  
setTimeout(function() {  
    iframe.remove();
}, 100);

可以到看有贊技術(shù)是通過自定義url協(xié)議來(lái)作為傳輸媒介,這樣 Android 就可以攔截這個(gè)請(qǐng)求,從而解析出相應(yīng)的方法和參數(shù)

不過此 url 中的參數(shù)是以鍵值對(duì)的方式傳遞,我是建議使用 Json 作為傳輸參數(shù)比較好,靈活清楚。

2、庫(kù)的封裝

有贊將 Js與 Native 通訊封裝了一個(gè)通用的方法,我這里直接復(fù)制過來(lái)分析下:

YouzanJsBridge = {  
    doCall: function(functionName, data, callback) {
        var _this = this;
        // 解決連續(xù)調(diào)用問題
        if (this.lastCallTime && (Date.now() - this.lastCallTime) < 100) {
            setTimeout(function() {
                _this.doCall(functionName, data, callback);
            }, 100);
            return;
        }
        this.lastCallTime = Date.now();

        data = data || {};
        if (callback) {
            $.extend(data, { callback: callback });
        }

        if (UA.isIOS()) {
            $.each(data, function(key, value) {
                if ($.isPlainObject(value) || $.isArray(value)) {
                    data[key] = JSON.stringify(value);
                }
            });
            var url = Args.addParameter('youzanjs://' + functionName, data);
            var iframe = document.createElement('iframe');
            iframe.style.width = '1px';
            iframe.style.height = '1px';
            iframe.style.display = 'none';
            iframe.src = url;
            document.body.appendChild(iframe);
            setTimeout(function() {
                iframe.remove();
            }, 100);
        } else if (UA.isAndroid()) {
            window.androidJS && window.androidJS[functionName] && window.androidJS[functionName](JSON.stringify(data));
        } else {
            console.error('未獲取platform信息,調(diào)取api失敗');
        }
    }
}

這樣不管是和iOS通信還是 Android,都只需要調(diào)用 YouzanJsBridge.doCall() 方法即可,講兩個(gè)平臺(tái)的不同屏蔽在封裝基礎(chǔ)上,這樣也有利于 Web 端代碼的整潔和代碼兼容。

當(dāng)然這里的Android 平臺(tái)是沒有 callback 回調(diào)的,如果你想實(shí)現(xiàn)兩端互調(diào)的機(jī)制,請(qǐng)參考下一個(gè)案例,里面會(huì)詳細(xì)介紹這部分。

3、一些優(yōu)化

將項(xiàng)目通用方法抽象

例如:

1.getData(datatype, callback, extra) H5從Native APP獲取數(shù)據(jù)
使用場(chǎng)景:H5需要從Native APP獲取某些數(shù)據(jù)的時(shí)候,可以調(diào)用這個(gè)方法。

2.putData(datatype, data) H5告訴Native APP一些數(shù)據(jù)
使用場(chǎng)景:H5告訴Native APP一些數(shù)據(jù),可以調(diào)用這個(gè)方法。

3.gotoWebview(url, page, data) Native APP新開一個(gè)Webview窗口,并打開相應(yīng)網(wǎng)頁(yè)

4.doAction(action, data) 功能上的一些操作

等等其他方法,我相信如果你自己寫過 native和js 調(diào)用demo,上面抽象出來(lái)的方法并不陌生,所以,如果你的業(yè)務(wù)沒有那么復(fù)雜,沒有像微信那樣,需要提供給數(shù)以萬(wàn)計(jì)開發(fā)者去用去擴(kuò)展,這種抽象出一些通用方法的方式不是為一種節(jié)省成本,快速迭代,方便的方式。

小結(jié):總之萬(wàn)變不離其宗,所有封裝或者框架的東西,使用的東西都還是最基本的方法,只是對(duì)基礎(chǔ)做一個(gè)什么樣程度擴(kuò)展,或深或淺,唯一的只要用著舒服就行。

二:JsBridge

1、使用方法:

注意Android 和 Web 使用方式

webView.registerHandler("submitFromWeb", new BridgeHandler() {

    @Override
    public void handler(String data, CallBackFunction function) {
      function.onCallBack("native submitFromWeb 方法, 返回 data");
    }
  });

  webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
    @Override
    public void onCallBack(String data) {
      Log.i(TAG, "onCallBack : " + data);
    }
  });

  window.WebViewJavascriptBridge.callHandler(
      'submitFromWeb'
      , {'param': '中文測(cè)試'}
            , function(responseData) {
    document.getElementById("show").innerHTML = "send get responseData from Android 端, data = " + responseData
  }
        );

  bridge.registerHandler("functionInJs", function(data, responseCallback) {
    document.getElementById("show").innerHTML = ("data from Java: = " + data);
    var responseData = "call functionInJs success,return android!";
    responseCallback(responseData);
  });

2、關(guān)鍵類作用:

在講 JsBridge 的實(shí)現(xiàn)之前,首先要講下各個(gè)文件的作用

  • Message.java:JsBridge 中的消息對(duì)象,用來(lái)封裝native與js交互時(shí)的json數(shù)據(jù),包含:callid、responseid、responseData、handlerName等字段。
  • WebViewJavascriptBridge.js:改js文件會(huì)被注入到各個(gè)頁(yè)面,和native中封裝處理Message消息邏輯類似,同樣提供了初始化、注冊(cè)Handler、調(diào)用Handler等方法,之后js都是通過此文件中的方法和native統(tǒng)一溝通。
  • WebViewJavascriptBridge.java:native 端,Bridge接口類,定義了發(fā)送信息的方法,由BridgeWebView來(lái)實(shí)現(xiàn),之后調(diào)用可以通過webview.send()方式調(diào)用。
  • BridgeWebView.java:WebView的子類,實(shí)現(xiàn)了WebViewJavascriptBridge接口,并提供了注冊(cè)、調(diào)用 Handler等方法,之后都是通過webview.registerHandler()或者webview.callHandler()方式調(diào)用js.
  • BridgeWebViewClient.java:WebViewClient的子類,重寫了ShouldOverrideUrlLoading,onPageFinish,onPageStart等方法。
  • BridgeHandler.java:作為native與web交互的通道。native通過Handler的名稱來(lái)找到響應(yīng)的Handler來(lái)操作,這樣才能實(shí)現(xiàn)js回調(diào)給native端,也就是registerHandler時(shí)候注冊(cè)的監(jiān)聽。
  • CallBackFunction.java:native定義的回調(diào)函數(shù), Handler處理完成后,用來(lái)給Js發(fā)送消息,對(duì)應(yīng)處理js中function中。

3、實(shí)現(xiàn)原理:

看完這些主要類的作用,在看流程就清晰了。

native 調(diào) js:

1、WebView.callHandler('handlerName','{}',callBack);
2、doSend 中組裝 要傳輸?shù)?Message 對(duì)象,并設(shè)置setCallbackId(生成唯一的id是為了方便在js回調(diào)回來(lái)的時(shí)候在android端查找對(duì)于的 callback)
3、dispatchMessage javascript:WebViewJavascriptBridge._handleMessageFromNative(message);方法 message 為上一步的Message 對(duì)象對(duì)應(yīng)的 Json 數(shù)據(jù)。
4、在 Js _dispatchMessageFromNative 函數(shù)中,根據(jù) messageJSON json 數(shù)據(jù)中的字段 handlerName 找到對(duì)對(duì)應(yīng)的 handler 方法執(zhí)行。
handler = messageHandlers[message.handlerName];handler 這個(gè)就是在 html 中 registerHandler 注冊(cè)的回調(diào)function(data, responseCallback)函數(shù)
5、之后執(zhí)行 html 中 responseCallback(responseData); 會(huì)觸發(fā) WebViewJavascriptBridge.js 文件中的 _doSend函數(shù),_doSend 通過 messagingIframe.src 形式傳給 android端,shouldOverrideUrlLoading 接受,并攔截內(nèi)容處理。
6、第一次:攔截執(zhí)行到 webView.flushMessageQueue()方法,并調(diào)用 responseCallbacks.put(jsUrl,returnCallback);,同時(shí)調(diào)用 javascript:WebViewJavascriptBridge._fetchQueue(); 來(lái)查詢消息的返回值,并執(zhí)行一次messagingIframe.src。
7、第二次:攔截執(zhí)行到 webView.handlerReturnData() 方法,并調(diào)用上一步注冊(cè)的 CallBackFunction.onCallBack,然后根據(jù)responseId 即 android 傳過來(lái)的 message.callbackId,找到 使用者注冊(cè)的 CallBackFunction 回調(diào)。

本次的流程就走完了,看幾十遍就看懂了哈。

Js -> android:

1、window.WebViewJavascriptBridge.callHandler
2、執(zhí)行 Js 函數(shù) _doSend() 觸發(fā) messagingIframe.src
3、android 端攔截,shouldOverrideUrlLoading
4、第一次:攔截執(zhí)行到 webView.flushMessageQueue()方法,并調(diào)用 responseCallbacks.put(jsUrl,returnCallback);,同時(shí)調(diào)用 javascript:WebViewJavascriptBridge._fetchQueue(); 來(lái)查詢消息的返回值,并執(zhí)行一次messagingIframe.src。
5、第二次:攔截執(zhí)行到 webView.handlerReturnData() 方法,并調(diào)用上一步注冊(cè)的 CallBackFunction.onCallBack -> responseId 為空 -> handler.handler(m.getData(), responseFunction)-> 外部回調(diào)( function.onCallBack("native submitFromWeb 方法, 返回 data");)-> queueMessage -> dispatchMessage ->loadUrl(javascriptCommand) -> 回調(diào)結(jié)束

注意兩端的每一次通訊,Android 端的 shouldOverrideUrlLoading 方法都會(huì)執(zhí)行兩次,但是攜帶url不同,這里要注意兩次訪問是相互配合的,沒有第一次的消息查詢,也就不會(huì)有第二次的數(shù)據(jù)返回回調(diào)。

4、小結(jié)

通過url攔截方式,注入一個(gè)本地js文件,來(lái)橋接native和web,屏蔽了一些通性工作,在使用上方式相同,好理解,通過兩次來(lái)回調(diào)用實(shí)現(xiàn)了可回調(diào)function。

github 上也有很多類似的方案,這里就不一一分析了,如果你不是看的這個(gè)庫(kù),建議好好看看,挺巧妙的機(jī)制。

這塊邏輯也是看了好久,挺繞的,可以結(jié)合打log和debug來(lái)分析。

三:DSBridge-Android

1、使用方法

注意Android 和 Web 使用方式

    webView.callHandler("addValue",new Object[]{1,"hello"},new CallBackFunction(){
        @Override
        public void onCallBack(String retValue) {
            Log.d("jsbridge","call succeed,return value is "+retValue);
        }
    });
   webView.callHandler("test",null);

   dsBridge.call("testNever", {msg: "testSyn"});

   dsBridge.call("testNoArgAsyn", function (v) {
       alert(v);
   });

2、關(guān)鍵類作用:

  • DWebView:WebView的子類,提供了注冊(cè)、調(diào)用 Handler等方法,之后都是通過 webview.callHandler()方式調(diào)用js.
  • CompletionHandler:作為native與web交互的通道,異步回調(diào)的關(guān)鍵操作類。
  • OnReturnValue:js 回調(diào) android的接口,根據(jù) handlerMap 儲(chǔ)存的id作為 handler 標(biāo)示即OnReturnValue實(shí)現(xiàn)類。
  • JsApi:需要注冊(cè)到 Js java 類。

看完這些主要類的作用,在看流程就清晰了。

3、實(shí)現(xiàn)原理:

本質(zhì)還是使用的系統(tǒng)默認(rèn) addJavascriptInterface 方式,其中做了擴(kuò)展。

首先需要將這段js注入到 Web 頁(yè)面

function getJsBridge() {
    window._dsf = window._dsf || {};
    return {
        call: function(b, a, c) {
            "function" == typeof a && (c = a, a = {});
            if ("function" == typeof c) {
                window.dscb = window.dscb || 0;
                var d = "dscb" + window.dscb++;
                window[d] = c;
                a._dscbstub = d
            }
            a = JSON.stringify(a || {});
            return window._dswk ? prompt(window._dswk + b, a) : "function" == typeof _dsbridge ? _dsbridge(b, a) : _dsbridge.call(b, a)
        },
        register: function(b, a) {
            "object" == typeof b ? Object.assign(window._dsf, b) : window._dsf[b] = a
        }
    }
}

dsBridge = getJsBridge();

native -> js:

1、webview.callHandler(method,args,handler);
2、拼接Js,并生成唯一的callID,將 handler保存到map中;
3、執(zhí)行js,同時(shí)回調(diào) java returnValue(),然后回調(diào)使用者調(diào)用。

js -> android:

1、dsBridge.call(method,json,function)-> native call(methodName,args);
2、通過反射來(lái)查找methodName對(duì)應(yīng)的方法,并注冊(cè) CompletionHandler;
3、JsApi 通過 handler.complete() 方法,并拼裝js方法和參數(shù),再次調(diào)用js函數(shù)實(shí)現(xiàn)回調(diào)操作,并刪除上一次的 callback id。

4、小結(jié):

這個(gè)方案比較簡(jiǎn)單,使用了系統(tǒng)的注入方式,雖然 4.2以下存在漏洞,但是它里面只能反射包含 @JavascriptInterface 注解的方法,所以和4.2以上注入是一樣的,也是安全的。

相對(duì)案例一,案例二實(shí)現(xiàn)方式比較簡(jiǎn)單粗暴,也比較容易懂。

不過這個(gè)庫(kù)在我的 4.2 設(shè)備上有 bug,下面函數(shù)執(zhí)行時(shí)找不到 dsBridge 對(duì)象,導(dǎo)致 Native 調(diào)用 Js 失敗。

 dsBridge.register('addValue',function(l,r){
     return l+r;
 })

可能和內(nèi)核執(zhí)行有關(guān)系,我這做了修復(fù),放到 function 函數(shù)中執(zhí)行就可以了,如果你也遇到,可以參考我的修復(fù)方法 DSBridge-Android


九、WebView 緩存原理分析和應(yīng)用

這部分內(nèi)容可以參考這兩篇博文:

寫的已經(jīng)很清楚了,我這里就不贅述了。

主要包含一下內(nèi)容:

1、WebView 的5中緩存類型,以及每個(gè)緩存類型工作原理、相同點(diǎn)和不同點(diǎn)、。
2、緩存在手機(jī)上的存儲(chǔ)。
3、每種緩存機(jī)制案例。

如果你想通過過濾來(lái)減緩 WebView 請(qǐng)求網(wǎng)絡(luò),可以參考 rexxar-android 中關(guān)于攔截url操作讀取本地操作。

作者:無(wú)名小子的雜貨鋪
鏈接:http://www.lxweimin.com/p/fd61e8f4049e
來(lái)源:簡(jiǎn)書
簡(jiǎn)書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評(píng)論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,727評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,193評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,411評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,777評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,978評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,216評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,657評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,960評(píng)論 2 373