--《摘自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的尷尬