Webview和js與native互調

Webview和js與native互調

前端相關知識

同源策略:
當域名和端口名相同則稱爲同源

XMLHttpRequest 對象
可以在頁面加載後與服務器進行收發包
可以只更新局部界面

跨域:
瀏覽器的同源策略導致了跨域
在瀏覽器中 不同來源的網站是不能相互訪問 避免被隨意修改內容

HTML 標籤語言 用於設計網頁的樣式、內容 HTML5 新的第五代HTML規範 有<video 標籤等
若網站使用了HTML5 則會有<HTML5 標籤
CSS 由key value構成 HTML中加入CSS標籤 用於美化界面
JS 腳本語言 用於實現動態頁面 即響應點擊之類的

mime type 媒體類型 type/subtype text/html
content type 類似mime type

WebView相關知識

1 WebView在 Android 4.4 之前使用的是 Webkit內核,在 Android 4.4 以後切換到了 Chromium 內核 通過chromium渲染引擎去渲染webview界面

2 Webview在 Android 7.0 以上直接使用了 google的webview(com.google.android.webview) 直接以apk的形式加入 而非之前的系統webview(com.android.webview) 並且若系統中安裝了chrome 則該webview會直接用該chrome作爲渲染,並且隨着chrome的更新而更新

3 Android8.0系統開始,默認開啓WebView多進程模式,即WebView運行在獨立的沙盒進程中
而在8.0以下,WebView會開啓線程,但是不會開啓進程 因此建議針對WebView開啓子進程去進行相關的操作
並且一般只起一個webview進程在一個程序中 其他不同的多個WebView存在這個進程中就好

1 針對webView所在的Activity設置android:process
2 bindService的方式 service也是要去開一個進程的
3 底層fork進程方式

4 查看webview的版本 在設置->應用->打開所有應用或者系統應用->看到一個叫 Android system webview的應用

5 替換系統的webview 在設置->開發者模式->webview實現裏面替換webview
(1) 若系統使用的是 com.google.android.webview 然後下載的是com.google.android.webview(google play下載的 android system webview)
則直接安裝會替換
(2) 若系統使用的是 com.android.webview 然後下載的是com.google.android.webview(google play下載的 android system webview)
得在設置->開發者模式->webview實現 裏面替換webview
(3) 若系統使用的是 com.android.webview 然後下載的是com.android.webview 要替換 得root
https://www.jianshu.com/p/1ddb10cfdef9

6 第三方Webview庫: 騰訊的x5 以及crosswalk 可以作爲庫代替系統的webview 而bromite庫的webview是com.android.webview
都是更改了webview內核的 對於chrome apk或者chrome_beta_apk 有的手機也是可以直接替換爲這個webview的

7 WebView只能加載http/https開頭的url 其他URL是會加載失敗的 報錯:error:-10 ERR_UNKNOWN_URL_SCHEME


遠程調試webview

1 打開應用的WebView調試權限:

WebView.setWebContentsDebuggingEnabled(true);

2 adb連接真機 然後用 chrome打開網站 chrome://inspect
即可查看連接的設備和該設置使用的WebView版本號

3 點擊inspect
可以查看到這個設備此時WebView展示的內容,以及和瀏覽器打開F12相同的Web調試欄


WebView接口

1 public void loadUrl (String url)

// 打開本地sd卡內的index.html文件 
wView.loadUrl("content://com.android.htmlfileprovider/sdcard/index.html");
// 打開assets目錄下的html文件
wView.loadUrl("file:///android_asset/webviewTest.html")
// 打開指定URL的html文件 
wView.loadUrl("http://www.baidu.com"); 

2 public void loadData (String data,
String mimeType,
String encoding)

直接加載html的字符串

String content = "<p><font color='red'>hello baidu!</font></p>";
webview.loadData(content, "text/html", "UTF-8");

String unencodedHtml =
     "<html><body>'%28' is the code for '('</body></html>";
 String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING);
 webView.loadData(encodedHtml, "text/html", "base64");

3 public boolean canGoBack ()
獲取該URL是否有back history

