【插件&热修系列】插件编程和动态设计思想篇

2,698 阅读4分钟

引言

上一篇我们学习了热修中的ClassLoader方案设计,主要是用来如何加载插件的;

在了解了宿主加载不同插件的姿势后,我们接下来看如何实现插件编程,同时了解著名的腾讯团队之一(PS:据说是腾讯视频等的某个事业群)是如何用插件化来实现动态化的设计的~

插件编程

大致流程

1)插件工程开发,其实就是一个app工程,实现插件业务

2)开发过程,插件需要不断调试,看下宿主加载和使用是否正常,那么需要把插件频繁拷贝到宿主能用的地方(如:asset等),这个可以用脚本实现

3)宿主验证

上面是简要的流程,接下来我们来看每一个流程的细节介绍和简单实现。

插件工程

这里是一个app模块,里面主要是实现插件自己的业务,比如:酷狗App里面,有听歌/直播等模块,每一个模块其实就是一个插件apk

插件相关脚本

在开发完插件功能后,插件apk需要拷贝到宿主的工程或者其他地方给宿主加载使用,这个过程涉及脚本的自动化,比如shadow的sample-manager插件apk的脚本(注意下面脚本都是在宿主的gradle文件中实现的):

1)宿主的gradle文件中,把插件的拷贝时机挂载在宿主的build过程中,这个在开发过程中比较方便,具体实现:

tasks.whenTaskAdded { task ->
    if (task.name == "generateDebugAssets") {
        generateAssets(task, 'debug')
    }
    if (task.name == "generateReleaseAssets") {
        generateAssets(task, 'release')
    }
}

2)generateAssets 的实现:

/***
 * 1)generateAssetsTask, 系统task的锚点
 * 2)buildType,debug/release
 * */
def generateAssets(generateAssetsTask, buildType) {
    println "daviAndroid generateAssets"

    def moduleName = 'sample-manager'
    def pluginManagerApkFile = file("${project(":sample-manager").getBuildDir()}" + "/outputs/apk/${buildType}/"
            + "${moduleName}-${buildType}.apk")
    //createCopyTask 挂在在  《generateAssetsTask, 系统task的锚点》的前面
    generateAssetsTask.dependsOn createCopyTask(
            ':sample-manager',
            buildType,
            moduleName,
            'pluginmanager.apk',
            pluginManagerApkFile,
            "assemble${buildType.capitalize()}"
    )
}

3)createCopyTask 实现:

//把 制定模块的apk(如:sample-manager )拷贝到 本工程的 build/generated/assets/xx/
def createCopyTask(projectName, buildType, name, apkName, inputFile, taskName) {
    println "daviAndroid createCopyTask"

    def outputFile = file("${getBuildDir()}/generated/assets/${name}/${buildType}/${apkName}")
    outputFile.getParentFile().mkdirs()

    return tasks.create("copy${buildType.capitalize()}${name.capitalize()}Task", Copy) {
        group = 'build'
        description = "复制${name}到assets中."
        from(inputFile.getParent()) {
            include(inputFile.name)
            rename { outputFile.name }
        }
        into(outputFile.getParent())
    }.dependsOn("${projectName}:${taskName}")
}

PS1:注意这里拷贝到了宿主的《build/generated/assets/xx/》,在宿主中通过openAsset方式读取,所以工程需要配置下assets的指向

 sourceSets {
        debug {
            assets.srcDir('build/generated/assets/sample-manager/debug/')
        }
        release {
            assets.srcDir('build/generated/assets/sample-manager/release/')
        }
    }

PS2:完整代码戳这里>>

验证

在上面已经完成了插件的开发,插件的拷贝;下面就是宿主在拷贝的制定目录进行加载和使用,关于加载的方式上一节有讲不同的方式,可以根据自己的业务需要进行选择,具体代码戳这里>>

插件化实现动态设计

背景

我们都知道腾讯的shadow框架中有一个特点就是全动态设计,插件框架的代码(如:插件的配置升级等业务)成为了插件的一部分,实现迭代不再受宿主打包了旧版本插件框架所限制

插件的配置升级动态化实现

插件在迭代过程中,会有在不同的业务下用不同的插件,那么这里就需要实现插件的下载/更新等逻辑;

正常情况下这些逻辑是写在宿主里面的,但是shadow就把这这部分逻辑通过插件的思维动态化起来了,这样后续策略等要调整,就不会面临不能调整的困惑

下面我们来看下shadow是怎么实现的,讲解的过程是根据代码展开,代码位置戳这里>>

实现过程

1)设计思想

22.png

a)宿主:以依赖接口的方式,加载插件里面的接口实现,实现插件的动态化

b)接口:乔接宿主和插件

c)插件:实现插件的下载业务/预加载其他插件等

当下载业务/预加载逻辑等需变动的时候,只需要改变插件的实现,然后重新下发即可

2)插件模块

下面是工程架构情况: 6ca8ac2ebffdc356dd6fac2cc4242faa.png

a)关于constant,一些常量

333.png

b)关于dynamic-manager

44.png

跨进程Service的实现机制,插件的加载等业务都是基于这样的IPC方案作为承载实现

c)关于manager

555.png

插件加载/更新/下载等具体实现类,如:加载插件的类型/插件的bean/插件zip的配置描述等

d)关于dynamic-host

首先要注意的是,这个模块对于插件来说只是编译的时候生效,然后是不打入插件的,和宿主的公共模块

777.png

主要是一些宿主要用的,插件也要供的公共模块,比如:自定义的ApkClassLoader,宿主需要用来加载插件,然后sample-manager插件管理器需要用来加载其他插件

3)宿主模块

下面是工程架构情况:

8.png

1)关于app

9.png

宿主启动模块,涉及启动触发插件更新时机/插件拷贝到本地等

2)关于constant,一些常量(和插件公共模块,具体见上面的插件部分)

3)关于dynamic-host

上面插件部分介绍了主要的功能,但是和插件区别的是这里模块是打到了宿主里面

4)关于commons-io

文件IO相关库

结尾

哈哈,该篇就写到这里(一起体系化学习,一起成长)

Tips

更多精彩内容,请关注 ”Android热修技术“ 微信公众号