1.全部代码
2.步骤理解
1.占位式插件化理解
1.感觉上就是有运行的apk(平时运行到手机上的apk)跳转到无运行的apk(只有打包出来,都是没有运行到手机上)。这个无运行的apk叫做插件(就是无法执行startActivity,这样就无法实现跳转了,直接跳转就报错了,怎么办呢?)。在实际开发中这个插件(无运行apk)由服务器下载到指定位置,然后app呢就去拿apk,进行更新(判断有没有,这里可能是个数字,我们根据数字与之前拿到的数字进行对比,如果大就更新,既删除然后下载),然后加载里面的类,资源(layout),实现跳转。
2.无宿主环境的插件怎么实现跳转?其实就是通过代理的形式来让它有运行环境。这里再创建一个代理的activity,让它去加载插件的activity,并且给它环境,既就是MainActivity ---->ProxyActivity-------->ProxyActivity,ProxyActivity-------->ProxyActivity表示内部跳转。
2.宿主App MainActivity代码
1.加载插件
其实加载插件就是根据插件apk来加载class和资源(layout等)
创建PluginManager类,是个单例(快捷方式创建),传入参数Activity
加载之前肯定要找到apk的路径,没apk,加载个锤子class,和资源。apk路经如下位置(这个要自己build然后获取插件的apk,别搞到主apk去了)
PluginManager是个单例,构造方法传入一个参数Context
//加载插件
PluginManager.getInstance(this).loadPlugin();
public void loadPlugin(){
//获取apk的路径 /storage/emulated/0/p.apk
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
//判断文件是否为空
if (!file.exists()) {
Log.d(TAG, "loadPlugin: 插件不存在");
return;
}
String pluginPaht = file.getAbsolutePath();// /storage/emulated/0/p.apk
//加载类
loadClass(pluginPaht);
//加载布局
loadLayout(pluginPaht);
}
- 加载class
//加载类
loadClass(pluginPaht);
方法
DexClassLoader类来加载类:
第一个参数是apk路径
第二个参数是:缓存目录(应该是用来保存解压的dex文件的目录吧)
第三个参数是:不知道干嘛的,为空就可以了,好像跟C有关
第四个参数是:一般为context.getClassLoader(),父亲加载器
/**
* 根据apk的路径来进行加载类
* @param pluginPaht apk的路径 /storage/emulated/0/p.apk
*/
private void loadClass(String pluginPaht) {
/**
* dexClassLoader需要一个缓存目录,最好用应用私有的,不然会被攻击
* /data/data/当前应用的包名/pDir
*/
File fileDir = context.getDir("pDir", Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(pluginPaht, fileDir.getAbsolutePath(), null, context.getClassLoader());
}
- 加载layout
//加载布局
loadLayout(pluginPaht)
/**
* 根据apk的路径来进行加载布局资源
* @param pluginPaht apk的路径 /storage/emulated/0/p.apk
*/
private void loadLayout(String pluginPaht) {
try {
//加载资源
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);//他是类类型Class
addAssetPath.invoke(assetManager,pluginPaht);//插件包路径
//宿主的资源配置
Resources r = context.getResources();
//特殊的resources 加载插件里面的资源 Resources
//参数2,3 资源配置信息
resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
2.启动插件里面的activity(实际是跳转到运行activity的代理activity,然后让他来加载代码),这里要传入值,这值跟跳转到哪个插件有直接关系
startPluginActivity是一个按钮的监听
//启动插件里面的Activity
public void startPluginActivity(View view) {
//占位 代理Activity
//获取apk的绝对路径 // sdcard/p.apk
File file = new File(Environment.getExternalStorageDirectory(), "p.apk");
String path = file.getAbsolutePath();
//获取插件包 里面的Activity
PackageManager packageManager = getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
ActivityInfo activityInfo = packageInfo.activities[0];//插件里的第一个启动的Activity
Log.d(TAG, "startPluginActivity: "+activityInfo.name);
//跳转到宿主的代理activity,并且传入要跳转到的插件名
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className",activityInfo.name);
startActivity(intent);
}
3.宿主的Proxyactivity,这个被作为插件的activity,然后他去加载插件的代码,这样就是传说中的插件跳转(其实它还是在运行apk里面进行跳转,不同的是内容由插件apk决定)
1.首先肯定得有插件的类和资源吧
这里PluginManager类有对外提供获取方法,而这两个就是apk获取的类与资源
/**
* 获取资源方法
* @return 插件apk的布局资源
*/
@Override
public Resources getResources() {
return PluginManager.getInstance(this).getResources();
}
/**
* 获取类的方法
* @return 插件apk的类
*/
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance(this).getDexClassLoader();
}
2.有类与资源了,然后再加载。根据包名来加载类。思路:根据包名—>加载大概类—>获取构造方法—>获取详细类—>强转为父接口,传this,传bundle
//真正加载插件里面的activity com.netease.plugin_package.PluginActivity
String className = getIntent().getStringExtra("className");
try{
//根据包名加载大概的类
Class mPluginActivityClass = getClassLoader().loadClass(className);
//根据大概类获取构造方法
Constructor constructor = mPluginActivityClass.getConstructor(new Class[]{});
//根据构造方法来获取具体的类
Object mPluginActivity = constructor.newInstance(new Object[]{});
//强制转换为父类的实现接口
ActivityInterface activityInterface= (ActivityInterface) mPluginActivity;
//向插件注入宿主环境
activityInterface.insertAppContext(this);
Bundle bundle = new Bundle();
bundle.putString("appName", "我是宿主传递过来的信息");
// 执行插件里面的onCreate方法
activityInterface.onCreate(bundle);
}catch (Exception e){
e.printStackTrace();
}
3.ProxyActivity重写startActivity方法
- 为什么要重写呢?因为要实现插件里,activity之间的跳转,这个可不是普通的跳转
主要是自己跳转到自己,既ProxyActivity跳转到ProxyActivity。然后传入一个包名,用于跳转到哪个插件的Activity。
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent proxyIntent = new Intent(this, ProxyActivity.class);
proxyIntent.putExtra("className",className);//包名+TestActivity
super.startActivity(proxyIntent);
}
4.ActivityInterface类,其实就是一个公共的Activity接口,他在创建的stander包里,用于传Activity(Context)。插件包和主包都要添加这个依赖module
public interface ActivityInterface {
void onCreate(Bundle savedInstanceState);
void onStart();
void onResume();
void onDestroy();
/**
* 作用:把宿主(app)的环境 给插件
* @param appActivity
*/
void insertAppContext(Activity appActivity);
}
5.插件里的baseActivity类,都是调用传过来的activity里面的方法,这个activity就是代理ProxyActivity。
/**
* 创建日期:2021/9/28 9:18
*
* @author tony.sun
* 类说明:
*/
public class BaseActivity extends Activity implements ActivityInterface {
public Activity 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() {
}
/**
* 获取对应的上下文,这里是proxyActivity
* @param appActivity
*/
@Override
public void insertAppContext(Activity appActivity) {
this.appActivity=appActivity;
}
/**
* 找id
* @param id
* @return
*/
public View findViewById(int id){
return appActivity.findViewById(id);
}
/**
* 跳转
* @param intent
*/
public void startActivity(Intent intent){
Intent intentNew = new Intent();
intentNew.putExtra("className",intent.getComponent().getClassName());//根据包名跳转
appActivity.startActivity(intentNew);//这里是调用ProxyActivity里的startActivity
}
/**
* 设置布局resId
* @param resId
*/
public void setContentView(int resId) {
appActivity.setContentView(resId);//这里是调用ProxyActivity里的setContentView
}
}
6.PluginActivity
插件里的类
onCreate,setContentView,findViewById,startActivity这调用的是自定义的onCreate类的方法,不是activity里面的。
public class PluginActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//this会报错,因为它没有宿主条件
Toast.makeText(appActivity, "我是插件", Toast.LENGTH_SHORT).show();
findViewById(R.id.bt_start_activity).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(appActivity,TestActivity.class));
}
});
}
}