public void postUrl(String url, byte[] postData)
post方式加載URL 這裏的格式是 x-www-form-urlencoded
即 參數格式爲 xx=xx&xx=xx

設置cookie
這裏針對域名設置cookie即可

private fun setCookie(url: String) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            CookieManager.getInstance().setAcceptThirdPartyCookies(docsListWebView, true)
            CookieManager.getInstance().flush()
        }
        val cookieManager = CookieManager.getInstance()
        cookieManager.setAcceptCookie(true)
        /*cookieManager.removeSessionCookie(ValueCallback<Boolean>())//移除
        cookieManager.removeAllCookie()*/
        cookieManager.setCookie(url, "key=value")
        val newCookie = cookieManager.getCookie(url) // 獲取你設置的Cookie

4 getUrl()
獲取當前webview正在顯示的URL

5 pauseTimers() resumeTimers()
當應用程序被切換到後臺時回調,注意:該方法針對全應用程序的WebView,它會暫停所有webview的layout,parsing,javascripttimer。降低CPU功耗
對應是resumeTimers() 恢復pauseTimers時的動作
若沒有及時的resumeTimers 表現是界面加載不出來

記得搭配使用resumeTimers()

如果在webview回收時,調用了 pauseTimers() 有時得在重新初始化的時候 調用resumeTimers 不然webview有些功能用不了或者顯示不了啥的


WebViewClient接口

主要是WebView原生相關的

1 onPageStarted(WebView view, String url, Bitmap favicon):
當WebView開始加載一個URL時會回調該方法

2 onPageFinished(WebView view, String url):
當WebView加載一個URL完成後會回調該方法(1個iframe)
注意坑 onPagedFinished 有時並不是真正的pagedFinished 回調時機有時有問題

3 shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean
加載URL或者發生了重定向的時候 會回調這個界面 若是默默地加載了別的URL 不是重定向 則不一定會回調這個接口
request即爲js請求的URL
當URL即將加載到當前WebView時(1個iframe),會觸發這個回調
return true則攔截不跳轉 . false則跳轉
~不要使用 WebView.loadUrl(同一個url) 又 return true 這樣會阻止之前的URL加載又重新加載同一個URL 浪費資源
~不適用於post請求
~不適用https

若不創建WebViewClient重寫shouldOverrideUrlLoading 會默認通過瀏覽器打開URL
要內部跳轉 則要重寫該方法
而要部分URL跳轉到瀏覽器 則

// 根據URL判斷
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(i);

**4 onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) **
當webView加載頁面/重定向/默默地加載一些URL失敗 會回調該接口 然後再回調onPageFinished
不一定要在這裏返回錯誤就直接彈重新加載失敗的框 比如該界面加載了一個上報的URL,界面不會顯示,也不是重定向。只是加載了一個網絡請求,但是這個網絡請求加載失敗了(-6 ERR_CONNECTION等) 導致會回調這個接口
所以針對網絡錯誤 -2 ERR_INTERNET_DISCONNECTED/ERR_NAME_NOT_RESOLVED 才進行重新加載處理

通過標誌位 進行回調onReceivedError而不回調onPageFinished
onReceivedError{
        mIsFailed = true
        }
        override fun onPageFinished(view: WebView?, url: String?) {
        if (!mIsFailed) {
            Log.d(TAG, "onPageFinished,url:$url")
            xxx
        }
        mIsFailed = false
        super.onPageFinished(view, url)
    }


WebChromeClient接口

主要是chrome相關的

1 onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result):
當WebView中的頁面中調用js的prompt函數時會回調改方法讓native去彈框
若return true則表示接受彈框
JsPromptResult 爲返回給js的結果

2 onConsoleMessage(ConsoleMessage consoleMessage):
當WebView中的頁面通過js調用console來輸出日誌時會回調改方法

3 onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array>,fileChooserParams: FileChooserParams):Boolean
該接口用於Web界面調用native的資源文件 如打開相冊之類的(見下方具體流程)

