原文链接: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')')

注意点

  1. 需要在主线程调用才会生效
  2. 如果在oncreate中直接调用loadurl不会生效原因是 webview需要加载完成才能调用webview加载完成的回调函数是在webclinet 中
  3. 如果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+的 他的实现比较完整

Android webView与js 交互以及jsbridge框架源码分析_第1张图片 这里写图片描述

这是他的具体实现 代码不多 其中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按钮

Android webView与js 交互以及jsbridge框架源码分析_第2张图片 这里写图片描述

然后就可以开始搞事了
以上是jsbridge具体实现的思路

更多相关文章

  1. Android简单文件浏览器源代码
  2. The import com.google cannot be resolved解决方法
  3. 在Android中解析ls 命令得到目录列表的方法
  4. Android利用setLayoutParams在代码中调整布局(Margin和居中)

随机推荐

  1. Android(安卓)— 创建和修改 Fragment 的
  2. android:visibility
  3. android之sax解析xml文件
  4. android中调用相册里面的图片并返回
  5. android中的一个属性动画,可以显示更多的
  6. TP 支付订单、购物车页面数据、购物车(九
  7. localStorage实现本地存储读取CSS样式
  8. localStorage实现本地存储读取CSS样式
  9. 访问器属性,类与构造器函数,document.query
  10. 怎么P身份證、改手持报图片内容、换头像