欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android插件化-RePlugin项目集成与使用详解

程序员文章站 2023-11-12 08:45:28
前言:前一段时间新开源了一种全面插件化的方案-- replugin,之前一种都在关注 droidplugin 并且很早也在项目中试用了,但最终没有投入到真正的生产环节,一方...

前言:前一段时间新开源了一种全面插件化的方案-- replugin,之前一种都在关注 droidplugin 并且很早也在项目中试用了,但最终没有投入到真正的生产环节,一方面是项目中没有特别需要插件化的需求,另一方面也考虑到 droidplugin 不是特别稳定,android系统每更新一次 droidplugin 可能就会出现一些 bug,毕竟 hook 了 android 原生的太多东西,系统一旦更新引发 bug 是在所难免的。当然,这些并不能否认 droidplugin 的优秀,它的原理和思路值得我们深入探究、学习,前一段时间更新过几篇插件化的原理分析的文章(基于 drodiplugin 原理)学习过程中不得不叹服作者的思路和技术深度!前几篇小白也能看懂的插件化系列文章仍然会不定期更新,但目前我们可以先来学习学习 replugin,毕竟多学无害,也能互相参考他们的思路,比较优缺点。

1.什么是replugin?

在android开发领域,有关插件化的讨论一直热度不减。目前市面上的插件化方案虽然很多,但多数只能实现某些功能的插件化,距离开发者的预期尚有相当差距。对此,在近期gmtc全球移动技术大会上,360手机卫士主程序架构负责人张炅轩宣布,360的插件化框架replugin已经可以实现“全面插件化”,同时具有出色的稳定性和灵活性,可适用于各种类型的应用上。
“replugin预计7月份开源,这将是我们献给安卓世界最好的礼物。”360如是说。

2.replugin有什么用?

replugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的replugin team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。

3.replugin官方介绍

其主要优势有:

  1. 极其灵活:主程序无需升级(无需在manifest中预埋组件),即可支持新增的四大组件,甚至全新的插件
  2. 非常稳定:hook点仅有一处(classloader),无任何binder hook!如此可做到其崩溃率仅为“万分之一”,并完美兼容市面上近乎所有的android rom
  3. 特性丰富:支持近乎所有在“单品”开发时的特性。包括静态receiver、task-affinity坑位、自定义theme、进程坑位、appcompat、databinding等
  4. 易于集成:无论插件还是主程序,只需“数行”就能完成接入
  5. 管理成熟:拥有成熟稳定的“插件管理方案”,支持插件安装、升级、卸载、版本管理,甚至包括进程通讯、协议版本、安全校验等
  6. 数亿支撑:有360手机卫士庞大的数亿用户做支撑,三年多的残酷验证,确保app用到的方案是最稳定、最适合使用的

一、集成主工程

1、在项目根目录的 build.gradle 下添加 replugin host gradle 依赖:

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:2.3.3'
    // 1、添加replugin host gradle依赖
    classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.1'
  }
}

2、在 app/build.gradle 下添加 replugin host library 依赖(为了更清晰的表示出代码添加的位置,将原有代码也一并贴出):
apply plugin: 'com.android.application'

android {
  compilesdkversion 26
  buildtoolsversion "26.0.1"
  defaultconfig {
    applicationid "cn.codingblock.repluginstudy"
    minsdkversion 21
    targetsdkversion 26
    versioncode 1
    versionname "1.0"
    testinstrumentationrunner "android.support.test.runner.androidjunitrunner"
  }
  buildtypes {
    release {
      minifyenabled false
      proguardfiles getdefaultproguardfile('proguard-android.txt'), 'proguard-rules.pro'
    }
  }
}

apply plugin: 'replugin-host-gradle'// 集成 replugin 添加的配置

// 集成 replugin 添加的配置
repluginhostconfig {
  useappcompat = true // 如果项目需要支持 appcomat,则需要将此配置置为 true
  // 如果应用需要个性化配置坑位数量,则需要添加以下代码进行配置
//  countnottranslucentstandard = 6
//  countnottranslucentsingletop = 2
//  countnottranslucentsingletask = 3
//  countnottranslucentsingleinstance = 2
}

