android中使用tmf框架插件化开发的问题
最近项目开发使用的是tmf框架,其中大多数都是通过源生和H5交互的方式来实现的,大体实现和别的三方框架是一样的,需要按照tmf的官方文档引入一些lib和依赖,添加一些配置等。
我主要就我们在使用做一简单的总结。
大致是使用是项目中创建一个抽象的父类(TMFBaseJsApi)让其extends JsApi,其中JsApi是tmf框架中的类。在TMFBaseJsApi中重写两个核心的方法,分别是:
public abstract class TMFBaseJsApi extends JsApi {
protected final String TAG = "TMFBaseJsApi";
protected long clickTimeMillis = System.currentTimeMillis();
protected static final String STATUS = "status";
protected static final String MESSAGE = "message";
protected static final String CALLBACK_DATA = "callbackData";//回调数据
protected static final String STATUS_SUCC = "0";//成功
protected static final String STATUS_FIAL = "1";//失败
protected static final String STATUS_NETWORK_NONE = "-9999";//无网
protected Activity activity;
@Override
public String method() {
return pluginName();
}
@Override
public void handle(BaseTMFWeb baseTMFWeb, JsCallParam jsCallParam) {
Logs.d(TAG, "TMFBaseJsApi-名称:" + pluginName() + ",参数:" + jsCallParam.paramStr);
this.activity = baseTMFWeb.mActivity;
execute(baseTMFWeb, jsCallParam);
}
以上两个重要的方法,第一个是H5所调用的插件的名字,第二个是handle()这个方法H5调用源生的入口方法,同时您看到在这个方法中调了execute(baseTMFWeb,jsCallParam)这个方法,该方法也是一个抽象方法。
/**
* 调用入口
*/
public abstract void execute(BaseTMFWeb baseTMFWeb, JsCallParam jsCallParam);
该方法是每个客户端写的插件被调到的入口,例如以扫码这个插件为例简单说一下。
目前我们的需求是扫二维码将结果给H5,让H5对扫码的结果进行一些处理。因此我需要些一个插件(也就是一个类)这里定义为addScan(人家定义的类名,莫笑),所有的插件都是继承父类TMFBaseJsApi,需要重写核心的方法,代码如下:
public class addScan extends TMFBaseJsApi {
@Override
public String pluginName() {
return "addScan";
}
`` @Override
public void execute(final BaseTMFWeb baseTMFWeb, final JsCallParam jsCallParam) {
this.baseTMFWeb = baseTMFWeb;`
}
这就写好了一个插件,这里pluginName一定要和H5保持一致,否则调用不到,如果这个插件被H5调用到,则execute()方法就会被执行,而jSCallParam 则是H5需要给源生端传的参数,通常是JSONObject,通常是通过解析Json来拿到我们需要获取的值。比如我们这一步需要从H5获得一些参数,我们只需要加以下代码:
```java
@Override
public void execute(final BaseTMFWeb baseTMFWeb, final JsCallParam jsCallParam) {
this.baseTMFWeb = baseTMFWeb;
//注册扫描结果事件
if (!EventBus.getDefault().isRegistered(this)){
EventBus.getDefault().register(this);
}
try {
JSONObject json = new JSONObject(jsCallParam.paramStr);
JSONObject temp1 = new JSONObject(json.optString("scanCallback"));
// scanFunc = new TestClass();
scanFunc=new JsCallbackFunc();
scanFunc.sessionId = temp1.optString("sessionId");
scanFunc.funcId = temp1.optInt("funcId");
JSONObject temp2 = new JSONObject(json.optString("leftBtnFunc"));
leftFunc = new JsCallbackFunc();
leftFunc.sessionId = temp2.optString("sessionId");
leftFunc.funcId = temp2.optInt("funcId");
JSONObject temp3 = new JSONObject(json.optString("centerBtnFunc"));
centerFunc = new JsCallbackFunc();
centerFunc.sessionId = temp3.optString("sessionId");
centerFunc.funcId = temp3.optInt("funcId");
JSONObject temp4 = new JSONObject(json.optString("rightBtnFunc"));
rightFunc = new JsCallbackFunc();
rightFunc.sessionId = temp4.optString("sessionId");
rightFunc.funcId = temp4.optInt("funcId");
JSONObject temp5 = new JSONObject(json.optString("rightUpBtnFunc"));
rightUpFunc = new JsCallbackFunc();
rightUpFunc.sessionId = temp5.optString("sessionId");
rightUpFunc.funcId = temp5.optInt("funcId");
} catch (Exception e) {
e.printStackTrace();
//如果H5插件未加载完成
if(null==scanFunc && null==rightFunc && null==leftFunc && null==centerFunc && null==rightUpFunc){
mOnCheckPluginInter.onPluginIsReady(false);
}else{
mOnCheckPluginInter.onPluginIsReady(true);
}
}
}
我分别从H5端获取了4个callBack,这4个callBack分别是JsCallbackFunc的对象,而JsCallbackFunc是tmf提供的js与源生交互的桥,它只有class文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.tencent.tmf.webview.api.callback;
import android.text.TextUtils;
import android.util.Log;
import com.tencent.tmf.webview.api.TMFWebConfig;
import com.tencent.tmf.webview.api.base.BaseTMFWeb;
import com.tencent.tmf.webview.api.utils.EmptyUtils;
import com.tencent.tmf.webview.api.utils.GsonHelper;
import java.util.Iterator;
import org.json.JSONObject;
public class JsCallbackFunc {
public String sessionId;
public int funcId;
public JsCallbackFunc() {
}
public void callbackJs(final BaseTMFWeb webView, String resultJsonStr) {
try {
if (EmptyUtils.isEmpty(webView)) {
return;
}
String var3 = GsonHelper.getInstance().toJson(this);
final JSONObject var4 = new JSONObject(var3);
if (EmptyUtils.isEmpty(var4)) {
return;
}
if (!TextUtils.isEmpty(resultJsonStr)) {
JSONObject var5 = new JSONObject(resultJsonStr);
if (!EmptyUtils.isEmpty(var5)) {
Iterator var6 = var5.keys();
while(var6.hasNext()) {
String var7 = var6.next().toString();
if (!TextUtils.isEmpty(var7)) {
var4.put(var7, var5.get(var7));
}
}
}
}
Runnable var9 = new Runnable() {
public void run() {
webView.mTmfWebView.loadUrl(String.format("javascript:handleMessageFromTcs('onCallback', %s)", var4.toString()));
if (TMFWebConfig.DEBUG) {
Log.i(TMFWebConfig.TAG, "【CallbackH5】callback result: " + var4.toString());
}
}
};
webView.getWebViewHolder().getHandler().post(var9);
} catch (Throwable var8) {
if (TMFWebConfig.DEBUG) {
Log.e(TMFWebConfig.TAG, "【CallbackH5】callback exception: " + var8.getMessage());
}
}
}
}
这个就是JsCallBackFun提供的class文件,而我们每次将值传给H5都是通过这个桥的对象来传递。你应该也看到了我在execute中新增了EventBus对象,这个主要是用来处理完操作将值回传给H5的,比如扫完码后将值给插件,插件通过桥,将值放在callBackData中,然后H5去取值。但是我们自己定义的插件中必须要注册插件,注册的方式如下:
ITMFWeb mWebContainer;
//这里我们是自己封装的,通过H5ContainerBuilder来创建ITMFWeb对象
mWebContainer = H5ContainerBuilder.createH5Container(this, webviewX5Client2, webviewX5ChromeClient2);
//注册插件
mWebContainer.addJsApi(new addScan());
注册完插件就可以确保能被h5调到,扫码完将结果个插件的代码:
YTCallbackEvent routeEvent = new YTCallbackEvent();
routeEvent.type = RouterConfig.QRCODE_SCAN;
routeEvent.data = result;
EventBus.getDefault().post(routeEvent);
//在插件addScan中接收事件
@Subscribe
public void onQrCodeResultEvent(YTCallbackEvent event){
if (event.type.equals(RouterConfig.QRCODE_SCAN)){
String result = event.data;
}
}
这里需要将结果给H5,android和H5交互的方式是json,因此需要构造一个json 给H5
JSONObject backJson = new JSONObject();
try {
backJson.put(STATUS, STATUS_SUCC);
backJson.put(CALLBACK_DATA, result == null ? "" : result );
} catch (JSONException e) {
e.printStackTrace();
}
scanFunc.callbackJs(baseTMFWeb, backJson.toString());
}
可以看到是通过scanFunc.callbackJs()方法将值传回去的,问题就在这里。我构造的json是以下这种格式:
{
"status":"0","callbackData":"wxp://f2f095552C5DDEC3E3390922B0CA92E017FE262E35FE02F69D0B9CA613B58781DB3?p=eJxdT8tSwzAM%2FBr52JETEisHH9KSHrlQuLuJ2xr8IrgD%2BXuUwAlJMztaSTtaN2mlVNUhNiTrhgiFGT8OabL6gbBCDnHx5qphaKEj6I8izZOdn5Ku1nErOWspSZzN%2BP4ye30rJX9C3UN15ErZxt09uhSzWXZjCsyZty97ZrxZn1dYwrMLd28Kb3F%2FmVMsrCT4IthYXo1308kFqyVu8fvBacnMtCIansDQQNcAteubRLBXG4NAjzAo6BXsUQQ7H0zZvLFZ%2BSezCv%2BzUr5jH4quEX8Az9lUyg%3D%3D"}
但是H5那边拿到的结果就是不合适,和他几番核对后发现原来是我这边将特殊字符://,转义为“//”,但是这个操作到底是怎么形成的,因此通过单步进入JsCallBackFun中,发现java源生的JSONObject居然给我转义了,深深给我埋了一个坑,
```java
String var3 = GsonHelper.getInstance().toJson(this);
final JSONObject var4 = new JSONObject(var3);
if (EmptyUtils.isEmpty(var4)) {
return;
}
从var4就发现特殊字符被转义了,因此我知道原因了,一生气之下将这个JsCallBackFunc进行了重写,当然只是为了测试,因此随便写了一个类名为TestClass.java
package com.yitong.mobile.biz.launcher.jsapi.qrcodescan;
import android.text.TextUtils;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.tencent.tmf.webview.api.TMFWebConfig;
import com.tencent.tmf.webview.api.base.BaseTMFWeb;
import com.tencent.tmf.webview.api.utils.EmptyUtils;
import com.tencent.tmf.webview.api.utils.GsonHelper;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.util.Iterator;
public class TestClass {
public String sessionId;
public int funcId;
public TestClass() {
}
public void callbackJs(final BaseTMFWeb webView, String resultJsonStr) {
try {
if (EmptyUtils.isEmpty(webView)) {
return;
}
final ScanResult scanResult = new Gson().fromJson(resultJsonStr, ScanResult.class);
scanResult.setFuncId(this.funcId);
scanResult.setSessionId(this.sessionId);
/* String var3 = GsonHelper.getInstance().toJson(this);
final JSONObject var4 = new JSONObject(var3);
if (EmptyUtils.isEmpty(var4)) {
return;
}
if (!TextUtils.isEmpty(resultJsonStr)) {
*//* ScanResult scanResult = new Gson().fromJson(resultJsonStr, ScanResult.class);
var4.put("status", scanResult.getStatus());
var4.put("callbackData", scanResult.getCallbackData());*//*
*//*JSONObject var5 = new JSONObject(resultJsonStr);
if (!EmptyUtils.isEmpty(var5)) {
Iterator var6 = var5.keys();
while(var6.hasNext()) {
String var7 = var6.next().toString();
if (!TextUtils.isEmpty(var7)) {
var4.put(var7, var5.get(var7));
}
}
}*//*
}*/
Runnable var9 = new Runnable() {
public void run() {
webView.mTmfWebView.loadUrl(String.format("javascript:handleMessageFromTcs('onCallback', %s)", new Gson().toJson(scanResult)));
if (true) {
Log.i(TMFWebConfig.TAG, "【CallbackH5】callback result: " + new Gson().toJson(scanResult));
}
}
};
webView.getWebViewHolder().getHandler().post(var9);
} catch (Throwable var8) {
if (true) {
Log.e(TMFWebConfig.TAG, "【CallbackH5】callback exception: " + var8.getMessage());
}
}
}
}
package com.yitong.mobile.biz.launcher.jsapi.qrcodescan;
public class ScanResult {
private String status;
/**
* callbackData : wxp://f2f095552C5DDEC3E3390922B0CA92E017FE262E35FE02F69D0B9CA613B58781DB3?p=eJxdT8tSwzAM%2FBr52JETEisHH9KSHrlQuLuJ2xr8IrgD%2BXuUwAlJMztaSTtaN2mlVNUhNiTrhgiFGT8OabL6gbBCDnHx5qphaKEj6I8izZOdn5Ku1nErOWspSZzN%2BP4ye30rJX9C3UN15ErZxt09uhSzWXZjCsyZty97ZrxZn1dYwrMLd28Kb3F%2FmVMsrCT4IthYXo1308kFqyVu8fvBacnMtCIansDQQNcAteubRLBXG4NAjzAo6BXsUQQ7H0zZvLFZ%2BSezCv%2BzUr5jH4quEX8Az9lUyg%3D%3D
*/
private String callbackData;
public String sessionId;
public int funcId;
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public int getFuncId() {
return funcId;
}
public void setFuncId(int funcId) {
this.funcId = funcId;
}
public String getCallbackData() {
return callbackData;
}
public void setCallbackData(String callbackData) {
this.callbackData = callbackData;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
采用三方的gson来代替源生的JSONObject,避免了特殊字符被转义的发生。
然后在addScan中将scanFunc修改为
private TestClass scanFunc;
替换了private JsCallbackFunc scanFunc;
接着在调用
String data = StringEscapeUtils.unescapeJava(backJson.toString());
scanFunc.callbackJs(baseTMFWeb, data);
这样就可以ok啦,轻松绕过了JscallBackFunc导致的问题。
当然这个问题的核心在于当我们遇到一些class文件后,发现它有问题,如果轻微的绕过修改,代替其工作的功能,当然你可以使用一个base64就能搞定的问题,不用这么麻烦,但是觉得这个思路我还是喜欢的,记录生活的随笔,请勿吐槽,谢谢。