zoukankan      html  css  js  c++  java
  • Activity插件化解决方案

    --摘自《android插件化开发指南》

    1.宿主App加载插件中的类

    2.最简单的插件化方案就是在宿主的androidmanifest.xml中申明插件中的四大组件

    把插件dex合并到宿主dex中,那么宿主app对应的classloader就可以加载插件中的任意类

    /**
     * 由于应用程序使用的ClassLoader为PathClassLoader
     * 最终继承自 BaseDexClassLoader
     * 查看源码得知,这个BaseDexClassLoader加载代码根据一个叫做
     * dexElements的数组进行, 因此我们把包含代码的dex文件插入这个数组
     * 系统的classLoader就能帮助我们找到这个类
     *
     * 这个类用来进行对于BaseDexClassLoader的Hook
     * 类名太长, 不要吐槽.
     * @author weishu
     * @date 16/3/28
     */
    public final class BaseDexClassLoaderHookHelper {
    
        public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
                throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
            // 获取 BaseDexClassLoader : pathList
            Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList");
    
            // 获取 PathList: Element[] dexElements
            Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements");
    
            // Element 类型
            Class<?> elementClass = dexElements.getClass().getComponentType();
    
            // 创建一个数组, 用来替换原始的数组
            Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);
    
            // 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数
            Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};
            Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};
            Object o = RefInvoke.createObject(elementClass, p1, v1);
    
            Object[] toAddElementArray = new Object[] { o };
            // 把原始的elements复制进去
            System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
            // 插件的那个element复制进去
            System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);
    
            // 替换
            RefInvoke.setFieldObject(pathListObj, "dexElements", newElements);
        }
    }

    加载插件中的资源

    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);
    
            mNowResources = newResources;
            //需要清理mTheme对象,否则通过inflate方式加载资源会报错
            //如果是activity动态加载插件,则需要把activity的mTheme对象也设置为null
            RefInvoke.setFieldObject(mBaseContext, "mTheme", null);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
    public class TestActivity1 extends ZeusBaseActivity {
        private final static String TAG = "TestActivity1";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_test1);
    
            findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        Intent intent = new Intent();
    
                        String activityName = "jianqiang.com.hostapp.ActivityA";
                        intent.setComponent(new ComponentName("jianqiang.com.hostapp", activityName));
    
                        startActivity(intent);
    
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    以上方案有2个缺点,就是新增的Activity无法在宿主AndroidManifest文件中预先占位了;并且插件和宿主的资源都合并到了一起,资源id会有冲突

    启动没有在AndroidManifest中声明的插件Activity(采用欺上瞒下的做法)

    class MockClass1 implements InvocationHandler {
    
        private static final String TAG = "MockClass1";
    
        Object mBase;
    
        public MockClass1(Object base) {
            mBase = base;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            Log.e("bao", method.getName());
    
            if ("startActivity".equals(method.getName())) {
                // 只拦截这个方法
                // 替换参数, 任你所为;甚至替换原始Activity启动别的Activity偷梁换柱
    
                // 找到参数里面的第一个Intent 对象
                Intent raw;
                int index = 0;
    
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Intent) {
                        index = i;
                        break;
                    }
                }
                raw = (Intent) args[index];
    
                Intent newIntent = new Intent();
    
                // 替身Activity的包名, 也就是我们自己的包名
                String stubPackage = "jianqiang.com.activityhook1";
    
                // 这里我们把启动的Activity临时替换为 StubActivity
                ComponentName componentName = new ComponentName(stubPackage, StubActivity.class.getName());
                newIntent.setComponent(componentName);
    
                // 把我们原始要启动的TargetActivity先存起来
                newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);
    
                // 替换掉Intent, 达到欺骗AMS的目的
                args[index] = newIntent;
    
                Log.d(TAG, "hook success");
                return method.invoke(mBase, args);
            }
    
            return method.invoke(mBase, args);
        }
    }
    class MockClass2 implements Handler.Callback {
    
        Handler mBase;
    
        public MockClass2(Handler base) {
            mBase = base;
        }
    
        @Override
        public boolean handleMessage(Message msg) {
    
            switch (msg.what) {
                // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
                // 本来使用反射的方式获取最好, 这里为了简便直接使用硬编码
                case 100:
                    handleLaunchActivity(msg);
                    break;
            }
    
            mBase.handleMessage(msg);
            return true;
        }
    
        private void handleLaunchActivity(Message msg) {
            // 这里简单起见,直接取出TargetActivity;
    
            Object obj = msg.obj;
    
            // 把替身恢复成真身
            Intent raw = (Intent) RefInvoke.getFieldObject(obj, "intent");
    
            Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
            raw.setComponent(target.getComponent());
    
            //修改packageName,这样缓存才能命中
            ActivityInfo activityInfo = (ActivityInfo) RefInvoke.getFieldObject(obj, "activityInfo");
            activityInfo.applicationInfo.packageName = target.getPackage() == null ?
                    target.getComponent().getPackageName() : target.getPackage();
    
            try {
                hookPackageManager();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        private static void hookPackageManager() throws Exception {
    
            // 这一步是因为 initializeJavaContextClassLoader 这个方法内部无意中检查了这个包是否在系统安装
            // 如果没有安装, 直接抛出异常, 这里需要临时Hook掉 PMS, 绕过这个检查.
            Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread");
    
            // 获取ActivityThread里面原始的 sPackageManager
            Object sPackageManager = RefInvoke.getFieldObject(currentActivityThread, "sPackageManager");
    
            // 准备好代理对象, 用来替换原始的对象
            Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
            Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),
                    new Class<?>[] { iPackageManagerInterface },
                    new MockClass3(sPackageManager));
    
            // 1. 替换掉ActivityThread里面的 sPackageManager 字段
            RefInvoke.setFieldObject(currentActivityThread, "sPackageManager", proxy);
        }
    }
    public class AMSHookHelper {
    
        public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
    
        /**
         * Hook AMS
         * 主要完成的操作是  "把真正要启动的Activity临时替换为在AndroidManifest.xml中声明的替身Activity",进而骗过AMS
         */
        public static void hookAMN() throws ClassNotFoundException,
                NoSuchMethodException, InvocationTargetException,
                IllegalAccessException, NoSuchFieldException {
    
            //获取AMN的gDefault单例gDefault,gDefault是final静态的
            Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");
    
            // gDefault是一个 android.util.Singleton<T>对象; 我们取出这个单例里面的mInstance字段
            Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", gDefault, "mInstance");
    
            // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
            Class<?> classB2Interface = Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(
                    Thread.currentThread().getContextClassLoader(),
                    new Class<?>[] { classB2Interface },
                    new MockClass1(mInstance));
    
            //把gDefault的mInstance字段,修改为proxy
            RefInvoke.setFieldObject("android.util.Singleton", gDefault, "mInstance", proxy);
        }
    
        /**
         * 由于之前我们用替身欺骗了AMS; 现在我们要换回我们真正需要启动的Activity
         * 不然就真的启动替身了, 狸猫换太子...
         * 到最终要启动Activity的时候,会交给ActivityThread 的一个内部类叫做 H 来完成
         * H 会完成这个消息转发; 最终调用它的callback
         */
        public static void hookActivityThread() throws Exception {
    
            // 先获取到当前的ActivityThread对象
            Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");
    
            // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
            Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH");
    
            //把Handler的mCallback字段,替换为new MockClass2(mH)
            RefInvoke.setFieldObject(Handler.class,
                    mH, "mCallback", new MockClass2(mH));
        }
    }

    动态替换Activity的插件化方案

    1)为插件创建一个LoadedApk对象,并把它事先放到mPackages缓存中。这样getPackageInfo方法就会直接返回这个插件的LoadedApk对象,也就是永远命中缓存,永远不会走下面创建LoadedApk对象的逻辑

    2)反射得到插件的loadedApk对象的mClassLoader字段,设置为插件的ClassLoader。

    public class LoadedApkClassLoaderHookHelper {
    
        public static Map<String, Object> sLoadedApk = new HashMap<String, Object>();
    
        public static void hookLoadedApkInActivityThread(File apkFile) throws ClassNotFoundException,
                NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, InstantiationException {
    
            // 先获取到当前的ActivityThread对象
            Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread");
    
            // 获取到 mPackages 这个静态成员变量, 这里缓存了dex包的信息
            Map mPackages = (Map) RefInvoke.getFieldObject(currentActivityThread, "mPackages");
    
            //准备两个参数
            // android.content.res.CompatibilityInfo
            Object defaultCompatibilityInfo = RefInvoke.getStaticFieldObject("android.content.res.CompatibilityInfo", "DEFAULT_COMPATIBILITY_INFO");
            //从apk中取得ApplicationInfo信息
            ApplicationInfo applicationInfo = generateApplicationInfo(apkFile);
    
            //调用ActivityThread的getPackageInfoNoCheck方法loadedApk,得到,上面两个数据都是用来做参数的
            Class[] p1 = {ApplicationInfo.class, Class.forName("android.content.res.CompatibilityInfo")};
            Object[] v1 = {applicationInfo, defaultCompatibilityInfo};
            Object loadedApk = RefInvoke.invokeInstanceMethod(currentActivityThread, "getPackageInfoNoCheck", p1, v1);
    
            //为插件造一个新的ClassLoader
            String odexPath = Utils.getPluginOptDexDir(applicationInfo.packageName).getPath();
            String libDir = Utils.getPluginLibDir(applicationInfo.packageName).getPath();
            ClassLoader classLoader = new CustomClassLoader(apkFile.getPath(), odexPath, libDir, ClassLoader.getSystemClassLoader());
            RefInvoke.setFieldObject(loadedApk, "mClassLoader", classLoader);
    
            //把插件LoadedApk对象放入缓存
            WeakReference weakReference = new WeakReference(loadedApk);
            mPackages.put(applicationInfo.packageName, weakReference);
    
            // 由于是弱引用, 因此我们必须在某个地方存一份, 不然容易被GC; 那么就前功尽弃了.
            sLoadedApk.put(applicationInfo.packageName, loadedApk);
        }
    
        /**
         * 这个方法的最终目的是调用
         * android.content.pm.PackageParser#generateActivityInfo(android.content.pm.PackageParser.Activity, int, android.content.pm.PackageUserState, int)
         */
        public static ApplicationInfo generateApplicationInfo(File apkFile)
                throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
    
            // 找出需要反射的核心类: android.content.pm.PackageParser
            Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
            Class<?> packageParser$PackageClass = Class.forName("android.content.pm.PackageParser$Package");
            Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
    
    
            // 我们的终极目标: android.content.pm.PackageParser#generateApplicationInfo(android.content.pm.PackageParser.Package,
            // int, android.content.pm.PackageUserState)
            // 要调用这个方法, 需要做很多准备工作; 考验反射技术的时候到了 - -!
            // 下面, 我们开始这场Hack之旅吧!
    
            // 首先拿到我们得终极目标: generateApplicationInfo方法
            // API 23 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            // public static ApplicationInfo generateApplicationInfo(Package p, int flags,
            //    PackageUserState state) {
            // 其他Android版本不保证也是如此.
    
    
            // 首先, 我们得创建出一个Package对象出来供这个方法调用
            // 而这个需要得对象可以通过 android.content.pm.PackageParser#parsePackage 这个方法返回得 Package对象得字段获取得到
            // 创建出一个PackageParser对象供使用
            Object packageParser = packageParserClass.newInstance();
    
            // 调用 PackageParser.parsePackage 解析apk的信息
            // 实际上是一个 android.content.pm.PackageParser.Package 对象
            Class[] p1 = {File.class, int.class};
            Object[] v1 = {apkFile, 0};
            Object packageObj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage", p1, v1);
    
    
            // 第三个参数 mDefaultPackageUserState 我们直接使用默认构造函数构造一个出来即可
            Object defaultPackageUserState = packageUserStateClass.newInstance();
    
            // 万事具备!!!!!!!!!!!!!!
            Class[] p2 = {packageParser$PackageClass, int.class, packageUserStateClass};
            Object[] v2 = {packageObj, 0, defaultPackageUserState};
            ApplicationInfo applicationInfo = (ApplicationInfo)RefInvoke.invokeInstanceMethod(packageParser, "generateApplicationInfo", p2, v2);
    
            String apkPath = apkFile.getPath();
            applicationInfo.sourceDir = apkPath;
            applicationInfo.publicSourceDir = apkPath;
    
            return applicationInfo;
        }
    }

    上述代码的思想是,反射PackageParser的generateApplicationInfo方法,硬生生地创建出一个ApplicationInfo对象

    加载插件中类的方案1:为每个插件创建一个ClassLoader

    public class CustomClassLoader extends DexClassLoader {
    
        public CustomClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
            super(dexPath, optimizedDirectory, libraryPath, parent);
        }
    }

    总结就是把插件apk对应的LoadedApk对象,直接放入了缓存里,然后把这个LoadedApk对象的ClassLoader改为插件的ClassLoader

    加载插件中类的方案2:合并多个dex

    1)根据宿主的ClassLoader,获取宿主的dexElements字段

      首先反射出BaseDexClassLoader的pathList字段,它是DexPathList类型的

      然后反射出DexPathList的dexElements字段,这是个数组

    2)根据插件的apkFile,反射出一个Element类型的对象,这就是插件dex

    3)把插件dex和宿主dexElements合并成一个新的dex数组,替换宿主之前的dexElements字段

    public final class BaseDexClassLoaderHookHelper {
    
        public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
                throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
            // 获取 BaseDexClassLoader : pathList
            Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList");
    
            // 获取 PathList: Element[] dexElements
            Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements");
    
            // Element 类型
            Class<?> elementClass = dexElements.getClass().getComponentType();
    
            // 创建一个数组, 用来替换原始的数组
            Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);
    
            // 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数
            Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};
            Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};
            Object o = RefInvoke.createObject(elementClass, p1, v1);
    
            Object[] toAddElementArray = new Object[] { o };
            // 把原始的elements复制进去
            System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
            // 插件的那个element复制进去
            System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);
    
            // 替换
            RefInvoke.setFieldObject(pathListObj, "dexElements", newElements);
        }
    }

    解决插件化Activity启动模式的问题,使用的是占位Activity的思想,即事先为这3中启动模式创建很多的StubActivity,占位activity是什么启动模式,对应的插件activity就是什么启动模式

    加载插件中类的方案3:修改app原生的ClassLoader

    class ZeusClassLoader extends PathClassLoader {
        private List<DexClassLoader> mClassLoaderList = null;
    
        public ZeusClassLoader(String dexPath, ClassLoader parent) {
            super(dexPath, parent);
    
            mClassLoaderList = new ArrayList<DexClassLoader>();
        }
    
        /**
         * 添加一个插件到当前的classLoader中
         */
        protected void addPluginClassLoader(DexClassLoader dexClassLoader) {
            mClassLoaderList.add(dexClassLoader);
        }
    
        @Override
        protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
            Class<?> clazz = null;
            try {
                //先查找parent classLoader,这里实际就是系统帮我们创建的classLoader,目标对应为宿主apk
                clazz = getParent().loadClass(className);
            } catch (ClassNotFoundException ignored) {
    
            }
    
            if (clazz != null) {
                return clazz;
            }
    
            //挨个的到插件里进行查找
            if (mClassLoaderList != null) {
                for (DexClassLoader classLoader : mClassLoaderList) {
                    if (classLoader == null) continue;
                    try {
                        //这里只查找插件它自己的apk,不需要查parent,避免多次无用查询,提高性能
                        clazz = classLoader.loadClass(className);
                        if (clazz != null) {
                            return clazz;
                        }
                    } catch (ClassNotFoundException ignored) {
    
                    }
                }
            }
            throw new ClassNotFoundException(className + " in loader " + this);
        }
    }
    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 volatile ClassLoader mNowClassLoader = null;          //系统原始的ClassLoader
        public static volatile ClassLoader mBaseClassLoader = null;         //系统原始的ClassLoader
    
        public static void init(Application application) {
            //初始化一些成员变量和加载已安装的插件
            mPackageInfo = RefInvoke.getFieldObject(application.getBaseContext(), "mPackageInfo");
            mBaseContext = application.getBaseContext();
            mNowResources = mBaseContext.getResources();
    
            mBaseClassLoader = mBaseContext.getClassLoader();
            mNowClassLoader = mBaseContext.getClassLoader();
    
            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;
    
                        Utils.extractAssets(mBaseContext, apkName);
    
                        PluginItem item = generatePluginItem(apkName);
                        plugins.add(item);
    
                        pluginPaths.add(item.pluginPath);
                    }
                }
    
                reloadInstalledPluginResources(pluginPaths);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            ZeusClassLoader classLoader = new ZeusClassLoader(mBaseContext.getPackageCodePath(), mBaseContext.getClassLoader());
    
            File dexOutputDir = mBaseContext.getDir("dex", Context.MODE_PRIVATE);
            final String dexOutputPath = dexOutputDir.getAbsolutePath();
            for(PluginItem plugin: plugins) {
                DexClassLoader dexClassLoader = new DexClassLoader(plugin.pluginPath,
                        dexOutputPath, null, mBaseClassLoader);
                classLoader.addPluginClassLoader(dexClassLoader);
            }
    
            RefInvoke.setFieldObject(mPackageInfo, "mClassLoader", classLoader);
            Thread.currentThread().setContextClassLoader(classLoader);
            mNowClassLoader = classLoader;
    
        }
    
        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();
            }
        }
    }

    调用

    Intent intent = new Intent();
    String serviceName = PluginManager.plugins.get(0).packageInfo.packageName + ".TestService1";
    intent.setClass(this, getClassLoader().loadClass(serviceName));
    startService(intent);

    以上的方案对于四大组件都是适用的

    欢迎关注我的微信公众号:安卓圈

  • 相关阅读:
    小程序--获取手机型号
    小程序---换行
    小程序 页面禁止左右上下滑动
    小程序---数据列表 隔行变色
    小程序 视频播放出来的坑
    小程序-----上传图片
    小程序---提交成功弹框
    小程序——Tab切换
    接收请求参数及数据回显
    重定向与转发
  • 原文地址:https://www.cnblogs.com/anni-qianqian/p/10103843.html
Copyright © 2011-2022 走看看