dependencies {
  compile filetree(dir: 'libs', include: ['*.jar'])
  androidtestcompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
  })
  compile 'com.android.support:appcompat-v7:26.+'
  compile 'com.android.support.constraint:constraint-layout:1.0.2'
  compile 'com.qihoo360.replugin:replugin-host-lib:2.2.1' // 集成 replugin 添加的配置
  testcompile 'junit:junit:4.12'
}

以上代码有三点需要注意:

  1. 需要将 apply plugin: 'replugin-host-gradle' 放在 android {...} 之后。
  2. 如果项目需要支持 appcomat,则需要将 repluginhostconfig 的 userappcompat 置为 true。
  3. 如果应用需要个性化配置坑位数量,则需要在 repluginhostconfig 中添加以下代码进行配置:
countnottranslucentstandard = 6

countnottranslucentsingletop = 2

countnottranslucentsingletask = 3

countnottranslucentsingleinstance = 2 

3、让工程的 application 直接继承自 repluginapplication:

public class myapplication extends repluginapplication { } 

当然,同时不要忘了在 androidmanifest 对 myapplication 的相关配置。

说明:有时候由于项目原有结构的需要,我们可能不能直接使用继承 repluginapplication 的方式,这个问题看来 replugin 开发者也想到了,所以还特地多了一种选择,下面是项目的 application 不继承 repluginapplication 的方式:

public class myapplication extends application {

  @override
  protected void attachbasecontext(context base) {
    super.attachbasecontext(base);
    replugin.app.attachbasecontext(this);
  }

  @override
  public void oncreate() {
    super.oncreate();
    replugin.app.oncreate();
  }

  @override
  public void onlowmemory() {
    super.onlowmemory();
    replugin.app.onlowmemory();
  }

  @override
  public void ontrimmemory(int level) {
    super.ontrimmemory(level);
    replugin.app.ontrimmemory(level);
  }

  @override
  public void onconfigurationchanged(configuration newconfig) {
    super.onconfigurationchanged(newconfig);
    replugin.app.onconfigurationchanged(newconfig);
  }
}

二、集成插件

新建一个工程做为插件app,这里为了方便起见,直接在主工程中新建了一个 module。

1、同集成主工程类似,在根目录的 build.gradle 添加 replugin plugin gradle 依赖(若是单独创建插件工程,则不需要添加注释1下面的代码):

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:2.3.3'
    // 1、添加replugin host gradle依赖(主工程用)
    classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.1'
    // 2、添加replugin plugin gradle依赖(插件工程用)
    classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.1'
  }
}

2、在 app/build.gradle 中添加 replugin-plugin-gradle 插件和 replugin-plugin-lib 依赖:

apply plugin: 'com.android.application'

android {
  ...
}

apply plugin: 'replugin-plugin-gradle' // 集成 replugin 添加的配置

dependencies {
  compile filetree(dir: 'libs', include: ['*.jar'])
  androidtestcompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
  })
  compile 'com.android.support:appcompat-v7:26.+'
  compile 'com.android.support.constraint:constraint-layout:1.0.2'
  compile 'com.qihoo360.replugin:replugin-plugin-lib:2.2.1' // 集成 replugin 添加的配置
  testcompile 'junit:junit:4.12'
}

三、管理插件

replugin 对插件定义两种方式一种是外置插件、一种是内置插件。

  1. 外置插件:即从网络下载或者从sd卡中获得的,以 .apk 结尾。
  2. 内置插件:内置于 app 之中,并随 app 一并发版,需要将插件 apk 改成 .jar 结尾放入主程序的assets/plugins目录。

(一)外置插件的安装(升级)、启动、卸载

安装插件:

plugininfo plugininfo = replugin.install(environment.getexternalstoragedirectory().getpath().tostring() + "/plugin1.apk");

system.out.println(plugininfo); 

同时别忘了添加文件读写的权限。 输出日下:

复制代码 代码如下:

