前端相关知识
同源策略:
当域名和端口名相同则称为同源
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>