4 onProgressChanged(view: WebView!, newProgress: Int)
網頁加載進度的回調

5 open fun onReceivedTitle(view: WebView!, title: String!): Unit
web設置的title

WebViewClient和WebChromeClient是在主線程上被回調的


WebSettings接口

webSetting.supportZoom(true) // 支持縮放
webSetting.displayZoomControls = true; // 是否顯示縮放工具
webSetting.builtInZoomControls = false // 設置是否支持縮放
webSetting.textZoom = 100; // 設置文字縮放 100即爲100%
webSetting.useWideViewPort = true // 使用ViewPort
webSetting.loadWithOverviewMode = true // 縮放至屏幕的大小
webSetting.javaScriptEnabled = true // 支持js
webSetting.domStorageEnabled = true // 開啓DOM
val appCachePath = context.cacheDir.absolutePath // 設置緩存路徑和支持緩存
webSetting.setAppCachePath(appCachePath)
webSetting.setAppCacheEnabled(true)
webSetting.allowFileAccess = true // 設置支持文件流
webSetting.javaScriptCanOpenWindowsAutomatically = true // 支持通過JS打開新窗口
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); // API19以下默認爲MIXED_CONTENT_ALWAYS_ALLOW API21以上爲MIXED_CONTENT_NEVER_ALLOW
webSettings.allowFileAccess = true // 設置支持文件訪問
webSettings.savePassword = false; // 不保存密碼



JS和native互調

首先設置支持JavaScript

WebSettings setting = webview.getSettings();
setting.setJavaScriptEnabled(true);

一、native調JS
1 public void loadUrl (String url)
不帶返回值的 直接loadUrl()加載js執行js函數

2 evaluateJavascript(String script, ValueCallback resultCallback)
帶返回值的 可以直接拿回調 回調是在JavaBridge線程上被回調的
JS去註冊方法 這邊就可以調用

網頁必須加載完畢(onPagedFinished) JS方法得是全局方法才能調用JS方法
可以傳遞和回調各種基本類型的數據 傳過去的時候是String 但是js會解析成爲相應的類型
注意:這裏針對的是這個webview加載的所有頁面都會調用js方法 所以得做好區分 業務邏輯做區分過濾

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {  //sdk>19纔有
   mWebView?.evaluateJavascript("javascript:getJSData('params from java',123,true)", ValueCallback { value ->
                Log.d(TAG, "valueCallback:$value")
            })
    });

若傳遞的參數是String 則要加上’ ’
返回值若是空 則返回String格式的"null"

都是在主進程中調用 evaluateJavascript必須在主進程中調, 只是webview內核會開啓線程執行的


二、 JS調native
1 方法劫持 自己制定僞協議 通過js和native的某些接口回調過來 然後解析協議做相應native方法調用
1.1 WebChromeClient的onJsPormpt 當JS調用彈出輸入框時會回調
1.2 WebChromeClient的console.log 當觸發打印日誌的時候會回調
1.3 每個 iframe 會發送URL 然後會回調 WebClient的shouldOverrideUrLoading()

// appName://JSCalljava?method=calljavamethod&xx=xxfunc#cbn(callbacknumber)
// url檢查 安全性校驗 根據method分類EB分發 或者 上層註冊方法下來(funcName,handler)根據funcName調handler
// 回調 則通過 不帶返回值的 loadurl的方式 直接帶上cbn

2 addJavascriptInterface(Object object, String objectName)
一定要做版本判斷 Android 4.2 否則4.2以下的機器可以通過js調用任何java代碼 甚至可以反射改東西

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            mWebView?.addJavascriptInterface(JSCallJavaMgr(), "JSCallJavaMgr")
        }

2.1 object:創建java對象 傳給js調用 objectName:將java對象用"objectName"表示
js就可以調用這個類的對象中被 @JavascriptInterface修飾的方法 參數直接傳遞就好
返回值
2.2 提供js調用的接口

    @JavascriptInterface
fun getJavaSIB(str: String, i: Int, b: Boolean): Boolean {
    Log.d(TAG, "params:$str, $i, $b")
    return true/"hello from java"
}

