[Android]Android 插件化-续

1,248 阅读2分钟

前言

在上一篇文章中,讲述了Android 的三种实现插件的方式。不过在最后一种中,按照前面所讲述的内容实现有一个限制。

我们之前传递文件是通过evaluateJavascript,相当于注入javascript 代码,这种方式会限制我们传递的数据的长度,当我们传递一个大一点的文件就会出现问题,亟待一个更好的方式实现文件的传递。

MessageChannel

在web 开发中,会使用iframe,将一个页面嵌入到另一个页面中,此时,如果需要在这两个页面中传递数据,用的就是MessageChannel。不止页面之间可以使用这种方式,native 与WebView 中的页面也可以使用这种方式。

  1. 第一种

    在html 中,通过window 注册一个message 的listener

    window.addEventListener("message", onMessage);
    
    function onMessage(e) {
        ele.src = "data:image/png;base64," + e.data;
        // Use the transfered port to post a message back to the main frame
        e.ports[0].postMessage("Message back from the IFrame");
    }
    

    然后在Android 端,首先生成一对MessageChannel。返回的是一个数组,包含两个MessageChannel。

    然后是注册WebMessageCallback

    val baseUrl = "http://www.example.com"
    val messageChannel = if (WebViewFeature.isFeatureSupported(WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL)) {
        WebViewCompat.createWebMessageChannel(webView)
    } else null
    if (messageChannel != null && WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK)) {
        messageChannel[0].setWebMessageCallback(object : WebMessagePortCompat.WebMessageCallbackCompat() {
            override fun onMessage(port: WebMessagePortCompat, message: WebMessageCompat?) {
                Log.d(TAG, "onMessage() called with: port = $port, message = ${message?.data}")
                super.onMessage(port, message)
            }
        })
    }
    

    然后在页面加载完成后发送消息

    override fun onPageFinished(view: WebView?, url: String?) {
        super.onPageFinished(view, url)
        url ?: return
        view ?: return
        messageChannel?.let {
            if (WebViewFeature.isFeatureSupported(WebViewFeature.POST_WEB_MESSAGE)) {
                val webMessageCompat = WebMessageCompat(content, it)
                WebViewCompat.postWebMessage(view, webMessageCompat, Uri.parse(baseUrl))
            }
        }
    
    }
    

    注意加载内容的方式。如果我们的内容是本地文件,需要手动置顶一个origin

    webView.loadDataWithBaseURL(baseUrl, indexFile.readText(), null, null, null)
    

    如果是http 或者https 等自带地址的就无所谓了。

    理论上是这样啦,但是实际使用的时候会出现错误

    java.lang.IllegalStateException: Port is already started

    一旦通过messageChannel[0].setWebMessageCallback 设置监听,然后再发送消息就会出现错误。

    如果不设置监听,发送消息是没有问题的,但是这样双向的沟通就变成了单向的了。不过,我们还有第二种

  2. 第二种

    在Android 端

    val baseUrl = "http://www.example.com"
    if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
        WebViewCompat.addWebMessageListener(webView, "test", setOf(baseUrl)) { view, message, sourceOrigin, isMainFrame, replyProxy ->
            Log.d(TAG, "onCreate() called with: view = $view, message = ${message.data}, sourceOrigin = $sourceOrigin, isMainFrame = $isMainFrame, replyProxy = $replyProxy")
        }
    }
    

    我们传递一个"test" ,然后我们在javascript 中便可以通过test 发送消息以及监听数据。

    test.onmessage = function(event) {
        // prints "Got it!" when we receive the app's response.
        console.log(event.data);
    }
    test.postMessage("hello from yue-html")
    

    但是,这种方式也有一个问题,无法从native 主动发送消息,只能在html 发送消息之后,才能在addWebMessageListener 中通过replyProxy.postMessage 发送消息。

最后

这种方式与evaluateJavascript 不同,MessageChannel 足以之后发送大文件。