这篇博客以EuexImage插件为例简单总结了Appcan的Egnine框架通过反射调用插件的流程。
前端调用
首先,在js中调用插件
1 | var uexImageParam = { |
原生调用
原生整个调用流程如下图
Android java调用栈信息如下图
Prompt
英文翻译为Prompt: 驱使,驱动, onJsPrompt即当js被调用时的意思,在Js中调用window.prompt(message, value)
时,WebChromeClient.onJsPrompt()就会收到回调, onJsPrompt()方法的message参数的值正是Js的方法window.prompt()的message的值。 从上边的栈信息可以看到,首先执行CBrowserMainFrame
回调方法 onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result),该类继承了android android.webkit
中的WebChromeClient类。该方法在源码中注释如下,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* Tell the client to display a prompt dialog to the user. If the client
* returns true, WebView will assume that the client will handle the
* prompt dialog and call the appropriate JsPromptResult method. If the
* client returns false, a default value of false will be returned to to
* javascript. The default behavior is to return false.
* @param view The WebView that initiated the callback.
* @param url The url of the page requesting the dialog.
* @param message Message to be displayed in the window.
* @param defaultValue The default value displayed in the prompt dialog.
* @param result A JsPromptResult used to send the user's reponse to
* javascript.
* @return boolean Whether the client will handle the prompt dialog.
*/
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
return false;
}
在选择了一张图片后,该方法接受的参数具体如下:
view : EBrowerView (父类为WebView)
url: file:///android_asset/gf/apply/uploadCertificates.html
message内容如下1
AppCan_onJsParse:{"uexName":"uexImage","method":"openPicker","args":[{"min":0,"max":1,"quality":0.1,"detailedInfo":true,"style":0},0],"types":["object","function"]}
defaultValue: ""
1 |
|
如上代码,在该回调方法中先将字符串“AppCan_onJsParse:”
截取掉, 然后调用appCanJsParse(JsPromptResult, WebView,String)对js进行解析, 在该方法中根据传入的EBrowserView获取到EUExManager(注意: 因为它没有继承EuexBase,所以并不是一个plugin),然后调用uexManager.dispatch(parseStr)解析截取后的js,在dispatch
反射调用插件入口类中的方法,然后通过result的confirm(String )将解析的回调给前端。
此时执行uexManager.dispatch(parseStr)
返回的结果如下 ,因为此时还没有开始选择图片,所以result为null:1
{"code": 200, "result": null}
图片选择插件EuexImage的页面都是原生功能实现,当选择好一张图片后,点击完成后通过插件入口类中的callbackTojs方法结果回调前端,此时会再次调用到onJsPrompt方法, 下方是message参数的内容,从中可以看到,已经包含了选好的图片的路径。
1 | {"uexName":"uexImage","method":"onPickerClosed","args":["{\"isCancelled\":false,\"data\":[\"/storage/emulated/0/Android/data/com.syc.activity.startmode.bigpictest/cache/uex_image_temp/temp_1554210050454.jpg\"],\"detailedImageInfo\":[{\"localPath\":\"/storage/emulated/0/Android/data/com.syc.activity.startmode.bigpictest/cache/uex_image_temp/temp_1554210050454.jpg\"}]}"],"types":["string"]} |
这时会在再次重复上边appCanJsParse的方法解析一遍,然后将解析的结果返回给前端。此时uexManager.dispatch(parseStr)
方法执行的结果应该是
1 | {"code": 200, "result": true} |
但是不清楚实际上为什么会返回如下结果 , 有待研究
1 | {"code": 201, "result": "NoSuchMethodException:onPickerClosed [class [Ljava.lang.String;]"} |
dispatch(parseStr)方法
这个方法中反射调用了插件入口类中的方法。
假设 parseStr的内容如下:
1
{"uexName":"uexImage","method":"openPicker","args":[{"min":0,"max":1,"quality":0.1,"detailedInfo":true,"style":0},0],"types":["object","function"]}
在这个方法中首先将parseStr
转换为一个对象, 获取 uexName
、 method
、 args
、types
这四个属性1
2
3
4
5AppCanJsVO appCanJs = (AppCanJsVO)JSON.parseObject(parseStr, AppCanJsVO.class);
String pluginName = appCanJs.uexName;
String methodName = appCanJs.method;
List<Object> appCanJsArgs = appCanJs.args;
List<String> appCanJsTypes = appCanJs.types;

从上图可以看到appCanJsArgs:就是前端刚才传的两个参数,uexImageParam对象和function 对应的id,为了不翻看之前的代码影响阅读的流畅性,将前端调用代码再次贴上。
1 | var uexImageParam = { |
appCanJsTypes: 参数的类型, 第一个参数类型为对象,第二个参数类型为function
创建一个String数组 ,然后遍历 appCanJsArgs,将遍历的值存放到该数组中.
如果参数类型为 string 获取对应的value
如果参数是 function 将int类型的functionId转换为String类型
如果参数是 object 转换json格式
具体代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16String[] params = new String[length];
for (int i = 0; i < length; i++) {
String type = (String)appCanJsTypes.get(i);
String arg;
if(appCanJsArgs.get(i) instanceof String) {
arg = (String)appCanJsArgs.get(i);
} else if("function".equals(type)) {
if(appCanJsArgs.get(i) instanceof Double) {
arg = String.valueOf(((Double)appCanJsArgs.get(i)).intValue());
} else {
arg = String.valueOf(appCanJsArgs.get(i));
}
} else {
arg = DataHelper.gson.toJson(appCanJsArgs.get(i));
}
}
最关键的步骤来了,也就是反射调用的流程如下 :
首先获取所有的第三方插件1
ELinkedList<EUExBase> plugins = this.getThirdPlugins();
假设这里只集成了uexImage插件和uexXhttp Mgr插件,

刚才上边已经解析到了插件的名字和要反射调用的方法, 然后对插件列表进行迭代, 当迭代到前插件时,通过callMethod(plugin, methodName, params)
反射调用插件中的方法
反射调用方法的代码如下:
1 | public String callMethod(EUExBase plugin, String methodName, String[] params) { |
到此为止,从收到前端的js,然后遍历本地的第三方插件,再反射调用插件中的方法,Engine反射调用插件这个流程就算完成了。
博客编号: 25