--《摘自android插件化开发指南》
1.有些项目,整个app只有一个Activity,切换页面全靠Fragment,盛行过一时,但有点极端
2.Activity切换fragment页面
第一步:FragmentLoaderActivity作为Fragment的承载容器
<activity android:name=".FragmentLoaderActivity"> <intent-filter> <action android:name="jianqiang.com.hostapp.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
public class BaseHostActivity extends Activity { private AssetManager mAssetManager; private Resources mResources; private Theme mTheme; protected String mDexPath; protected ClassLoader dexClassLoader; protected void loadClassLoader() { File dexOutputDir = this.getDir("dex", Context.MODE_PRIVATE); final String dexOutputPath = dexOutputDir.getAbsolutePath(); dexClassLoader = new DexClassLoader(mDexPath, dexOutputPath, null, getClassLoader()); } protected void loadResources() { try { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); addAssetPath.invoke(assetManager, mDexPath); mAssetManager = assetManager; } catch (Exception e) { e.printStackTrace(); } Resources superRes = super.getResources(); mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); mTheme = mResources.newTheme(); mTheme.setTo(super.getTheme()); } @Override public AssetManager getAssets() { return mAssetManager == null ? super.getAssets() : mAssetManager; } @Override public Resources getResources() { return mResources == null ? super.getResources() : mResources; } @Override public Theme getTheme() { return mTheme == null ? super.getTheme() : mTheme; } }
public class FragmentLoaderActivity extends BaseHostActivity { private String mClass; @Override protected void onCreate(Bundle savedInstanceState) { mDexPath = getIntent().getStringExtra(AppConstants.EXTRA_DEX_PATH); mClass = getIntent().getStringExtra(AppConstants.EXTRA_CLASS); super.onCreate(savedInstanceState); setContentView(R.layout.activity_fragment_loader); loadClassLoader(); loadResources(); try { //反射出插件的Fragment对象 Class<?> localClass = dexClassLoader.loadClass(mClass); Constructor<?> localConstructor = localClass.getConstructor(new Class[] {}); Object instance = localConstructor.newInstance(new Object[] {}); Fragment f = (Fragment) instance; FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); ft.add(R.id.container, f); ft.commit(); } catch (Exception e) { Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); } } }
第二步:
MainActivity跳转到FragmentLoaderActivity,传两个参数(dexPath和fragment的名称),FragmentLoaderActivity根据参数加载对应的Fragment
Intent intent = new Intent(AppConstants.ACTION); intent.putExtra(AppConstants.EXTRA_DEX_PATH, mPluginItems.get(position).pluginPath); intent.putExtra(AppConstants.EXTRA_CLASS, mPluginItems.get(position).packageInfo.packageName + ".Fragment1"); startActivity(intent);
3.插件内部的Fragment跳转
public class BaseFragment extends Fragment { private int containerId; public int getContainerId() { return containerId; } public void setContainerId(int containerId) { this.containerId = containerId; } }
public class Fragment2 extends BaseFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment2, container, false); if (getArguments() != null) { String username = getArguments().getString("username"); TextView tv = (TextView)view.findViewById(R.id.label); tv.setText(username); } view.findViewById(R.id.btnReturn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { //从栈中将当前fragment推出 getFragmentManager().popBackStack(); } }); return view; } }
public class Fragment1 extends BaseFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment1, container, false); view.findViewById(R.id.load_fragment2_btn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { 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(); } }); return view; } }
其实就是利用FragmentManager动态切换Fragment技术来实现
4.插件Fragment跳转插件外部的Fragment(包括宿主中的,另一个插件中的)
第一步:把宿主和插件的资源都合并到一起,这样就能想用哪个资源就用哪个资源
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(); } } }
第二步:把所有的插件的ClassLoader都放进一个集合MyClassLoaders,在FragmentLoaderActivity中,使用MyClassLoaders来加载相应插件的Fragment
public class MyClassLoaders { public static final HashMap<String, DexClassLoader> classLoaders = new HashMap<String, DexClassLoader>(); }
public class FragmentLoaderActivity extends Activity { private DexClassLoader classLoader; @Override protected void onCreate(Bundle savedInstanceState) { //String pluginName = getIntent().getStringExtra(AppConstants.EXTRA_PLUGIN_NAME); 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; } }
fragment插件化的好处避开了Activity必须要面对AMS的尴尬