Android高级进阶之-插件化开发原理与实践
写在前面,什么是插件化开发?
所谓插件化开发就是将APP中的一些功能模块单独抽离出来,打包成可以单独运行的apk包(当然如果需要一些登录态或者运行参数环境时不可以单独运行,但是技术条件上是可以的),当APP程序需要运行这些模块的时候,就可以直接加载这些模块apk,然后运行。
举个易懂的例子,支付宝内部集成了很多功能模块,其中就有类似淘票票这样的不可能在支付宝一个apk包就全部打包好,这样不仅安装包体积过大,也不利于功能模块的拔插。
现在给出插件化开发的总体思路,然后我们逐个击破。
首先宿主APP提供ProxyActivity,当宿主需要打开插件包中的Activity时,一律是启动的ProxyActivity,在启动ProxyActivity的intent中携带我们真正需要去打开的插件包中Activity的类全名。
Intent intent = new Intent(context, ProxyActivity.class);
intent.putExtra("className", className);//className 对应插件包中需要被打开的Activity的类全名
return intent;
ProxyActivity会被正常启动,但是这个ProxyActivity会在其onCreate回调中去反射出这个真实需要被开启的Activity对象,
Class activityClass = getClassLoader().loadClass(className);
Constructor constructor = activityClass.getConstructor(new Class[]{});
Activity instance = constructor.newInstance(new Object[]{});
现在我们拿到了instance对象,当然它不是系统new出来的,它的生命周期方法都需要我们去通知,那么接下来就好办了,我们重写ProxyActivity所有的生命周期方法,然后去手动调用instance相对应的生命周期方法。
public class ProxyActivity extends Activity {
private IPluginActivity mPluginActivity;//用来接收插件包所有Activity的接口对象
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String className = getIntent().getStringExtra("className");
try {
Class activityClass = getClassLoader().loadClass(className);
Constructor constructor = activityClass.getConstructor(new Class[]{});
Object instance = constructor.newInstance(new Object[]{});
mPluginActivity = (PluginBaseActivity) instance;
mPluginActivity.onCreate(savedInstanceState);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onStart() {
super.onStart();
mPluginActivity.onStart();
}
@Override
protected void onResume() {
super.onResume();
mPluginActivity.onResume();
}
@Override
protected void onPause() {
super.onPause();
mPluginActivity.onPause();
}
@Override
protected void onStop() {
super.onStop();
mPluginActivity.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
mPluginActivity.onDestroy();
}
}
当然ProxyActivity中还有一些回调方法需要去通知mPluginActivity做出对应的调用,这里不一一例举。
在Activity中有很重要的两个回调方法:
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance().getPluginBean().getDexClassLoader();
}
@Override
public Resources getResources() {
return PluginManager.getInstance().getPluginBean().getResources();
}
- getClassLoader返回的是ClassLoader 对象,Activity内部在使用反射new对象时,都会去使用这里返回的ClassLoader 来进行反射,所以我们ProxyActivity中需要提供的应该是当前加载插件包对应的ClassLoader 。
- getResources返回的是Resources 对象,Activity内部在使用资源文件时,都会去使用这里返回的Resources 来获取资源,所以我们ProxyActivity中需要提供的应该是当前加载插件包对应的Resources 。
相信讲到这里,你大概知道插件化开发打开插件包中某个Activity的原理了:我们宿主APP提供了一个ProxyActivity,我们打开插件包Activity其实都是打开的ProxyActivity,只不过ProxyActivity不做任何其他事情,只负责实例化插件包Activity,并且将ProxyActivity中的所有事件驱动通知给插件包Activity,也就是ProxyActivity调用了插件包Activity中的代码来实现插件包要实现的功能。
这个时候我们再来看看如何去加载外部插件包apk,并且获得classLoader和resource:
File pluginFile;//插件包文件
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(pluginFile.getAbsolutePath(), PackageManager.GET_ACTIVITIES);
File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
DexClassLoader dexClassLoader = new DexClassLoader(pluginFile.getAbsolutePath(), dexOutFile.getAbsolutePath()
, null, context.getClassLoader());
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, pluginFile.getAbsolutePath());
Resources resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
上述代码中,PackageInfo 可以获取插件包manifest中所有注册了的组件信息,例如主Activity的全类名的获取为:
packageInfo.activities[0].name
OK,到了这里我们可以贴上来插件包中,所有Activity都需要继承的BaseActivity代码:
public abstract class PluginBaseActivity extends AppCompatActivity implements IPluginActivity {
protected Activity that;//宿主Activity
@Override
public void attach(ProxyActivity proxyActivity) {
if (that != null)
throw new RuntimeException("Plugin's activity already has been attached!");
this.that = proxyActivity;
attachBaseContext(proxyActivity);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
if (that == null) {
super.onCreate(savedInstanceState);
}
}
@Override
public void onStart() {
if (that == null) {
super.onStart();
}
}
@Override
public void onResume() {
if (that == null) {
super.onResume();
}
}
@Override
public void onPause() {
if (that == null) {
super.onPause();
}
}
@Override
public void onStop() {
if (that == null) {
super.onStop();
}
}
@Override
public void onDestroy() {
if (that == null) {
super.onDestroy();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (that == null) {
super.onSaveInstanceState(outState);
}
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
if (that == null) {
super.onRestoreInstanceState(savedInstanceState);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (that == null) {
return super.onTouchEvent(event);
}
return false;
}
@Override
public void onBackPressed() {
if (that == null) {
super.onBackPressed();
}
}
@Override
public void setContentView(View view) {
if (that == null) {
super.setContentView(view);
} else {
that.setContentView(view);
}
}
@Override
public void setContentView(int layoutResID) {
if (that == null) {
super.setContentView(layoutResID);
} else {
that.setContentView(layoutResID);
}
}
@Override
public void startActivity(Intent intent) {
if (that == null) {
super.startActivity(intent);
} else {//篡改intent打开页面为proxyActivity
intent.putExtra("className", intent.getComponent().getClassName());
intent.setClassName(intent.getComponent().getPackageName(), ProxyActivity.class.getName());
that.startActivity(intent);
}
}
@Override
public ComponentName startService(Intent intent) {
if (that == null) {
return super.startService(intent);
} else {
intent.putExtra("className", intent.getComponent().getClassName());
intent.setClassName(intent.getComponent().getPackageName(), ProxyService.class.getName());
return that.startService(intent);
}
}
@Override
public View findViewById(int id) {
if (that == null) {
return super.findViewById(id);
} else {
return that.findViewById(id);
}
}
@Override
public Intent getIntent() {
if (that == null) {
return super.getIntent();
} else {
return that.getIntent();
}
}
@Override
public Window getWindow() {
if (that == null) {
return super.getWindow();
} else {
return that.getWindow();
}
}
@Override
public WindowManager getWindowManager() {
if (that == null) {
return super.getWindowManager();
} else {
return that.getWindowManager();
}
}
}
public interface IPluginActivity {
void attach(ProxyActivity proxyActivity);
void onCreate(@Nullable Bundle savedInstanceState);
void onStart();
void onResume();
void onPause();
void onStop();
void onDestroy();
void onSaveInstanceState(Bundle outState);
void onRestoreInstanceState(Bundle savedInstanceState);
boolean onTouchEvent(MotionEvent event);
void onBackPressed();
void setContentView(View view);
void setContentView(int layoutResID);
void startActivity(Intent intent);
ComponentName startService(Intent intent);
View findViewById(int id);
Intent getIntent();
Window getWindow();
WindowManager getWindowManager();
}
IPluginActivity 除了声明一个attach()方法外,其它都是activity默认提供的方法。插件包的PluginBaseActivity 之所以对that做非空判断目的就是为了允许插件包apk可以单独安装并且运行。
好了,写到这里献上源码。
链接:https://share.weiyun.com/5ne9oLj