基于Fragment的插件化

宿主App的Activity想要加载插件中的Fragment,一般需要在进入插件的Fragment时要使用插件的ClassLoader和Resource对象。这就要求我们替换ClassLoader和Resource。我们首先在宿主app中使用一个FragmentLoaderActivity类来存放要加载的Fragment,然后按照以下步骤进行实现:

  1. 把所有插件的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);
        }

        ...
    }
  1. 把宿主和插件的资源都合并在一起,这样就能想用哪个资源就用哪个资源。
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);
    }
}
  1. 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);

源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Cesium 是一个基于 WebGL 实现的三维地球可视框架,它支持自定义 shader。如果你想要在 Vue 中使用 Cesium,并自定义 shader,可以按照以下步骤进行: 1. 安装 cesium 库 在 Vue 项目中,你可以使用 npm 或 yarn 命令安装 cesium 库。 ```bash npm install cesium --save # 或者 yarn add cesium ``` 2. 在 Vue 中引入 cesium 库 在组件中引入 cesium 库。 ```javascript import Cesium from "cesium/Cesium"; import "cesium/Widgets/widgets.css"; ``` 3. 创建 Cesium Viewer 在组件中创建 Cesium Viewer,以渲染三维场景。 ```javascript mounted() { this.viewer = new Cesium.Viewer(this.$refs.cesiumContainer); } ``` 4. 创建自定义 shader 使用 Cesium 提供的 ShaderProgram 类,可以创建自己的 shader。 ```javascript const vertexShaderSource = ` attribute vec3 position; attribute vec4 color; varying vec4 v_color; void main() { gl_Position = czm_modelViewProjection * vec4(position, 1.0); v_color = color; } `; const fragmentShaderSource = ` varying vec4 v_color; void main() { gl_FragColor = v_color; } `; const shaderProgram = new Cesium.ShaderProgram({ context: this.viewer.scene.context, vertexShaderSource, fragmentShaderSource, }); ``` 5. 应用自定义 shader 在渲染场景之前,应用自定义 shader。 ```javascript this.viewer.scene.postProcessStages.add( new Cesium.PostProcessStage({ fragmentShader: shaderProgram.fragmentShaderSource, vertexShader: shaderProgram.vertexShaderSource, }) ); ``` 这样,你就可以在 Vue 中使用 Cesium,并自定义 shader 了。注意,自定义 shader 需要对 WebGL 有一定的了解,如果你不熟悉 WebGL,可以先了解一下 WebGL 的基础知识。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值