“我报名参加金石计划1期挑战——分割10万奖池,这是我的第2篇文章,点击检查活动详情”

目录

  • Android 干货共享:WebView 优化(1)—— 缓存办理、收回复用、网页秒开、白屏检测

  • Android 干货共享:WebView 优化(2)—— 桥接规划、独立进程、跨进程通讯

前语

下面接着上篇博客的 Demo 继续完善

桥接

WebView 的桥接,也便是 App 能够调用 Web 中的 js 办法,js 也能够调用 App 中的办法

一般调用

js 调用 App 办法

以完成 js 调用 App 完成展现 toast 为例,Demo 为了便利直接在 BaseWebView 中添加以下代码:

open class BaseWebView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : WebView(context, attrs), LifecycleEventObserver {
    init {
        // 省掉其他代码...
        // 添加桥接
        addJavascriptInterface(this, "bridge")
    }
    // 添加注解 表明 js 能够调用该办法
    @JavascriptInterface
    fun showToast(message: String){
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
    }
}

新建 test_default_bridge.html 文件

<!DOCTYPE html>
<html>
<head>
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
    <script src='../js/bridge.js'></script>
    <style type="text/css">
   .bn {
      padding: 8px 20px;
      width: 100%;
      height: auto;
      margin: 0 auto;
      text-align: center;
      margin-top: 20px;
   }
    </style>
</head>
<body>
<div class="detail-content" id="app-vote">
    <div style="display: flex; flex-direction: column;">
        <button class="bn" onclick="showToast()">调用原生 App 展现 Toast</button>
    </div>
</div>
</body>
<script type='text/javascript'>
function showToast() {
   // bridge 要和 BaseWebView 中 addJavascriptInterface 第二个参数对应
   window.bridge.showToast('hello world')
}
</script>

让 WebView 加载 test_default_bridge.html 后测验,作用图:

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

App 调用 js 办法

App 调用 js 就比较简略了直接运用 WebView 的 evaluateJavascript 办法即可:

// 写正确 办法名 和 参数 即可
mWebView.evaluateJavascript("javascript:showToast('hello world')") {}

指令形式

作为开发者,肯定不期望写好的基类(BaseWebView)被频频改动,假如选用上述 Demo 中的办法完成桥接通讯,那每添加一个桥接都需求修正 BaseWebView 类,这儿就需求用规划形式来进行重构。 这儿为什么用指令形式?

  1. 通讯双发都契合 恳求方宣布恳求,要求履行某个操作;接纳方收到恳求,履行对应操作
  2. 而且都契合单指令单接纳者
  3. 恳求方和接纳方能够独立开来,不必知道对方的指令接口(经过封装后达成,调用同一办法完成不同指令)
  4. 下降耦合,新指令(桥接办法)很简略加入到项目中(下面会结合 APT 完成)

完成进程首要考虑几个方面:

  1. 一般桥接肯定要 Android iOS 一起完成,要考虑易用性。
  2. 新增桥接办法时不能频频改动已有代码,低耦合。
  3. 支撑回调,WebView 的 evaluateJavascript 办法支撑回调,那么 js 调用 Android 办法也要支撑回调。
  4. 桥接参数传递选择了 json 格局,增删参数便利。

桥接完成

流程图

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

App 供给发送指令桥接

首要新增桥接参数实体类:

data class JsBridgeMessage(
    @SerializedName("command")
    val command: String?, // 指令
    @SerializedName("params")
    val params: JsonObject?, // 参数
)

新增指令接口:

interface IBridgeCommand {
    fun exec(params: JsonObject?)
}

上述示例中展现 toast 的桥接改为指令,新增 ToastCommand 类:

class ToastCommand : IBridgeCommand {
    override fun exec(params: JsonObject?) {
        if (params != null && params["message"] != null) {
            ToastUtils.showShort(params["message"].asString)
        }
    }
}

BaseWebView 中添加 sendCommond 桥接办法:

class BaseWebView{
    // 省掉其他代码...
    init{
        addJavascriptInterface(this, "bridge")
    }
    @JavascriptInterface
    fun sendCommand(json: String?) {
        if (json.isNullOrEmpty()) {
            // 反常调用处理
            return
        }
        try {
            val message = GsonUtils.fromJson(json, JsBridgeMessage::class.java)
            // 成功拿到参数后 待会在这儿分发指令
            // ...
        } catch (e: JsonSyntaxException) {
            e.printStackTrace()
        }
    }
}

指令分发器

