写在前面,什么是插件化开发?

所谓插件化开发就是将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可以单独安装并且运行。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