插件化-占位式插件化
Android中的插件化,即将业务解耦封装为一个单独的插件,根据不同的业务需求,进行不同的组合,动态进行替换,并可对插件进行管理、更新,后期对插件也可进行版本管理等操作。
插件化主要概念
- 宿主:即需要提供运行环境的apk,也就是我们安装在手机上的应用程序。
- 插件:即独立的功能封装成的小apk,无法直接运行,需要依赖于宿主环境,否则无法运行。用于动态更新内容。
- 标准:提供给插件使用的我们定义的标准库,用于宿主和插件之间的环境交互。
宿主
宿主中应提供ProxyActivity
类,为帮插件中的Activity代理/占位,给插件中提供宿主的环境。其中加载插件中的apk,需要使用到ClassLoader
类加载器和Resources
资源管理。即在ProxyActivity
代理类中重写getClassLoader
和getResources
方法。
getClassLoader()和getResources()
fun loadPlugin() {
try {
val file = File(Environment.getExternalStorageDirectory().toString() + File.separator + DEX_APK_NAME)
if (!file.exists()) {
Log.d(TAG, "插件包 不存在...");
return;
}
val pluginPath = file.absolutePath
//dexClassLoader需要一个缓存地址
val fileDir = mContext.getDir("myClassLoaderDir",Context.MODE_PRIVATE)
/**
* 加载插件中的class
*/
mDexClassLoader = DexClassLoader(pluginPath, fileDir.absolutePath, null, mContext.classLoader)
val mAssetManager = AssetManager::class.java.newInstance()
// 我们要执行此方法,为了把插件包的路径 添加进去
// public final int addAssetPath(String path) 通过反射获取
val method = mAssetManager.javaClass.getMethod("addAssetPath", String.javaClass)
method.invoke(mAssetManager, pluginPath)
val r = mContext.resources
/**
* 加载插件中的资源layout等
*/
mResources = Resources(mAssetManager, r.displayMetrics, r.configuration)
} catch (e: Exception) {
e.printStackTrace()
}
}
即我们从服务器下载的apk进行获取,通过DexClassLoader
类和Resources
类的到ClassLoader和resources,给予宿主环境使用。
标准库
定义Activity、Service、Receiver提供接口标准
示例:
public interface ActivityInterface {
/**
* 把宿主(app)的环境 给 插件
* @param appActivity
*/
void insertAppContext(Activity appActivity);
// 生命周期方法
void onCreate(Bundle savedInstanceState);
void onStart();
void onResume();
void onDestroy();
}
public interface ServiceInterface {
/**
* 把宿主(app)的环境 给 插件
*/
void insertAppContext(Service appService);
public IBinder onBind(Intent intent);
public int onStartCommand(Intent intent, int flags, int startId);
public void onCreate();
public void onDestroy();
}
public interface ReceiverInterface {
public void onReceive(Context context, Intent intent);
}
Activity
宿主环境跳转到插件的Activity
val file =
File(Environment.getExternalStorageDirectory().toString() + File.separator + PluginManager.DEX_APK_NAME)
val path: String = file.absolutePath
// 获取插件包 里面的 Activity
val packageManager = packageManager
val packageInfo =
packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES)
val activityInfo = packageInfo.activities[0]
// 占位 代理Activity
val intent = Intent(this, ProxyActivity::class.java)
intent.putExtra("className", activityInfo.name)
startActivity(intent)
即获取插件中的定义跳转的Activity,通过PackageManager获取全类名,使用我们的代理类ProxyActivity进行跳转。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 真正的加载 插件里面的 Activity,获取到需要跳转的全类名
String className = getIntent().getStringExtra("className");
try {
//通过类加载器,加载我们要跳转的类
Class mPluginActivityClass = getClassLoader().loadClass(className);
// 实例化 插件包里面的 Activity
Constructor constructor = mPluginActivityClass.getConstructor(new Class[]{});
Object mPluginActivity = constructor.newInstance(new Object[]{});
//获取在插件类中实现的Activity标准ActivityInterface
ActivityInterface activityInterface = (ActivityInterface) mPluginActivity;
// 给插件库注入宿主环境
activityInterface.insertAppContext(this);
//传值
Bundle bundle = new Bundle();
bundle.putString("appName", "我是宿主传递过来的信息");
// 执行插件里面的onCreate方法
activityInterface.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
其中在我们的插件类中集成BaseActivity实现标准ActivityInterface
public class BaseActivity extends Activity implements ActivityInterface {
public Activity appActivity; // 宿主的环境
@Override
public void insertAppContext(Activity appActivity) {
this.appActivity = appActivity;
}
@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 onDestroy() {
}
public void setContentView(int resId) {
appActivity.setContentView(resId);
}
public View findViewById(int layoutId) {
return appActivity.findViewById(layoutId);
}
@Override
public void startActivity(Intent intent) {
Intent intentNew = new Intent();
intentNew.putExtra("className", intent.getComponent().getClassName());
appActivity.startActivity(intentNew);
}
@Override
public ComponentName startService(Intent service) {
Intent intent = new Intent();
intent.putExtra("className", service.getComponent().getClassName());
return appActivity.startService(intent);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return appActivity.registerReceiver(receiver, filter);
}
@Override
public void sendBroadcast(Intent intent) {
appActivity.sendBroadcast(intent);
}
}
**注意:在此类中重写需要的方法,使用宿主环境进行调用。**否则会由于在插件中没有可运行的环境而导致崩溃。
插件中的Activity之间的跳转
1、通过调用我们重写的startActivity(Intent intent)
,其中会使用宿主环境的appActivity.startActivity(intentNew)
调用到宿主的startActivity方法。
2、在宿主环境的ProxyActivity中就会调用
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent intentNew = new Intent(this, ProxyActivity.class);
intentNew.putExtra("className", className);
// 要给TestActivity 进栈
super.startActivity(intentNew);
}
重新调用占位式的ActivityProxyActivity.class
,执行onCreate方法,获取全类名通过类加载器再进行跳转操作。
Service
启动Service服务。与Activity类似,即startService(Intent(appActivity, TestService::class.java))
,在BaseActivity中重写
@Override
public ComponentName startService(Intent service) {
Intent intent = new Intent();
intent.putExtra("className", service.getComponent().getClassName());
return appActivity.startService(intent);
}
调用宿主环境的
@Override
public ComponentName startService(Intent service) {
String className = service.getStringExtra("className");
Intent intentNew = new Intent(this, ProxyService.class);
intentNew.putExtra("className", className);
return super.startService(intentNew);
}
使用代理类ProxyService.class
,重写需要的方法,
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String className = intent.getStringExtra("className");
try {
//通过全类名,加载插件中的服务类
Class mPluginServiceClass = PluginManager.Companion.getInstance(this).getClassLoader().loadClass(className);
Object mTestServiceInterface = mPluginServiceClass.newInstance();
//获取服务标准ServiceInterface
ServiceInterface serviceInterface = (ServiceInterface) mTestServiceInterface;
//启动插件中的Service的onStartCommand
serviceInterface.onStartCommand(intent, flags, startId);
} catch (Exception e) {
e.printStackTrace();
}
return super.onStartCommand(intent, flags, startId);
}
注意:插件中的Service要实现ServiceInterface
BroadcastReceiver
动态广播
同样ProxyActivity重写registerReceiver(MyReceiver(),intentFilter)
和sendBroadcast(intent)
,调用到宿主环境中。
宿主环境:
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
String pluginMyReceiverClassName = receiver.getClass().getName();
return super.registerReceiver(new ProxyReceiver(pluginMyReceiverClassName), filter);
}
@Override
public void sendBroadcast(Intent intent) {
super.sendBroadcast(intent);
}
宿主环境中的代理Receiver:
public class ProxyReceiver extends BroadcastReceiver {
private String mPluginReceiverClassName;
public ProxyReceiver(String mPluginReceiverClassName) {
//完整类名
this.mPluginReceiverClassName = mPluginReceiverClassName;
}
@Override
public void onReceive(Context context, Intent intent) {
try {
Class receiver = PluginManager.Companion.getInstance(context).getClassLoader().loadClass(mPluginReceiverClassName);
Object serviceData = receiver.newInstance();
ReceiverInterface receiverInterface = (ReceiverInterface) serviceData;
receiverInterface.onReceive(context, intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
静态广播
静态广播需要在AndroidManifest.xml中注册,所以只能通过解析apk,拿到apk中注册的静态广播信息进行注册处理。
分析:
- apk应用在data/app下,通过PackManagerService的File systemAppDir = new File(Environment.getRootDirectory(), “app”);得到其中的数据
- 调用scanDirLI->scanPackageChildLI方法扫描其中的数据,返回PackageParser.Package,即返回此APK文件解析的完整软件包,其中我们可以看到public final ArrayList receivers = new ArrayList(0);所有的静态广播集合
- 遍历receivers,得到PackageParser类下的Activity类,其中参数ActivityInfo的父类中包含public String name;即:Public name of this item. From the “android:name” attribute.拿到
- 通过反射得到PackageParser类下的generateActivityInfo方法获取到ActivityInfo,得到receiverClassName静态注册全类名,最后调用mContext.registerReceiver(broadcastReceiver, intentFilter)进行注册
在宿主环境中拿到静态广播信息,代码如下:
/**
* 解析APK,得到静态注册的广播的清单文件,并拿到
* 使用反射
*/
fun parserApkAction() {
try {
val file = File(Environment.getExternalStorageDirectory().toString() + File.separator + DEX_APK_NAME)
if (!file.exists()) {
Log.d(TAG, "插件包 不存在...");
return;
}
val pluginPath = file.absolutePath
//通过 PackageParse中的parsePackage(File packageFile, int flags)用来解析apk包
val packageParseClass = Class.forName("android.content.pm.PackageParser")
//得到类的实体
val packageParseAny = packageParseClass.newInstance()
//拿到parsePackage方法
val packageParseMethod = packageParseClass.getMethod("parsePackage", File::class.java, Int::class.java)
//执行此方法拿到Package
val mPackage = packageParseMethod.invoke(packageParseAny, file, PackageManager.GET_ACTIVITIES)
//Package中的receivers为注册的广播的集合,需要拿到这个集合
val receiversField = mPackage.javaClass.getDeclaredField("receivers")
//得到他的实例
val receivers = receiversField.get(mPackage)
val arrayList = receivers as ArrayList<*>
// 此Activity 不是组件的Activity,是PackageParser里面的内部类
for (activity: Any in arrayList) { // mActivity --> <receiver android:name=".StaticReceiver"> 即在ActivityInfo中
// 获取 <intent-filter> intents== 很多 Intent-Filter
// 通过反射拿到 intents
val mComponentClass =
Class.forName("android.content.pm.PackageParser\$Component")
val intentsField: Field = mComponentClass.getDeclaredField("intents")
val intents: ArrayList<IntentFilter> = intentsField[activity] as ArrayList<IntentFilter>
//需要获取PackageUserState
val packageUserState = Class.forName("android.content.pm.PackageUserState")
//获取userId
val mUserHandle =
Class.forName("android.os.UserHandle")
val userId = mUserHandle.getMethod("getCallingUserId").invoke(null) as Int
//获取ActivityInfo拿到他的name就是.StaticReceiver具体类名
//ActivityInfo generateActivityInfo(Activity a, int flags,
// PackageUserState state, int userId)
val generateActivityInfoMethod = packageParseClass.getDeclaredMethod("generateActivityInfo",activity.javaClass, Int::class.java, packageUserState, Int::class.java)
generateActivityInfoMethod.isAccessible = true
val activityInfo = generateActivityInfoMethod.invoke(null,activity, 0, packageUserState.newInstance(), userId) as ActivityInfo
val receiverClassName = activityInfo.name
val mStaticReceiverClass = getClassLoader().loadClass(receiverClassName)
val broadcastReceiver = mStaticReceiverClass.newInstance() as BroadcastReceiver
for (intentFilter: IntentFilter in intents) {
// 注册广播
mContext.registerReceiver(broadcastReceiver, intentFilter)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}