宿主App的Activity想要加载插件中的Fragment,一般需要在进入插件的Fragment时要使用插件的ClassLoader和Resource对象。这就要求我们替换ClassLoader和Resource。我们首先在宿主app中使用一个FragmentLoaderActivity类来存放要加载的Fragment,然后按照以下步骤进行实现:
- 把所有插件的ClassLoader都放进一个集合MyClassLoaders,在FragmentLoaderActivity中使用MyClassLoaders来加载相应插件的Fragment。
public class MyClassLoaders {
// 存放插件的ClassLoader,key为插件apk在sd中的路径;value为插件apk对应的classLoader
public static final HashMap<String, DexClassLoader> classLoaders = new HashMap<String, DexClassLoader>();
}
在MainActivity的onCreate()里进行存放
private void initData() {
File file1 = getFileStreamPath("plugin1.apk");
File file2 = getFileStreamPath("plugin2.apk");
File[] plugins = {file1, file2};
for (File plugin : plugins) {
PluginItem item = new PluginItem();
item.pluginPath = plugin.getAbsolutePath();
item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
mPluginItems.add(item);
String mDexPath = item.pluginPath;
File dexOutputDir = this.getDir("dex", Context.MODE_PRIVATE);
final String dexOutputPath = dexOutputDir.getAbsolutePath();
DexClassLoader dexClassLoader = new DexClassLoader(mDexPath,
dexOutputPath, null, getClassLoader());
MyClassLoaders.classLoaders.put(plugin.getPath(), dexClassLoader);
}
...
}
- 把宿主和插件的资源都合并在一起,这样就能想用哪个资源就用哪个资源。
public class PluginManager {
public final static List<PluginItem> plugins = new ArrayList<PluginItem>();
// 正在使用的Resources
public static volatile Resources mNowResources;
//原始的application中的BaseContext,不能是其他的,否则会内存泄漏
public static volatile Context mBaseContext;
//ContextImpl中的LoadedAPK对象mPackageInfo
private static Object mPackageInfo = null;
public static void init(Application application) {
//初始化一些成员变量和加载已安装的插件
mPackageInfo = RefInvoke.getFieldObject(application.getBaseContext(), "mPackageInfo");
mBaseContext = application.getBaseContext();
mNowResources = mBaseContext.getResources();
try {
AssetManager assetManager = application.getAssets();
String[] paths = assetManager.list("");
ArrayList<String> pluginPaths = new ArrayList<String>();
for(String path : paths) {
if(path.endsWith(".apk")) {
String apkName = path;
PluginItem item = generatePluginItem(apkName);
plugins.add(item);
Utils.extractAssets(mBaseContext, apkName);
pluginPaths.add(item.pluginPath);
}
}
reloadInstalledPluginResources(pluginPaths);
} catch (Exception e) {
e.printStackTrace();
}
}
private static PluginItem generatePluginItem(String apkName) {
File file = mBaseContext.getFileStreamPath(apkName);
PluginItem item = new PluginItem();
item.pluginPath = file.getAbsolutePath();
item.packageInfo = DLUtils.getPackageInfo(mBaseContext, item.pluginPath);
return item;
}
private static void reloadInstalledPluginResources(ArrayList<String> pluginPaths) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, mBaseContext.getPackageResourcePath());
for(String pluginPath: pluginPaths) {
addAssetPath.invoke(assetManager, pluginPath);
}
Resources newResources = new Resources(assetManager,
mBaseContext.getResources().getDisplayMetrics(),
mBaseContext.getResources().getConfiguration());
RefInvoke.setFieldObject(mBaseContext, "mResources", newResources);
//这是最主要的需要替换的,如果不支持插件运行时更新,只留这一个就可以了
RefInvoke.setFieldObject(mPackageInfo, "mResources", newResources);
//清除一下之前的resource的数据,释放一些内存
//因为这个resource有可能还被系统持有着,内存都没被释放
//clearResoucesDrawableCache(mNowResources);
mNowResources = newResources;
//需要清理mtheme对象,否则通过inflate方式加载资源会报错
//如果是activity动态加载插件,则需要把activity的mTheme对象也设置为null
RefInvoke.setFieldObject(mBaseContext, "mTheme", null);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
在Application的attachBaseContext进行初始化
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
PluginManager.init(this);
}
}
- FragmentLoaderActivity用来加载Fragment页面
public class FragmentLoaderActivity extends Activity {
private DexClassLoader classLoader;
/**
* 根据插件Apk的路径获取插件Apk的ClassLoader,通过插件Apk的ClassLoader创建出插件Fragment的实例对象进行加载
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
String mClass = getIntent().getStringExtra(AppConstants.EXTRA_CLASS);
String mDexPath = getIntent().getStringExtra(AppConstants.EXTRA_DEX_PATH);
classLoader = MyClassLoaders.classLoaders.get(mDexPath);
super.onCreate(savedInstanceState);
FrameLayout rootView = new FrameLayout(this);
rootView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
rootView.setId(android.R.id.primary);
setContentView(rootView);
BaseFragment fragment = null;
try {
if(classLoader == null) {
fragment = (BaseFragment) getClassLoader().loadClass(mClass).newInstance();
} else {
fragment = (BaseFragment) classLoader.loadClass(mClass).newInstance();
}
fragment.setContainerId(android.R.id.primary);
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(android.R.id.primary, fragment);
ft.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Resources getResources() {
return PluginManager.mNowResources;
}
}
<activity android:name=".FragmentLoaderActivity"
android:exported="true">
<intent-filter>
<action android:name="jianqiang.com.hostapp.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
宿主Activity加载插件Plugin1中的Fragment1
跳转FragmentLoaderActivity,并传递参数插件Apk的路径、插件Apk的Fragment1的完整路径
Intent intent = new Intent(AppConstants.ACTION);
intent.putExtra(AppConstants.EXTRA_DEX_PATH, mPluginItems.get(position).pluginPath);
intent.putExtra(AppConstants.EXTRA_PACKANE_NAME, mPluginItems.get(position).packageInfo.packageName);
intent.putExtra(AppConstants.EXTRA_CLASS, mPluginItems.get(position).packageInfo.packageName + ".Fragment1" );
startActivity(intent);
插件内部的Fragment跳转
利用FragmentManager动态切换Fragment技术来实现
Fragment2 fragment2 = new Fragment2();
Bundle args = new Bundle();
args.putString("username", "baobao");
fragment2.setArguments(args);
getFragmentManager()
.beginTransaction()
.addToBackStack(null) //将当前fragment加入到返回栈中
.replace(Fragment1.this.getContainerId(), fragment2).commit();
插件Fragment跳转宿主的Fragment3
Fragment fragment3 = (Fragment) RefInvoke.createObject("jianqiang.com.hostapp.Fragment3");
Bundle args = new Bundle();
args.putString("username", "baobao");
fragment3.setArguments(args);
getFragmentManager()
.beginTransaction()
.addToBackStack(null) //将当前fragment加入到返回栈中
.replace(Fragment1.this.getContainerId(), fragment3).commit();
插件1的Fragment跳转插件2的Fragment
String dexPath = null;
for(PluginItem item : PluginManager.plugins) {
if(item.pluginPath.contains("plugin2")) {
dexPath = item.pluginPath;
break;
}
}
Intent intent = new Intent(AppConstants.ACTION);
intent.putExtra(AppConstants.EXTRA_DEX_PATH, dexPath);
intent.putExtra(AppConstants.EXTRA_PACKANE_NAME, "com.chinatsp.plugin2");
intent.putExtra(AppConstants.EXTRA_CLASS, "com.chinatsp.plugin2.Fragment4");
startActivity(intent);