Android(安卓)webView与js 交互以及jsbridge框架源码分析
16lz
2022-06-11
原文链接:http://blog.csdn.net/qq_22329521/article/details/73610277
最近在处理android webView与js的通信上的问题,作为总结
1.简单篇
如何实现简单的android 调用js 与js调用android
让webview做一下操作 private void init(Context context){ WebSettings setting =getSettings(); setting.setJavaScriptEnabled(true);//支持js setWebViewClient(new WebClient(this)); setWebChromeClient(new WebChromeClient()); //添加js调用android的方法这是关键 前者是个对象,后者是个字符串 在js中是在window.android可以直接获取到 addJavascriptInterface(new JavaScriptinterface(context,this), "android"); //这是开启js的调试下面再讲 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { setWebContentsDebuggingEnabled(true); } } public class JavaScriptinterface{ Context context; public JavaScriptinterface(Context c) { context = c; } //这个注解可以点击进去看官方描述 是 带有此注释的标记可用于JavaScript代码 @JavascriptInterface public void toastMessage(String message) { Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); } }如果js 要调用这个方法调用window.anroid.toastMessage('即可')
注意这里的toastMessage 是JavaScriptinterface 中被@JavascriptInterface注解的toastMessage方法
这是js调用android中的方法
android 调用js 的方法
//java这样调用即可 方法前需要加javascriptwebview.loadUrl('javascript:jsalert('111')')
注意点
- 需要在主线程调用才会生效
- 如果在oncreate中直接调用loadurl不会生效原因是 webview需要加载完成才能调用webview加载完成的回调函数是在webclinet 中
- 如果alert没效果,需要在setWebChromeClient(new WebChromeClient());
String javascriptCommand='javascript:jsalert('111')'; if (Thread.currentThread() == Looper.getMainLooper().getThread()) { this.loadUrl(javascriptCommand); }else{ ((Activity) getContext()).runOnUiThread(new Runnable() { @Override public void run() { //此时已在主线程中,可以更新UI了 loadUrl(javascriptCommand); } }); } } public class JsWebClient extends WebViewClient { public static final String TAG="JsWebClient"; private JsWebView jsWebView; public JsWebClient(JsWebView jsWebView) { this.jsWebView = jsWebView; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Log.e(TAG,url); return super.shouldOverrideUrlLoading(view,url); } //当这个回调函数触发了webview才可以发送消息 @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); }}
简单封装
js与android中的通信还是通过字符串来处理的//传递过来基本的数据格式大致为{ type:type,//类型 funName:funName,//当js调android 让android是否有回调方法到js中的某个方法 option:option,//object,内部带个个参数}//回复js{ type:'success'/'fail'... data:data} @JavascriptInterface public void postMessage(String data) { try { JSONObject jsonObject = new JSONObject(data); String type = jsonObject.getString("type"); JSONObject option=jsonObject.getJSONObject("option"); String funName=jsonObject.getString('funName'); switch(type){ case 'abc': break; case 'bcd': Object obj=.... .... dispathMessage(funName,obj,'success') break; } } catch (JSONException e) { e.printStackTrace(); if(!TextUtils.isEmpt(type)){ dispathMessage(funName,null,'fail') } } } //真正发送给js 的方法 loadurl是异步加载所以如果在web为加载完成发送无效果 public void dispathMessage(String funName,Object obj,String type){ if(TextUtils.isEmpt(type))return; JSONObject jsonObject = new JSONObject(); try { if(obj!=null) jsonObject.put("data",new Gson().toJson(obj)); jsonObject.put("type",type); } catch (JSONException e) { e.printStackTrace(); } final String javascriptCommand=String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, funName,jsonObject.toString()); if (Thread.currentThread() == Looper.getMainLooper().getThread()) { this.loadUrl(javascriptCommand); }else{ ((Activity) getContext()).runOnUiThread(new Runnable() { @Override public void run() { //此时已在主线程中,可以更新UI了 loadUrl(javascriptCommand); } }); } }public class BridgeUtil { final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:%s('%s')";}
js的封装还没做但是大体是如上
框架分析
接下来看下github上的一个JsBridge这个框架 start约3000+的 他的实现比较完整
这里写图片描述这是他的具体实现 代码不多 其中assets中的js文件是为了注入他的封装的js代码 他的交互方式与上面不同具体实现
这是他封装的webviewpublic class BridgeWebView extends WebView implements WebViewJavascriptBridge { private final String TAG = "BridgeWebView"; //加载自己的js文件 ,然后别人的html调用它的js来传递消息 public static final String toLoadJs = "WebViewJavascriptBridge.js"; //callId为键,记录回调的集合 Map responseCallbacks = new HashMap(); //本地注册一个方法名让js调用 Map messageHandlers = new HashMap(); BridgeHandler defaultHandler = new DefaultHandler(); //发送消息的对象,因为webview加载要时间所有他这里先用队里存储 private List startupMessage = new ArrayList(); public List getStartupMessage() { return startupMessage; }//发送消息private void queueMessage(Message m) { //这边只有只为null的时候才会进入发送消息,置为null是在BridgeWebViewClient的onPageFinished中 if (startupMessage != null) { startupMessage.add(m); } else { dispatchMessage(m); } }public class BridgeWebViewClient extends WebViewClient { private BridgeWebView webView; public BridgeWebViewClient(BridgeWebView webView) { this.webView = webView; } //这就是他处理交互的另一个方式 @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { try { url = URLDecoder.decode(url, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据 webView.handlerReturnData(url); return true; } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { // webView.flushMessageQueue(); return true; } else { return super.shouldOverrideUrlLoading(view, url); } } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); } //这里是是webview加载完成做到的操作 @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if (BridgeWebView.toLoadJs != null) { //在当前webview中加载一段自己的js文件 BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs); } // if (webView.getStartupMessage() != null) { for (Message m : webView.getStartupMessage()) { webView.dispatchMessage(m); } //这个里webview加载完成将队列之为null 之后就会走dispatchMessage这个方法 webView.setStartupMessage(null); } } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { super.onReceivedError(view, errorCode, description, failingUrl); }}
我们观察的他的封装对象和交互方式
public class Message { //每个message都有一个callbackid 传递给js ,js吧这个返回给客户端然后客户端在responseCallbacks中查找返回id的方法 private String callbackId; //callbackId //回复id private String responseId; //responseId private String responseData; //responseData private String data; //data of message private String handlerName; //name of handler}BridgeUtil 对象封装了js与android之间的数据传递格式根据format 处理后做相应的操作
他的交互方式放在了BridgeWebViewClient的shouldOverrideUrlLoading 这个方法
shouldOverrideUrlLoading是当网页的超链接相应是回调到WebClinet的shouldOverrideUrlLoading方法中return true本地处理
false是调整相应的链接
然后我们看他的js代码
我们直接看他发送消息的代码 //sendMessage add message, 触发native处理 sendMessage function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); responseCallbacks[callbackId] = responseCallback; message.callbackId = callbackId; } //这就是message队列 队列的原因后面会将 sendMessageQueue.push(message); //这个messagingIframe.src 设置这一串东西 messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }//messagingIframe 是iframe元素 添加到element中设置为不可见 然后在发送数据中设置src function _createQueueReadyIframe(doc) { messagingIframe = doc.createElement('iframe'); messagingIframe.style.display = 'none'; doc.documentElement.appendChild(messagingIframe); }
//在iframe的src属性设置url webviewclient中的对url进行拦截做处理 public boolean shouldOverrideUrlLoading(WebView view, String url) { try { url = URLDecoder.decode(url, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据 webView.handlerReturnData(url); return true; } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { // webView.flushMessageQueue(); return true; } else { return super.shouldOverrideUrlLoading(view, url); } }
_doSent发送的方法里面的src的内容最终会进入 webView.flushMessageQueue(); 因为字符串匹配的原因
//这里的_fetchQueue就是js中的_fetchQueue方法 实际调用了那个方法 final static String JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();";void flushMessageQueue() { if (Thread.currentThread() == Looper.getMainLooper().getThread()) { loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() { @Override public void onCallBack(String data) { // deserializeMessage //js发送过来的数据分离出来相应的去处理 List list = null; try { list = Message.toArrayList(data); } catch (Exception e) { e.printStackTrace(); return; } if (list == null || list.size() == 0) { return; } for (int i = 0; i < list.size(); i++) { Message m = list.get(i); String responseId = m.getResponseId(); // 是否是response if (!TextUtils.isEmpty(responseId)) { CallBackFunction function = responseCallbacks.get(responseId); String responseData = m.getResponseData(); function.onCallBack(responseData); responseCallbacks.remove(responseId); } else { CallBackFunction responseFunction = null; // if had callbackId final String callbackId = m.getCallbackId(); if (!TextUtils.isEmpty(callbackId)) { responseFunction = new CallBackFunction() { @Override public void onCallBack(String data) { Message responseMsg = new Message(); responseMsg.setResponseId(callbackId); responseMsg.setResponseData(data); queueMessage(responseMsg); } }; } else { responseFunction = new CallBackFunction() { @Override public void onCallBack(String data) { // do nothing } }; } BridgeHandler handler; if (!TextUtils.isEmpty(m.getHandlerName())) { handler = messageHandlers.get(m.getHandlerName()); } else { handler = defaultHandler; } if (handler != null){ handler.handler(m.getData(), responseFunction); } } } } }); } } function _fetchQueue() { //将之前的message都序列出来一次发送到客户端 var messageQueueString = JSON.stringify(sendMessageQueue); sendMessageQueue = []; //android can't read directly the return data, so we can reload iframe src to communicate with java messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); } //将callback放到responseCallbacks public void loadUrl(String jsUrl, CallBackFunction returnCallback) { this.loadUrl(jsUrl); responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback); }
至于android 调用js代码//message中已经包含了方法名final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');";void dispatchMessage(Message m) { String messageJson = m.toJson(); //escape special characters for json string messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2"); messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\""); String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson); if (Thread.currentThread() == Looper.getMainLooper().getThread()) { this.loadUrl(javascriptCommand); } }
查看js中
//提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以 function _handleMessageFromNative(messageJSON) { console.log(messageJSON); if (receiveMessageQueue && receiveMessageQueue.length > 0) { receiveMessageQueue.push(messageJSON); } else { _dispatchMessageFromNative(messageJSON); } }/提供给native使用, function _dispatchMessageFromNative(messageJSON) { setTimeout(function() { //解析message var message = JSON.parse(messageJSON); var responseCallback; //java call finished, now need to call js callback function if (message.responseId) { responseCallback = responseCallbacks[message.responseId]; if (!responseCallback) { return; } responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else { //直接发送 if (message.callbackId) { var callbackResponseId = message.callbackId; responseCallback = function(responseData) { _doSend({ responseId: callbackResponseId, responseData: responseData }); }; } var handler = WebViewJavascriptBridge._messageHandler; if (message.handlerName) { //根据message中方法名去messageHandlers中去找,messageHandlers是js中注册了的function集合 handler = messageHandlers[message.handlerName]; } //查找指定handler try { handler(message.data, responseCallback); } catch (exception) { if (typeof console != 'undefined') { console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception); } } } }); }
android 调试js代码
之前代码中有一段
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { setWebContentsDebuggingEnabled(true); }
开启后 在chrome浏览器中 输入chrome://inspect 然后能找到当前应用有个insert按钮
这里写图片描述然后就可以开始搞事了
以上是jsbridge具体实现的思路
更多相关文章
- android 录音和播放
- android 调用.net的web Service开发 (KSOAP2)
- The import com.google cannot be resolved解决方法
- Android的线程使用来更新UI----Thread、Handler、Looper、TimerT
- Android中Services简析
- android中引用javascript和在javascript中引用java的简单例子
- Android应用开发提高系列(5)——Android动态加载(下)——加载已安装A
- 浅谈Java中Collections.sort对List排序的两种方法
- Python list sort方法的具体使用