背景
公司项目是通过webView.addJavascriptInterface(Object obj, String interfaceName)进行web和原生的交互的,android端通常会定义类似以下的多个方法,这样做的好处是便于阅读,一看便知方法是干什么的,需要什么参数;坏处是不便于扩展,一旦web端传错参数,或者调了一个android端没有的方法时,会导致各种问题,每当需要扩展时,web端总是需要考虑android端的版本兼容问题,因此考虑一个通用的方法来处理双方的交互(正好前些日子学了Javapoet和apt没东西练手hhhhh)。
@JavascriptInterface public void func(){ } @JavascriptInterface public void func(int a, int b){} @JavascriptInterface public void func1(int a, String b){ }
交互
Android端只暴露一个方法给web端调用,双方约定好数据类型,比如一个json字符串,action代替原来的方法用作请求标志,data作为扩展参数,如
{ "action" : “REQUEST_PLAY_VIDEO”, "data" : { "url" : "http://xxxx" } }
同时暴露一个接口出来用作交互,如
@JavascriptInterface public void request(String request) {}
这样,交互问题就解决了,当web需要扩展时,只需往json字符串里添加额外的参数即可,android端这边解析json,由于低版本没有解析额外的参数因此会忽略掉,不会导致网页报错等问题,高版本或者补丁包添加参数处理即可。到这里基本可以了,但,还是会存在一些问题:
参数是json字符串,要解析
解析后要判断action,做相应的处理,如果带数据data,还要解析转成Java对象
简单的判断action免不了switch,每添加一个方法就要写一个case过于麻烦
基于以上问题,把交互方式封装一下。
实现
封装一个简单的JsBridge,要实现的功能如下:
自动解析json,android端拿到的是对应的Java对象
简单的扩展action的方式
简单的绑定方式,如JsBridge.bind(webview, object)
其他,如错误处理等
思路很简单:通过注解标记与web交互的类和action对应的实现方法,通过Javapoet和apt生成相应的类,该类包含了真正的交互方法(@JavascriptInterface标记的),在该方法里解析json参数得到action,switch判断action对应的实现方法,再调用即可。
定义一些约束
注解
/** * js和android互相交互的js名 * author : pxq * date : 19-10-24 下午9:39 */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface Bridge { //js交互名 String name(); //交互方法 String jsMethod(); } /** * js请求android端的action * author : pxq * date : 19-10-24 下午9:38 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface JsAction { //action名 String value(); } /** * Js Request异常,如解析失败等 * author : pxq * date : 19-10-26 下午2:27 */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.METHOD) public @interface JsError { } /** * 未处理的Js Request * author : pxq * date : 19-10-26 下午2:25 */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.METHOD) public @interface UnHandle { }
获取interfaceName的接口
注意到webView.addJavascriptInterface(Object obj, String interfaceName)需要一个interfaceName,为此我们还需要定义一个接口,让生成的类去实现它,以便通过强转的方式获取该值。
/** * 约束类,用来获取webview.addJavascriptInterface(obj, name)的name * author : pxq * date : 19-10-26 上午2:20 * @see android.webkit.WebView#addJavascriptInterface(Object, String) */ public interface IJsBridge { //获取JavascriptInterface名 String getName(); }
这里生成的类有点讲究,务必生成在同一个包下,同时加上后缀加以区分,接下来就是javapoet的使用了。类生成之后,最后是绑定到webview上,有了前面的基础,这个就简单了,直接看代码:
public static void bind(WebView webView, Object bridge){ String className = bridge.getClass().getCanonicalName() + JS_BRIDGE_SUFFIX; Log.e(TAG, "bind: " + className); try { Class<?> jsBridgeClazz = Class.forName(className); IJsBridge jsBridge = (IJsBridge) jsBridgeClazz.getConstructor(bridge.getClass()).newInstance(bridge); Log.e(TAG, "bind: " + jsBridge.getName()); webView.getSettings().setJavaScriptEnabled(true); webView.addJavascriptInterface(jsBridge, jsBridge.getName()); } catch (Exception e) { e.printStackTrace(); } }
效果
定义个简单的类测试一下:
//action对应的实现方法 @Bridge(name = "android", jsMethod = "request") public class BridgeTest { private static final String TAG = "BridgeTest"; @JsAction("test") public void test(){ Log.i(TAG, "test: "); } @JsAction("testData") public void testData(TestBean test){ Log.i(TAG, "testData: " + test.name +" " + test.data); } @UnHandle public void UnHandle(String request){ Log.w(TAG, "UnHandle: " + request); } @JsError public void error(String request, Exception e){ Log.e(TAG, "error: " +request, e); } } //生成的交互类 public class BridgeTest$$Bridge implements IJsBridge { public BridgeTest mHandler; public BridgeTest$$Bridge(BridgeTest mHandler) { this.mHandler = mHandler; } @JavascriptInterface public void request(String request) { try { handleRequest(request); } catch (Exception e) { mHandler.error(request, e); } } public String getName() { return "android"; } void handleRequest(String request) throws Exception { String action = JsonParser.getAction(request); switch(action) { case "test": mHandler.test(); break; case "testData": mHandler.testData(JsonParser.parse(request, TestBean.class)); break; default : mHandler.UnHandle(request); } } }
test.html
<html> <body> <input type="button" value="无数据" onclick="call()" style="height:40px;" > <p> <input type="button" value="带数据" onclick="callWithData()" style="height:40px;"> </p> <p> <input type="button" value="默认处理(UnHandle)" onclick="callUnHandle()" style="height:40px;"> </p> <p> <input type="button" value="错误处理(Error)" onclick="callError()" style="height:40px;"> </p> <script> function call(){ var x = "{\"action\":\"test\"}" callAndroid(x); } function callWithData() { var x = "{\"action\":\"testData\", \"data\": {\"name\" : \"pxq\", \"data\" : \"testdata\"}}" callAndroid(x); } function callUnHandle() { var x = "{\"action\":\"UnHandleTest\", \"data\": {\"name\" : \"pxq\"}}" callAndroid(x); } function callError() { var x = "{\"actions\":\"ErrorTest\", \"data\": {\"name\" : \"pxq\"}}" //这里把action 改成了 actions会导致解析失败 callAndroid(x); } function callAndroid(x) { window.android.request(x); } </script> </body> </html>
绑定一下
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); WebView webView = new WebView(this); JsBridge.bind(webView, new BridgeTest()); setContentView(webView); webView.loadUrl("file:///android_asset/test.html"); } }
相关推荐
0评论