JsBridge

作者: 刘尔泽 | 来源:发表于2017-10-11 14:37 被阅读37次

引言

就是 WebView 跟 android native 之间的一些交互。
也就是 让 js 代码 跟 app 的java 代码之间互相调用的 桥梁

image.png

反正是我自己的笔记,应该也没人看吧,这几个类 封装的。
后来改了一个版本 封装成接口,也就是 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 掉的方法,每次把要新的方法统一管理在这里。
为下一步别的操作好了很多

相关文章

网友评论

      本文标题:JsBridge

      本文链接:https://www.haomeiwen.com/subject/qxnbyxtx.html