学习完组件化,紧接着又学习了插件化,这里写了一个小demo记录一下学习成果。首先我们来了解一下什么是插件化?每个业务组件就是一个独立的apk,然后通过主app动态加载部署业务组件apk。那么插件化的好处是是什么呢?首先可以实现业务组件解耦,能够实现业务组件热插拔;其次可以更改产品迭代模式,可分为主app和组件app业务;最后可以改善产品更新过程,可以在不影响用户的情况下实现业务组件的更新以及bug修复。
接下来我们来了解一下插件化需要的思想。我们知道主app的apk文件是被系统安装调用,这个工作由系统来完成,那我们的插件apk呢?这就需要我们开发者自己来实现加载的过程,简言之,需要将插件apk文件看成是一个非apk的文件,只是一个和apk文件相似的一个文件,调用插件即用某种特殊的方式打开这个文件。
接下来,我们来了解一下插件化的步骤,首先我们来分析一下一个apk文件的结构,我们将一个apk文件解压后,会形成dex、images、xml几个文件,dex文件靠DexClassLoader来加载,图片以及xml资源靠Resources来加载。当我们了解了这些以后,我们要来加载插件apk文件,就要创建DexClassLoader来加载dex文件,创建Resources来加载资源文件,当然我们还需要管理插件的activity生命周期。
好了,说了这么多,接下来我们就按照这个思路来上代码。
1,创建PluginApk,这里面主要包括PackageInfo、Resources、AssetManager、DexClassLoader
public class PluginApk {
public PackageInfo mPackageInfo;
public Resources mResources;
public AssetManager mAssetManager;
public DexClassLoader mDexClassLoader;
public PluginApk(PackageInfo packageInfo, Resources resources, DexClassLoader dexClassLoader) {
mPackageInfo = packageInfo;
mResources = resources;
mAssetManager = resources.getAssets();
mDexClassLoader = dexClassLoader;
}
}
2,创建PluginManager,这里面主要是来创建DexClassLoader、Resources等来加载apk
public class PluginManager {
private static final PluginManager instance = new PluginManager();
private Context mContext;
private PluginApk mPluginApk;
private PluginManager() {
}
public static PluginManager getInstance() {
return instance;
}
public PluginApk getPluginApk() {
return mPluginApk;
}
public PluginManager init(Context context) {
mContext = context.getApplicationContext();
return this;
}
/**
* 加载apk文件
* @param apkPath
*/
public void loadApk(String apkPath) {
PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES
| PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS);
if (packageInfo == null) {
return;
}
AssetManager manager = createAssetMangager(apkPath);
Resources resources = createResources(manager);
DexClassLoader loader = createClassLoader(apkPath);
mPluginApk = new PluginApk(packageInfo, resources, loader);
}
private AssetManager createAssetMangager(String apkPath) {
try {
AssetManager am = AssetManager.class.newInstance();
Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
method.invoke(am, apkPath);
return am;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private DexClassLoader createClassLoader(String apkPath) {
File apkFile = new File(apkPath);
return new DexClassLoader(apkPath, apkFile.getAbsolutePath(), null, mContext.getClassLoader());
}
private Resources createResources(AssetManager am) {
Resources res = mContext.getResources();
return new Resources(am, res.getDisplayMetrics(), res.getConfiguration());
}
}
3,创建一个代理activity来管理插件activity的生命周期
public class ProxyActivity extends Activity {
private String mClassName;
private PluginApk mPluginApk;
private IPlugin mIPlugin;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mClassName = getIntent().getStringExtra(Constant.CLASS_NAME);
mPluginApk = PluginManager.getInstance().getPluginApk();
lauchPluginApk();
}
private void lauchPluginApk() {
if (mPluginApk == null) {
// throw new RuntimeException("请先加载apk");
Toast.makeText(this, "请先加载apk", Toast.LENGTH_SHORT).show();
}
try {
Class> loadClass = mPluginApk.mDexClassLoader.loadClass(mClassName);
Log.e("====>", loadClass.getName() + "");
Object instance = loadClass.newInstance();
if (instance instanceof IPlugin) {
mIPlugin = (IPlugin) instance;
mIPlugin.onAttach(this);
Bundle bundle = new Bundle();
bundle.putInt(Constant.FROM, IPlugin.FROM_EXTERNAL);
mIPlugin.onCreate(bundle);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Resources getResources() {
return mPluginApk == null ? super.getResources() : mPluginApk.mResources;
}
@Override
public AssetManager getAssets() {
return mPluginApk == null ? super.getAssets() : mPluginApk.mAssetManager;
}
@Override
public ClassLoader getClassLoader() {
return mPluginApk == null ? super.getClassLoader() : mPluginApk.mDexClassLoader;
}
}
IPlugin中主要管理activity的生命周期的方法
public interface IPlugin {
int FROM_INTERNAL = 0;//来自内部的跳转
int FROM_EXTERNAL = 1;//来自外部的跳转
void onAttach(Activity activity);
void onCreate(Bundle savedInstanceState);
void onStart();
void onResume();
void onRestart();
void onStop();
void onPause();
void onDestroy();
void onActivityResult(int requestCode, int resultCode, @Nullable Intent data);
}
4,创建PluginActivity实现IPlugin,插件中的activity需要继承
public class PluginActivity extends Activity implements IPlugin {
private int mFrom = FROM_INTERNAL;
private Activity mActivity;
public Bundle mBundle;
@Override
public void onAttach(Activity activity) {
mActivity = activity;
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mFrom = savedInstanceState.getInt(Constant.FROM);
}
if (mFrom == FROM_INTERNAL) {
super.onCreate(savedInstanceState);
mActivity = this;
}
}
@Override
public void setContentView(int layoutResID) {
if (mFrom == FROM_INTERNAL) {
super.setContentView(layoutResID);
} else {
mActivity.setContentView(layoutResID);
}
}
@Override
public void onStart() {
if (mFrom == FROM_INTERNAL) {
super.onStart();
}
}
@Override
public void onResume() {
if (mFrom == FROM_INTERNAL) {
super.onResume();
}
}
@Override
public void onRestart() {
if (mFrom == FROM_INTERNAL) {
super.onRestart();
}
}
@Override
public void onStop() {
if (mFrom == FROM_INTERNAL) {
super.onStop();
}
}
@Override
public void onPause() {
if (mFrom == FROM_INTERNAL) {
super.onPause();
}
}
@Override
public void onDestroy() {
if (mFrom == FROM_INTERNAL) {
super.onDestroy();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (mFrom == FROM_INTERNAL) {
super.onActivityResult(requestCode, resultCode, data);
}
}
}
5,在插件的入口activity需要继承PluginActivity
public class MainActivity extends PluginActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
6,在主app中加载插件apk和跳转到插件apk
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//加载插件apk文件
if (PluginManager.getInstance().getPluginApk() == null) {
String path = copyAssetsFileToAppFiles("moduleplugin.apk");
PluginManager.getInstance().init(this).loadApk(path);
}
findViewById(R.id.btnLoad).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String path = copyAssetsFileToAppFiles("moduleplugin.apk");
PluginManager.getInstance().loadApk(path);
}
});
findViewById(R.id.btnNext).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, ProxyActivity.class);
intent.putExtra(Constant.CLASS_NAME, "com.carlt.moduleplugin.MainActivity");
startActivity(intent);
}
});
}
加载整个apk的过程,是仿照系统加载apk的过程。最后附上demo的源码,有兴趣的同学可以下载后运行验证。
https://github.com/allenlzhang/CarltPluginDemo