Engine反射调用插件流程

这篇博客以EuexImage插件为例简单总结了Appcan的Egnine框架通过反射调用插件的流程。

前端调用

首先,在js中调用插件

1
2
3
4
5
6
7
8
var uexImageParam = {
"min": 0,
"max": 1,
"quality": 0.1, //压缩质量,
"detailedInfo": true,
"style": 0
}
uexImage.openPicker(uexImageParam, function(error, info) {});

原生调用

原生整个调用流程如下图

Android java调用栈信息如下图
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
2
3
4
5
6

EBrowserView browserView = (EBrowserView) view;
final EUExManager uexManager = browserView.getEUExManager();
if (uexManager != null) {
result.confirm(uexManager.dispatch(parseStr));
}

如上代码,在该回调方法中先将字符串“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转换为一个对象, 获取 uexNamemethodargstypes 这四个属性

1
2
3
4
5
AppCanJsVO 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
2
3
4
5
6
7
8
var uexImageParam = {
"min": 0,
"max": 1,
"quality": 0.1, //压缩质量,
"detailedInfo": true,
"style": 0
}
uexImage.openPicker(uexImageParam, function(error, info) {});

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
16
String[] 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
2
3
4
5
6
7
8
9
10
11
12
13
14
public String callMethod(EUExBase plugin, String methodName, String[] params) {
try {
//反射调用插件中的方法
Method targetMethod = plugin.getClass().getMethod(methodName, new Class[]{String[].class});
return getReturn(200, targetMethod.invoke(plugin, new Object[]{params}));
} catch (InvocationTargetException var7) {
BDebug.e(new Object[]{plugin.getUexName(), methodName, " InvocationTargetException"});
if(BDebug.DEBUG) {
var7.printStackTrace();
}
return getReturn(203, "InvocationTargetException:" + var7.getMessage());
}

}

到此为止,从收到前端的js,然后遍历本地的第三方插件,再反射调用插件中的方法,Engine反射调用插件这个流程就算完成了。

博客编号: 25