可以傳遞和回調各種基本類型的數據 但是Object好像不行 JsonObject是不行的 得JS先JSON.stringify(json) java這邊用String接收再解析成Json
在此處返回值的話,是同步的 要異步的可以根據自己定義callbackNumber 然後通過Native調js傳回調
是獲取不到java對象的屬性的

JS調native的native方法中,此時是在專門的JavaBridge線程中的 若要在這裏再調JS相關的方法,可能會報錯W/WebView: java.lang.Throwable: A WebView method was called on thread ‘JavaBridge’. All WebView methods must be called on the same thread.

2.3 在js中就可以調用 < input type="button" value="close" onClick="window.objectName.backToApp()" />


完整性校驗是將JsApi調用或Event、Callback的原始數據通過SHA1生成摘要,用於JavaScript和Java兩端進行校驗,防止數據的僞造。

動態proguard其實就是字符串替換,每次加載新的頁面後,在注入框架中的JS代碼前,對JS代碼某些特定字符串用一個隨機串替換掉,防止JS代碼被攔截、解析和僞造等。
動態proguard做如下處理
動態替換JS方法名或變量名;
動態插入參數或變量;(無用變量,用於打亂JS代碼,防止被解析)
加入無用代碼邏輯,防止關鍵邏輯被分析出來;

安全性問題 js注入? 跳垃圾網址?

1 不直接在 WebView 中使用 JavaScript,請勿調用 setJavaScriptEnabled()
2 addJavaScriptInterface()要在4.2及以上版本 並且用於應用 APK內含的JavaScript 否則不信任的網站可能會調用這些方法造成攻擊
3 4.4之前用的是webkit 必須確認 WebView 對象只顯示值得信任的內容。
要確保您的應用在 SSL 中不會暴露給潛在的漏洞,請使用可更新的安全 Provider 對象(如更新您的安全提供程序以防範 SSL 攻擊中所述)
如果您的應用必須從開放網絡渲染內容,請考慮提供您自己的渲染程序,以便使用最新的安全補丁程序確保讓其保持最新狀態
4 界面的跳轉 也要進行合法性檢查 避免跳轉到非法界面
5 檢查 JavaScript 界面中沒有任何通過 addJavascriptInterface 調用而導致添加的對象
6 在 WebView 加載不受信任的內容之前,通過 removeJavascriptInterface 從 shouldInterceptRequest 中移除 JavaScript 界面內的對象
7 如果您的應用需要向 WebView 的 JavaScript 界面提供對象,請確保 WebView 不會通過未加密的連接加載網絡內容。您可以在清單中將 android:usesCleartextTraffic 設爲 false,或設置禁止 HTTP 流量的網絡安全配置。您也可以防止任何受影響的 WebView 通過 loadUrl 加載採用 HTTP 協議的網址

WebView常見注意坑:

1 webview及時關閉銷燬 不然他會開啓一些線程 沒關閉乾淨 佔用內存
最好自己創建 然後自己銷燬

    if(mWebView != null) {
        mWebView.clearHistory();
        mWebView.clearCache(true);
        mWebView.loadUrl("about:blank"); // clearView() should be changed to loadUrl("about:blank"), since clearView() is deprecated now
        mWebView.freeMemory(); 
        // mWebView.pauseTimers();
        mWebView = null; // Note that mWebView.destroy() and mWebView = null do the exact same thing
    }

2 webview移到後臺 要暫停JS的操作 避免耗電

// webView 處於激活狀態,能正常加載和響應網頁
webView.onResume();

// webView 處於暫停狀態,當頁面失去焦點切換到後臺時調用
// 處於 pause 狀態的 WebView 會停止動畫和計算,但是不會停止 JavaScript 的執行
webView.onPause();

//暫停所有 WebView 的 layout、parsing、JavaScriptTimers 以降低 CPU 消耗(全局有效)
webView.pauseTimers();

// 恢復 pauseTimers 的暫停狀態
webView.resumeTimers();

