同源策略:
当域名和端口名相同则称为同源
XMLHttpRequest 对象
可以在页面加载后与服务器进行收发包
可以只更新局部界面
跨域:
浏览器的同源策略导致了跨域
在浏览器中 不同来源的网站是不能相互访问 避免被随意修改内容
HTML 标签语言 用于设计网页的样式、内容 HTML5 新的第五代HTML规范 有
mime type 媒体类型 type/subtype text/html
content type 类似mime type
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
1 打开应用的WebView调试权限:
WebView.setWebContentsDebuggingEnabled(true);
2 adb连接真机 然后用 chrome打开网站 chrome://inspect
即可查看连接的设备和该设置使用的WebView版本号
3 点击inspect
可以查看到这个设备此时WebView展示的内容,以及和浏览器打开F12相同的Web调试栏
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 = "hello baidu!
";
webview.loadData(content, "text/html", "UTF-8");
String unencodedHtml =
"'%28' is the code for '('";
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())//移除
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有些功能用不了或者显示不了啥的
主要是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)
}
主要是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
该接口用于Web界面调用native的资源文件 如打开相册之类的(见下方具体流程)
4 onProgressChanged(view: WebView!, newProgress: Int)
网页加载进度的回调
5 open fun onReceivedTitle(view: WebView!, title: String!): Unit
web设置的title
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; // 不保存密码
首先设置支持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对象的属性的
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代码,防止被解析)
加入无用代码逻辑,防止关键逻辑被分析出来;
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 协议的网址
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端:
Android端:
1 授予基本权限
2 重写WebChromeClient的onShowFileChooser(webView: WebView, filePathCallback: ValueCallback
web端调用了上方的input标签,则会调用这个接口,然后我们通过Intent打开系统相册或者支持该Intent的第三方应用来选择图片
override fun onShowFileChooser(
webView: WebView,
filePathCallback: ValueCallback>,
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>? = 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标签中
2 使用https
测试WebView JS代码
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()