Android-插件化一插桩实现Activity的加载
2022-03-07 本文已影响0人
何以邀微微
1.插件化定义
插件化,就是把一些核心复杂依赖度高的业务模块封装成插件,然后根据不同的业务进行不同的组合,动态进行替换。
2.插件化跟组件化的差异
组件化:是将一个app分成多个模块,每个模块都是一个组件,开发过程中我们可以让这些组件相互依赖或者单独调试部分组件,但是最终发布的时候是将这些组件合并成一个统一的apk,这就是组件化。
插件化:跟组件化不同,插件化开发就是将整个app拆分成很多模块,每个模块都是apk(组件化每个模块是一个lib),最终发布的时候宿主和插件apk分开打包,插件apk通过动态下发的方式发送到宿主apk,这就是插件化。
3.插件化的优势
- 插件和宿主分开编译,减少宿主apk的编译时间
- 并发开发,宿主和插件apk研发过程中可以独立进行,互不影响。
- 动态更新插件,不需要安装,下载之后就可以打开
- 可以解决65535问题
4.下面介绍插桩式插件化研发的流程
- 设计一套标准,让插件app能够像安装的app一样运行起来
- 根据标准编写插件apk
- 加载插件apk
- 添加插桩点
编写插件标准,这个编写一个独立的model中,方便管理
public interface MSInterfaceActivity {
public void attach(Activity proxyActivity);
/**
* 生命周期
* @param savedInstanceState
*/
public void onCreate(Bundle savedInstanceState);
public void onStart();
public void onResume();
public void onPause();
public void onStop();
public void onDestroy();
public void onSaveInstanceState(Bundle outState);
public boolean onTouchEvent(MotionEvent event);
public void onBackPressed();
}
根据这个标准编写插件apk应用的代码,这个里边包含两个java文件一个是BaseActivity,一个是MainActivity
class BaseActivity extends AppCompatActivity implements MSInterfaceActivity {
protected Activity that;
@Override
public void attach(Activity proxyActivity) {
that =proxyActivity;
}
@Override
public void setContentView(View view) {
if (that!=null){
that.setContentView(view);
}else{
super.setContentView(view);
}
}
@Override
public void setContentView(int layoutResID) {
that.setContentView(layoutResID);
}
public View findViewById(int id){
return that.findViewById(id);
}
@Override
public Intent getIntent() {
if(that!=null){
return that.getIntent();
}
return super.getIntent();
}
@Override
public ClassLoader getClassLoader() {
return that.getClassLoader();
}
@Override
public void startActivity(Intent intent) {
Intent newIntent = new Intent();
newIntent.setClassName("className",intent.getComponent().getClassName());
that.startActivity(newIntent);
}
@Override
public void onCreate(Bundle savedInstanceState) {
}
@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) {
}
}
插件的主页面添加了一个ImageView,给控件设置了一个图片并添加了一个点击事件,点击之后响应Toast提示。
public class MainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View viewById = findViewById(R.id.image);
viewById.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(that,"风景",Toast.LENGTH_SHORT).show();
}
});
}
}
下面是宿主apk的内容:
这里将插件apk放置在assets里边,来模拟从网络上下载下来的逻辑
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
添加文件的读写权限
public class PluginLoadManager {
private static final String TAG= PluginLoadManager.class.getSimpleName();
private static final PluginLoadManager ourInstance = new PluginLoadManager();
public static PluginLoadManager getInstance() {
return ourInstance;
}
private PackageInfo packageInfo;
private Resources resources;
//dex 类加载器
private DexClassLoader dexClassLoader;
private PluginLoadManager() {
}
/**
* 加载插件
* 加载插件的目的就是要得到Resource对象以及dexClassLoader
* @param context 上下文
* @param pluginPath plugin文件的绝对路径
*/
public void loadPath(Context context,String pluginPath) {
// File filesDir = context.getDir("plugin", Context.MODE_PRIVATE);
Log.d(TAG,"pluginPath="+pluginPath);
PackageManager packageManager = context.getPackageManager();
//得到安装包的信息
packageInfo = packageManager.getPackageArchiveInfo(pluginPath, PackageManager.GET_ACTIVITIES);
// activity 名字
File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
//第一个参数是dex的路径,第二个是优化路径 最后一个参数是父类加载器
dexClassLoader = new DexClassLoader(pluginPath,
dexOutFile.getAbsolutePath(), null, context.getClassLoader());
try {
//反射得到AssetManager
AssetManager assetManager = AssetManager.class.newInstance();
//方法名和参数类型 addAssetPath官方已经不建议使用这个方法了,推荐使用setApkAssets
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager,pluginPath); //方法在assetManager上面 需要传递一个路径当做参数
//创建一个Resource 第一个参数就是AssetManager 但是AssetManager的实例化被隐藏了,所以只能通过反射的方式得到
resources = new Resources(assetManager,
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;
}
}
增加插件加载管理类,这个里边的代码逻辑我做了注释,加载的过程中插件apk
- 通过getPackageArchiveInfo这个方法得到了安装包的信息
- 通过DexClassLoader加载了插件apk的源码,并对实例化DexClassLoader的参数做了介绍
- 通过反射得到了AssetManager,反射这个的目的是为了实例化Resources
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestPermission();
}
//申请文件读写权限
private void requestPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Log.i(TAG,"用户申请过权限,但是被拒绝了(不是彻底决绝)");
// 申请权限
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE},1);
} else {
Log.i(TAG,"申请过权限,但是被用户彻底决绝了或是手机不允许有此权限(依然可以在此再申请权限)");
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE},1);
}
}
}
// 拷贝到
public void load(View view){
loadPlugin();
}
private void loadPlugin() {
File fileDir=this.getDir("plugin", Context.MODE_PRIVATE);
String name="plugindemo.apk";
String filePath=new File(fileDir, name).getAbsolutePath();
Log.d(TAG,filePath);
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
InputStream is = null;
FileOutputStream os = null;
try {
//从assets里边加载插件apk,模拟网络下载
is = getAssets().open("plugindemo-debug.apk");
os = new FileOutputStream(filePath);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
File f = new File(filePath);
if (f.exists()) {
Toast.makeText(this, "插件下载完成", Toast.LENGTH_SHORT).show();
}
PluginLoadManager.getInstance().loadPath(this,filePath);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//页面跳转
public void click(View view) {
Intent intent=new Intent(this,ProxyActivity.class);
intent.putExtra("className", PluginLoadManager.getInstance().getPackageInfo().activities[0].name);
startActivity(intent);
}
}
页面里边两个按钮,一个是加载插件一个是跳转按钮,页面跳转的部分通过className字段将真正需要加载的插件页面全名通过参数进行传递到插件页面。
/**
* 代理Activity,这个Activity只是一个壳,插装式插件化实现
*/
public class ProxyActivity extends AppCompatActivity {
//需要加载的类名
private String className;
private MSInterfaceActivity msInterfaceActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
className = getIntent().getStringExtra("className");
try {
//得到Activity的Class
Class<?> activityClass = getClassLoader().loadClass(className);
//得到构造方法
Constructor<?> constructor = activityClass.getConstructor(new Class[]{});
//得到这个类的对象
Object object = constructor.newInstance();
if (object instanceof MSInterfaceActivity){
msInterfaceActivity= (MSInterfaceActivity) object;
}
if (msInterfaceActivity!=null){
msInterfaceActivity.attach(this);
Bundle bundle=new Bundle();
msInterfaceActivity.onCreate(bundle);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent newIntent=new Intent(this,ProxyActivity.class);
newIntent.setClassName("className",className);
super.startActivity(newIntent);
}
@Override
public ClassLoader getClassLoader() {
return PluginLoadManager.getInstance().getDexClassLoader();
}
@Override
public Resources getResources() {
return PluginLoadManager.getInstance().getResources();
}
@Override
protected void onStart() {
super.onStart();
msInterfaceActivity.onStart();
}
@Override
protected void onResume() {
super.onResume();
msInterfaceActivity.onResume();
}
@Override
protected void onPause() {
super.onPause();
msInterfaceActivity.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
msInterfaceActivity.onDestroy();
}
}
这个是专门用来加载插件页面的,通过解析得到的需要跳转的className通过反射得到这个页面的对象,并注入context以及调用onCreate方法。
运行效果完美实现,需要源码学习留下你的邮箱。