WebView佔用內存大 可能會有OOM問題
連續開啓多個WebView頁面,此時棧底的Activity被銷燬了,返回時Activity需要重新創建

建議單獨抽出Activity放WebView


模擬點擊屏幕

private void clickPlay(WebView view) {
        // mimic onClick() event on the center of the WebView
        long delta = 100;
        long downTime = SystemClock.uptimeMillis();
        float x = view.getLeft() + (view.getWidth() / 2);
        float y = view.getTop() + (view.getHeight() / 2);

        MotionEvent tapDownEvent = MotionEvent.obtain(downTime, downTime + delta, MotionEvent.ACTION_DOWN, x, y, 0);
        tapDownEvent.setSource(InputDevice.SOURCE_CLASS_POINTER);
        MotionEvent tapUpEvent = MotionEvent.obtain(downTime, downTime + delta + 2, MotionEvent.ACTION_UP, x, y, 0);
        tapUpEvent.setSource(InputDevice.SOURCE_CLASS_POINTER);

        view.dispatchTouchEvent(tapDownEvent);
        view.dispatchTouchEvent(tapUpEvent);
    }

Web打開系統相冊

Web端:

<input id="fileImage" type="file" name="fileselect" accept="image/*">

Android端:
1 授予基本權限

   <!-- 讀寫手機存儲 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

2 重寫WebChromeClient的onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array>,fileChooserParams: FileChooserParams):Boolean 接口*
web端調用了上方的input標籤,則會調用這個接口,然後我們通過Intent打開系統相冊或者支持該Intent的第三方應用來選擇圖片

override fun onShowFileChooser(
        webView: WebView,
        filePathCallback: ValueCallback<Array<Uri>>,
        fileChooserParams: FileChooserParams
    ): Boolean {
        webViewBaseActivity.mFilePathCallback = filePathCallback
        val i = Intent(Intent.ACTION_GET_CONTENT)
        i.addCategory(Intent.CATEGORY_OPENABLE)
        i.type = "image/*"
        webViewBaseActivity.startActivityForResult(Intent.createChooser(i, "Image Chooser"), WebViewBaseActivity.FILE_CHOOSER_REQ_CODE)
        return true
    }

3在onActivityResult()中將選擇的圖片內容通過ValueCallback的onReceiveValue方法返回Uri給Web,這樣Web就知道我們選擇了什麼文件

internal var mFilePathCallback: ValueCallback<Array<Uri>>? = null
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == FILE_CHOOSER_REQ_CODE && resultCode == Activity.RESULT_CANCELED) {
            Log.i(TAG, "onActivityResult:Canceled")
            mFilePathCallback?.onReceiveValue(null)
        }
        if (requestCode == FILE_CHOOSER_REQ_CODE && resultCode == Activity.RESULT_OK) {
            val result = data?.dataString
            if (result != null) {
               Log.i(TAG, "onActivityResult:$result")
                mFilePathCallback?.onReceiveValue(arrayOf(Uri.parse(result)))
            } else {
                Log.i(TAG, "onActivityResult:result is null")
                mFilePathCallback?.onReceiveValue(null)
            }
        }
        super.onActivityResult(requestCode, resultCode, data)
    }

通過系統自帶的瀏覽器訪問網頁

// 打開網址 這個是通過打開android自帶的瀏覽器進行的打開網址
    Uri uri = Uri.parse(str);
    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    if (intent.resolveActivity(getPackageManager()) != null) {
        // 網址正確 跳轉成功
        startActivity(intent);
    } else {
        //網址不正確 跳轉失敗 提示錯誤
        Toast.makeText(MainActivity.this, "網址輸入錯誤,請重新輸入!", Toast.LENGTH_SHORT).show();

    }

問題

1 Duplicate showFileChooser result
解決: onShowFileChooser 自己處理了的話 return true就好

2 點擊取消後 再次打開沒反應
原因:沒有回調onShowFileChooser
解決: 主動再set一個null回調

   if (requestCode == FILE_CHOOSER_REQ_CODE && resultCode == Activity.RESULT_CANCELED) {
            Log.i(TAG, "onActivityResult:Canceled")
            mFilePathCallback?.onReceiveValue(null)
        }

