Android插件化1-插桩式-Activity插件化
Android插件化2-插桩式-Service插件化
Android插件化3-插桩式-动态广播插件化
GitHub:https://github.com/345166018/AndroidPlugin
动态加载技术分为:
动态加载技术:在应用程序运行时,动态加载一些程序中原本不存在的可执行文件并运行这些文件里的代码逻辑,可执行文件总的来说分为两个,一个是动态链接库so,另一种是dex相关文件(dex文件包含jar/apk文件)。
插件化的作用:主要用于解决应用越来越庞大以及功能模块的解耦,所以小项目中一般用的不多。业务复杂,模块的解耦。应用的接入。65536限制,内存占用大。
热修复的作用:主要用于修复bug。
在宿主 App 中使用反射加载插件中的类 A, A 是没有生命周期的,就是一个普普通通的类而已 。比如插件里的 Activity , 就算我们“欺骗了” AMS 检查 AndroidManifest 的过程,这个Activity 也启动不了,类似 onResume 、 onPaused 这些生命周期函数都不能被正常调用,因为宿主 App 根本就不把它当作 Activity 来对待 。为此,我们在宿主 App 中设计一个代理类 ProxyActivity ,这是一个 Activity ,是宿主 App所认识的 。 让 ProxyActivity 内部有一个对插件 ActivityA 的引用,让ProxyActivity 的任何生命周期函数都调用 ActivityA 中同名函数。
Activity 是 App 中使用频率最高的组件,各种插件化框架的主要精力都放在 Activity 上。
Activity 的插件化需要解决 3 方面的技术问题:
新建Android项目,并创建两个Module:
Android Library
;Phone Module
。将pluginstand作为依赖库分别添加到app Module和taopiaopiao Module中。
因为插件(tiaopiaopiao)只是一个apk,并没有安装到手机上,不具有生命周期,这就需要通过获取宿主(app)的生命周期。
在pluginstand中创建一个接口PayInterfaceActivity,包含了各个生命周期的方法,如下:
public interface PayInterfaceActivity {
void attach(Activity proxyActivity);
void onCreate(Bundle saveInstanceState);
void onStart();
void onResume();
void onPause();
void onStop();
void onDestroy();
void onSaveInstanceState(Bundle outState);
boolean onTouchEvent(MotionEvent event);
void onBackPressed();
}
在taopiaopiao中创建一个基类BaseActivity实现PayInterfaceActivity接口,如下:
public class BaseActivity extends Activity implements PayInterfaceActivity {
protected Activity that;
@Override
public void attach(Activity proxyActivity) {
//1
this.that = proxyActivity;
}
@Override
public void setContentView(View view) {
if(that != null){
that.setContentView(view);
} else {
super.setContentView(view);
}
}
@Override
public void setContentView(int layoutResID) {
if(that != null){
that.setContentView(layoutResID);
} else {
super.setContentView(layoutResID);
}
}
@Override
public void startActivity(Intent intent) {
Intent m = new Intent();
m.putExtra("className",intent.getComponent().getClassName());
that.startActivity(m);
}
@Override
public View findViewById(int id) {
return that.findViewById(id);
}
@Override
public void onCreate(Bundle saveInstanceState) {
}
@Override
public void onStart() {
}
@Override
public void onResume() {
}
@Override
public void onPause() {
}
@Override
public void onStop() {
}
@Override
public void onDestroy() {
}
@Override
public void onSaveInstanceState(Bundle outState) {
}
}
注释1:通过attach方法,将宿主(app)的ProxyActivity传入
插件taopiaopiao中的主页面TaoMainActivity,如下:
public class TaoMainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
//1
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//2
findViewById(R.id.img).setOnClickListener(new View.OnClickListener() {
//3
@Override
public void onClick(View v) {
Toast.makeText(that,"插件",Toast.LENGTH_SHORT).show();
startActivity(new Intent(that, SceondActivity.class));//4
}
});
}
}
注释1:覆写了BaseActivity中的onCreate方法。
注释2:调用BaseActivity的setContentView方法。
注释3:调用BaseActivity的findViewById方法。
注释4:调用BaseActivity的startActivity方法实现在插件中跳转到另一个页面。
在MainActivity有两个按钮,一个用于加载插件,一个用于跳转到插件的页面
public void load(View view) {
PluginManager.getInstance().loadPath(this);
}
public void click(View view) {
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
startActivity(intent);
}
下面是具体实现。
将taopiaopiao中生成的apk拷贝到data/data/com.hongx.plugin/app_plugin
创建PluginManager用于加载apk,并获取Resources、DexClassLoader和PackageInfo,如下:
public class PluginManager {
private static final PluginManager ourInstance = new PluginManager();
private DexClassLoader dexClassLoader;
private Resources resources;
private PackageInfo packageInfo;
public static PluginManager getInstance(){
return ourInstance;
}
public PluginManager() {
}
public void loadPath(Context context){
File filesDir = context.getDir("plugin", Context.MODE_PRIVATE);
String name = "plugin.apk";
String path = new File(filesDir, name).getAbsolutePath();
PackageManager packageManager = context.getPackageManager();
packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
//activity
///data/data/com.hongx.plugin/app_dex/plugin.dex
File dex = context.getDir("dex", Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(path, dex.getAbsolutePath(), null, context.getClassLoader());
//resource
try {
AssetManager manager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
//path = /data/data/com.hongx.plugin/app_plugin/plugin.apk
addAssetPath.invoke(manager, path);
resources = new Resources(manager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
public Resources getResources(){
return resources;
}
public DexClassLoader getDexClassLoader(){
return dexClassLoader;
}
public PackageInfo getPackageInfo(){
return packageInfo;
}
}
public class ProxyActivity extends Activity {
//需要加载插件的全类名
private String className;
PayInterfaceActivity payInterfaceActivity;
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
className = getIntent().getStringExtra("className");//1
try {
//TaoMainActivity
Class<?> aClass = getClassLoader().loadClass(className);
Constructor constructor = aClass.getConstructor(new Class[]{
});
Object in = constructor.newInstance(new Object[]{
});//2
payInterfaceActivity = (PayInterfaceActivity) in;
payInterfaceActivity.attach(this);//3
//如果需要参数,可以使用Bundle
Bundle bundle = new Bundle();
payInterfaceActivity.onCreate(bundle);//4
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent intent1 = new Intent(this, ProxyActivity.class);
intent1.putExtra("className", className);
super.startActivity(intent1);
}
//重写加载类
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance().getDexClassLoader();
}
//重写加载资源
@Override
public Resources getResources() {
return PluginManager.getInstance().getResources();
}
@Override
protected void onStart() {
super.onStart();
payInterfaceActivity.onStart();
}
@Override
protected void onResume() {
super.onResume();
payInterfaceActivity.onResume();
}
@Override
protected void onStop() {
super.onStop();
payInterfaceActivity.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
payInterfaceActivity.onDestroy();
}
}
注释1:在上面已经提到点击页面跳转按钮跳转至插件的TaoMainActivity。PluginManager.getInstance().getPackageInfo()得到的是插件的包信息,activities[0]对应的就是ToMainActivity,也就是获取到ToMainActivity的类名并将类名传递到ProxyActivity中以供使用。
注释2:通过类加载器和反射机制获取到了TaoMainActivity对象。
注释3:将ProxyActivity传递到TaoMainActivity中。
注释4:调用了TaoMainActivity的onCreate方法。
再来分析下TaoMainActivity:
public class TaoMainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//1
findViewById(R.id.img).setOnClickListener(new View.OnClickListener() {
//2
@Override
public void onClick(View v) {
Toast.makeText(that,"插件",Toast.LENGTH_SHORT).show();
startActivity(new Intent(that, SceondActivity.class));
}
});
}
}
@Override
public void setContentView(int layoutResID) {
if(that != null){
that.setContentView(layoutResID);
} else {
super.setContentView(layoutResID);
}
}
BaseActivity的startActivity方法如下:
@Override
public void startActivity(Intent intent) {
Intent m = new Intent();
m.putExtra("className",intent.getComponent().getClassName());
that.startActivity(m);
}
最后还是使用了ProxyActivity的startActivity方法,如下:
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent intent1 = new Intent(this, ProxyActivity.class);
intent1.putExtra("className", className);
super.startActivity(intent1);
}
className就是SecondActivity的类名,流程和加载TaoMainActivity一样,也是将类名传递到ProxyActivity中。
GitHub