张津瑜厕所门事件 8分钟视频9分52秒流出

母子乱伦:和儿子做了,我该怎么办?

中俄曾约定:1996年收回海参崴

去泰国看了一场“成人秀”,画面尴尬到让人窒息.....

2021年推特网黄Top10排行榜

生成图片,分享到微信朋友圈

自由微信安卓APP发布,立即下载! | 提交文章网址
查看原文

WebView 干货分享:独立进程、跨进程通信、桥接设计

孙先森Blog 鸿洋 2023-01-25

本文作者


作者:孙先森Blog

链接:

https://juejin.cn/post/7143026094289977381

本文由作者授权发布。


前言

下面接着上篇博客Android 干货分享:WebView 优化(1)—— 缓存管理、回收复用、网页秒开、白屏检测 的 Demo 继续完善。

https://juejin.cn/post/7143025767268810759


1
桥接

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 后测试,效果图:

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 格式,增删参数方便。

桥接实现

流程图

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 文件:
下面是 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>


测试效果和普通调用是一样的,这里就不放效果图了,为什么呢?因为这条流程还没搞定,还有一个很重要的细节 ——— 回调。

回调支持

流程图

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>


效果图:
toast 一加成功展示,看一下回调输出的日志:
到这里为止用命令模式封装的桥接设计就大功告成了!

利用 apt 自动注册桥接

目前的实现后续每个命令的增删都需要在 BridgeCommandHandler 类中手动修改 mCommandMap 的初始化代码,这肯定是不合理的。网上也有很多博客利用 @AutoService 实现自动注册,这里分享一个我个人的方案,我是利用 APT 去扫描自定义注解生成一个工具类,BridgeCommandHandler 调用工具类方法实现自动注册的,下面简单说一下实现过程。

注意

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

新建 Moudle

首先新建两个 Java or Kotlin Library
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 文件:
文件生成成功后修改 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() }
}


运行测试:

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


2
独立进程

WebView 独立进程,就是让 Web 相关功能单独使用一个进程。
优点:
  1. 进程隔离,Web 进程发生异常不会导致主进程闪退。
  2. 分担主进程内存压力。
缺点:
  1. Application 每个进程启动都会初始化,造成多次初始化。
  2. 跨进程通信要注意的细节很多。(静态成员变量问题、sharedpreferences操作等等)
虽然有一定的缺点,当 App 越做业务越繁杂,内存占用变高,让其中一些功能模块独立进程运行能够有效解决内存占用高的问题。

查看进程

命令:
adb shell ps -A |grep com.sunhy.demo


可以看出目前 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 再次用命令行查看进程信息:
现在 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 的重点就不贴代码了,仅分享下思路,下面是最后的重头戏了。


2
跨进程通信


为什么要跨进程通信

思考一个问题,就以当前 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() 能够获取到用户信息吗?
并不能,原因很简单,在上面 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 进程调用为例,目前的调用流程图:
黑色框代表 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)
        }
    }
}

效果图


最后

Demo源码

源码地址:WebViewSimpleDemo

https://github.com/RDSunhy/WebViewSimpleDemo


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

如果我的博客分享对你有点帮助,不妨点个赞支持下!



最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


推荐阅读

我是如何从Android开发转framework开发的?
一文搞懂 Gradle 配置,7.0之后有哪些变化?
App防抓包的 8 个实践

扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

文章有问题?点此查看未经处理的缓存