什么插件化
每一个业务组件都是一个独立的apk,然后通过主app动态加载部署业务组件apk。
插件化好处
- 业务组件解耦,能够实现业务组件热插拔
- 更改产品迭代模式,可分为主app以及次业务app
- 改善产品更新过程,可以在不影响用户的情况下实现业务组件更新以及bug修复
插件化 “思想”
主App被系统 “安装” 调用,这个过程由系统提高,而插件apk并非被系统安装,简而言之,需要将插件apk看成一个 “非apk” 文件,只是一个结构复杂的文件,在主app需要时会调用这个文件的一些资源。调用插件即用某种特殊的方式打开这个文件。
插件化步骤
分析主app
- 主App打包完成后,会形成dex,images,xml资源
- dex靠PathClassLoader加载
- 图片以及xml资源靠Resource加载
代码实现
- 创建DexClassLoader加载插件代码
- 创建Resource加载资源文件
- 管理插件Activity生命周期
项目结构
注:主APP项目运行在手机上,插件APP项目打包成APK,保存到主项目私有目录下,他们都引用连接的library
插件实体对象
package com.shangyi.android.pluginlibrary;
import android.content.pm.PackageInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;
import dalvik.system.DexClassLoader;
/**
* <pre>
* .----.
* _.'__ `.
* .--(Q)(OK)---/$\
* .' @ /$$$\
* : , $$$$$
* `-..__.-' _.-\$/
* `;_: `"'
* .'"""""`.
* /, FLY ,\
* // \\
* `-._______.-'
* ___`. | .'___
* (______|______)
* </pre>
* 包 名 : com.shangyi.android.pluginlibrary
* 作 者 : FLY
* 创建时间 : 2019/5/8
* 描述: 插件apk信息的实体对象
*/
public class PluginApk {
public PackageInfo mPackageInfo;//apk的解析
public DexClassLoader mDexClassLoader;//dex靠PathClassLoader加载
public Resources mResources;// 图片以及xml资源靠Resource加载
public AssetManager mAssetManager;//用于支持创建Resources
public PluginApk(PackageInfo mPackageInfo, DexClassLoader mDexClassLoader, Resources mResources) {
this.mPackageInfo = mPackageInfo;
this.mDexClassLoader = mDexClassLoader;
this.mResources = mResources;
this.mAssetManager = mResources.getAssets();
}
}
插件apk的管理类
package com.shangyi.android.pluginlibrary;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.util.Log;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
/**
* <pre>
* .----.
* _.'__ `.
* .--(Q)(OK)---/$\
* .' @ /$$$\
* : , $$$$$
* `-..__.-' _.-\$/
* `;_: `"'
* .'"""""`.
* /, FLY ,\
* // \\
* `-._______.-'
* ___`. | .'___
* (______|______)
* </pre>
* 包 名 : com.shangyi.android.pluginlibrary
* 作 者 : FLY
* 创建时间 : 2019/5/8
* 描述: 插件apk的管理
*/
public class PluginManager {
private static PluginManager instance;
private PluginManager() {
}
public static PluginManager getInstance() {
if (instance == null) {
instance = new PluginManager();
}
return instance;
}
private Context mContext;
private PluginApk mPluginApk;
public void init(Context context) {
this.mContext = context;
}
/**
* 检测是否调用初始化方法
*/
private void checkInitialize() {
if (mContext == null) {
throw new ExceptionInInitializerError("请先调用 PluginManager.getInstance().init(this) 初始化!");
}
}
//加载插件apk,服务器下载保存路径
public void loadApk(String apkPath) {
checkInitialize();
PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(apkPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
if (packageInfo == null) {
Log.d("PluginManager", "loadApk: 加载插件apk失败");
return;
}
DexClassLoader dexClassLoader = createDexClassLoader(apkPath);
AssetManager assetManager = createAssetManager(apkPath);
Resources resources = createResources(assetManager);
mPluginApk = new PluginApk(packageInfo, dexClassLoader, resources);
}
public PluginApk getPluginApk() {
return mPluginApk;
}
//创建访问插件apk的DexClassLoader对象加载插件代码
private DexClassLoader createDexClassLoader(String apkPath) {
File file = mContext.getDir("dex", Context.MODE_PRIVATE);
return new DexClassLoader(apkPath, file.getAbsolutePath(), null, mContext.getClassLoader());
}
//访问插件apk的Aseetmanger对象
private AssetManager createAssetManager(String apkPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
method.invoke(assetManager, apkPath);
return assetManager;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//创建访问插件apk的Resource对象加载资源文件
private Resources createResources(AssetManager assetManager) {
Resources resources = mContext.getResources();
return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
}
}
管理Activity生命周期
package com.shangyi.android.pluginlibrary;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
/**
* <pre>
* .----.
* _.'__ `.
* .--(Q)(OK)---/$\
* .' @ /$$$\
* : , $$$$$
* `-..__.-' _.-\$/
* `;_: `"'
* .'"""""`.
* /, FLY ,\
* // \\
* `-._______.-'
* ___`. | .'___
* (______|______)
* </pre>
* 包 名 : com.shangyi.android.pluginlibrary
* 作 者 : FLY
* 创建时间 : 2019/5/8
* 描述: 判断调用方式 管理Activity生命周期
*/
public interface IPlugin {
String FROM_KEY = "FromKey"; //判断调用的传参key
int FROM_INTERNAL = 0; // 从内部调用 手机系统调用
int FROM_EXTERNAL = 1; // 从外边调用 主app调用
//绑定,代理Activity,传入插件上下文
void attach(Activity proxyActivity);
void onCreate(Bundle savedInstanceState);
void onStart();
void onRestart();
void onResume();
void onActivityResult(int requestCode, int resultCode, Intent data);
void onPause();
void onStop();
void onDestroy();
}
连接插件的activity
package com.shangyi.android.pluginlibrary;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
/**
* <pre>
* .----.
* _.'__ `.
* .--(Q)(OK)---/$\
* .' @ /$$$\
* : , $$$$$
* `-..__.-' _.-\$/
* `;_: `"'
* .'"""""`.
* /, FLY ,\
* // \\
* `-._______.-'
* ___`. | .'___
* (______|______)
* </pre>
* 包 名 : com.shangyi.android.pluginlibrary
* 作 者 : FLY
* 创建时间 : 2019/5/9
* 描述: 连接插件的activity
*/
public class PuglinActivity extends Activity implements IPlugin {
//判断是否是从主APP调用的,如果是不做任何操作
//如果是系统调用的不带次参数,需要调用父类方法
private int mFrom = FROM_INTERNAL;
//插件的上下文,代理用
private Activity mProxyActivity;
@Override
public void attach(Activity proxyActivity) {
mProxyActivity = proxyActivity;
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mFrom = savedInstanceState.getInt(FROM_KEY);
}
if (mFrom == FROM_INTERNAL) super.onCreate(savedInstanceState);
}
public Activity getProxyActivity() {
return mProxyActivity;
}
@Override
public void setContentView(int layoutResID) {
if (mFrom == FROM_INTERNAL) {
super.setContentView(layoutResID);
} else if (mProxyActivity != null) {
mProxyActivity.setContentView(layoutResID);
} else {
Log.e("PuglinActivity", "插件的上下文为空");
}
}
@Override
public void onStart() {
if (mFrom == FROM_INTERNAL) super.onStart();
}
@Override
public void onRestart() {
if (mFrom == FROM_INTERNAL) super.onRestart();
}
@Override
public void onResume() {
if (mFrom == FROM_INTERNAL) super.onResume();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mFrom == FROM_INTERNAL) super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onPause() {
if (mFrom == FROM_INTERNAL) super.onPause();
}
@Override
public void onStop() {
if (mFrom == FROM_INTERNAL) super.onStop();
}
@Override
public void onDestroy() {
if (mFrom == FROM_INTERNAL) super.onDestroy();
}
}
代理activity - 实质调用需要manifest注册
package com.shangyi.android.pluginlibrary;
import android.app.Activity;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
/**
* <pre>
* .----.
* _.'__ `.
* .--(Q)(OK)---/$\
* .' @ /$$$\
* : , $$$$$
* `-..__.-' _.-\$/
* `;_: `"'
* .'"""""`.
* /, FLY ,\
* // \\
* `-._______.-'
* ___`. | .'___
* (______|______)
* </pre>
* 包 名 : com.shangyi.android.pluginlibrary
* 作 者 : FLY
* 创建时间 : 2019/5/9
* 描述: 代理activity
*/
public class ProxyActivity extends Activity {
public static final String CLASS_NAME = "className";
private String mClassName;
private PluginApk mPluginApk;
private IPlugin mIPlugin;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mClassName = getIntent().getStringExtra(CLASS_NAME);
mPluginApk = PluginManager.getInstance().getPluginApk();
launchPlaginActivity();
}
//启动activity
private void launchPlaginActivity() {
if (mPluginApk == null) {
Log.e("ProxyActivity: ", "加载不了插件apk文件");
return;
}
try {
Class<?> clazz = mPluginApk.mDexClassLoader.loadClass(mClassName);
// 实例化Activity 注意:这里的activity是没有生命周期,也没有上下文环境的
Object object = clazz.newInstance();
if (object instanceof IPlugin) {
mIPlugin = (IPlugin) object;
mIPlugin.attach(this);
Bundle bundle = new Bundle();
bundle.putInt(IPlugin.FROM_KEY, IPlugin.FROM_EXTERNAL);
mIPlugin.onCreate(bundle);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Resources getResources() {
return mPluginApk != null ? mPluginApk.mResources : super.getResources();
}
@Override
public AssetManager getAssets() {
return mPluginApk != null ? mPluginApk.mAssetManager : super.getAssets();
}
@Override
public ClassLoader getClassLoader() {
return mPluginApk != null ? mPluginApk.mDexClassLoader : super.getClassLoader();
}
}
主界面activity
package com.fly.newstart.plugin;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import com.fly.newstart.R;
import com.fly.newstart.common.base.BaseActivity;
import com.fly.newstart.utils.FileUtils;
import com.shangyi.android.pluginlibrary.PluginManager;
import com.shangyi.android.pluginlibrary.ProxyActivity;
public class MainPluginActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_plugin);
PluginManager.getInstance().init(this);
findViewById(R.id.btnLoad).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//模拟服务器下载插件apk
String apkPath = FileUtils.copyAssetAndWrite(MainPluginActivity.this, "pluginapk-debug.apk");
//加载apk
PluginManager.getInstance().loadApk(apkPath);
}
});
findViewById(R.id.btnSkip).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//指定跳转的类名
Intent intent = new Intent();
intent.setClass(MainPluginActivity.this, ProxyActivity.class);
intent.putExtra(ProxyActivity.CLASS_NAME, "com.shangyi.android.pluginapk.PluginActivity");
startActivity(intent);
}
});
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onRestart() {
super.onRestart();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
MainPluginActivity界面XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/MatchMatch"
android:gravity="center"
android:orientation="vertical"
tools:context="com.fly.newstart.plugin.MainPluginActivity">
<TextView style="@style/WrapWrap"
android:text="这里是主App"/>
<Button
android:id="@+id/btnLoad"
style="@style/WrapWrap"
android:text="加载插件app" />
<Button
android:id="@+id/btnSkip"
style="@style/WrapWrap"
android:text="跳转到插件app" />
</LinearLayout>
文件处理工具
package com.fly.newstart.utils;
import android.content.Context;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
/**
* <pre>
* .----.
* _.'__ `.
* .--(Q)(OK)---/$\
* .' @ /$$$\
* : , $$$$$
* `-..__.-' _.-\$/
* `;_: `"'
* .'"""""`.
* /, FLY ,\
* // \\
* `-._______.-'
* ___`. | .'___
* (______|______)
* </pre>
* 包 名 : com.fly.newstart.utils
* 作 者 : FLY
* 创建时间 : 2019/5/9
* 描述: 文件处理工具类
*/
public class FileUtils {
/**
* 将Assets目录的fileName文件拷贝到app缓存目录
*
* @param context
* @param fileName
* @return
*/
public static String copyAssetAndWrite(Context context, String fileName) {
try {
File cacheDir = context.getCacheDir();
if (!cacheDir.exists()) {
cacheDir.mkdir();
}
File outFile = new File(cacheDir, fileName);
if (!outFile.exists()) {
boolean res = outFile.createNewFile();
if (res) {
InputStream is = context.getAssets().open(fileName);
FileOutputStream fos = new FileOutputStream(outFile);
byte[] buffer = new byte[is.available()];
int byteCount;
while ((byteCount = is.read(buffer)) != -1) {
fos.write(buffer, 0, byteCount);
}
fos.flush();
is.close();
fos.close();
Toast.makeText(context, "下载成功", Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(context, "文件以存在", Toast.LENGTH_LONG).show();
}
return outFile.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
插件activity
package com.shangyi.android.pluginapk;
import android.os.Bundle;
import com.shangyi.android.pluginlibrary.PuglinActivity;
public class PluginActivity extends PuglinActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plugin);
}
}
PluginActivity 的XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context="com.shangyi.android.pluginapk.PluginActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是插件app界面"/>
</LinearLayout>