2 WebView ResourceNotFound
在5.0系統上出現過
在Android之前系統是將Webview當作一個單獨的組建放在Framework中,因此webview的資源無論如何都是可以加載到的
而4.4後 使用的是Chromium內核 可以通過Chromium應用更換系統WebView
http://yourbay.me/all-about-tech/2019/08/19/plugin-webview-res-not-found/
https://blog.csdn.net/wuxiaameng0/article/details/49028433


常見錯誤:

問題1:
Binary XML file line #7: Error inflating class android.webkit.WebView
Error inflating class android.webkit.WebView
java.lang.UnsupportedOperationException: For security reasons, WebView is not allowed in privileged processes

原因:當 WebView在共享了系統進程(sharedUserId)的app上時 會進行安全性檢測
WebViewFactory創建sProviderInstance時會進行安全性檢測 當發現app共享了系統進程會拋出:
throw new UnsupportedOperationException(
“For security reasons, WebView is not allowed in privileged processes”);

解決:通過Hook思想 採用反射手段
在創建WebView之前 創建 sProviderInstance 對象,把它塞到 WebViewFactory 類裏面

 public static void hookWebView() {
        int sdkInt = Build.VERSION.SDK_INT;
        try {
            Class<?> factoryClass = Class.forName("android.webkit.WebViewFactory");
            Field    field        = factoryClass.getDeclaredField("sProviderInstance");
            field.setAccessible(true);
            Object sProviderInstance = field.get(null);
            if (null != sProviderInstance) {
                Logger.t(TAG).d("sProviderInstance isn't null");
                return;
            }
            Method getProviderClassMethod;
            if (sdkInt > 22) {  // above 22
                getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
            } else if (sdkInt == 22) {  // method name is a little different
                getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
            } else {  // no security check below 22
                Logger.t(TAG).d("no need to Hook WebView");
                return;
            }
            getProviderClassMethod.setAccessible(true);
            Class<?>       providerClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);
            Class<?>       delegateClass = Class.forName("android.webkit.WebViewDelegate");
            Constructor<?> providerConstructor = providerClass.getConstructor(delegateClass);
            if (null != providerConstructor) {
                providerConstructor.setAccessible(true);
                Constructor<?> declaredConstructor = delegateClass.getDeclaredConstructor();
                declaredConstructor.setAccessible(true);
                sProviderInstance =
                    providerConstructor.newInstance(declaredConstructor.newInstance());
                Logger.t(TAG).d("sProviderInstance: " + sProviderInstance);
                field.set("sProviderInstance", sProviderInstance);
            }
            Logger.t(TAG).d("Hook WebView done!");
        } catch (Throwable e) {
            Logger.t(TAG).e("Throwable is " + e.toString());
        }
    }

2 No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
解決:
1 mWebView.getSettings().setAllowFileAccessFromFileURLs(true);
mWebView.getSettings().setAllowUniversalAccessFromFileURLs(true);

2 加 httpServletResponse.setHeader(“Access-Control-Allow-Origin”, “*”);
httpServletResponse.setHeader(“Access-Control-Allow-Headers”, “Origin, X-Requested-With, Content-Type, Accept”);

3 反射方式 使http https 本地化 找不到 mWebViewCore

4 在webView laodurl請求中添加header

3 Failed to execute ‘play’ on ‘HTMLMediaElement’: API can only be initiated by a user gesture., 3359
解決:
mWebViewSetting.setMediaPlaybackRequiresUserGesture(false);
使WebView不需要用戶手勢去自動播放

4 部分機型通過Googlecast推流 不能播放youtube的視頻 但是能看到進度條 視頻名稱等 就是畫面不顯示
解決:
1 替換騰訊x5內核(換包名即可 很方便) 或開源庫XWalk(要稍微改動一下代碼)
2 查看機型的系統webview內核 更換該內核爲google的webview 或者 更換這個webview的版本

