JsBridge


引言

就是 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 掉的方法,每次把要新的方法统一管理在这里。
为下一步别的操作好了很多

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容