一.原理
在插件化中,宿主是安装在手机中的正在运行的APP,插件是没有经过安装的APK文件。插件化可以实现宿主APP直接加载插件APK,使插件APK不用安装就可以运行在宿主APP中。
由于插件APK没有安装,它是没有上下文(Context)环境的,想要运行插件APK,需要把宿主的Context传递给插件APK。在占位式插件化中,当需要启动插件的Activity时,实际是创建了宿主APP中的代理Activity,在代理Activity中,通过反射拿到插件APK的Activity对象,然后执行插件APK的Activity对象的onCreate等方法。从而实现在宿主中的代理Activity上加载插件的Activty。
二.Activity通信
1.通信标准
在项目中新建Android Library,该模块主要提供宿主和插件需要遵循的标准,宿主和插件都需要依赖该模块。首先新建ActivityInterface接口,
public interface ActivityInterface {
void insertAppContext(Activity activity);
void onCreate(Bundle savedInstanceState);
void onStart();
void onResume();
void onDestroy();
}
2.插件APK
新建一个Phone Module作为插件,新建一个BaseActivity继承Activity实现ActivityInterface接口,作为插件中所有Activity的父类。
public class BaseActivity extends Activity implements ActivityInterface {
public Activity appActivity;
@Override
public void insertAppContext(Activity activity) {
this.appActivity = activity;
}
@SuppressLint("MissingSuperCall")
@Override
public void onCreate(Bundle savedInstanceState) {
}
...
}
新建PluginActivity继承BaseActivity,作为插件的第一个Activity。
public class PluginActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toast.makeText(appActivity,"Plugin",Toast.LENGTH_SHORT).show();
}
}
3.宿主APP
在宿主中,新建PluginManager类,要运行插件APK,需要获得APK的类对象和资源对象,因此,在PluginManager中需要提供可以加载插件类的类加载器DexClassLoader和插件资源文件Resources。
public void loadPlugin(){
try {
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "plugin_package-debug.apk");
if (!file.exists()){
return;
}
String pluginPath = file.getAbsolutePath();
File fileDir = context.getDir("pDir",Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(pluginPath,fileDir.getAbsolutePath(),null,context.getClassLoader());
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPathmethod = assetManager.getClass().getMethod("addAssetPath",String.class);
addAssetPathmethod.invoke(assetManager,pluginPath);
Resources r = context.getResources();
this.resources = new Resources(assetManager,r.getDisplayMetrics(),r.getConfiguration());
}catch (Exception e){
e.printStackTrace();
}
}
新建ProxyActivity,每次对插件Activity的加载,实际都是创建的ProxyActivity,在ProxyActivity的onCreate方法中,使用类加载器加载插件中的Activity类。
public class ProxyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String className = getIntent().getStringExtra("className");
try {
Class pluginActivityClass = getClassLoader().loadClass(className);
Constructor constructor = pluginActivityClass.getConstructor(new Class[]{});
Object pluginActivity = constructor.newInstance(new Object[]{});
ActivityInterface activityInterface = (ActivityInterface)pluginActivity;
activityInterface.insertAppContext(this);
Bundle bundle = new Bundle();
bundle.putString("info","from ProxyActivity");
activityInterface.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
}
当在宿主MainActivity中跳转至插件的Activity时,进行如下操作:
public void startActivity(View view) {
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "plugin_package-debug.apk");
PackageManager packageManager = getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_ACTIVITIES);
ActivityInfo activityInfo = packageInfo.activities[0];
Intent intent = new Intent(this,ProxyActivity.class);
intent.putExtra("className",activityInfo.name);
startActivity(intent);
}
这样就完成了从宿主Activiyu至插件Activity的跳转。当从插件Activity跳转至另一插件Activity时,新建一个SecondActivity作为跳转后的Activity。流程是在插件Activity再启动一个ProxyActivity,在ProxyActivity加载SecondActivity。首先重写插件BaseActivity的startActivity:
@Override
public void startActivity(Intent intent) {
Intent intentNew = new Intent();
appActivity.startActivity(intentNew);
}
以上代码最后执行到ProxyActivity.startActivity,重写该方法:
@Override
public ComponentName startService(Intent intent) {
String className = intent.getStringExtra("className");
Intent intentNew = new Intent(this,ProxyService.class);
intentNew.putExtra("className",className);
return super.startService(intentNew);
}
PluginActivity跳转SecondActivity时只需执行startActivity(new Intent(appActivity,SecondActivity.class))即可。Service和Broadcast的通信原理与Activity相同,都是通过ProxyService和ProxyReceiver实现。
三.Broadcast静态注册
APK中对AndroidManifest.xml的解析是在PackageManagerService进行的,通过PackageParser类的parsePackage方法。因此,要得到插件中AndroidManifest.xml的内容,需要通过反射调用PackageParser类的parsePackage方法。
首先获取PackageParser对象,并执行parsePackage方法。
Class packageParserClass = Class.forName("android.content.pm.PackageParser");
Object packageParser = packageParserClass.newInstance();
Method parsePackageMethod = packageParserClass.getMethod("parsePackage", File.class, int.class);
Object mPackage = parsePackageMethod.invoke(packageParser,file, PackageManager.GET_ACTIVITIES);
parsePackage方法返回Package对象,Package是PackageParser的内部类,有一个成员变量ArrayList receivers保存了AndroidManifest.xml中所有receiver。
Field receiversField = mPackage.getClass().getDeclaredField("receivers");
ArrayList receivers = (ArrayList) receiversField.get(mPackage);
遍历每个receiver,拿到其名称和对应的所有intentFilter。
首先拿到所有intentFilter。
Class componentClass = Class.forName("android.content.pm.PackageParser$Component");
Field intentsField = componentClass.getDeclaredField("intents");
ArrayList<IntentFilter> intents = (ArrayList<IntentFilter>) intentsField.get(activity);
拿到receiver的name
Class mPackageUserStateClass = Class.forName("android.content.pm.PackageUserState");
Method generateActivityInfoMethod = packageParserClass.getMethod("generateActivityInfo", activity.getClass(), int.class, mPackageUserStateClass, int.class);
generateActivityInfoMethod.setAccessible(true);
Class userHandleClass = Class.forName("android.os.UserHandle");
int userId = (int)userHandleClass.getMethod("getCallingUserId").invoke(null);
ActivityInfo activityInfo = (ActivityInfo)generateActivityInfoMethod.invoke(null, activity, 0,mPackageUserStateClass.newInstance(), userId);
String receiverClassName = activityInfo.name;
拿到BroadcastReceiver对象,并注册所有intentFilter
Class receiverClass = getDexClassLoader().loadClass(receiverClassName);
BroadcastReceiver broadcastReceiver = (BroadcastReceiver)receiverClass.newInstance();
for (IntentFilter intentFilter : intents){
context.registerReceiver(broadcastReceiver,intentFilter);
}