文章目录
占位式,也叫插装式。运行的APP,也称之为宿主。
Activity通信
通过宿主来加载Plugin Activity
1. 环境准备
项目分为3个基础模块,分别是 宿主module(可以启动),插件module(最终会打包成单独apk文件),标准module(Android Library)。宿主和插件分别依赖标准
- 新建 插件module:
plugin_package
,该module只是为打包成apk文件。 - 新建 标准module:
standard
,该module是为了维护宿主和插件。是一个activity library - 添加依赖关系,宿主和插件分别依赖
标准stander
标准制定(组件的管理):
- 制定标准
/** * standard 模块 * 制定组件Activity的标准 */ public interface ActivityInterface { /** * 把宿主的环境给插件 * @param appActivity */ void insertAppActivity(Activity appActivity); void onCreate(Bundle savedInstanceState); void onResume(); void onPause(); void onStop(); void onDestroy(); }
- 插件的组件实现该标准
/** * 插件 module 中的 Activity组件基类 */ public class BaseActivity extends Activity implements ActivityInterface { protected Activity appActivity; //宿主的 Activity环境 @Override public void insertAppActivity(Activity appActivity) { this.appActivity = appActivity; } @SuppressLint("MissingSuperCall") @Override public void onCreate(Bundle savedInstanceState) { } @SuppressLint("MissingSuperCall") @Override public void onResume() { } @SuppressLint("MissingSuperCall") @Override public void onPause() { } @SuppressLint("MissingSuperCall") @Override public void onStop() { } @SuppressLint("MissingSuperCall") @Override public void onDestroy() { } }
插件的特点:插件没有组件环境,可以将宿主的组件环境给插件,可以通过标准传递给插件
加载插件:①加载插件中的Activity组件,②加载插件中的资源文件
2. 加载
在宿主APP,通过用户触发加载插件apk文件;而APK文件通过服务器下发,保存在本地存储。
加载包含两部分:①加载插件的Class ②加载插件的layout
/**
* 加载插件:
* 1. 加载activity
* 2.加载layout
*/
public void loadPlugin() {
try {
// 1. 加载plugin class
File file = getPluginPath();
if (!file.exists()) {
Log.e(TAG, "loadPlugin: 插件不存在 ");
return;
}
String pluginPath = file.getAbsolutePath();
// dexClassLoader需要一个缓存目录
// getDir 生成路径: /data/data/当前应用包名/pDir。
String optimizedDirectory = mContext.getDir("pDir", Context.MODE_PRIVATE).getAbsolutePath();
/**
* 加载class
* @param String dexPath, 插件路径
* @param String optimizedDirectory, 加载插件apk 需要一个缓存目录
* @param String librarySearchPath, 底层c/c++ 库的目录
* @param ClassLoader parent ClassLoader
*/
mDexClassLoader = new DexClassLoader(pluginPath, optimizedDirectory, null, mContext.getClassLoader());
// 2. 加载plugin layout
//
//
// public final class AssetManager 不能直接new 需要通过反射调用
AssetManager assetManager = AssetManager.class.newInstance();
// 执行 android.content.res.AssetManager#addAssetPath ,将插件包的路径添加进去,加载resource 可以加载zip apk文件
/**
* @param parameterTypes 不是真正的类型,而是类类型
*/
Method addAssetPathMethod = assetManager.getClass().getMethod("addAssetPath", String.class);//
/**
* @param obj 执行的方法所对应的类对象
* @param args 执行方法的参数
*/
addAssetPathMethod.invoke(assetManager,pluginPath);
// 至此,assetManager就可以加载资源文件
//
//
/**
* @param assets 资源管理类
* @param metrics 资源的配置信息 对应不同的资源分辨率
* @param config
*/
Resources res = mContext.getResources(); // 宿主资源配置信息
mResources = new Resources(assetManager, res.getDisplayMetrics(), res.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
3. 启动插件
Activity启动采用任务栈的启动,由于插件Activity没有安装是不能启动的,采用代理
Activity
启动插件Activity
。
宿主Activity中,触发启动插件。
// 启动插件里面的Activity
public void startPluginActivity(View view) {
PackageManager packageManager = getPackageManager();
String pluginPath = PluginManager.getPluginPath().getAbsolutePath();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(pluginPath, PackageManager.GET_ACTIVITIES);
// for (int i = 0; i < packageInfo.activities.length; i++) {
// Log.e(TAG, "packageInfo.activities:" + packageInfo.activities[i]);
// }
// 拿到第一个activity
ActivityInfo activityInfo = packageInfo.activities[0];
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", activityInfo.name);
startActivity(intent);
}
在代理Activity中做真正的启动,首先要将宿主的环境
注入到插件
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String className = getIntent().getStringExtra("className");
//真正的加载 插件里面的Activity
// 动态获取className , 不能写死(包名+类名)
// String className = null;
try {
// 拿到插件包中的第一个Activity
Class pluginClass = getClassLoader().loadClass(className);
// 实例化插件包中的Activity
Constructor constructor = pluginClass.getConstructor(new Class[]{});
Object pluginActivity = constructor.newInstance(new Object[]{});
// 强转为ActivityInterface
activityInterface = (ActivityInterface) pluginActivity;
// 将宿主的环境注入给插件
activityInterface.insertAppActivity(this);
// 执行插件onCreate 方法
// 可以从宿主 携带参数 给 插件
Bundle bundle = new Bundle();
bundle.putString("fromAppInfo", "我是来自宿主的一条信息");
// 间接调用pluginActivity的onCreate方法
activityInterface.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
插件Activity 的onCreate
方法:
// com.purang.plugin_package.PluginMainActivity#onCreate
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 在父类BaseActivity 通过 宿主环境 appActivity 来加载 Resource
setContentView(R.layout.activity_main_plugin);
Log.e(TAG, "onCreate: ");
// toast 不能通过this,只能通过宿主传递过来的appActivity 环境
Toast.makeText(appActivity, "我是插件", Toast.LENGTH_SHORT).show();
// 子类复写了父类的onCreate方法, 继续调用onCreate
String info = savedInstanceState.getString("fromAppInfo");
Log.e(TAG, "子类收到宿主传递的信息: " + info);
}
4. 插件Activity的生命周期
在 宿主的ProxyActivity
中,通过activityInterface.onCreate(bundle);
启动pluginActivity。因为插件的Activity实现了这一标准。所以,插件的Activity的生命周期也可以通过这种方式来加载:
/**
* 代理/占位Activity 用来启动插件 Activity
*/
public class ProxyActivity extends Activity {
private ActivityInterface activityInterface;
@Override
protected void onStart() {
super.onStart();
if (activityInterface != null)
activityInterface.onStart();
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... 省略代码
// 强转为ActivityInterface
activityInterface = (ActivityInterface) pluginActivity;
activityInterface.onCreate(bundle);
// ... 省略代码
}
@Override
protected void onResume() {
super.onResume();
if (activityInterface != null)
activityInterface.onResume();
}
@Override
protected void onPause() {
super.onPause();
if (activityInterface != null)
activityInterface.onPause();
}
}
插件PluginMainActivity
代码:
public class PluginMainActivity extends BaseActivity {
private static final String TAG = PluginMainActivity.class.getSimpleName();
@Override
public void onStart() {
super.onStart();
Log.e(TAG, "onStart: ");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 在父类BaseActivity 通过 宿主环境 appActivity 来加载 Resource
setContentView(R.layout.activity_main_plugin);
Log.e(TAG, "onCreate: ");
}
@Override
public void onResume() {
super.onResume();
Log.e(TAG, "onResume:");
}
@Override
public void onPause() {
super.onPause();
Log.e(TAG, "onPause:");
}
}
日志输出:
2020-02-22 22:00:05.065 7863-7863/com.purang.plugin E/PluginMainActivity: onCreate:
2020-02-22 22:00:05.072 7863-7863/com.purang.plugin E/PluginMainActivity: 子类收到宿主传递的信息: 我是来自宿主的一条信息
2020-02-22 22:00:05.074 7863-7863/com.purang.plugin E/PluginMainActivity: onStart:
2020-02-22 22:00:05.075 7863-7863/com.purang.plugin E/PluginMainActivity: onResume:
2020-02-22 22:00:27.941 7863-7863/com.purang.plugin E/PluginMainActivity: onPause:
可以看出,先调用了onCreate
来加载Activity,然后才是onStart onResume
。
插件内部 Activity 加载
在插件内部实现跳转到新的Activity实现分析:
由于是在插件内部没有上下文环境,所以所有涉及到上下文环境的操作,都必须借助宿主 ProxyActivity
注入给插件的上下文 appActivity
来实现。如:findViewById startActivity
等等。Activity跳转桥接到ProxyActivity,在ProxyActivity内部实现自己跳转自己
查看Activity task stack
Running activities (most recent first):
TaskRecord{459ab29 #11615 A=com.purang.plugin U=0 StackId=1967 sz=3}
Run #2: ActivityRecord{4528a48 u0 com.purang.plugin/.ProxyActivity t11615}
Run #1: ActivityRecord{40d3487 u0 com.purang.plugin/.ProxyActivity t11615}
Run #0: ActivityRecord{43aa009 u0 com.purang.plugin/.MainActivity t11615}
改变ProxyActivity launchMode
,尝试采用Activity的四种启动模式,查看任务栈及生命周期方法。具体分析查看
Service通信
service实现思路与activity思路一致,具体查看代码。