10-30 16:10:23.769 20280-20280/cn.codingblock.repluginstudy i/system.out: pinfo { <cn.codingblock.plugin1:1(4)> [apk] [dex_extracted] processes=[] js={"pkgname":"cn.codingblock.plugin1","name":"cn.codingblock.plugin1","low":10,"high":10,"ver":1,"verv":2814792716779521,"path":"\/data\/user\/0\/cn.codingblock.repluginstudy\/app_p_a\/-347346251.jar","type":11,"frm_ver":4} dex=/data/data/cn.codingblock.repluginstudy/app_p_od/-347346251.dex nlib=/data/data/cn.codingblock.repluginstudy/app_p_n/-347346251 }

安装成功了! (升级插件也是用 install() 方法,不可降级,同本版可覆盖安装)

启动插件

先来看一下 replugin.java 中启动插件相关的源码

/**
 * 创建一个用来定向到插件组件的intent <p>
 * <p>
 * 推荐用法: <p>
 * <code>
 * intent in = replugin.createintent("clean", "com.qihoo360.mobilesafe.clean.cleanactivity");
 * </code> <p>
 * 当然,也可以用标准的android创建方法: <p>
 * <code>
 * intent in = new intent(); <p>
 * in.setcomponent(new componentname("clean", "com.qihoo360.mobilesafe.clean.cleanactivity"));
 * </code>
 *
 * @param pluginname 插件名
 * @param cls    目标全名
 * @return 可以被replugin识别的intent
 * @since 1.0.0
 */
public static intent createintent(string pluginname, string cls) {
  intent in = new intent();
  in.setcomponent(createcomponentname(pluginname, cls));
  return in;
}

/**
 * 开启一个插件的activity <p>
 * 其中intent的componentname的key应为插件名(而不是包名),可使用createintent方法来创建intent对象
 *
 * @param context context对象
 * @param intent 要打开activity的intent,其中componentname的key必须为插件名
 * @return 插件activity是否被成功打开?
 * fixme 是否需要exception来做?
 * @see #createintent(string, string)
 * @since 1.0.0
 */
public static boolean startactivity(context context, intent intent) {
  // todo 先用旧的开启activity方案,以后再优化
  componentname cn = intent.getcomponent();
  if (cn == null) {
    // todo 需要支持action方案
    return false;
  }
  string plugin = cn.getpackagename();
  string cls = cn.getclassname();
  return factory.startactivitywithnoinjectcn(context, intent, plugin, cls, ipluginmanager.process_auto);
}

根据 replugin 的 startactivity() 和 createintent() 方法注释中的示例可知,启动插件需要先用插件的名字和目标activity的全路径创建一个 intent,然后调用 replugin.startactviity() 启动即可:

intent intent = replugin.createintent("plugin1", "cn.codingblock.plugin1.mainactivity");

if (!replugin.startactivity(mainactivity.this, intent)) {

  toast.maketext(mcontext, "启动失败", toast.length_long).show();

} 

点击按钮,输出如下:

10-30 16:21:02.464 20280-20280/cn.codingblock.repluginstudy d/replugin.ws001: start activity: intent=intent { cmp=plugin1/cn.codingblock.plugin1.mainactivity } plugin=plugin1 activity=cn.codingblock.plugin1.mainactivity process=-2147483648

10-30 16:21:02.464 20280-20280/cn.codingblock.repluginstudy d/replugin.ws001: start activity: intent=intent { cmp=plugin1/cn.codingblock.plugin1.mainactivity } plugin=plugin1 activity=cn.codingblock.plugin1.mainactivity process=-2147483648 download=true

10-30 16:21:02.464 20280-20280/cn.codingblock.repluginstudy d/replugin.ws001: plugin=plugin1 not found, start download ...

10-30 16:21:02.469 20280-20280/cn.codingblock.repluginstudy d/replugin.ws001: isneedtodownload(): v5 file not exists. plugin = plugin1 

启动失败了!(插件名称确实是:plugin1,而不是 plugin1 )

把 ==createintent() 方法的第一参数换成插件的包名 cn.codingblock.plugin1 ==试一试,居然可以了。

但是,注释总不会这样赤裸裸的坑我们吧!

卸载插件

