Android与H5交互
前言
native和H5混合开发越来越普遍,于是了解native和H5的通信方式就很有必要。
一、webview使用
private void settingProperty(){
myJavascriptInterface = new MyJavascriptInterface();
WebSettings webSettings = mWebView.getSettings();
//支持js
webSettings.setJavaScriptEnabled(true);
//支持通过JS打开新窗口
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//缩放至屏幕的大小
webSettings.setLoadWithOverviewMode(true);
//将图片调整到适合webview的大小
webSettings.setUseWideViewPort(false);
//设置缓存方式
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
//开启DOM storage API功能
webSettings.setDomStorageEnabled(true);
//开启 database storage API 功能
webSettings.setDatabaseEnabled(true);
String cacheDirPath = getCacheDir().getAbsolutePath()+ "/webViewCache";
//设置数据库缓存路径
webSettings.setDatabasePath(cacheDirPath);
//开启Application H5 Caches 功能
webSettings.setAppCacheEnabled(true);
//设置Application Caches 缓存目录
webSettings.setAppCachePath(cacheDirPath);
/**对系统API在19以上的版本作了兼容。因为4.4以上系统onPageFinished在恢复图片加载时, 如果存在多张图片引用的是相同的src时,会只有一个image标签得到加载,因而对于这样的系统我们就先直接加载 */
if (Build.VERSION.SDK_INT >= 19){
webSettings.setLoadsImagesAutomatically(true);
} else {
webSettings.setLoadsImagesAutomatically(false);
}
}
WebViewClient
WebViewClient:处理webview的各种通知与请求事件
public class MyWebViewClient extends WebViewClient {
/**
* @return true:打开新的url(重定向)时,webview不会加载该url,需要自己处理;false:系统继续加载该url;
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//TODO 重写此方法,实现对网页中超链接的拦截
view.loadUrl(url);
return true;
}
// 网页开始加载
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
// 网页加载完成
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
// 网页加载错误
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
}
- 若没有设置 WebViewClient ,url重定向时则由系统(Activity Manager)处理该 url,通常是使用浏览器打开或弹出浏览器选择对话框。
- 若设置 WebViewClient 且shouldOverrideUrlLoading返回 true ,url重定向时应由应用的代码处理该 url,WebView 不处理,也就是程序员自己做处理。
- 若设置 WebViewClient 且shouldOverrideUrlLoading返回 false,url重定向时则说明由 WebView 处理该 url,即用 WebView 加载该 url。
注:url不需要重定向时,没有设置 WebViewClient也可以在WebView中打开,且与shouldOverrideUrlLoading返回值无关。
WebChromeClient
WebChromeClient:辅助webview处理javascript对话框,网页的icon和title,以及加载进度条等。
/**
1. 重写webview的浏览器客户端
2. 主要功能是辅助webview处理javascript对话框,网页的icon和title,以及加载进度条等
*/
public class MyWebChromeClient extends WebChromeClient {
// 监听页面加载进度
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
// 监听网页标题
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
//TODO 接收html的title标签,可根据html的title改变而改变
}
// 监听网页图标
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
super.onReceivedIcon(view, icon);
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return s
}
}
二、H5调用Android方法
1)@JavascriptInterface注解 (同步调用)
提供接口供js调用(@JavascriptInterface注解标注该方法是供js调用)
// “android”是为js接口取的名字
WebView.addJavascriptInterface(new MyJavascriptInterface(), “android”);
public class MyJavascriptInterface{
@JavascriptInterface
public String h5NativtCallback(){
LogUtils.i(TAG, "h5NativeCallback");
return "js 调用 android 的回调 "+(num_android++);
}
}
注:H5调用android native方法:window.android.h5NativtCallback();即window.js接口名.加方法名
2)通过WebviewClient拦截url协议 (异步调用)
- Android native和h5定好协议;
- 通过WebviewClient的shouldOverrideUrlLoading拦截指定的url协议头。
3)通过WebChromeClient拦截弹窗 (同步调用)
通过WebChromeClient的onJsAlert()、onJsConfirm()和onJsPrompt()分别拦截js的alert()、confirm()和prompt()方法
三、Android调用H5方法
- WebView#loadUrl()
mWebView.loadUrl("javascript:callbackFromNative('fromAndroid!')"
注:loadUrl方法中的字符串参数构成:”javascript:”+js方法,其中js方法中的参数是传递给js方法的参数
2. List item
mWebview.evaluateJavascript(
"javascript:callbackFromNative('data from android!‘)“,
new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) { }
});
区别:loadUrl需要刷新页面,且需要回调时要通过@JavaScriptInterface注解的方式实现;而evaluateJavascript不需要刷新页面,自带回调。
四、webview常见的问题
1)判断WebView是否已经滚动到页面底端
getScrollY():当前可见区域的顶端距整个页面顶端的距离,也就是当前内容滚动的距离.
getHeight()或getBottom():当前WebView 这个容器的高度
getContentHeight :整个html 的原始高度,但并不等同于当前整个页面的高度,因为WebView 有缩放功能, 所以当前整个页面的实际高度为原始html 的高度再乘上缩放比例.
因此,准确判断WebView是否已经滚动到页面底端:
if(WebView.getContentHeight*WebView.getScale() == (webview.getHeight()+WebView.getScrollY())){ //已经处于底端 }
2)处理WebView中的非超链接请求
有时候需要加上请求头,但是非超链接的请求,没有办法再shouldOverrinding中拦截并用webView.loadUrl(String url,HashMap headers)方法添加请求头。
首先需要在url中加特殊标记/协议, 如在shouldInterceptRequest()方法中,可以拦截到所有的网页中资源请求(比如加载JS,图片以及Ajax请求等等),然后将要添加的请求头,以get形式拼接到url末尾。
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,String url) {
// 非超链接(如Ajax)请求无法直接添加请求头,现拼接到url末尾,这里拼接一个imei作为示例
String ajaxUrl = url;
// 如标识:req=ajax
if (url.contains("req=ajax")) {
ajaxUrl += "&imei=" + imei;
}
return super.shouldInterceptRequest(view, ajaxUrl);
}
3)屏蔽webview长按事件
webview长按时将会调用系统的复制控件,可以通过
mWebView.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return true;
}
});
4)WebView #onPageFinished()多次调用
WebView 在Android4.4的手机上onPageFinished()回调会多调用一次(具体原因待追查)。因此,需要尽量避免在onPageFinished()中做业务操作,否则会导致重复调用,还有可能会引起逻辑上的错误。
5)WebView#addJavaScriptInterface()引起的安全问题.
注入恶意的js代码,尤其是在已经获取root权限的手机上,通过恶意程序可能会利用该漏洞安装或者卸载应用。
- JS中可以遍历window对象,找到存在“getClass”方法的对象的对象,然后再通过反射的机制,得到Runtime对象;
- 若当前应用具有读写SDCard的权限,即android.permission.WRITE_EXTERNAL_STORAGE,便可以调用静态方法来访问文件;
参考:http://blog.csdn.net/leehong2005/article/details/11808557
6)如何避免WebView内存泄露
如:WebView页面中播放了音频,退出Activity后音频仍然在播放
- 初始化webview
不在xml中定义 Webview ,而是在需要的时候在Activity中创建,并且Context使用 getApplicationgContext(),webview就不会持有activity的引用LinearLayout.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MatchParent,ViewGroup.LayoutParams.MatchParent); WebView wv = new WebView(getApplicationContext()); wv.setLayoutParams(lp); linearyLayout.addView(wv);
- 销毁webview
在销毁 webview 的时候,先让 webview 加载空内容,清空历史,然后将webview 从父容器移除 ,然后再销毁webview。@Override protected void onDestroy(){ //先加载空内容 if(wv!=null){ //先加载空内容 wv.loadDataWithBaseUrl(null,"","text/html","utf-8",null); //清空历史 wv.clearHistory() //从布局中移除 ((ViewGroup)wv.getParent()).removeView(wv); //然后销毁 wv.destroy(); //然后置为空 wv=null; } super.onDestroy(); }
参考
[1] : https://www.jb51.net/article/100758.htm
[2] : https://blog.csdn.net/qq_24530405/article/details/52067474
[3] : https://blog.csdn.net/carson_ho/article/details/52693322
[4] : https://www.cnblogs.com/lee0oo0/p/4026774.html
[5] : https://www.jianshu.com/p/d3e033c48323