分发器首要担任指令参数合法性校验、履行指令,这儿用单例形式完成:

class JsBridgeInvokeDispatcher {
    companion object {
        // 省掉不必要的代码...
        // 单例
        fun getInstance(): JsBridgeInvokeDispatcher {
            // ...
        }
    }
    // 露出给外部办法 分发调用
    fun sendCommand(view: BaseWebView, message: JsBridgeMessage?) {
        LogUtils.d(TAG, "sendCommand()", "message: $message")
        if (checkMessage(message)){
            // 校验指令通往后 履行指令
            excuteCommand(view, message)
        }
    }
    // 校验指令、参数 合法性
    private fun checkMessage(message: JsBridgeMessage?): Boolean{
        if (message == null) {
            return false
        }
        // ...
        return true
    }
    //履行指令
    private fun excuteCommand(view: BaseWebView, message: JsBridgeMessage?){
        //...
    }
}

履行指令再抽出一个类来,BridgeCommandHandler 首要担任指令的注册、指令的逻辑履行:

class BridgeCommandHandler {
    companion object {
        // 省掉不必要的代码...
        // 单例
        fun getInstance(): BridgeCommandHandler {
            // ...
        }
    }
    // 用于切线程
    private val mHandle = Handler(Looper.getMainLooper())
    // 指令注册 暂时用 map 手动添加 后续修正
    private val mCommandMap by lazy {
        val map = ArrayMap<String, IBridgeCommand>().apply {
            put("showToast", ToastCommand())
        }
        return@lazy map
    }
    // 露出给外部办法 分发调用
    fun handleBridgeInvoke(command: String?, params: String?) {
        // map 中存在指令 则履行
        if (mCommandMap.contains(command)) {
            mHandle.post { // 切换到主线程 获取指令 履行
                mCommandMap[command]!!.exec(
                    GsonUtils.fromJson(params, JsonObject::class.java)
                )
            }
        }
    }
}

再回到 JsBridgeInvokeDispatcher 的 excuteCommand 履行办法里调用一下 BridgeCommandHandler :

private fun excuteCommand(view: BaseWebView, message: JsBridgeMessage?){
    BridgeCommandHandler.getInstance().handleBridgeInvoke(message.command, message.params)
}

到这儿 App 端现已为 js 端做好了调用准备,这儿毕竟是个 Demo 下面咱们自己来搞一下 js 那边的封装。

js 文件封装

仍是在 assets 的 js 目录下,新建 bridge.js 文件:

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

下面是 js 文件内容,算是一个东西类吧,Android 开发其实不必特别重视 js 代码,就不详细解说了,代码中都有注释,直接贴代码:

var jsBridge = {};
// 系统判别
jsBridge.os = {
    'isAndroid': Boolean(navigator.userAgent.match(/android/ig)),
    'isIOS': Boolean(navigator.userAgent.match(/iphone|ipod|iOS/ig))
};
// 发送指令 参数解说:command 指令;params 参数json格局
jsBridge.sendCommand = function(command, params) {
   // 构建 message 目标
   var message = {
      'command': command
   }
   if (params && typeof params === 'object') { // 支撑传参
       message['params'] = params // 参数
   }
   if (jsBridge.os.isAndroid) { // android 桥接调用
      window.bridge.sendCommand(JSON.stringify(message))
   } else if (jsBridge.os.isIOS) { // ios 桥接调用 偷来的代码 不必太重视
      window.webkit.messageHandlers.bridge.sendCommand(JSON.stringify(message))
   }
}
window.jsBridge = jsBridge;

修正最初事例中展现 toast 的 html 文件:

// 省掉了无关代码 只展现了引进 bridge.js 和 办法调用
// ...
<head>
    <script src='./js/bridge.js'></script>
</head>
// ...
<script type='text/javascript'>
function showToast() {
   // window.bridge.showToast('hello world')
   // 上面的桥接调用修正为
   var params = {
     'message': 'hello world'
   }
   window.jsBridge.sendCommand('showToast', params)
}
</script>

测验作用和一般调用是相同的,这儿就不放作用图了,为什么呢?因为这条流程还没搞定,还有一个很重要的细节 ———— 回调

回调支撑

流程图

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

Web 端完成

这次先来修正 js 文件部分,在 bridge.js 中维护一个回调 map,而且桥接调用支撑传递回调办法:

