文章目錄
佔位式,也叫插裝式。運行的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思路一致,具體查看代碼。