插件化是2017年前Android开发界最火的一种开发方式,利用插件化,可以对项目进行业务拆分,达到不需要上线就可以对现有的Apk客户端进行 功能模块和资源的添加和更新。插件化的原理大致就是通过服务端下发apk插件,然后客户端接收到之后,利用技术手段加载这些插件中的类和资源, 从而达到动态更新的功能模块和字资源的目的。这里的资源包括各种xml,图片,字符串等。
插件化的实现手段有很多中,Android开发业内也开源了许多的开源框架,但是从原理上可以分为三种,占位式插件化,Hook式插件化,LoadedApk式插件化。占位式插件化就是利用代理Activity,Service,BroadCast这种方式对插件中的我们写的Activity,Service, BroadCast进行代理,利用代理对这些插件类进行正真的操作,达到一系列的声明周期方法的回调实现。Hook式插件化是对现有的整个Activity,Service,BroadCast的启动过程中的某个环节进行拦截,在拦截期间,将正常启动并通过检查的Activity在最终快被实际调用阶段进行替换处理,这样就达到了Hook式插件化的操作,但是有个前提就是将插件的Elements融合到宿主的Elements中, 这样就与宿主用的是同一个Context。LoadedApk插件化是用的插件自己定义的ClassLoader,来对插件进行加载。
占位式插件化简单来说就是用代理类代替插件中的类来执行相关的生命周期方法,用的是宿主的上下文Context。 具体的实现步骤
标准库是用来定义插件中的类需要实现的接口,具体包含了Activity,Service的声明周期方法,方便在代理类进行调用。 我们先以实现Activity的插件化为例进行实现,后面的再实现BroadCast,Service的插件化。 首先创建一个library, 名称为standard, 用作定义插件类的接口,下面的是插件Activity的接口。
public interface ActivityInterface {
String TARGET_ACTIVITY_NAME = "TARGET_ACTIVITY_NAME";
String PLUGIN_NAME = "PLUGIN_NAME";
void insertBundleData(Bundle bundle);
void insertAppContext(Activity activity);
void onCreate(Bundle savedInstanceState);
void onStart();
void onResume();
void onPause();
void onStop();
void onDestroy();
}
我们创建一个Android项目,名称为plugin_mall,该项目就是我们的插件化apk生成项目。在gradle中添加对standard的libray的依赖。
dependencies{
...
implementation project(":standard")
}
public class BaseActivity extends Activity implements ActivityInterface {
private static final String TAG = "BaseActivity";
protected Activity activity;
protected Bundle bundle;
@Override
public void insertBundleData(Bundle bundle) {
this.bundle = bundle;
}
public String getStringInBundle(String key){
if (bundle == null){
return "";
}
return bundle.getString(key);
}
public int getIntInBundle(String key){
if (bundle == null){
return -1;
}
return bundle.getInt(key);
}
@Override
public void insertAppContext(Activity activity) {
this.activity = activity;
}
public void setContentView(@LayoutRes int layoutId){
activity.setContentView(layoutId);
}
public View findViewById(@IdRes int resId){
return activity.findViewById(resId);
}
@Override
public void startActivity(Intent intent) {
Intent newIntent = new Intent();
String componentName = intent.getComponent().getClassName();
newIntent.putExtra(TARGET_ACTIVITY_NAME, componentName);
newIntent.putExtra(PLUGIN_NAME, BuildConfig.PLUGIN_NAME);
activity.startActivity(newIntent);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return activity.registerReceiver(receiver, filter);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
activity.unregisterReceiver(receiver);
}
// 这里没有上下文环境 所以必须使用宿主Activity的实例来替代
@Override
public void sendBroadcast(Intent intent) {
activity.sendBroadcast(intent);
}
@SuppressLint("MissingSuperCall")
@Override
public void onCreate(Bundle savedInstanceState) {
}
@SuppressLint("MissingSuperCall")
@Override
public void onStart() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onResume() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onPause() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onStop() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onDestroy() {
}
}
// 具体的插件业务类
public class PluginMallActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mall_main);
}
}
有了上面的两步之后,插件部分的代码算是完成了,下来就是考虑如何使用插件,我们需要通过启动代理Activity,然后在启动成功在回调OnCreate方法的时候,获取我们需要的插件信息,比如启动的目标Activity的类全名,通过这个类全名,我们利用类加载器将该类加载进来,然后实例化,最终将我们的代理Activity的注入到插件类中,这样插件类中就具有和宿主一样的Activity上下文环境了。
// 代理ProxyActivity
public class ProxyActivity extends Activity {
public static final String TARGET_ACTIVITY_NAME = "TARGET_ACTIVITY_NAME";
public static final String PLUGIN_NAME = "PLUGIN_NAME";
// 因为我们要加载插件中的类,所以需要的是能够加载外部dex的DexClassLoader
// 所以需要重写ClassLoader,否则默认的就是PathClassLoader, 只能加载已安装的apk中的类
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance(this).getDexClassLoader();
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
}
// 加载插件 然后创建实例 然后调用实例的insertAppContext
// 方法将宿主Activity注入插件BaseActivity中 代理常见的使用方法
@SuppressWarnings("Unchecked_call")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
String clazzName = getIntent().getStringExtra(TARGET_ACTIVITY_NAME);
String pluginName = getIntent().getStringExtra(PLUGIN_NAME);
Bundle bundle = getIntent().getExtras();
String pluginPath = PluginManager.getInstance(this).getPluginPathByName(pluginName);
initResources(pluginPath);
Class clzz = getClassLoader().loadClass(clazzName);
Constructor constructor = clzz.getConstructor(new Class[]{});
Object object = constructor.newInstance();
ActivityInterface activityInterface = (ActivityInterface) object;
activityInterface.insertBundleData(bundle);
activityInterface.insertAppContext(this);
activityInterface.onCreate(savedInstanceState);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void startActivity(Intent intent) {
String componentName = intent.getStringExtra(TARGET_ACTIVITY_NAME);
String pluginName = intent.getStringExtra(PLUGIN_NAME);
Intent proxyIntent = new Intent(this, ProxyActivity.class);
proxyIntent.putExtra(ProxyActivity.TARGET_ACTIVITY_NAME, componentName);
proxyIntent.putExtra(ProxyActivity.PLUGIN_NAME, pluginName);
super.startActivity(proxyIntent);
}
@Override
public ComponentName startService(Intent service) {
String componentName = service.getStringExtra("serviceName");
Intent proxyIntent = new Intent(this, ProxyService.class);
proxyIntent.putExtra("serviceName", componentName);
return super.startService(service);
}
private ProxyBroadCastReceiver proxyBroadCastReceiver;
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
String receiverName = receiver.getClass().getName();
proxyBroadCastReceiver = new ProxyBroadCastReceiver(receiverName);
return super.registerReceiver(proxyBroadCastReceiver, filter);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
if (proxyBroadCastReceiver != null) {
super.unregisterReceiver(proxyBroadCastReceiver);
}
}
@Override
public void sendBroadcast(Intent intent) {
super.sendBroadcast(intent);
}
}
//PluginManager.java
public class PluginManager {
private DexClassLoader dexClassLoader;
private Resources resources;
private Context context;
private static volatile PluginManager pluginManager;
private ArrayMap<String, String> apkPaths;
public DexClassLoader getDexClassLoader() {
return dexClassLoader;
}
public Resources getResources() {
return resources;
}
private PluginManager(Context context) {
this.context = context;
this.apkPaths = new ArrayMap<>();
}
public static PluginManager getInstance(Context context) {
if (pluginManager == null) {
synchronized (PluginManager.class) {
if (pluginManager == null) {
pluginManager = new PluginManager(context);
}
}
}
return pluginManager;
}
//从Asset中加载apk到包安装目录下 然后创建DexClassLoader对象
//DexClassLoader对象用于加载非应用自身的类
//一个DexClassLoader对应一个apk文件 不是的可以加载多个apk dexPath 的例子可以是 a.apk:b.apk:c.apk
// String dexPath 包含class.dex的apk、jar文件路径,多个用文件分隔符(默认是:)分隔
public void loadPlugin() {
try {
File dexFileDir = context.getDir("plugin", Context.MODE_PRIVATE);
if (dexFileDir == null || dexFileDir.listFiles() == null || dexFileDir.list().length == 0) {
return;
}
StringBuilder stringBuilder = new StringBuilder();
File[] dexFiles = dexFileDir.listFiles();
for (File file : dexFiles) {
stringBuilder.append(file.getAbsolutePath()).append(":");
}
int lastCharIndex = stringBuilder.lastIndexOf(":");
stringBuilder.deleteCharAt(lastCharIndex);
String dexPath = stringBuilder.toString();
File optimizeDir = context.getDir("opt", Context.MODE_PRIVATE);
String optimizedPath = optimizeDir.getAbsolutePath();
dexClassLoader = new DexClassLoader(dexPath, optimizedPath, null, context.getClassLoader());
Resources r = context.getResources();
AssetManager assetManager = AssetManager.class.newInstance();
// 调用 public int addAssetPath(String path) path 插件的路径
//Method method = assetManager.getClass().getMethod("addAssetPaths", String[].class);
//method.invoke(assetManager, (Object) paths);
Method method = assetManager.getClass().getMethod("addAssetPath", String.class);
String[] paths = new String[dexFiles.length];
for (int i = 0; i < dexFiles.length; i++) {
paths[i] = dexFiles[i].getAbsolutePath();
String apkName = paths[i].substring(paths[i].lastIndexOf("/")+1);
apkPaths.put(apkName, paths[i]);
method.invoke(assetManager, paths[i]);
}
resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
public String getPluginPathByName(String pluginName){
return apkPaths.get(pluginName);
}
}
好了,有了上面的步骤之后,我们就可以启动代理Activity,然后利用代理Activity, 正常启动插件Activity。首先需要在宿主项目中注册代理ProxyActivity
<activity android:name=".plugin.ProxyActivity"/>
启动代理Activity, 然后在ProxyActivity中获取类信息,实例化插件类,注入宿主上下文,利用代理来实现具体的生命周期方法回调。
val intent = Intent(this, ProxyActivity::class.java)
intent.putExtra(ProxyActivity.TARGET_ACTIVITY_NAME, "com.zhiyunyi.plugin_mall.PluginMallActivity")
intent.putExtra(ProxyActivity.PLUGIN_NAME, "plugin_mall.apk")
startActivity(intent)
上面完成了代码相关的插件加载,但是还是有个问题,就是apk中的资源如何加载,它是独立于已安装Apk的,如何将这些图片,xml, 字符串加载进来正常使用?问题在AssetManager. 我们通过Context的getResources方法最终可以发现Resources对象是由AssetManager构建出来的。
//ResourcesManager.java
Resources getTopLevelResources(String resDir,
String[] splitResDirs,
String[] overlayDirs, String[] libDirs,
int displayId,
Configuration overrideConfiguration,
CompatibilityInfo compatInfo) {
...
AssetManager assets = new AssetManager();
if (resDir != null) {
if (assets.addAssetPath(resDir) == 0) {
return null;
}
}
if (splitResDirs != null) {
for (String splitResDir : splitResDirs) {
if (assets.addAssetPath(splitResDir) == 0) {
return null;
}
}
}
r = new Resources(assets, dm, config, compatInfo);
...
}
从上看到可以通过AssetManager的addAssetPath方法将外部apk文件中的资源文件加载进来,然后创建Resources对象。这种方式创建出来的只能访问插件自身的资源文件,对于宿主的资源访问不到。
public class ProxyActivity extends Activity {
private Resources resources;
private AssetManager assetManager;
@Override
public Resources getResources() {
if (resources == null) {
return super.getResources();
}
return resources;
}
@Override
public AssetManager getAssets() {
if (assetManager == null){
return super.getAssets();
}
return assetManager;
}
private void initResources(String pluginPath) {
try {
Resources r = super.getResources();
AssetManager asset = AssetManager.class.newInstance();
assetManager = asset;
Method method = asset.getClass().getMethod("addAssetPath", String.class);
method.invoke(asset, pluginPath); // 插件的路径
resources = new Resources(asset, r.getDisplayMetrics(), r.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
// 加载插件 然后创建实例 然后调用实例的insertAppContext
// 方法将宿主Activity注入插件BaseActivity中 代理常见的使用方法
@SuppressWarnings("Unchecked_call")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
String clazzName = getIntent().getStringExtra(TARGET_ACTIVITY_NAME);
String pluginName = getIntent().getStringExtra(PLUGIN_NAME);
Bundle bundle = getIntent().getExtras();
String pluginPath = PluginManager.getInstance(this).getPluginPathByName(pluginName);
initResources(pluginPath); // 初始化插件资源
Class clzz = getClassLoader().loadClass(clazzName);
Constructor constructor = clzz.getConstructor(new Class[]{});
Object object = constructor.newInstance();
ActivityInterface activityInterface = (ActivityInterface) object;
activityInterface.insertBundleData(bundle);
activityInterface.insertAppContext(this);
// 回调插件Activity类的onCreate方法
activityInterface.onCreate(savedInstanceState);
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上就是对Activity的站桩式插件写法。其他的Service, BroadCast类似,都是通过代理来是显示具体的业务。
// Service的标准接口
public interface ServiceInterface {
public void insertAppService(Service service);
public void onCreate() ;
public int onStartCommand(Intent intent, int flags, int startId);
public void onDestroy();
}
public class BaseService extends Service implements ServiceInterface {
private Service service;
@Override
public void insertAppService(Service service) {
this.service = service;
}
@Override
public void onCreate() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return Service.START_STICKY_COMPATIBILITY;
}
@Override
public void onDestroy() {
service.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
public class PluginZhuShouService extends BaseService{
private static final String TAG = "PluginZhuShouService";
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: ");
new Timer().schedule(new TimerTask() {
@Override
public void run() {
Log.e(TAG, "run: doSomething...");
}
}, 2000, 20000);
return super.onStartCommand(intent, flags, startId);
}
}
以上就是Service插件化的所有代码。 Service代理部分代码
public class ProxyService extends Service {
private static final String TAG = "ProxyService";
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 获取Service的实例 然后执行ProxyService注入 用ProxyService 代替执行相关的方法
String serviceClazzName = intent.getStringExtra("serviceName");
Log.e(TAG, "onStartCommand: " + serviceClazzName);
try {
Class serviceClazz = PluginManager.getInstance(this).getDexClassLoader().loadClass(serviceClazzName);
Object object = serviceClazz.newInstance();
ServiceInterface serviceInterface = (ServiceInterface) object;
serviceInterface.insertAppService(this);
serviceInterface.onStartCommand(intent, flags, startId);
} catch (Exception e) {
e.printStackTrace();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
//ProxyActivity.java
//这里处理的是插件内部启动Service的情况
@Override
public ComponentName startService(Intent service) {
String componentName = service.getStringExtra("serviceName");
Intent proxyIntent = new Intent(this, ProxyService.class);
proxyIntent.putExtra("serviceName", componentName);
return super.startService(service);
}
在宿主项目的清单文件中进行注册
<service android:name=".plugin.ProxyService"/>
启动服务
val intent = Intent(this, ProxyService::class.java)
intent.putExtra("serviceName","com.zhiyunyi.plugin_zhushou.PluginZhuShouService")
startService(intent)
广播的标准插件接口
// 广播代理接口
public interface BroadCastInterface {
public void onReceive(Context context, Intent intent);
}
插件BroadCast实现广播代理接口
public class MyBroadCastReceiver extends BroadcastReceiver
implements BroadCastInterface {
private static final String TAG = "MyBroadCastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "onReceive: "+intent.getAction());
Toast.makeText(context, "我是插件内的广播", Toast.LENGTH_LONG).show();
}
}
以上是动态广播部分插件化的全部代码,下面来看广播的代理部分代码
public class ProxyBroadCastReceiver extends BroadcastReceiver {
private String broadCastReceiverClazzName;
public ProxyBroadCastReceiver(String name) {
this.broadCastReceiverClazzName = name;
}
@Override
public void onReceive(Context context, Intent intent) {
try {
Class clazz = PluginManager.getInstance(context).getDexClassLoader().loadClass(broadCastReceiverClazzName);
Object object = clazz.newInstance();
BroadCastInterface broadCastInterface = (BroadCastInterface) object;
broadCastInterface.onReceive(context, intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//ProxyActivity.java
private ProxyBroadCastReceiver proxyBroadCastReceiver;
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
String receiverName = receiver.getClass().getName();
proxyBroadCastReceiver = new ProxyBroadCastReceiver(receiverName);
return super.registerReceiver(proxyBroadCastReceiver, filter);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
if (proxyBroadCastReceiver != null) {
super.unregisterReceiver(proxyBroadCastReceiver);
}
}
@Override
public void sendBroadcast(Intent intent) {
super.sendBroadcast(intent);
}
最终调用是在插件中进行广播的发送
//PluginZhushouActivity.java
private static final String ACTION = "com.zhiyunyi.plugin_zhushou.MyBroadCastReceiver";
// 注册广播
private MyBroadCastReceiver myBroadCastReceiver;
myBroadCastReceiver = new MyBroadCastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION);
registerReceiver(myBroadCastReceiver, intentFilter);
// 发送广播
Intent intent = new Intent();
intent.setAction(ACTION);
sendBroadcast(intent);
以上是全部的动态广播插件化处理所有代码,其实还有一种广播,注册在插件中的静态广播,注册在清单文件中的,是需要解析APK,获取到相应的信息,进行广播注册才能接收。
public class StaticBroadCastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "我是插件内的静态广播", Toast.LENGTH_LONG).show();
}
}
注册在插件的清单文件中,成为静态广播
<receiver android:name=".StaticBroadCastReceiver">
<intent-filter>
<action android:name="com.zhiyunyi.plugin_zhushou.static"></action>
</intent-filter>
<intent-filter>
<action android:name="com.zhiyunyi.plugin_zhushou.static"></action>
</intent-filter>
</receiver>
对插件apk中的静态广播进行解析处理
// 解析Parse 清单文件获取组件信息
// 反射系统源码,来解析 apk 文件里面的 所有信息
public void parserApkAction() {
try {
File dexFileDir = context.getDir("plugin", Context.MODE_PRIVATE);
if (dexFileDir == null || dexFileDir.listFiles() == null || dexFileDir.list().length == 0) {
throw new RuntimeException("没有找到插件文件");
}
File[] listFiles = dexFileDir.listFiles();
// 遍历 plugin 插件 然后解析其中的静态广播 并进行注册
for (File file : listFiles) {
Logger.e("parseApkAction "+file.getAbsolutePath());
parseManifestReceivers(file);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void parseManifestReceivers(File bugFixDexClassesFile) throws Exception{
// 实例化 PackageParser对象
Class mPackageParserClass = Class.forName("android.content.pm.PackageParser");
Object mPackageParser = mPackageParserClass.newInstance();
// 1.执行此方法 public Package parsePackage(File packageFile, int flags),就是为了,拿到Package
Method mPackageParserMethod = mPackageParserClass.getMethod("parsePackage", File.class, int.class); // 类类型
// 为什么必须是PackageManager.GET_ACTIVITIES?
Object mPackage = mPackageParserMethod.invoke(mPackageParser, bugFixDexClassesFile, PackageManager.GET_ACTIVITIES); // 执行方法
// 继续分析 Package
// 得到 receivers
Field receiversField = mPackage.getClass().getDeclaredField("receivers");
// receivers 本质就是 ArrayList 集合
Object receivers = receiversField.get(mPackage);
ArrayList arrayList = (ArrayList) receivers;
// 此Activity 不是组件的Activity,是PackageParser里面的内部类
for (Object mActivity : arrayList) { // mActivity --> <receiver android:name=".StaticReceiver">
// 获取 <intent-filter> intents== 很多 Intent-Filter
// 通过反射拿到 intents
Class mComponentClass = Class.forName("android.content.pm.PackageParser$Component");
Field intentsField = mComponentClass.getDeclaredField("intents");
// Component是Activity的父类
ArrayList<IntentFilter> intents = (ArrayList) intentsField.get(mActivity); // intents 所属的对象是谁 ?
// 我们还有一个任务,就是要拿到 android:name=".StaticReceiver"
// activityInfo.name; == android:name=".StaticReceiver"
// 分析源码 如何 拿到 ActivityInfo
Class mPackageUserState = Class.forName("android.content.pm.PackageUserState");
Class mUserHandle = Class.forName("android.os.UserHandle");
int userId = (int) mUserHandle.getMethod("getCallingUserId").invoke(null);
/**
* 执行此方法,就能拿到 ActivityInfo
* public static final ActivityInfo generateActivityInfo(Activity a, int flags,
* PackageUserState state, int userId)
*/
Method generateActivityInfoMethod = mPackageParserClass.getDeclaredMethod("generateActivityInfo", mActivity.getClass()
, int.class, mPackageUserState, int.class);
generateActivityInfoMethod.setAccessible(true);
// 执行此方法,拿到ActivityInfo
ActivityInfo mActivityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, mActivity, 0, mPackageUserState.newInstance(), userId);
String receiverClassName = mActivityInfo.name; // com.netease.plugin_package.StaticReceiver
Class mStaticReceiverClass = getDexClassLoader().loadClass(receiverClassName);
BroadcastReceiver broadcastReceiver = (BroadcastReceiver) mStaticReceiverClass.newInstance();
for (IntentFilter intentFilter : intents) {
// 注册广播
context.registerReceiver(broadcastReceiver, intentFilter);
}
}
}
// 在宿主App中对插件Apk进行解析
PluginManager.getInstance(this).loadPlugin()
在宿主App中发送广播,在插件的广播接收器中就可以接收了
val intent = Intent();
intent.action = "com.zhiyunyi.plugin_zhushou.static"
sendBroadcast(intent)