// 省掉其他代码 只贴 新增 修正的代码
// 回调办法 map
jsBridge.mapCallbacks = {}
// 回调处理 供给给 App 指令履行完成后调用此办法 依据 key 从 map 中取出 触发回调
jsBridge.postBridgeCallback = function(key, data){
    var obj = jsBridge.mapCallbacks[key]; // 从 map 中拿出 function
    if(obj.callback){ // 存在则调用
        obj.callback(data); // 调用 有参数则传递参数 这儿回调参数也规划为 JSON 格局
        delete jsBridge.mapCallbacks[key]; // 从 map 中移除
    }else{ // 不存在 反常处理
        console.log('jsBridge postBridgeCallback', '回调不存在: ' + key)
    }
}
// 生成回调map key 的办法 选用 固定前缀+时刻戳+随机码 的办法 
// 避免短时刻内并发调用呈现重复的key
function generateCallbackKey(){
    return "jsBridgeCallback_" + new Date().getTime() + "_" + randomCode();
}
// 生成随机码 避免并发重复
function randomCode(){
    var code = ""
    for(var i = 0; i < 6; i++){
        code += Math.floor(Math.random() * 10)
    }
    return code;
}
// 发送指令 办法修正 添加 callback 回调办法参数
jsBridge.sendCommand = function(command, params, callback) {
   var message = {
      'command': command
   }
   if (params && typeof params === 'object') { // 支撑传参
       message['params'] = params
   }
   // sendCommand 办法首要添加了这个 if 判别
   // 回调 key 固定放在 bridgeCallback 字段中,app 客户端判别 callback 是否有值即可
   if (callback && typeof callback === 'function') { // 支撑回调 判别是否是回调办法
        var key = generateCallbackKey() // 生成回调key
        jsBridge.mapCallbacks[key] = { 'callback': callback } // 回调办法放入 map 中
        message['params']['bridgeCallback'] = key // 参数新增字段 bridgeCallback
   }
   if (jsBridge.os.isAndroid) { // android 桥接调用
      window.bridge.sendCommand(JSON.stringify(message))
   } else if (jsBridge.os.isIOS) { // ios 桥接调用 偷来的代码 不必太重视
      window.webkit.messageHandlers.bridge.sendCommand(JSON.stringify(message))
   }
}

App 端完成

首要 JsBridgeMessage 添加 bridgeCallback 字段:

data class JsBridgeMessage(
    //省掉...
    @SerializedName("bridgeCallback")
    val bridgeCallback: String? // 回调 key
)

新增回调接口 IBridgeCallbackInterface :

interface IBridgeCallbackInterface {
    /**
     * callback 回调key
     * params   参数 json 格局
     */
    fun handleBridgeCallback(callback: String, params: String)
    // 从参数中获取回调 key 的办法
    fun getCallbackKey(params: JsonObject?): String? {
        if (params == null) {
            return null
        }
        if (params["bridgeCallback"] == null) {
            return null
        }
        return params["bridgeCallback"].asString
    }
}

修正 IBridgeCommand 中 exec 办法参数,添加回调接口参数

interface IBridgeCommand {
    fun exec(params: JsonObject?, callback: IBridgeCallbackInterface?)
}

修正 BridgeCommandHandler 的 handleBridgeInvoke 办法参数,添加回调接口参数:

fun handleBridgeInvoke(command: String?, params: String?, bridgeCallback: IBridgeCallbackInterface?) {
    // ... 
    mCommandMap[command]!!.exec(
        GsonUtils.fromJson(params, JsonObject::class.java),
        bridgeCallback // 回调传递给 Command
    )
}

最终修正 JsBridgeInvokeDispatcher 的 excuteCommand 办法:

class JsBridgeInvokeDispatcher {
    // ...
    private fun excuteCommand(view: BaseWebView, message: JsBridgeMessage?){
        // 完成 IBridgeCallbackInterface
        val callback = object : IBridgeCallbackInterface{
            override fun handleBridgeCallback(callback: String, params: String) {
                view.postBridgeCallback(callback, params)
            }
        }
        BridgeCommandHandler.getInstance().handleBridgeInvoke(
            message?.command, 
            GsonUtils.toJson(message?.params), 
            callback
        )
    }
}

BaseWebView 中供给触发回调办法:

class BaseWebView{
    // 省掉其他代码
    fun postBridgeCallback(key: String?, data: String?) {
        post {
          evaluateJavascript("javascript:window.jsBridge.postBridgeCallback(`$key`, `$data`)") {}
        }
    }
}

最终修正一下 ToastCommand 中的代码:

