【回転】JsBridge JavaScriptとJavaの相互呼び出しを実現


http://mp.weixin.qq.com/s?__biz=MzI1NjEwMTM4OA==&mid=2651231789&idx=1&sn=f11650ad0e18ddc12ece6e7559d5084c&scene=0#wechat_redirect
フロントエンドWeb JavaScript(以下、Jsと略す)とJavaの相互呼び出しは、携帯電話アプリケーションでますます一般的になり、JsBridgeが最も一般的なソリューションです.
1.JsがJavaを呼び出し、JavaがJsを呼び出す
Android開発では、Js呼び出しJavaを実現するには、4つの方法があります:1.JavascriptInterface 2. WebViewClient.shouldOverrideUrlLoading() 3 .WebChromeClient.onConsoleMessage() 4. WebChromeClient.onJsPrompt()
1.1 JavascriptInterface
これはAndroidが提供するJsとNative通信の公式ソリューションです.まずJavaコードは、Js呼び出しに提供する役割を果たすクラスを実現します.
public class JavascriptInterface {

    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
    }
}

そしてこのクラスをWebViewのJavascriptInterfaceに追加します.webView.addJavascriptInterface(new JavascriptInterface(), “javascriptInterface”); Jsコードでは、このNativeのクラスのメソッドをjavascriptInterfaceで直接呼び出すことができます.
function showToast(toast) {
    javascript:javascriptInterface.showToast(toast);
}

しかし、この公式提供のソリューションはAndroid 4.2以前に重大なセキュリティ・ホールがありました.Android 4.2以降,@JavascriptInterfaceを加えて解決される.したがって、JavascriptInterfaceは、低バージョンのシステムと互換性があることを考慮すると適切ではありません.
1.2 WebViewClient.shouldOverrideUrlLoading()
この方法の役割はすべてのWebViewのUrlジャンプを遮断することである.ページは特殊なフォーマットのUrlジャンプを構築することができ、shouldOverrideUrlLoadingはUrlをブロックしてフォーマットを判断し、Nativeは自分の論理を実行することができます.
public class CustomWebViewClient extends WebViewClient {

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (isJsBridgeUrl(url)) {
            // JSbridge     
            return true;
        }
        return super.shouldOverrideUrlLoading(view, url);
    }
}

1.3 WebChromeClient.onConsoleMessage()
これはAndroidがJsにNativeコードにログ情報を印刷するAPIをデバッグするために提供するとともに、JsとNativeコードの通信方法の一つとなっている.Jsコードでconsoleを呼び出す.log(‘xxx’)メソッド.
console.log('log message that is going to native code')

NativeコードのWebChromeClientにconsoleMessage()でコールバックされます.consoleMessage.Message()が得たのはJsコードconsole.ロゴ(‘xxx’)の内容.
public class CustomWebChromeClient extends WebChromeClient {

    @Override
    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
        super.onConsoleMessage(consoleMessage);
        String msg = consoleMessage.message();//JavaScript   Log  
    }
}

1.4 WebChromeClient.onJsPrompt()
実はWebChromeClientを除いてonJsPrompt()、そしてWebChromeClient.onJsAlert()とWebChromeClient.onJsConfirm().名前の通り、この3つのJsがNativeコードに与えるコールバックインタフェースの役割は、それぞれヒント情報、警告情報、確認情報を示すことである.AlertとconfirmはJsの使用率が高いため,JsBridgeのソリューションではonJsPrompt()を選択する傾向にある.Jsで呼び出す
window.prompt(message, value)

WebChromeClient.onJsPrompt()はコールバックされます.onJsPrompt()メソッドのmessageパラメータの値はまさにJsメソッドwindow.prompt()のmessageの値.
public class CustomWebChromeClient extends WebChromeClient {

    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        //   JS      
        result.confirm();
        return true;
    }
}

1.5 Java呼び出しJs
前述の4つの通信方式はいずれもJs通信NativeのJavaであるが,逆にJava通信Jsには1つの方式しかない.それはWebViewを呼び出すことですloadUrl()は、あらかじめ定義されたJsメソッドを実行します.
webView.loadUrl(String.format("javascript:WebViewJavascriptBridge._handleMessageFromNative(%s)", data));

2. JsBridge
次はJsBridgeというオープンソースコンポーネント(https://github.com/lzyzsd/JsBridge)JsBridgeの原理を説明します
2.1 Java呼び出しJsのfunctionInJsメソッドのフローチャート
【转】JsBridge实现JavaScript和Java的互相调用_第1张图片
2.2 JsBridgeのUML図
【转】JsBridge实现JavaScript和Java的互相调用_第2张图片
2.3 JsBridge作業手順説明
2.3.1 WebViewロードhtmlページ
webView.registerHandler(“submitFromWeb”, …);これはJavaレイヤが「submitFromWeb」というインタフェースメソッドを登録し、Jsに呼び出しを提供することを目的としています.この「submitFromWeb」のインタフェースメソッドのコールバックはBridgeHandlerです.handler().webView.callHandler(“functionInJs”, …, new CallBackFunction());JavaレイヤがJsをアクティブに呼び出す「functionInJs」メソッドです.
public class MainActivity extends Activity implements OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        webView = (BridgeWebView) findViewById(R.id.webView);
        webView.loadUrl("file:///android_asset/demo.html");
        webView.registerHandler("submitFromWeb", new BridgeHandler() {

            @Override
            public void handler(String data, CallBackFunction function) {
                Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
                function.onCallBack("submitFromWeb exe, response data    from Java");
            }

        });

        webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
            @Override
            public void onCallBack(String data) {

            }
        });
    }
}

