1.什么是插件化开发
首先我们先来看看通过插件化开发后的APP是什么样的效果。这里就用最典型的插件化APP 360手机卫士 来演示一下什么是插件化的APP。
可以看到,打开应用后在切换到工具箱中有很多功能,我的工具中先是有8个自带的功能,然后点击更多工具可以去添加,点添加后先是下载,下载完成就可以打开使用了。
我们再来首先看看APP的大小,才15.62M,是不是很惊讶啊。
在上面演示的图片中那些工具其实都是插件,而且在我的工具中8个自带的工具是我后面要说的内置插件,而我添加的那个“健康管理”工具就是外置插件,区别就是内置插件在安装完主体应用后就安装好了,而外置插件是在主体应用运行时安装的,最重要的一点是,插件也是一个APP,后缀名为.apk。而这个主体应用专业点说法就叫做宿主APP,反正都是APP。
简单概括插件化开发:
通过宿主APP可以运行未安装的APP。
当然,只能运行“成为插件”的APP。
2.插件化开发的优缺点:
优点:
- 模块解耦,应用程序扩展性强
- 解除单个dex函数不能超过 65535的限制
- 动态升级,下载更新节省流量
- 高效开发(编译速度更快)
缺点:
- 增加了主应用程序的逻辑难度
- 技术有难度,目前一些成熟的框架都是闭源的
3.插件化框架—RePlugin介绍
3.1什么是RePlugin?
RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。
3.2RePlugin有什么用?
可以让你的项目很容易的成为插件化APP以及像开发单品一样来开发插件。
3.3官方文档
4.RePlugin的基本使用
这里分为两部分,宿舍APP的开发和插件APP的开发,然后每部分在一步一步的来讲。
4.1宿主APP
这里分为3步
- 将RePlugin接入到您的主程序
- 安装一个插件
- 打开一个插件的Activity
4.1.1新建一个项目并接入RePlugin
新建一个项目
接入Replugin
第 1 步:添加 RePlugin Host Gradle 依赖
在项目根目录的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-host-gradle 依赖:
buildscript {
dependencies {
classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.1'
...
}
}
第 2 步:添加 RePlugin Host Library 依赖
在 app/build.gradle 中应用 replugin-host-gradle 插件,并添加 replugin-host-lib 依赖:
// 注意,这里要放在"android{}"这后面,因为要先读取applicationId
apply plugin: 'replugin-host-gradle'
/**
* 配置项均为可选配置,默认无需添加
* 更多可选配置项参见replugin-host-gradle的RepluginConfig类
* 可更改配置项参见 自动生成RePluginHostConfig.java
*/
repluginHostConfig {
/**
* 是否使用 AppCompat 库
* 不需要个性化配置时,无需添加
*/
useAppCompat = true
/**
* 背景不透明的坑的数量
* 不需要个性化配置时,无需添加
*/
countNotTranslucentStandard = 6
countNotTranslucentSingleTop = 2
countNotTranslucentSingleTask = 3
countNotTranslucentSingleInstance = 2
}
dependencies {
compile 'com.qihoo360.replugin:replugin-host-lib:2.2.1'
...
}
务必注意
以下请务必注意:
- 请一定要确保符合Gradle开发规范,也即“必须将包名写在applicatonId”,而非AndroidManifest.xml中(通常从Eclipse迁移过来的项目可能出现此问题)。如果不这么写,则有可能导致运行时出现“Failed to find provider info for com.ss.android.auto.loader.p.main”的问题。
- 请将apply plugin: ‘replugin-host-gradle’放在 android{} 块之后,防止出现无法读取applicationId,导致生成的坑位出现异常
- 如果您的应用需要支持AppComat,则除了在主程序中引入AppComat-v7包以外,还需要在宿主的build.gradle中添加下面的代码若不支持AppComat则请不要设置此项:
repluginHostConfig {
useAppCompat = true
}
开启useAppCompat后,我们会在编译期生成AppCompat专用坑位,这样插件若使用AppCompat的Theme时就能生效了。若不设置,则可能会出现“IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.”的异常。
- 如果您的应用需要个性化配置坑位数量,则需要在宿主的build.gradle中添加下面的代码:
repluginHostConfig {
/**
* 背景不透明的坑的数量
*/
countNotTranslucentStandard = 6
countNotTranslucentSingleTop = 2
countNotTranslucentSingleTask = 3
countNotTranslucentSingleInstance = 2
}
更多可选配置项参见replugin-host-gradle的RepluginConfig类
第 3 步:配置 Application 类
让工程的 Application 直接继承自 RePluginApplication。
如果您的工程已有Application类,则可以将基类切换到RePluginApplication即可。或者您也可以用“非继承式”接入。
public class MainApplication extends RePluginApplication {
}
既然声明了Application,自然还需要在AndroidManifest中配置这个Application。
<application
android:name=".MainApplication"
... />
备选:“非继承式”配置Application
若您的应用对Application类继承关系的修改有限制,或想自定义RePlugin加载过程(慎用!),则可以直接调用相关方法来使用RePlugin。
public class MainApplication 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();
/* Not need to be called if your application's minSdkVersion > = 14 */
RePlugin.App.onLowMemory();
....
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
/* Not need to be called if your application's minSdkVersion > = 14 */
RePlugin.App.onTrimMemory(level);
....
}
@Override
public void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
/* Not need to be called if your application's minSdkVersion > = 14 */
RePlugin.App.onConfigurationChanged(config);
....
}
}
针对“非继承式”的注意点
- 所有方法必须在UI线程来“同步”调用。切勿放到工作线程,或者通过post方法来执行
- 所有方法必须一一对应,例如 RePlugin.App.attachBaseContext 方法只在Application.attachBaseContext中调用
- 请将RePlugin.App的调用方法,放在“仅次于super.xxx()”方法的后面
4.1.2安装插件
插件又分为内置插件和外置插件,不过,无论是内置,还是外置插件,还需理解:不是所有的APK都能作为 RePlugin 的插件并安装进来的。必须要严格按照RePlugin的《插件接入指南》中所述完成接入,其编译出的APK才能成为插件,且这个APK同时也可以被安装到设备中。
内置插件
内置插件是指可以“随着主程序发版”而下发的插件,通常这个插件会放到主程序的Assets目录下。
针对内置插件而言,开发者可无需调用安装方法,由RePlugin来“按需安装”。
外置插件
外置插件是指可通过“下载”、“放入SD卡”等方式来安装并运行的插件。
添加内置插件
添加一个内置插件是非常简单的,甚至可以“无需任何Java代码”。只需两步即可:
- 将APK改名为:[插件名].jar
- 放入主程序的assets/plugins目录
这样,当编译主程序时,我们的“动态编译方案”会自动在assets目录下生成一个名叫“plugins-builtin.json”文件,记录了其内置插件的主要信息,方便运行时直接获取。
必须改成“[插件名].jar”后,才能被RePlugin-Host-Gradle识别,进而成为“内置插件”。
[插件名]可以是“包名”,也可以是“插件别名”。
安装外置插件
要安装一个插件,只需使用 RePlugin.install 方法,传递一个“APK路径”即可。
RePlugin.install(“/sdcard/exam.apk”);
注意
- 无论安装还是升级,都会将“源文件”“移动”(而非复制)到插件的安装路径(如app_p_a)上,这样可大幅度节省安装和升级时间,但显然的,“源文件”也就会消失
- 若想改变这个行为,您可以参考RePluginConfig中的 setMoveFileWhenInstalling() 方法
- 升级插件和此等同,故不再赘述
4.1.3打开一个插件的Activity
//RePlugin.createIntent()中的参数是,插件名称,要打开的插件的Activity的全名称
RePlugin.startActivity(MainActivity.this, RePlugin.createIntent("pluginA", "com.fu.plugina.MainActivity"));
好,下面来看看该如何开发插件。
4.2插件APP
4.2.1新建一个项目并接入RePlugin
新建一个项目
接入RePlugin
第 1 步:添加 RePlugin Plugin Gradle 依赖
在项目根目录的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-plugin-gradle 依赖:
buildscript {
dependencies {
classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.1'
...
}
}
第 2 步:添加 RePlugin Plugin Library 依赖
在 app/build.gradle 中应用 replugin-plugin-gradle 插件,并添加 replugin-plugin-lib 依赖:
apply plugin: 'replugin-plugin-gradle'
dependencies {
compile 'com.qihoo360.replugin:replugin-plugin-lib:2.2.1'
...
}
后面就可以向开发单品一样开发插件APP了。
4.2.2配置插件名
要声明插件别名,需要在插件的AndroidManifest.xml中声明以下Meta-data:
<meta-data
android:name="com.qihoo360.plugin.name"
android:value="[你的插件别名]" />
5.演示
这里我就演示外置插件的安装和打开。
我在宿主APP的MainActivity中添加了两个按钮,一个按钮是安装插件,一个按钮是打开插件的MainActivity。
安装插件需要文件的路径,这里我准备把插件放在缓存目录中,先把路径写上,然后先启动了RepluginDemo这个程序,这时插件app还没放到手机上,这时点击安装插件会说文件不存在。
宿主APP中的MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private android.widget.Button btnInstallPlugin;
private android.widget.Button btnOpenPlugin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.btnInstallPlugin = (Button) findViewById(R.id.btn_install_plugin);
this.btnOpenPlugin = (Button) findViewById(R.id.btn_open_plugin);
btnInstallPlugin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
File file = new File(getCacheDir() + File.separator + "/pluginA.apk");
if (file.exists()) {
PluginInfo info = RePlugin.install(file.getAbsolutePath());
Log.i(TAG, "installPluginInfo: " + info.toString());
} else {
Toast.makeText(MainActivity.this, "文件不存在", Toast.LENGTH_SHORT).show();
}
}
});
btnOpenPlugin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//打开插件的Activity
RePlugin.startActivity(MainActivity.this, RePlugin.createIntent("pluginA", "com.fu.plugina.MainActivity"));
}
});
}
}
然后插件APP中我就在MainActivity中放了一个按钮,点击按钮弹出toast
插件APP的MainActivity.java
public class MainActivity extends AppCompatActivity {
private android.widget.Button btntoast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.btntoast = (Button) findViewById(R.id.btn_toast);
btntoast.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "我是插件", Toast.LENGTH_SHORT).show();
}
});
}
}
编译打包插件APP,并把打包出来的APK改名为“pluginA.apk”(因为我们在代码中写的要安装名为pluginA.apk的插件)然后放到宿主APP的缓存目录下。
好,这时我们点击APP中的安装插件。
安装完成后打印的信息。
然后我们再点击打开插件
成功打开插件。
参考文章: