引言
就是 WebView 跟 android native 之间的一些交互。
也就是 让 js 代码 跟 app 的java 代码之间互相调用的 桥梁
反正是我自己的笔记,应该也没人看吧,这几个类 封装的。
后来改了一个版本 封装成接口,也就是 JShandler 这个类,每要加一个方法就在这个接口里加一个 方法。
大概先写写吧,就是 webview 其实 就是一个组件,内部浏览器内核这样渲染,其实就是 webview 就是一个层
第一步
WebSettings webSettings = getSettings();
webSettings.setJavaScriptEnabled(true);
第二步:
this.addJavascriptInterface(mContext, "WebViewJavascriptBridge");
第三步:
两个重要的监听
mWebView.setWebViewClient(new MyWebViewClient()); extends WebViewClient
mWebView.setWebChromeClient(new MyWebChromeClient()); extends WebChromeClient
第四步:
/**
* js方法调用native中的方法
* BaseShangChengWebViewActivity5_w2
*
* @param invokeMethod 要执行的native方法名称
* @param param 传递给native中方法的参数 json格式
* @param callbackMethod 回调js中的方法名称,如果非空
*/
@JavascriptInterface
public void callHandler(final String invokeMethod, final String param, final String callbackMethod) {
LogUtil.d("callHandler----------------invokeMethod = " + invokeMethod + "---- param = " + param);
final InvokeLocal invokeAnnotation = this.getClass().getAnnotation(InvokeLocal.class);
if (invokeAnnotation == null) return;
java.util.List<String> methods = Arrays.asList(invokeAnnotation.method().split(","));
if (!methods.contains(invokeMethod)) return;
try {
Method method = this.getClass().getMethod(invokeMethod, String.class, String.class);
method.invoke(this, param, callbackMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
其实 在
@InvokeLocal(method = "login,weixin_pay,ali_pay,loadpage,toast,loadingbar,set_useraddress,user_rank,dialog,get_location,back_reload," + "get_pay_message,get_pay_isok,goto_home,network_status,open_browser,union_pay,set_alarm,openShareDialog,open_cameraWidget," +
"start_upload,get_allmethod,isgoback,destroy_history,refresh,goto_speciallist,get_locationv2,dialogv2,get_nativeinfo," +
"get_alarmdealdata,im_status,open_imv2,to_detail,view_didappear,open_imagewidget,goto_mobilerecharge,goto_brandlist,tracklogs," +
"qq_pay,to_detailv2,set_title,openShareDialogv2,open_contacts,shop_favorite,get_cart_num,bind_phone,rich_scan,favoritev2,getfav_statusv2,goback," +
"open_share,issupport_scheme,refreshv2,set_notification,open_verify,isShowNativeView,set_alarm_vedio,get_alarm_vediodata,set_brand_alarm,get_alarmbranddata," +
"open_video,pageinfo,set_titlebar_btn,open_shareV2,getShuMengInfo,sale_reminder,set_clipboard,download_app,is_app_installed,goto_systemset,open_shareV3," +
"cancel_reminder_after_canel_fav,pay_status,open_camera_video,start_upload_video")
//,open_camera_video,start_upload_video
//交易
public class DealCommonWebViewActivity6_w3 extends BaseShangChengWebViewActivity5_w2 implements ShareResultReceiver.ShareResultListener {}
这个 act 中可以看到头部的注解。
也就是说这个注解的字符串就是一些 提供给js 去掉的方法,然后 通过第三步的 监听到的方法中分别去
遍历这些方法名字,去反射找到方法 执行。
第五步:
总的来说这个交互就完成了,其实就是js 让webview 去执行完了一些java 代码,但是很多时候我们需要返回给js 给webview 一些结果。
这个过程 不会有一个链接去监听什么的,而是在我们执行完成之后,主动再去调用 js 方法代码 告诉webview,怎么知道方法名呢? 怎么告诉呢? 方法名其实我们就可以在我们的方法中 需要 H5 方 告诉我们一个方法,这个方法是我们执行后要去调用告诉他们结果的方法。在我们的拦截 方法中的 final String callbackMethod 参数。
怎么告诉呢,其实就是一次 webview的请求,一个url 的请求。
like this :
public void nativeCallBackJs(String callBackMethod) {
if (this.isFinishing()) return;
if (!StringUtil.isNull(callBackMethod)) {
final String url = "javascript: " + callBackMethod + "()";
LogUtil.d("url == " + url);
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mWebView != null) {
mWebView.loadUrl(url, WebViewHeadSetting.headMap);
}
}
});
}
}
而在我们设置给webview 的监听里面 ,可以监听到每个url 的访问
WebViewClient :
private class MyWebViewClient extends BaseWebViewClient {
@Override
// 这个方法每访问一个url 都会被回掉
// 可以看到我们拦截 还拦了scheme命中的
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("http")) {
view.loadUrl(url, WebViewHeadSetting.headMap);
return true;
} else {
mWebView.stopLoading();
SchemeHelper.startFromAllScheme(BaseNativeWebViewActivity4_w1.this, url);
return true;
}
}
@Override
// 一些异常
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
LogUtil.d("------------------error-----------------");
}
// @Override
// ?????
// public WebResourceResponse shouldInterceptRequest(WebView view,String url) {
// return new WebResourceResponse(response.ContentType, response.ContentEncoding, responseStream);
// }
}
接下看看 WebChromeClient
这个还需要再查查,也就是这个监听好像是比前面那个更全,比如 只要 H5 页面一有刷新 诸类的变化就会被回掉,还有进度条,title 上的信息等等
onConsoleMessage 这个方法里面可以看到我们做的事情,是判断发给我们的信息里面有没有 “goback”
这个就是万一 H5 页面中有弹窗的那种情况,我们其实是在 包装了webview 的 act 中监听了 物理返回键,当有物理返回键的操作时候,我们掉了js 的方法,告诉 H5 页面我们按了返回键,你看你要怎么办,
这时候在这方法里看到了,H5 方 想自己管就管,不管让我们操作,也就是有没有 “goback”。
包装了webview 的act 中的监听:
@Override
public void onBackPressed() {
if (mMoreLayoutBase.getVisibility() == View.VISIBLE) {
setMoreLayout(false);
return;
}
super.onBackPressed();
isBackClick = true;
nativeCallBackJs("$.calljs.goback");
}
private class MyWebChromeClient extends BaseWebChromeClient {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
loadProgress = newProgress;
if (isLineProgressLoading) {
mPBar.setVisibility(View.VISIBLE);
mPBar.setProgress(newProgress);
if (newProgress == 100) {
// hideWebViewTitle();
mPBar.setVisibility(View.GONE);
setStatusLoadedComplete();
}
} else {
if (newProgress >= 60) {
new Handler(getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
if (loadProgress >= 60) {
isFirstLoad = true;
setStatusLoadedComplete();
baseLayout.setLoadStats(BaseLayout.LOADED_OK);
}
}
}, 100);
} else if (isFirstLoad) {
isFirstLoad = false;
baseLayout.setLoadStats(BaseLayout.LOADING_PROGRESS);
}
}
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
if (consoleMessage == null || consoleMessage.message() == null) {
super.onConsoleMessage(consoleMessage);
}
if (consoleMessage != null && !StringUtil.isNull(consoleMessage.message()) && consoleMessage.message().contains("Uncaught")) {
if (consoleMessage.message().contains("goback")) {
LogUtil.w("----H5BackClick------" + consoleMessage.message() + "----------");
isNeedCloseThisActivity(true);
} else {
isNeedCloseThisActivity(true);
}
}
return super.onConsoleMessage(consoleMessage);
}
/**
* 自动解析新页面的title
*
* @param view
* @param title
*/
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
LogUtil.debug("tag", "商城收到的title: " + title);
handleNewTitleReceived(view, title);
}
}
还有一点要提的,比如想屏蔽 淘宝 天猫商品的 页面 出现的 登陆 下载等弹窗时候,在我们的webview去拦截一些js,在 MyWebViewClient 的 public void onPageFinished(WebView view, String url) {} 的时候
private class MyWebViewClient extends BaseWebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
LogUtil.d("--------------onPageFinished----------------" + url);
setWebViewBack();
String loginUrl = "http://login.m.taobao.com/login.htm";
String loginUrlHttps = "https://login.m.taobao.com/login.htm";
if (isNeedTaoBaoLoginTip) {
if (url.contains(loginUrl) || url.contains(loginUrlHttps)) {
setTaoBaoLoginTitle();
} else {
getLoginPage(); // 淘宝商品详情页面js跳转登录捕获
}
}
if (url.contains(loginUrl) || url.contains(loginUrlHttps)) {//淘宝登录页面的返回按钮都隐藏掉
setGoneTaoBaoLoginBackButton();
}
if (!StringUtil.isNull(dealCouponUrl) && url.equals(dealCouponUrl)) {//领券页面隐藏返回按钮
goneBackButtonOnCouponPage();
}
mWebView.saveTaoBaoCookie(mCurrentUrl);
loadChange();
onPageFinishedCallback(view, url);
}
}
还有个android版本 出现的漏洞问题 初始化问题。
//*******************************************************开启安全机制**************************************************//
/**
* 移除不安全的接口
*/
private void removeUnScureInterface() {
if (CommonWebViewUtils.isSecureModeOpen && hasHoneycomb() && !hasJellyBeanMR1()) {
//白帽子建议移除这三个不安全的接口
super.removeJavascriptInterface("searchBoxJavaBridge_");
super.removeJavascriptInterface("accessibility");
super.removeJavascriptInterface("accessibilityTraversal");
}
}
/**
* 版本>3.0
*
* @return
*/
private boolean hasHoneycomb() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
}
/**
* 版本>4.2
*
* @return
*/
private boolean hasJellyBeanMR1() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
}
最后要说的
目前的实现就是一个activity 包装了 webview,因为 act 有生命周期。 其实可以想办法用 fragment 包装下,因为fragment 也有生命周期,但是 fragment 可以塞到 act 中。
目前通过runtime 注解所有的 供js 调用的方法名,然后 遍历拿到方法,然后反射 执行。
也是初始化的时候
addJavascriptInterface(mContext, "WebViewJavascriptBridge");
我们相当于给了标记,然后传入了 context,通过context 拿到 那个 act,然后找到他的 注解 往下走的
这样很不方便,也不容易 服用,解耦操作:
看 JsHandler
public class H5WebViewNative extends WebView {
private static final String TAG = H5WebViewNative.class.getSimpleName();
private JSHandler jsHandler = null;
private OnScrollChangedCallback mOnScrollChangedCallback;
private Context mContext;
private OnPageFinishedListener onPageFinishedListener;
private OnPageStartLoadListener mOnPageStartLoadListener;
public void setJsHandler(JSHandler jsHandler) {
this.jsHandler = jsHandler;
this.removeJavascriptInterface("WebViewJavascriptBridge");
initJavascriptInterface();
}
protected void initJavascriptInterface() {
LogUtil.d(TAG, "initJavascriptInterface jsHandler == " + (jsHandler == null ? "null" : "not null") + "@" + this + " @" + Thread.currentThread().getId());
if (null == jsHandler) {
jsHandler = new JSHandler();
}
if (null == jsHandler.getmWebView()) {
jsHandler.setmWebView(this);
}
this.addJavascriptInterface(jsHandler, "WebViewJavascriptBridge");
}
也就是直接传过去的是一个 jshandler 对象
而
public class JSHandler implements JSInterface
class 里实现一些东西,跟本地掉 js 的方法,因为 只是访问一个url 嘛
JSInterface 里,放着所有的供js 掉的方法,每次把要新的方法统一管理在这里。
为下一步别的操作好了很多