赞
踩
1.在宿主工程根目录的build.gradle添加依赖,注意:我这里用的gradle时3.0,其他版本可能会有问题。
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.didi.virtualapk:gradle:0.9.8.3'
}
2.在App的工程模块的build.gradle添加使用gradle插件
apply plugin: 'com.didi.virtualapk.host'
...
dependencies {
...
implementation 'com.didi.virtualapk:core:0.9.5'
}
3.AppApplication.java类是继承了Application,覆写attachBaseContext函数,进行插件SDK初始化工作
- package com.rhino.virtualapkdemo;
-
- import android.app.Application;
- import android.content.Context;
-
- import com.didi.virtualapk.PluginManager;
-
-
- /**
- * @author LuoLin
- * @since Create on 2018/5/23.
- */
- public class AppApplication extends Application {
-
- @Override
- protected void attachBaseContext(Context base) {
- super.attachBaseContext(base);
- PluginManager.getInstance(base).init();
- }
- }
4.在使用插件之前加载插件,可以根据具体业务场景选择合适时机加载,demo在MainActivity的onCreate时加载,加载需要权限READ_EXTERNAL_STORAGE,这里直接贴我的MainActivity.java。
- package com.rhino.virtualapkdemo;
-
- import android.Manifest;
- import android.content.Intent;
- import android.content.pm.PackageManager;
- import android.os.Environment;
- import android.support.annotation.NonNull;
- import android.support.v4.app.ActivityCompat;
- import android.support.v4.content.ContextCompat;
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Button;
- import android.widget.Toast;
-
- import com.didi.virtualapk.PluginManager;
- import com.didi.virtualapk.internal.LoadedPlugin;
-
- import java.io.File;
-
- /**
- * @author LuoLin
- * @since Create on 2018/5/24.
- */
- public class MainActivity extends AppCompatActivity {
-
- /** 插件文件路径 **/
- private static final String PLUGIN_FILE_PATH = Environment.getExternalStorageDirectory()
- + File.separator + "plugin_demo.apk";
- /** 插件apk包名 **/
- private static final String PLUGIN_PACKAGE_NAME = "com.rhino.virtualapkplugindemo";
- /** 插件apk入口activity **/
- private static final String PLUGIN_LAUNCHER_ACTIVITY = "com.rhino.virtualapkplugindemo.PluginMainActivity";
-
- /** 请求权限请求码 **/
- public static final int REQUEST_PERMISSION_CODE = 111;
- /** 权限 **/
- public static final String[] PERMISSIONS = new String[]{
- Manifest.permission.READ_EXTERNAL_STORAGE
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- if (lacksPermissions(PERMISSIONS)) {
- // 开始请求权限
- requestPermissions(PERMISSIONS);
- } else {
- loadPlugin();
- }
-
- findViewById(R.id.show_second_activity).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent();
- intent.setClass(getApplicationContext(), SecondActivity.class);
- startActivity(intent);
- }
- });
- findViewById(R.id.run_plugin).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- showPluginActivity();
- }
- });
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- if (requestCode == REQUEST_PERMISSION_CODE) {
- loadPlugin();
- }
- }
-
- /** 判断是否缺少权限 **/
- private boolean lacksPermissions(String... permissions) {
- for (String permission : permissions) {
- if (ContextCompat.checkSelfPermission(getApplicationContext(), permission) == PackageManager.PERMISSION_DENIED) {
- return true;
- }
- }
- return false;
- }
-
- /** 请求权限 **/
- private void requestPermissions(String... permissions) {
- ActivityCompat.requestPermissions(this, permissions, REQUEST_PERMISSION_CODE);
- }
-
- /** 加载插件 **/
- private void loadPlugin() {
- PluginManager pluginManager = PluginManager.getInstance(this);
- //此处是当查看插件apk是否存在,如果存在就去加载(比如修改线上的bug,把插件apk下载,达到热修复)
- File pluginApk = new File(PLUGIN_FILE_PATH);
- if (pluginApk.exists()) {
- try {
- pluginManager.loadPlugin(pluginApk);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- /** 运行插件中activity **/
- private void showPluginActivity() {
- if (PluginManager.getInstance(this).getLoadedPlugin(PLUGIN_PACKAGE_NAME) == null) {
- Toast.makeText(getApplicationContext(),"插件未加载,请尝试重启APP", Toast.LENGTH_SHORT).show();
- return;
- }
- Intent intent = new Intent();
- intent.setClassName(PLUGIN_PACKAGE_NAME, PLUGIN_LAUNCHER_ACTIVITY);
- startActivity(intent);
- }
- }
至此,VirtualAPK插件功能就集成到宿主中了,接下来,我们看下插件工程是如何集成的。
1.在插件工程根目录的build.gradle添加依赖,注意:我这里用的gradle时3.0,其他版本可能会有问题。
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.didi.virtualapk:gradle:0.9.8.3'
}
2.在App的工程模块的build.gradle添加使用gradle插件和插件配置信息,信息需要放在文件最下面。
apply plugin: 'com.didi.virtualapk.plugin'
...
// 插件配置信息,放在文件最下面
virtualApk {
packageId = 0x6f // 插件资源id,避免资源id冲突
targetHost='../../GitHub/VirtualApkDemo/app' // 宿主工程的路径
applyHostMapping = true // 插件编译时是否启用应用宿主的apply mapping
}
解释一下上面3个参数的作用
3.生成插件,需要使用Gradle命令
gradle clean assemblePlugin 或者 gradlew clean assemblePlugin
执行成功后,会生成如下apk文件,即插件apk。
注意:这里的apk不能通过其他方式生成,如:run。
1.将插件apk重命名为plugin_demo.apk(与宿主MainActiivty中加载apk名称保持一致),然后拷贝到sdcard根目录。
2.运行宿主apk,点击“进入插件中activity”,会发现成功启动了插件的activity。
实验内容 | 现象 | 结论 |
宿主app加载插件apk文件,并启动插件中的Activity,以及activity是否需要在宿主manifest预注册? | 插件apk不需要安装,宿主app可以直接在代码中加载指定的插件apk文件,可以成功启动插件中activity,该activity没有在宿主中预注册。 | 插件不需要安装,动态加载后可以启动插件activity,插件activity不需要在宿主中预注册。(这里只测试了activity,四大组件也支持) |
修改插件ui界面,替换原来的apk文件,重启宿主app,观察修改能否及时生效? | 重启宿主app后,显示了修改后的界面。 | 支持插件apk文件动态替换。 |
宿主和插件中创建相同activity,观察启动的时候会加载哪一个activity? | 启动的是宿主中的activity。 | 插件中的class不能与宿主中class相同,加载的是宿主中class,不能做到替换。 |
宿主和插件中创建相同layout,观察宿主和插件分别会加载哪一个layout? | 宿主和插件都加载宿主中layout。 | Layout文件名称不能和宿主中重名,加载的是宿主中layout,不能做到替换,对于其他资源文件也是一样。 |
Atlas:https://alibaba.github.io/atlas/
VirtualAPK:https://github.com/didi/VirtualAPK/wiki
RePlugin:https://github.com/Qihoo360/RePlugin/wiki
框架 | Atlas | RePlugin | VirtualAPK |
定义 | Atlas是伴随着手机淘宝的不断发展而衍生出来的一个运行于Android系统上的一个容器化框架,我们也叫动态组件化(Dynamic Bundle)框架。 | RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案。 | VirtualAPK是滴滴出行自研的一款优秀的插件化框架。 |
功能 | 1.远程bundle 2.插件以so形式,放在sdcard,加载后会自动删除。 3.热修复,可以不升级apk就实现宿主和组件的更新。 | 1.远程bundle 2.插件以apk形式放在sdcard,加载后还在会自动备份apk到缓存目录。 | 1.远程bundle 2.插件以apk形式放在sdcard,加载后还在,删除会打不开。 |
更新插件方式 | 必须要和宿主一起,打差异补丁才能更新。 | 直接通过下载一个新的插件apk,然后调安装方法就能实现插件的更新。 | 直接通过下载一个新的插件apk,然后调安装方法就能实现插件的更新。 |
插件独立性 | 和宿主的依赖还是挺多,毕竟官方也强调是组件化,不是插件化。 | 插件不用声明和宿主的联系,所以你生成一个插件后,这个插件可以给其他宿主调用。 | 插件是一个独立的apk,但插件里面也定义和宿主的关联,就是说这个插件apk并不能给其他宿主用,只能给插件里面声明的那个宿主使用。 |
VirtualAPK不支持对宿主进行热修复。如果app需要热更新和插件的功能,推荐使用Atlas;如果插件无法很好地从宿主中拆出来,或者插件的运行和宿主有较多的交互,推荐使用VirtualAPK;如果同一个插件希望其他宿主也能用的话,那就只能RePlugin了,RePlugin就像一个应用市场,宿主仅仅是一个壳,然后把需要的插件下载加载使用就行,更新的话也无需更新宿主,直接更新插件就行。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。