5 加載完一個頁面onPageFinished接口被對調多次:
原因: 因爲框架中嵌入了多個iframe標籤,每個iframe標籤中對應的頁面加載完畢時,onPageFinished會被回調一次,從而導致onPageFinished接口被多次回調;

解決: 通過調用次數限定處理,在onPageStarted後,只有第一次onPageFinished調用才被認爲是頁面加載完畢,才做對應的邏輯處理;

6 Unable to create JsDialog without an Activity (H5界面不彈框)
因爲創建的WebView是通過applicationContext創建的 默認onJSAlert()是通過applicationContext彈出Dialog,而applicationContext不能彈出Dialog
可以通過xml中聲明WebView控件的方式 可以彈框 或者 傳入Activity的Context引用

7 Using WebView from more than one process at once with the same data directory is not supported
原因: Android P 引入的問題
在 Android 9 中,爲改善應用穩定性和數據完整性,應用無法再讓多個進程共享一個 WebView 數據目錄。通常情況下,此類數據目錄會存儲 Cookie、HTTP 緩存以及其他與網絡瀏覽有關的持久性和臨時性存儲
(不同進程裏開啓了WebView,而他們使用了同一個WebView目錄)

解決: 在Application初始化的時候,指定每個進程的WebView目錄
使用 WebView.setDataDirectorySuffix() 方法爲每個進程指定唯一的數據目錄後綴,然後再在相應進程中使用 WebView 的給定實例

注意:如果應用中的多個進程需要訪問同一網絡數據,您需要自行在這些進程之間複製該數據。例如,您可以調用 getCookie() 和 setCookie(),以在不同的進程之間手動傳輸 Cookie 數據

8 Android9.0以上設備打不開界面: net::ERR_CLEARTEXT_NOT_PERMITTED
原因: 因爲Android9.0以上設備默認情況下禁用明文支持
解決方式:
1 在manifest 中application節點添加

android:usesCleartextTraffic="true"
application標籤中
 <application
        ...
        android:usesCleartextTraffic="true"
        ...>

2 使用https


測試WebView JS代碼

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
    <script type="text/javascript">
    callNative('WebCallNativeTest', 19);

    function callNative(action, paramObj) {
        console.log("platform:"+navigator.platform)
      if (navigator.platform.match(/Mac/) !== null) {
        window.webkit.messageHandlers[action].postMessage(paramObj);
      } else if (navigator.platform.match(/iPhone|iPod|iPad/) !== null) {
        window.webkit.messageHandlers[action].postMessage(paramObj);
      } else if (navigator.platform.match(/Win/) !== null) {
        external[action](paramObj);
      } else if (navigator.platform.match(/Android|Linux armv7l|Linux armv8l/) !== null) {
        var rt = window.JSCallJavaMgr[action](paramObj);
        alert("調用了java方法,返回值:"+rt);
      }
    }

function getJSData(var1,var2,var3)
{
    alert("java調用了JS,參數爲:"+var1+", "+var2+", "+var3 + "var1類型爲:" + typeof var1 + "var2類型爲:" +
    typeof var2 + "var3類型爲:" + typeof var3)
    return "return from JS"
}
function getJSData2(var1)
{
    console.log("java調用了JS,參數爲:"+var1)
    return "return from JS"
}
function callMePlease(var1,var2)
{
    console.log("java調用了JS,參數爲:"+var1+", "+var2)
    return "return from JS"
}

    </script>
</head>
<body>
</body>
</html>

9 error:-10 ERR_UNKNOWN_URL_SCHEME
webview只能識別http, https這樣的協議.若加載或者重定向了其他的url 如(weixin:// alipay://) 則會報這個錯誤

10 error:-6 ERR_CONNECTION_CLOSED
錯誤原因是 cannot connect to the server
可以在onReceivedError中 打印出現該錯誤的URL 查看是哪個URL錯誤
定位具體的URL爲什麼連接失敗 或者直接攔截該URL

11 webview彈不出軟鍵盤
調一下 webview.requestFocus()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章