Android 插件化其实可以很简单,无外乎就是加载外部资源(可能是apk或jar或资源包、so 等等),再将加载的外部资源运行起来。
首先要明白的是,如果我们要打开插件中的某一个Activity页面,要么你这个页面要提前放在 manifest
、要么Hook(这里不讲Hook,系统限制多,兼容差);提前放在 manifest
的话,你也不能知道到底插件中会有多少个Activity。所以这里用的毕竟简单的方法,代理模式(用宿主中的一个PluginActivity
,里面不做任何操作,只需要继承Activity就好了,并提前放到 manifest
)。后续,我们所有的插件,都可以通过这个PluginActivity
代理来完成。接下来,我们来实践一下:
一、在宿主中加载外部apk
-
自定义
DexClassLoader
和Resources
目的是为了获取外部apk中的class 和资源信息 -
自定义
PluginMannerImpl
,统一管理加载工具
当我们的外部apk被下载,并存放在某一路径下时,可以通过该路径去加载:
public void loadPath(Context context, String path) {
File dexDir = context.getDir("dex", Context.MODE_PRIVATE);
mDexClassLoader = new DexClassLoader(path, dexDir.getAbsolutePath(), null, context.getClassLoader());
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method method = AssetManager.class.getMethod("addAssetPath", String.class);
method.invoke(assetManager, path);
mResources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
Log.e("loadPath", "path=" + path);
Log.e("loadPath", "mResources=" + mResources);
}
注意:这里的上下文 context
是由宿主app中上下文,插件中的上下文都是由宿主来提供的。
加载外部apk 成功后,现在我们要打开一个页面,并用插件中的资源(activity_main.xml)来展示在当前页面中。
二、创建代理PluginActivity,并替换 getClassLoader 和 getResources
class PluginActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//假设 插件apk 中也有一个 activity_main.xml,由于getResources()是从插件中获取的,
//所以这里的activity_main.xml 是插件中的
setContentView(R.layout.activity_main)
}
override fun getClassLoader(): ClassLoader {
return PluginMannerImpl.getPluginMannerImpl().dexClassLoader
}
override fun getResources(): Resources {
return PluginMannerImpl.getPluginMannerImpl().resources
}
}
到这里,PluginActivity 是在app 宿主中的一个Activity,并在 manifest 已经提前注册。 假设 插件apk和宿主app 中都有一个 activity_main.xml,但是由于getResources()是从插件中获取的,所以这里的activity_main.xml 是插件中的, 我们可以看看两个的区别:
两个页面是完全不一样的。但运行起来后,打开的PluginActivity 是 第二种样式(插件apk已经提前打包,并放在宿主app指定目录下)
(资源冲突的问题这里不讲,可以参考 插件资源id冲突处理 )
好了,资源已经能够加载并使用了,下一步,使用插件中的类。
三、插件的生命周期管理
我们插件中的 Activity
不是一个Android中的四大组件,只是一个普通类,我们为了达到Android中的四大组件的同等效果,要自己实现一套生命周期的方法,并且,所有有关上下文的地方都要替换成宿主的。
public interface LifeInterface {
void attach(Activity activity);
void onCreate(Bundle savedInstanceState);
void onResume();
void onStart();
void onPause();
void onStop();
void onDestroy();
}
单独创建这样的一个简单的生命周期接口,在插件中让LifeActivity
实现它。
/**
* 用到上下文的地方全部重写
*/
public class LifeActivity implements com.example.server.LifeInterface {
protected Activity context;
public void setContentView(int layout) {
if (context != null) {
context.setContentView(layout);
}
}
public void startActivity(Intent intent) {
if (context != null) {
if (intent==null||intent.getComponent()==null)return;
Intent newIntent=new Intent();
String className=intent.getComponent().getClassName();
if (TextUtils.isEmpty(className))return;
Log.e("startActivity","className="+className);
newIntent.putExtra("className",intent.getComponent().getClassName());
context.startActivity(newIntent);
}
}
public <T extends View> T findViewById(int id) {
if (context != null) {
return context.findViewById(id);
}
return null;
}
public Window getWindow(){
if (context != null) {
return context.getWindow();
}
return null;
}
public ClassLoader getClassLoader(){
if (context != null) {
return context.getClassLoader();
}
return null;
}
public LayoutInflater getLayoutInflater(){
if (context != null) {
return context.getLayoutInflater();
}
return null;
}
public WindowManager getWindowManager(){
if (context != null) {
return context.getWindowManager();
}
return null;
}
//attach上下文
@Override
public void attach(Activity activity) {
context = activity;
}
@Override
public void onCreate(Bundle savedInstanceState) {
}
@Override
public void onResume() {
}
@Override
public void onStart() {
}
@Override
public void onPause() {
}
@Override
public void onStop() {
}
@Override
public void onDestroy() {
}
}
这样,我们插件中的第一个页面也就很快好了
可以看到,TestActivity
的实现方法,调用,跟我们原来Android中的一样。但实际上他确不是一个真正的Activity,只是个普通类,当我们在宿主中要展示他的时候,是这样的方式:
private fun loadClassActivity() {
try {
val lass = classLoader.loadClass("com.example.server.TestActivity")//1、加载类并实例化
val cs = lass.getConstructor(*arrayOf())
val obj = cs.newInstance(*arrayOf())
Log.e("LifeInterface", " obj is LifeInterface=" + (obj is LifeInterface))
if (obj is LifeInterface) {//公共的组件
obj.attach(this)//2、传入上下文
obj.onCreate(Bundle().apply {//走生命周期
this.putString("sdk", "11111")
})
Log.e("LifeInterface", "obj=$obj")
}
} catch (e: Exception) {
e.printStackTrace()
}
}