replugin.uninstall("plugin1"); 

点击卸载,输入如下:

10-30 16:31:21.988 5006-5006/cn.codingblock.repluginstudy d/replugin.ws001: mp.pluginuninstall ... pluginname=plugin1

10-30 16:31:21.988 5006-5006/cn.codingblock.repluginstudy d/replugin.ws001: not installed. pluginname=plugin1 

没卸载成功?哈哈,这个简单,原套路把参数换成包名,果然可以了:

10-30 16:41:46.179 10193-10193/cn.codingblock.repluginstudy d/replugin.ws001: mp.pluginuninstall ... pluginname=cn.codingblock.plugin1

10-30 16:41:46.202 10193-10193/cn.codingblock.repluginstudy d/replugin.ws001: sendintent pr=cn.codingblock.repluginstudy intent=intent { act=action_uninstall_plugin (has extras) }

10-30 16:41:46.203 10193-10193/cn.codingblock.repluginstudy d/replugin.ws001: clear plugin cache. pn=cn.codingblock.plugin1

10-30 16:41:46.204 10193-10193/cn.codingblock.repluginstudy d/replugin.ws001: removeinfo plugin table: info=pinfo { <cn.codingblock.plugin1:1(4)> [apk] processes=[] js={"pkgname":"cn.codingblock.plugin1","name":"cn.codingblock.plugin1","low":10,"high":10,"ver":1,"verv":2814792716779521,"path":"\/data\/user\/0\/cn.codingblock.repluginstudy\/app_p_a\/-347346251.jar","type":11,"frm_ver":4,"used":true} dex=/data/user/0/cn.codingblock.repluginstudy/app_p_od/-347346251.dex nlib=/data/user/0/cn.codingblock.repluginstudy/app_p_n/-347346251 } rc=true

10-30 16:41:46.204 10193-10193/cn.codingblock.repluginstudy d/replugin.ws001: cached filename: cn.codingblock.plugin1 -> null

10-30 16:41:46.275 10193-10263/cn.codingblock.repluginstudy v/renderscript: 0xb34e8000 launching thread(s), cpus 4 

启动插件那里毕竟在官方教程里面找不到,但是 plugin.uninstall() 方法传入插件名即可这可是官方文档说的,这次不会是官方文档和源码注释合起伙来坑我们把? 经过多次试验后,有个有趣的发现:对于启动插件创建 intent 的createintent() 方法和 卸载插件的 replugin.uninstall() 方法,如果项目是使用继承 repluginapplication 方式的话,参数传包名才生效;如果不是继承的方式传插件名才生效!(本人是在一款小米3手机上试验的,由于并没有广泛测试,所以不保证其他手机也是这个套路)这真是奇了葩了!

卸载插件时有一点需要注意:如果插件正在运行,则不会立即卸载插件,而是将卸载诉求记录下来。直到所有“正在使用插件”的进程结束并重启后才会生效。(引自官方说明)

(二)内置插件

添加内置插件非常简单,首先在主工程的 assets 目录下创建一个 plugins 文件夹,然后将要作为插件的 apk 后缀名改成 .jar 并放入到新建的 plugins 文件夹下,剩下的事情就不用管了,交给 replugin 即可,也就说,框架会自动加载插件。

  1. 内置插件无需开发者安装,启动方式和外置插件一致,但不可删除。
  2. 内置插件可通过 replugin.install() 升级(需要先将升级包下载好),升级后等同于外置插件。

四、小结

初步体验了一下发现,虽然目前有可能会有那么一点坑需要踩一踩,在使用起来也不比 droidplugin 方便,需要在宿主和插件两端都要做集成工作。但总体明显发现,这次的插件化框架明显比以前那些的插件化框架资料更加的全面、丰富,而且从 wiki 上发现 replugin 团队充满了很大的热情在孜孜不倦维护、更新,并且计划明确,哪些功能在未来会添加、哪些功能在未来会被舍弃,一目了然,让我们更加看到了 replugin 美好的未来,我相信在未来的插件化领域即使 replugin 不能一家独大,也必然处于一个非常重要的地位!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。