mui框架下的js和安卓原生交互的实现方式
因为工作需要,研究了一下这方面的内容,官方文档写的很笼统,我觉得我有必要记录一下这个过程。
背景
为什么需要跟原生交互?其实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"]);
}