webview之复习
三.WebView面试
1.WebView的漏洞
JsBridge原理
如何提高WebView加载速度
1.WebView的漏洞:
API16之前存在远程代码执行安全漏洞,该漏洞源于程序没有正确限制使用WebView.addJavascriptInterface方法,远程攻击者可通过使用Java反射机制利用该漏洞执行任意Java对象的方法。
2. WebView销毁步骤
- WebView在其他容器上时(如LinearLayout),当销毁Activity时,需要在onDestroy()中先移除容器上的WebView,然后再将WebView.destroy(),这样就不会导致内存泄漏
3.webview的JsBridge原理:
- 客户端和服务端之间通过Javascript来互相调用各自的方法
3.如何提高WebView加载速度
1)、WebView硬件加速
Android3.0引入硬件加速,默认会开启,WebView在硬件加速的情况下滑动更加平滑,性能更加好,但是会出现白块或者页面闪烁的副作用,建议WebView暂时关闭硬件加速
2)、WebViewClient的onPageFinished
onPageFinished在每次完成页面的时候调用,但是遇到未加载完成的页面跳转其他页面时,就会一直调用,使用WebChromeClient.onProgressChanged可以替代
3)、WebView后台耗电
在WebView加载页面的时候,会自动开启线程去加载,如果不很好的关闭这些线程,就会导致电量消耗加大,可以采用暴力的方法,直接在onDestroy方法中System.exit(0)结束当前正在运行中的java虚拟机
4、WebView内存泄漏
由于WebView是依附于Activity的,Activity的生命周期和WebView启动的线程的生命周期是不一致的,这会导致WebView一直持有对这个Activity的引用而无法释放,
解决方案:
1) 独立进程,简单暴力,不过可能涉及到进程间通信(推荐);
2) 动态添加WebView,对传入WebView中使用的Context使用弱引用;
WebView销毁步骤:
WebView在其他容器上时(如:LinearLayout),当销毁Activity时,需要在onDestroy()中先移除容器上的WebView,然后再将WebView.destroy(),这样就不会导致内存泄漏
WebView的内存泄漏问题
原因:WebView会关联一个Activity,WebView执行的操作是在新线程当中回收,时间Activity没有办法确认,Activity的生命周期和WebView线程生命周期不一致导致WebView一直执行,因为WebView内部持有Activity的引用,导致Activity一直不能被回收,原理类似于匿名内部类持有外部类的引用一样。那么如何解决呢?解决方案如下:
-
独立进程,简单暴力,涉及到进程间通信。(开发过程中常用)
-
动态添加WebView,对传入WebView中使用Context弱引用,动态添加WebView意思在布局中创建一个ViewGroup用来放置WebView,Activity创建add进来,Activity停止时remove掉
5. WebView常见的坑
-
API 16之前版本存在远程代码执行漏洞,该漏洞源自于程序没有正确限制使用WebView.addJavascriptInterface方法,攻击者可以使用Java Reflection API利用该漏洞执行任意Java对象和方法。
-
WebView的销毁和内存泄漏问题。WebView的完全销毁是件麻烦事,一旦销毁流程不正确,极易容易导致内存泄漏。
-
jsbridge
通过javascript构建一个桥,桥的两端一个端是客户端,一端是服务端,它可以让本地端调用远端的web代码,也可以让远端调用本地的代码。 -
WebViewClient.onPageFinished(不靠谱) – > WebChromeClient.onProgressChanged(靠谱)
-
后台耗电(性能优化)
WebView开启网页时会自己开启线程,如果没有合理的销毁,那么残余线程就会一直运行,so这会非常耗电的,解决方案:有一种暴力方式就是Activity的onDestroy中调用System.exit()方法把虚拟机关闭,也可结合自己应用的WebView的情况设计出一个温柔的方案。 -
WebView硬件加速导致的页面渲染问题
WebView硬件加速偶尔导致页面白块,页面闪烁,但是加载速度比较快,解决方案:关闭硬件加速。
二.webview常见问题
1. webview 运行时不调用系统自带浏览器
mobView.loadUrl("http://www.csdn.net");
WebSettings wSet = mobView.getSettings();
wSet.setJavaScriptEnabled(true);
mobView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url){
// 重写此方法表明点击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边
view.loadUrl(url);
return true; }
});
最后把权限加上:<uses-permission android:name="android.permission.INTERNET" />
2. WebViewClient 和 WebChromeClient 区别?
WebView主要负责解析渲染,WebViewClient 和WebChromeClient是用来辅助WebView。
WebViewClient主要帮助WebView处理各种通知、请求事件的,比如:
onLoadResource
onPageStart
onPageFinish
onReceiveError
onReceivedHttpAuthRequest
WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等比如
onCloseWindow(关闭WebView)
onCreateWindow()
onJsAlert (WebView上alert无效,需要定制WebChromeClient处理弹出)
onJsPrompt
onJsConfirm
onProgressChanged
onReceivedIcon
onReceivedTitle
看上去他们有很多不同,多数只用WebViewClient就行。如果需要更丰富的处理效果,比如JS、进度条等,就要用到WebChromeClient。更多时候,可以这样
webView.setWebChromeClient(new WebChromeClient());
webView.setWebViewClient(new WebViewClient());//希望点击链接继续在当前browser中响应,必须覆盖 WebViewClient对象。
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl(url);
webview.setWebChromeClient(new MyWebChromeClient());
webview.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// return super.shouldOverrideUrlLoading(view, url);
view.loadUrl(url);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
mProgressDialog.show();
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mProgressDialog.hide();
}
});
private class MyWebChromeClient extends WebChromeClient{
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
textview.setText(title); }
}
3. JS调用android方法
webview.addJavascriptInterface(new JavaScriptInterfaceDemo(this), "JSInterfaceDemo");
//JSInterfaceDemo其实就是JavaScriptInterfaceDemo的别名,供HTML调用时使用
addJavascriptInterface(Object obj, String interfaceName)该方法将一个java对象绑定到一个javascript对象中,javascript对象名就是interfaceName,
如JSInterfaceDemo,作用域是Global。
这样初始化webview后,在webview加载的页面中就可以直接通过 javascript:window.JSInterfaceDemo访问到绑定的java对象了。
在HTML中如何调用呢,"window.JSInterfaceDemo.getResposeCode() " 其中getResposeCode需要在JavaScriptInterfaceDemo中实现
注意:addJavascriptInterface方法中要绑定的Java对象及方法要运行另外的线程中,不能运行在构造他的线程中,这也是使用Handler的目的。
public class JavaScriptInterfaceDemo{
@JavaScriptInterface
public void getResposeCode(final String msg){
mHandler.post(new Runnable(){
@Override
public void run() { }}
}
webview.addJavascriptInterface(new JavaScriptInterfaceDemo( this ), "JSInterfaceDemo");
一.WebView与 js的交互
11. Js通过WebView调用Java代码
从API19开始,Android提供@JavascriptInterface对象注解方式建立Javascript对象和Android原生对象绑定,提供给JavaScript调用函数必须有@JavascriptInterface。
方式1. 通过WebView的addJavascriptInterface() 进行对象映射
优点:使用简单, 仅将Android对象和JS对象映射即可;
缺点:存在严重的漏洞问题
步骤1:定义与JS对象映射关系的Android类:AndroidObj继承自Object类
public class AndroidObj extends Object {
// 定义JS调用的方法且必须加入@JavascriptInterface注解
@JavascriptInterface
public void hello(String msg) {
System.out.println("JS调用了Android的hello方法"); }
}
步骤2:将需JS代码以.html格式放到src/main/assets文件夹里:
javascript.html
<script> function callAndroid(){
// 由于对象映射,所以调用test对象等于调用Android映射的对象
jsObj.hello("js调用了android中的hello方法"); }
</script>
步骤3:在Android里通过WebView设置Android类与JS代码的映射
//通过addJavascriptInterface()将Java对象映射到JS对象. 参数1:Java对象名, 参数2:Javascript对象名
//AndroidtoJS类对象映射到js的test对象
webSettings.setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new AndroidObj(), "jsObj");
mWebView.loadUrl("file:///android_asset/javascript.html");
方式2. 通过WebViewClient的shouldOverrideUrlLoading (WebView view,String url)回调拦截url
原理:解析该 url 的协议;如果检测到是预先约定好的协议,就调用相应方法;
优点:不存在方式1的漏洞;
缺点:JS获取Android方法的返回值复杂。
如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl()去执行JS 方法把返回值传递回去代码如下:
mWebView.loadUrl("javascript:returnResult(" + result + ")");
JS:javascript.html
function returnResult(result){
alert("result is" + result);
}
3个步骤:
1.Java被Js调用的方法上加@JavascriptInterface注解;
2.WebView注册JavaScriptInterface;
3.js调用,如window.android.show("JavaScript called~!");
Js调用Android Toast方法
注册JavaScriptInterface:
webView.addJavascriptInterface(this, "jsBridge");
// addJavascriptInterface的作用是把this所代表的类映射为JavaScript中的android对象。
2) Webview调用Js
需要在主线程中调用
- 基本格式:
webView.loadUrl("javascript:methodName(parameterValues)");
- 调用Js无参无返回值函数:
String call = "javascript:readyToGo()";
webView.loadUrl(call);
- 调用Js有参无返回值函数
String call = "javascript:alertMessage(\"" + "content" + "\")";
webView.loadUrl(call);
- 调用Js有参有返回值函数
String call = "javascript:alertMessage(\"" + "content" + "\")";
webView.evaluateJavascript(call, new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {} });
3) 通过H5打开App的某个页面(类似deeplink方式 )?
在manifest文件中最开始启动的activity中添加:
<activity android:name=".activitys.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="host"
android:pathPrefix="/pathPrefix"
android:scheme="scheme" />
</intent-filter>
</activity>
// 注意host,pathPrefix,scheme都是自己自定义的,只要与h5页面调用的一致即可,如下所示
如果要跳转到指定的页面,在MainActivity的onCreate()中添加:
Intent intent = getIntent();
Uri uri = intent.getData();
if (uri != null) {
String routeId = uri.getQueryParameter("pid");
Intent intent0 = new Intent(MainActivity.this, ZhidingActivity.class);
startActivity(intent0);
}
uri.getQueryParameter("pid");获取h5页面传递的参数,如果没有的话可以忽略
注意一点,微信上对于app的唤醒有拦截,在浏览器中才可以起作用
1. Android调用JS代码的方法有2种:
1)通过WebView的loadUrl()
webSettings.setJavaScriptEnabled(true); // 设置与Js交互的权限
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.post(new Runnable() {
public void run() {
// 注意调用的JS方法名要对应上
mWebView.loadUrl("javascript:callJS()");
}
});
特别注意:JS代码调用一定要在 onPageFinished()回调之后才能调用,否则不会调用。onPageFinished()属于WebViewClient类的方法,主要在页面加载结束时调用。
特点:方便简洁。但效率低和获取返回值麻烦。
使用场景:不需要获取返回值,对性能要求较低时;
优点:该方法比第一种方法效率更高、使用更简洁。因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会;
2)Android4.4后才可使用(使用场景);
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果 }
});
2. 对于JS调用Android代码的方法有3种:
方式2:通过 WebViewClient 的方法shouldOverrideUrlLoading (webview,url)回调拦截 url
即JS需要调用Android的方法
步骤1:在JS约定所需要的Url协议
JS代码:javascript.html 以.html格式放到src/main/assets文件夹里
<script>
function callAndroid() { //约定url协议:
document.location = "js://webview?arg1=111&arg2=222";
}
</script>
当该JS通过Android的mWebView.loadUrl("file:///android_asset/javascript.html")加载后,就会回调shouldOverrideUrlLoading():
步骤2:在Android通过WebViewClient复写shouldOverrideUrlLoading(webview,url)
webSettings.setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 步骤2:根据协议的参数,判断是否是所需要的url
// 根据scheme(协议格式) &authority(协议名)判断(前两个参数)
//传入url = "js://webview?arg1=111&arg2=222"(约定好的需拦截的)
Uri uri = Uri.parse(url);
if (uri.getScheme().equals("js")) {
// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议
// 拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
//步骤3 执行JS所需要调用的逻辑
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}
法3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()输回调拦截JS对话框alert()、对话框confirm()、prompt()输入框消息
方式3的原理:onJsPrompt(webview, url, message, String defaultValue, JsPromptResult result)回调拦截JS对话框到他们的消息内容,然后解析即可。
常用的拦截是:拦截JS的输入框(即prompt()可以返回任意类型的值,操作最全面方便、更加灵活);
而alert()对话框没有返回值; confirm()对话框只能返回两种状态(确定 / 取消)两个值
步骤1:加载JS代码: javascript.html
<script>
function clickprompt(){
var result=prompt("js://demo?arg1=111&arg2=222");
alert("demo " + result);
}</script>
当使用mWebView.loadUrl("file:///android_asset/javascript.html")加载了上述JS代码后,就会触发回调onJsPrompt(),具体如下:
如果是拦截警告框(即alert()),则触发回调onJsAlert();
如果是拦截确认框(即confirm()),则触发回调onJsConfirm();
步骤2:在Android通过WebChromeClient复写onJsPrompt()
webSettings.setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 拦截输入框(原理同方式2), 根据协议参数,判断是否是所需要的url
// 参数message: 代表promt()内容(不是url) result代表输入框的返回值
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
//假定传入进来的 url = "js://webview?arg1=111&arg2=222"(约定好的拦截)
Uri uri = Uri.parse(message);
if ( uri.getScheme().equals("js")) {
// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议
// 所以拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
//参数result:代表消息框的返回值(输入值)
result.confirm("js调用了Android的方法成功啦");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);// 通过alert()和confirm()拦截的原理相同
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
});
一. Android调用JS代码方法有2种:
1. 通过WebView的loadUrl()
webSettings.setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.post(new Runnable() {
public void run() {
// 注意调用的JS方法名要对应上
mWebView.loadUrl("javascript:callJS()");
}});
注意:JS代码调用一定要在onPageFinished()回调之后才能调用,否则不会调用。
特点:方便简洁。但效率低和获取返回值麻烦。
使用场景:不需要获取返回值,对性能要求较低时;
2. 通过WebView的evaluateJavascript()
优点:比第一种方法效率更高、使用更简洁。因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会;
Android4.4后才可使用(使用场景);
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
// 此处为js返回的结果
}
});
1.2 对于JS调用Android代码的方法有3种:
1. 通过WebView的addJavascriptInterface() 进行对象映射
优点:使用简单, 仅将Android对象和JS对象映射即可;
缺点:存在严重的漏洞问题。
方式1:通过WebView的addJavascriptInterface())进行对象映射
步骤1:定义个与JS对象映射关系的Android类:AndroidObj继承自Object类
public class AndroidObj extends Object {
@JavascriptInterface
public void hello(String msg) {
// 定义JS调用的方法且必须加入@JavascriptInterface注解
System.out.println("JS调用了Android的hello方法");
}
}
步骤2:将需要调用的JS代码以.html格式放到src/main/assets文件夹里:javascript.html
<script>
function callAndroid(){
// 由于对象映射,所以调用test对象等于调用Android映射的对象
jsObj.hello("js调用了android中的hello方法");
}
</script>
步骤3:在Android里通过WebView设置Android类与JS代码的映射
webSettings.setJavaScriptEnabled(true);
// 通过addJavascriptInterface()将Java对象映射到JS对象. 参数1:Java对象名, 参数2:Javascript对象名
mWebView.addJavascriptInterface(new AndroidObj(), "jsObj"); // AndroidtoJS类对象映射到js的test对象
mWebView.loadUrl("file:///android_asset/javascript.html");
2. 通过 WebViewClient的shouldOverrideUrlLoading ()方法回调拦截 url
按约定好的协议解析该 url;
优点:不存在方式1的漏洞;
缺点:JS获取Android方法的返回值复杂。
如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl()去执行JS 方法把返回值传递回去代码如下:
mWebView.loadUrl("javascript:returnResult(" + result + ")");
步骤2:在Android通过WebViewClient复写shouldOverrideUrlLoading ()
webSettings.setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//传入url = "js://webview?arg1=111&arg2=222"(约定好的需拦截的)
Uri uri = Uri.parse(url);
if (uri.getScheme().equals("js")) {
// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议
// 拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
//步骤3 执行JS所需要调用的逻辑
// 可以在协议上带有参数并传递到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}
3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()回调拦截JS对话框alert()、confirm()、prompt()消息
方式3的原理:onJsPrompt()方法回调分别拦截JS对话框 得到他们的消息内容,然后解析即可。
常用的拦截是:拦截JS的输入框(即prompt()可以返回任意类型的值,操作最全面方便、更加灵活);而alert()对话框没有返回值;confirm()对话框只能返回两种状态(确定 / 取消)两个值