基于Fragment的插件化

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

原文地址:https://www.cnblogs.com/anni-qianqian/p/10118527.html