Android 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
但是如Windows是不会过滤的,这里业务侧自己做过滤,不然会有安全风险


远程调试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函数
若要异步回调:可以给请求加一个请求id,然后一个回调方法,里面带有请求的函数名和请求id,自己去找匹配的就好

2 evaluateJavascript(String script, ValueCallback resultCallback)
带返回值的 可以直接拿回调 回调是在JavaBridge线程上被回调的
JS去注册方法 这边就可以调用

网页必须加载完毕(onPagedFinished) JS方法得是全局方法才能调用JS方法
可以传递和回调各种基本类型的数据 传过去的时候是String 但是js会解析成为相应的类型

注意:这里针对的是这个webview加载的所有页面都会调用js方法,因为调用的是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内核会开启线程执行的

数据大小限制: 好像是2M不到

怎么传大数据、大图片到js:
1 分批思想
2 图片压缩
3 通过业务后台


二、 JS调native
1 方法劫持(即拦截shceme)的方式 自己制定伪协议 通过js和native的某些接口回调过来 然后解析协议做相应native方法调用
如:
1.1 WebChromeClient.onJsPrompt() 当JS调用弹出输入框时会回调
1.2 WebChromeClient的console.log 即WebChromeClient.onConsoleMessage()当触发打印日志的时候会回调
1.3 每个 iframe 会发送URL 然后会回调WebViewClient.shouldOverrideUrlLoading()

// 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()" />


WebView优化

1 针对WebView开启新的:web进程 因为系统的WebView控件坑比较多,内存泄露,卡顿啥的,避免影响主进程
2 统一了js和native互调的插件化处理,都在C++层分发js事件处理,然后js和native互调的能力由各端各自实现
3 参考下方 WebView常见注意坑
4 启动速度优化(非重WebView的项目 所以没做太多的优化)
提供一下思路:
启动到渲染大概流程:WebView初始化流程(启动浏览器内核)、网络请求资源、WebView渲染
1)在使用前预先初始化好WebView,从而减小耗时。 但是会有内存损耗问题
2)在WebView初始化的同时,通过Native来完成一些网络请求等过程,使得网络请求和webView出事哈能够并行操作
3)dom、css解析优化前端做


安全性问题

  • 中间还有后台进行保证 还有token进行保证

  • 防止数据传输过程中出现篡改和伪造 进行完整性校验: 将JsApi调用或Event、Callback的原始数据通过SHA1生成摘要,用于JavaScript和Java两端进行校验

  • 防止JS代码被注入、解析和伪造 使用了动态proguard: 其实就是字符串替换,每次加载新的页面后,在注入框架中的JS代码前,对JS代码某些特定字符串用一个随机串替换掉
    动态proguard做如下处理 动态替换JS方法名或变量名; 动态插入参数或变量;(无用变量,用于打乱JS代码,防止被解析)
    加入无用代码逻辑,防止关键逻辑被分析出来

  • 通过添加白名单配置对js调用native的接口进行域名限制

  • native调js相关安全
    1.addJavascriptInterface要避免 JavaScript通过调用而导致添加对象
    2.addJavaScriptInterface()要在4.2及以上版本 并且用于应用 APK内含的JavaScript,否则不信任的网站可能会调用这些方法造成攻击

  • 防止跳转垃圾网站、非法界面,甚至跳转系统应用
    1.Windows增加对scheme的限制,只支持http和https 不然跳转一些app就gg
    2.在 WebView 加载不受信任的内容之前,通过 removeJavascriptInterface
    shouldInterceptRequest 中移除 JavaScript 界面内的对象
    3.对URL进行合法性检查,拦截广告(github有一个公开广告规则列表)通过shouldInterceptRequest接口拿到request.url进行拦截,返回空的资源

fun createEmptyResource(): WebResourceResponse {
    return WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray()))
}

并且通过native调js的方法,隐藏相关元素

document.getElementsByClassName('Advertisement')[i].style.display = 'none'
  • 不直接在 WebView 中使用 JavaScript,请勿调用 setJavaScriptEnabled()

  • 4.4之前用的是webkit 必须确认 WebView 对象只显示值得信任的内容。 要确保您的应用在 SSL 中不会暴露给潜在的漏洞,请使用可更新的安全 Provider 对象(如更新您的安全提供程序以防范 SSL 攻击中所述)
    如果您的应用必须从开放网络渲染内容,请考虑提供您自己的渲染程序,以便使用最新的安全补丁程序确保让其保持最新状态

  • 如果您的应用需要向 WebView 的 JavaScript 界面提供对象,请确保 WebView
    不会通过未加密的连接加载网络内容。您可以在清单中将 android:usesCleartextTraffic 设为 false,或设置禁止
    HTTP 流量的网络安全配置。您也可以防止任何受影响的 WebView 通过 loadUrl 加载采用 HTTP 协议的网址

  • WebView应当禁止通过file协议访问文件


拦截页面、屏蔽元素

拦截页面:
shouldOverrideUrlLoading 加载URL或者发生了重定向的时候会回调这个接口 return true则拦截不跳转 . false则跳转
拦截广告(github有一个公开广告规则列表)通过shouldInterceptRequest接口拿到request.url进行拦截,返回空的资源

屏蔽元素
通过native调用js的代码,进行屏蔽

// 1 xxElement.style.display='none'
javascript:(
    function() {
            var len = document.getElementsByClassName('Advertisement').length;
            for(var i = 0; i < len; i ++ ){
                document.getElementsByClassName('Advertisement')[i].style.display = 'none'
            }
        }
)()

// 2 xxElement.remove();
javascript:document.getElementById('statusholder').remove();

WebView常见注意坑

1 WebView内存泄漏
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();

可能会有OOM
WebView解析网页时会申请Native堆内存用于保存页面元素,当页面较复杂或者打开了较多的页面时会有很大的内存占用。最终可能会回收一些资源,如栈底的Activity被销毁了,返回时Activity需要重新创建,甚至会出现crash
解决: 新起进程以及单独抽出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

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()


测试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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值