因为工作需要,研究了一下这方面的内容,官方文档写的很笼统,我觉得我有必要记录一下这个过程。

背景

为什么需要跟原生交互?其实mui是有在线打包的,在hbuilder里选择在线打包就可以生成apk,但是有些时候有特殊需求,比如我们需要调用一些只有java sdk的第三方服务,那就需要采用离线打包的方式,用java语言去调用第三方java sdk,再通过js调用java代码。

实现方式

其实这里有两种解决方案,一种是通过native.js来一个个加载原生类,再按java的写法调用,但是这样效率很低,加载每个原生类都会带来不小的性能开销,本文介绍的也不是这个。另一种方式就是通过5+runtime的js bridge调用自定义的java插件,我们来细说这种方式。

 

技术架构

依赖关系是从上向下,所以我们先从底层讲起。

Native部分

1.编写插件类

首先,我们在安卓离线打包工程里新建一个类继承io.dcloud.common.DHInterface.StandardFeature,这个类来自lib.5plus.base-release.aar,也就是5+runtime,然后在这个类中写实例方法,方法名可以随便取,只要js调用时跟这个一致就可以,但是参数是固定的,第一个参数是IWebView类型,代表调用这个原生方法的js所在的webview,第二个参数是JSONArray类型,它是js调用时传入的参数列表。

 

    /**
     * 同步调用原生的方法
     * @param webview 调用这个原生方法的js所在的webview
     * @param json js调用时传入的参数列表
     * @return 返回给js的值
     * */
    public String testFunctionSync(IWebview webview, JSONArray json){
        Log.e(TAG, String.format("webview = %s, jsonArray = %s", webview, json));
        Log.e(TAG, String.format("thread name = %s, id = %s", Thread.currentThread().getName(), Thread.currentThread().getId()));
        webview.evalJS("(function(){alert('webview.evalJS')})();");
        return JSUtil.wrapJsVar("reply from custom plugin");
    }
    /**
     * 异步调用原生的方法
     * @param webview 调用这个原生方法的js所在的webview
     * @param json js调用时传入的参数列表
     * */
    public void testFunctionAsync(IWebview webview, JSONArray json){
        try {
            Log.e(TAG, String.format("webview = %s, jsonArray = %s", webview, json));
            Log.e(TAG, String.format("thread name = %s, id = %s", Thread.currentThread().getName(), Thread.currentThread().getId()));
            Thread.sleep(3000);
            String callbackId = json.getString(0);
            String resultMsg = "after 3 seconds,the function return";
            JSUtil.execCallback(webview, callbackId, resultMsg, JSUtil.OK, false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

作为一个原生程序员,提到同步异步,我首先就会想它在安卓里是哪个线程,所以加上了线程信息的Log,经运行得知,同步调用的方法在安卓中是在名为JavaBridge的线程中运行,而异步调用的方法则是在主线程里运行的。所以,同步调用的方法可以直接执行耗时操作,异步执行的方法里要执行耗时操作必须另起线程,否则在操作执行完之前,页面都是卡住的状态,搞不好就ANR了。
这里顺便提一下,既然原生方法能拿到webview对象,那就可以不按套路出牌做很多事了,呵呵。如果两边不是一个公司开发的话,还真存在安全问题。这个不多考虑了

2.注册插件

在assets/data/dcloud_properties.xml中的features节点下新建以下节点

 

<feature name="customPlugin" value="com.gopha.qxtandroidwrapper.CustomPlugin"/>

这里的name是插件名,value是对应类的全名(带包名)。注册完之后,native部分就搞定了。

bridge部分

bridge中的东西是5+runtime提供的,我们只要会用就行了,主要就是这么三个方法:

 

    /**
     * 同步调用原生方法
     * @param service 插件名
     * @param action 方法名
     * @param args 传递参数列表
     * @return 原生方法的返回值
     * */
     String plus.bridge.execSync( String service, String action, Array<String> args );
    /**
     * 异步调用原生方法
     * @param service 插件名
     * @param action 方法名
     * @param args 传递参数列表
     * */
     void plus.bridge.exec( String service, String action, Array<String> args );
     /**
     * 获得回调id
     * @param onSuccess 成功时的回调函数
     * @param onFailed 失败时的回调函数
     * */
     String plus.bridge.callbackId(Function onSuccess, Function onFailed)

同步方法是有返回值的,而异步方法的返回值则是通过回调函数传递,所以其中第三个方法只有在异步调用的时候才会需要。

js部分

js部分没什么多说的,直接上代码:

 

    //同步调用原生方法                  
    nativeSync:function(){
        //调用customPlugin插件的testFunctionSync方法,传递了两个参数,分别是James和Tracy
        //用result变量接收返回值
        var result = plus.bridge.execSync("customPlugin", "testFunctionSync", ["James", "Tracy"]);
        alert(result);
        console.log(result);
        alert(JSON.stringify(plus.bridge));
    },
    //异步调用原生方法
    nativeAsync: function(){
        var bridge = plus.bridge;
        var success = function(msg){
            alert("onSuccess,msg = " + msg);
        };
        var failed = function(msg){
            alert("onFailed,msg = " + msg);
        }
        //获取回调的id
        var callbackId = bridge.callbackId(success, failed);
        //注意,这里要跟原生开发的人定好回调id在参数列表中的索引位置
        plus.bridge.exec("customPlugin", "testFunctionAsync", [callbackId, "secondParam"]);
    }

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