class ToastCommand : IBridgeCommand {
    override fun exec(params: JsonObject?) {
        if (params != null && params["message"] != null) {
            ToastUtils.showShort(params["message"].asString)
            //回调 测验 回来一个 message 给 web 端
            val key = getCallbackKey(params)
            if (!key.isNullOrEmpty()) {
                val data = mapOf("message" to "showToast is success!!")
                callback?.handleBridgeCallback(key, GsonUtils.toJson(data))
            }
        }
    }
}

为了测验回调作用 js 中新增一个带回调的调用:

<script type='text/javascript'>
// ...
function showToastWithCallback() {
    var params = {
        'message': 'hello world'
    }
    window.jsBridge.sendCommand('showToast', params, function(data){
       // 打印一下 回调中承受的参数
       console.log('触发回调成功! data:', data)
   })
}
</script>

作用图:

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

toast 一加成功展现,看一下回调输出的日志:

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

到这儿停止用指令形式封装的桥接规划就大功告成了!

运用 apt 主动注册桥接

现在的完成后续每个指令的增删都需求在 BridgeCommandHandler 类中手动修正 mCommandMap 的初始化代码,这肯定是不合理的。网上也有许多博客运用 @AutoService 完成主动注册,这儿共享一个我个人的计划,我是运用 APT 去扫描自定义注解生成一个东西类,BridgeCommandHandler 调用东西类办法完成主动注册的,下面简略说一下完成进程。

留意

Demo 中 APT 相关代码也是用 Kotlin 写的,Demo 整体也是个完完全全的 Kotlin 项目,那么 APT 生成的文件也自然是 Kotlin 文件最好

新建 Moudle

首要新建两个 Java or Kotlin Library

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

apt-annotations : 用来写自定义注解

apt-processor :用来写自定义注解处理器

apt-processor 添加依赖:

implementation project(path: ':apt-annotations')
// kotlin 文件生成库
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.6.10"
implementation "com.squareup:kotlinpoet:1.8.0"
implementation "com.google.auto.service:auto-service:1.0"
kapt "com.google.auto.service:auto-service:1.0"

moudle_web 引进两个 apt 模块:

implementation project(path: ':apt-annotations')
kapt project(path: ':apt-processor')

注解

apt-annotations 中新建自定义注解:

@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class JsBridgeCommand(
    val name: String // 指令名
)

注解处理器

apt-processor 中新建自定义注解处理器 JsBridgeCommandProcessor, APT 不是要点,就简略贴一下部分代码,详情能够去 Demo 中检查:

@AutoService(Processor::class)
class JsBridgeCommandProcessor : AbstractProcessor() {
    // 省掉其他代码...
    // 生成代码 途径
    val packageName = "com.sunhy.demo.apt"
    // 生成类办法
    val registerMethodBuilder = FunSpec.builder("autoRegist")
        .addComment("web jsbridge command auto load")
    // 定义局部变量
    val arrayMap = ClassName("android.util", "ArrayMap")
    val iBridgeCommand = ClassName("com.sunhy.demo.web.bridge", "IBridgeCommand")
    val arrayMapCommand = arrayMap.parameterizedBy(String::class.asTypeName(), iBridgeCommand)
    registerMethodBuilder.addStatement("val commandMap = %L()", arrayMapCommand)
    commandMap.forEach { (key, value) ->
        registerMethodBuilder.addStatement("commandMap[%S] = $value()", key)
    }
    // 办法回来类型
    registerMethodBuilder.returns(arrayMapCommand)
    registerMethodBuilder.addStatement("return commandMap")
    // 生成伴生目标
    val companionObject = TypeSpec.companionObjectBuilder()
        .addFunction(registerMethodBuilder.build())
        .build()
    // 生成类
    val clazzBuilder = TypeSpec.classBuilder("JsBridgeUtil")
        .addType(companionObject)
    //输出到文件...
    }
}

注解运用

给 ToastCommand 添加注解:

@JsBridgeCommand(name = "showToast")
class ToastCommand : IBridgeCommand {
    // ...
}

到这儿先编译一下项目,让 APT 生成 JsBridgeUtil 文件:

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

文件生成成功后修正 BridgeCommandHandler 中 mCommandMap 初始化代码:

class BridgeCommandHandler {
    //    private val mCommandMap by lazy {
    //        val map = ArrayMap<String, IBridgeCommand>().apply {
    //            put("showToast", ToastCommand())
    //        }
    //        return@lazy map
    //    }
    // 之前初始化代码 替换为 主动注册
    private val mCommandMap: ArrayMap<String, IBridgeCommand> by lazy { JsBridgeUtil.autoRegist() }
}

