1.全部代码
git源码
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(){
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
if (!file.exists()) {
Log.d(TAG, "loadPlugin: 插件不存在");
return;
}
String pluginPaht = file.getAbsolutePath();
loadClass(pluginPaht);
loadLayout(pluginPaht);
}
- 加载class
loadClass(pluginPaht);
方法 DexClassLoader类来加载类: 第一个参数是apk路径 第二个参数是:缓存目录(应该是用来保存解压的dex文件的目录吧) 第三个参数是:不知道干嘛的,为空就可以了,好像跟C有关 第四个参数是:一般为context.getClassLoader(),父亲加载器
private void loadClass(String pluginPaht) {
File fileDir = context.getDir("pDir", Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(pluginPaht, fileDir.getAbsolutePath(), null, context.getClassLoader());
}
- 加载layout
loadLayout(pluginPaht)
private void loadLayout(String pluginPaht) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager,pluginPaht);
Resources r = context.getResources();
resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
2.启动插件里面的activity(实际是跳转到运行activity的代理activity,然后让他来加载代码),这里要传入值,这值跟跳转到哪个插件有直接关系
startPluginActivity是一个按钮的监听
public void startPluginActivity(View view) {
File file = new File(Environment.getExternalStorageDirectory(), "p.apk");
String path = file.getAbsolutePath();
PackageManager packageManager = getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
ActivityInfo activityInfo = packageInfo.activities[0];
Log.d(TAG, "startPluginActivity: "+activityInfo.name);
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className",activityInfo.name);
startActivity(intent);
}
3.宿主的Proxyactivity,这个被作为插件的activity,然后他去加载插件的代码,这样就是传说中的插件跳转(其实它还是在运行apk里面进行跳转,不同的是内容由插件apk决定)
1.首先肯定得有插件的类和资源吧
这里PluginManager类有对外提供获取方法,而这两个就是apk获取的类与资源
@Override
public Resources getResources() {
return PluginManager.getInstance(this).getResources();
}
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance(this).getDexClassLoader();
}
2.有类与资源了,然后再加载。根据包名来加载类。思路:根据包名—>加载大概类—>获取构造方法—>获取详细类—>强转为父接口,传this,传bundle
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", "我是宿主传递过来的信息");
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);
super.startActivity(proxyIntent);
}
4.ActivityInterface类,其实就是一个公共的Activity接口,他在创建的stander包里,用于传Activity(Context)。插件包和主包都要添加这个依赖module
public interface ActivityInterface {
void onCreate(Bundle savedInstanceState);
void onStart();
void onResume();
void onDestroy();
void insertAppContext(Activity appActivity);
}
5.插件里的baseActivity类,都是调用传过来的activity里面的方法,这个activity就是代理ProxyActivity。
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() {
}
@Override
public void insertAppContext(Activity appActivity) {
this.appActivity=appActivity;
}
public View findViewById(int id){
return appActivity.findViewById(id);
}
public void startActivity(Intent intent){
Intent intentNew = new Intent();
intentNew.putExtra("className",intent.getComponent().getClassName());
appActivity.startActivity(intentNew);
}
public void setContentView(int resId) {
appActivity.setContentView(resId);
}
}
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);
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));
}
});
}
}
3.总结反思
1.申请存取权限
2.插件apk的位置要存放对且要build好
3.
4.
5.
6.
|