我々は次第にcallHandler()法の実現に深く入り込んだ.ここでdoSend()メソッドが呼び出され、ここではm.setCallbackId(callbackStr)メソッドの役割を説明したい.このメソッドで設定したcallbackIdは生成されるとJsだけでなくkey-valueペアとresponseCallbackペアでresponseCallbacksというMapに保存されます.その目的は、Jsが処理結果をJava層にコールバックした後、Java層がcallback Idに基づいて対応するresponseCallbackを見つけ、後続のコールバック処理を行うことである.
private void doSend(String handlerName, String data, CallBackFunction responseCallback) {
        Message m = new Message();
        if (!TextUtils.isEmpty(data)) {
            m.setData(data);
        }
        if (responseCallback != null) {
            String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
            responseCallbacks.put(callbackStr, responseCallback);
            m.setCallbackId(callbackStr);
        }
        if (!TextUtils.isEmpty(handlerName)) {
            m.setHandlerName(handlerName);
        }
        queueMessage(m);
    }

最終的にはBridgeWebViewですdispatchMessage(Message m)メソッドが呼び出すのはthisである.loadUrl()が呼び出されました_handleMessageFromNativeというJsメソッド.では、このJsの方法はどこから来たのでしょうか.
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);
        }
    }

2.3.2 WebViewJavascriptBridgeをロードします.js
WebViewClient.onPageFinished()の中のBridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs).assert/WebViewJavascriptBridgeに保存していますjsはWebViewにロードされます.
package com.github.lzyzsd.jsbridge;

public class BridgeWebViewClient extends WebViewClient {
  
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);

        if (BridgeWebView.toLoadJs != null) {
            BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs);
        }

        //
        if (webView.getStartupMessage() != null) {
            for (Message m : webView.getStartupMessage()) {
                webView.dispatchMessage(m);
            }
            webView.setStartupMessage(null);
        }
    }
}

2.3.3 WebViewJavascriptBridgeを分析する.js
WebViewJavascriptBridgeを見てみましょうjsのコードでfunction_が見つかりますhandleMessageFromNative()というJsメソッドです._handleMessageFromNative()メソッドで呼び出されます.dispatchMessageFromNative()メソッド.Javaレイヤからのアクティブコールを処理すると、「直接送信」のelseブランチが実行されます.message.callbackIdは取り出され、responseCallbackがインスタンス化され、Js処理が完了した後に結果データをJavaレイヤコードにコールバックするために使用されます.次はメッセージに従います.handleName(この解析例では、handleNameの値は「functionInJs」である)は、messageHandlersというMapでhandlerを取得し、最後にhandlerに渡して処理します.
function _dispatchMessageFromNative(messageJSON) {
    setTimeout(function() {
        var message = JSON.parse(messageJSON);
        var responseCallback;
        //java call finished, now need to call js callback function
        if (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) {
                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);
                }
            }
        }
    });
}

2.3.4ページHtml登録「functionInJs」メソッド
上の分析を続けると、messageHandlerはどこで設定されているのでしょうか.答えは最初にloadUrl(“file:///android_asset/demo.html”);ロードされたこのdemo.htmlにあります.bridge.registerHandler(「functionInJs」,...)ここに「functionInJs」が登録されています.

    
    ...
    
    
    ...
    
    
        ...

        connectWebViewJavascriptBridge(function(bridge) {
            bridge.init(function(message, responseCallback) {
                console.log('JS got a message', message);
                var data = {
                    'Javascript Responds': '    !'
                };
                console.log('JS responding with', data);
                responseCallback(data);
            });

            bridge.registerHandler("functionInJs", function(data, responseCallback) {
                document.getElementById("show").innerHTML = ("data from Java: = " + data);
                var responseData = "Javascript Says Right back aka!";
                responseCallback(responseData);
            });
        })
    

2.3.5「functionInJs」実行結果Javaへの返信
「funciontInJs」の実行後に呼び出されたresponseCallbackはまさに_dispatchMessageFromNative()はインスタンス化され、実際には呼び出されます.doSend()メソッド._doSend()メソッドは、まずMessageをsendMessageQueueにプッシュします.次にメッセージを変更します.src、ここでJava層のWebViewClientを出発します.shouldOverrideUrlLoading()のコールバック.
function _doSend(message, responseCallback) {
    if (responseCallback) {
        var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
        responseCallbacks[callbackId] = responseCallback;
        message.callbackId = callbackId;
    }

    sendMessageQueue.push(message);
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
     BridgeWebViewClient.shouldOverrideUrlLoading()  ,    webView.flushMessageQueue()   。

@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);
    }
}

webView.flushMessageQueue()はまずJsの_を実行しますflushQueue()メソッドで、CallBackFunctionが付属しています.Jsの_flushQueue()メソッドはsendMessageQueueのすべてのメッセージをJavaレイヤに返信します.CallBackFunctionとは、messageQueueを解析した後の1つのMessageをforループで処理することであり、まさにforループで「functionInJs」のJavaレイヤコールバックメソッドが実行される.
void flushMessageQueue() {
    if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
        loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {

            @Override
            public void onCallBack(String data) {
                // deserializeMessage
                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++) {
                    ...
                }
            }
        });
    }
}

これでJsBridgeの呼び出しプロセスの解析が完了します.JsBridgeはMessageQueueを使用しているが、分析すると少し回ります.しかし原理は変わらず、JsがJavaを呼び出すのはWebViewClient.を通じてshouldOverrideUrlLoading().もちろん、冒頭で別の3つの方法を紹介します.Java呼び出しJsはWebViewを経由する.loadUrl(“javascript:xxxx”).
参照先:
よくh 5と疎通します!いくつかの一般的なhybrid通信方式;AndroidはWebViewJavascriptBridgeを利用してjsとjavaのインタラクションを実現する.GitHub:lzyzsd/JsBridge;