运转测验:

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

后续在注册新桥接新建 Command 时只需求加上注解给予 name 指令名即可。

独立进程

WebView 独立进程,便是让 Web 相关功能单独运用一个进程。

长处:

  1. 进程阻隔,Web 进程产生反常不会导致主进程闪退
  2. 分管主进程内存压力

缺陷:

  1. Application 每个进程启动都会初始化,形成屡次初始化
  2. 跨进程通讯要留意的细节许多(静态成员变量问题、sharedpreferences操作等等)

虽然有必定的缺陷,当 App 越做业务越繁杂,内存占用变高,让其中一些功能模块独立进程运转能够有用处理内存占用高的问题。

检查进程

指令:

adb shell ps -A |grep com.sunhy.demo
Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

能够看出现在 Demo 只有一个进程。

完成 Web 进程

让 WebView 相关页面在一个新的进程运转非常简略,只需求在 AndroidManifest.xml 给对应的 Activity 添加 process :

<application>
    <activity
        android:name=".activity.NewsDetailActivity"
        android:exported="false" 
        android:process=":web"/> // 进程名
    <activity
        android:name=".activity.WebActivity"
        android:exported="false"
        android:process=":web"/> // 一切 WebView 相关页面都在 :web 进程中运转
</application>

运转 App 而且启动其中的 WebActivity 再次用指令行检查进程信息:

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

现在 Demo 现已有两个进程了,独立进程就现已完成了!

可是留意!!!多进程带来的缺陷不能忽视,Application 进行了屡次初始化,也就意味着之前在 Application 中写的 WebViewPool 初始化的代码初始化了两次,WebView 相关既然现已独立到新进程,那么主进程的 Application 还需求对 WebViewPool 初始化吗?

当然不需求!能够经过判别当时进程姓名进行不同的初始化操作:

class BaseApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        when(ProcessUtils.getCurrentProcessName()){
            "com.sunhy.demo" -> {
                // 主进程初始化...
            }
            "com.sunhy.demo:web" -> {
                // :web 进程程初始化...
                initWebViewPool()
            }
        }
    }
}

Application 规划

上面这种写法呢并不友好,当进程变多,或者因业务逻辑修正需求修正各个进程初始化逻辑时,又是会产生频频修正 BaseApplication 文件的问题,特别多人协作我们都担任不同的模块一起修正一个文件不免抵触。

这儿共享两个个人时刻过的思路:

  1. 运用抽象工厂形式,BaseApplication 中仅做生产调用,各个进程详细初始化逻辑写在各自的“孵化器”中。
  2. 和上面主动注册桥接办法相同运用 APT 完成,无非是把 map 中的桥接名替换为进程名,依据进程名取出“孵化器”进行初始化操作。

这不是关于 WebView 的要点就不贴代码了,仅共享下思路,下面是最终的重头戏了。

跨进程通讯

为什么要跨进程通讯

考虑一个问题,就以当时 Demo 来说,有一个登陆东西类:

object LoginUtils {
    private var userInfo: UserInfo? = null
    fun getUserInfo(): String{
        return GsonUtils.toJson(userInfo)
    }
    // 模拟登陆
    fun login(){
        this.userInfo = UserInfo("孙先森@", "ASDJKLQJDKL12KLDKL3KLJ1234KL12KLLDA")
    }
}

App 主进程初始化时进行登陆,那么在 web进程中经过调用 LoginUtils.getUserInfo() 能够获取到用户信息吗?

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

并不能,原因很简略,在上面 webview 独立进程后,用户登陆只在主进程进行,web 进程中对 LoginUtils 的调用归于一个全新的目标。

需求处理类似这种问题就要着手完成进程间通讯,将主进程的 userInfo 传递给 web 进程。

完成

添加登陆指令

@JsBridgeCommand(name = "getUserInfo")
class UserInfoCommand : IBridgeCommand{
    override fun exec(params: JsonObject?, callback: IBridgeCallbackInterface?) {
        val userInfoJson = LoginUtils.getUserInfo()
        val key = getCallbackKey(params)
        if (!key.isNullOrEmpty()) {
            callback?.handleBridgeCallback(key, userInfoJson)
        }
    }
}

js 调用

function getUserInfo() {
    var params = {}
    window.jsBridge.sendCommand('getUserInfo', params, function(data){
       console.log('用户信息:', data)
       if(data){
           $('.user').text(data);
       }
   })
}

剖析

以上述 getUserInfo 从 web 进程调用为例,现在的调用流程图:

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

黑色框代表 web 进程调用,赤色框代表 main 进程调用。能够看出分发器履行分发指令后就要进行跨进程通讯。

跨进程通讯

基础工作整完,下一步便是完成跨进程通讯。这儿选用 Android 中的 Binder 来完成跨进程通讯。首要新建 AIDL 文件:

// web 进程调用 主进程
// 也便是 js 调用 原生 桥接调用
// 表明web进程调用主进程
interface IBridgeInvokeMainProcess {
    void handleBridgeInvoke(String command, String params, IBridgeCallbackInterface bridgeCallback);
}

IBridgeCallbackInterface 之前现已定义为一般接口,首要用于处理桥接回调,那么相同改写为 AIDL 文件,Demo 没有删除 IBridgeCallbackInterface 文件为了避免命名抵触 AIDL 文件命名为 IBridgeInvokeWebProcess :

// 办法没有改变,由 IBridgeCallbackInterface 改为了 IBridgeInvokeWebProcess
interface IBridgeInvokeWebProcess {
    void handleBridgeCallback(String callback, String params);
}

先编译一下,没问题后接着修正 BridgeCommandHandler,它首要担任处理 js 调用原生办法,所以首要承继 IBridgeInvokeMainProcess.Stub(),对应修正其handleBridgeInvoke 办法参数:

class BridgeCommandHandler: IBridgeInvokeMainProcess.Stub() {
    // 省掉代码...
    // 修正最终一参数类型
    override fun handleBridgeInvoke(command: String?, params: String?, bridgeCallback: IBridgeInvokeWebProcess?) {
        // 省掉代码...
    }
}

接着修正 IBridgeCommand 中 exec 办法回调参数:

interface IBridgeCommand {
    // IBridgeCallbackInterface 改为 IBridgeInvokeMainProcess
    fun exec(params: JsonObject?, callback: IBridgeInvokeMainProcess?)
}

相关指令子类同时修正,这儿就不贴了。

依据上述剖析 BridgeCommandHandler 的调用在主进程,那么主进程需求一个 service 向外露出:

class BridgeCommandService: Service() {
    override fun onBind(intent: Intent?): IBinder {
        return BridgeCommandHandler.getInstance()
    }
}
// AndroidManifest.xml 中注册
<service android:name=".service.BridgeCommandService"/>

下一步就轮到修正指令分发器JsBridgeInvokeDispatcher ,首要承继 ServiceConnection 而且供给绑定 service 办法:

class JsBridgeInvokeDispatcher : ServiceConnection{
private var iBridgeInvokeMainProcess: IBridgeInvokeMainProcess? = null
    //省掉其他代码...
    // 获取 IBinder 目标
    fun bindService() {
        LogUtils.d(TAG, "bindService()")
        if (iBridgeInvokeMainProcess == null) {
            val i = Intent(BaseApplication.getInstance(), BridgeCommandService::class.java)
            BaseApplication.getInstance().bindService(i, this, Context.BIND_AUTO_CREATE)
        }
    }
    fun unbindService() {
        LogUtils.d(TAG, "unbindService()")
        iBridgeInvokeMainProcess = null
        BaseApplication.getInstance().unbindService(this)
    }
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        iBridgeInvokeMainProcess = IBridgeInvokeMainProcess.Stub.asInterface(service)
    }
    override fun onServiceDisconnected(name: ComponentName?) {
        iBridgeInvokeMainProcess = null
    }
    // excuteCommand 办法修正
    // callback 改为 IBridgeInvokeWebProcess.Stub
    // 经过 iBridgeInvokeMainProcess 跨进程调用 handleBridgeInvoke
    private fun excuteCommand(view: BaseWebView, message: JsBridgeMessage?) {
        val callback = object : IBridgeInvokeWebProcess.Stub() {
            override fun handleBridgeCallback(callback: String, params: String) {
                LogUtils.e(TAG, "当时进程: ${ProcessUtils.getCurrentProcessName()}")
                view.postBridgeCallback(callback, params)
            }
        }
        if (iBridgeInvokeMainProcess != null){
            iBridgeInvokeMainProcess?.handleBridgeInvoke(message?.command, parseParams(message?.params), callback)
        }
    }
}

作用图

Android 干货分享:WebView 优化(2)—— 桥接设计、独立进程、跨进程通信

最终

Demo源码

源码地址:WebViewSimpleDemo

Demo源码均为手写,参考文献已在第一篇文末说明。

THE END

假如我的博客共享对你有点协助,不妨点个赞支撑下!