美文网首页
Android的加壳与脱壳 之 Android类加载器(二)

Android的加壳与脱壳 之 Android类加载器(二)

作者: Sharkchilli | 来源:发表于2020-07-19 01:22 被阅读0次

    前言

    上一章我们讲述了Android中如何动态加载dex,并执行其中类中的方法。本文我们探讨Activity的加载,Activity加载调用OnCreate时所遇到的问题。

    需加载Dex编写

    我们可以在上一章中,加入要加载的Activity,其代码如下:
    TestActivity.java

    package com.shark.testdex;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    
    import androidx.annotation.Nullable;
    
    public class TestActivity extends Activity {
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.i("Shark","com.shark.testdex.TestActivity.onCreate()调用");
        }
    }
    

    加载apk编写

    对上一章使用同样的方式,去加载调用TestActivity的onCreate方法

    //直接使用context.startActivity(new Intent(context,clazz));方式能否启动Activity?
        public void startTestActivity(Context context,String dexfilepath){
    
            File optfile=context.getDir("opt_dex",0);
            File libfile=context.getDir("lib_path",0);
    
            DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),
                    libfile.getAbsolutePath(),MainActivity.class.getClassLoader());
    
            Class<?> clazz=null;
            try {
                clazz = dexClassLoader.loadClass("com.shark.testdex.TestActivity");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if(clazz!=null){
                context.startActivity(new Intent(context,clazz));
            }
    
        }
    

    运行结果如下:


    image.png

    查看Android源码可以发现组件是由LoadedApk的mClassLoader加载,而这个ClassLoader是一个PathClassLoader,所以由我们自己创建的ClassLoader并不在这个mClassLoader中。因此ActivityManagerService无法管理到TestActivity,所以报出上面错误。

    这里将有关Android app的加载流程 Activity加载流程 我们这里暂时不讨论只要先记住结论就好了
    两种解决方案:
    1、替换系统组件类加载器为我们的DexClassLoader,同时设置DexClassLoader的parent为系统组件类加载器;
    2、打破原有的双亲关系,在系统组件类加载器和BootClassLoader的中间插入我们自己的DexClassLoader即可;

    方案一

    image.png
    使用第一种解决方案代码如下
    具体解释在注释中已经写明,这里需要阅读Android源码,本文暂时不做讨论
     //替换系统组件类加载器为我们的DexClassLoader,同时设置DexClassLoader的parent为系统组件类加载器;
        public void startTestActivityFirstMethod(Context context,String dexfilepath){
    
            File optfile=context.getDir("opt_dex",0);
            File libfile=context.getDir("lib_path",0);
    
            DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),
                    libfile.getAbsolutePath(),MainActivity.class.getClassLoader());
            //替换系统组件类加载器为我们的DexClassLoader
            replaceClassloader(dexClassLoader);
    
            Class<?> clazz=null;
            try {
                clazz = dexClassLoader.loadClass("com.shark.testdex.TestActivity");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            context.startActivity(new Intent(context,clazz));
        }
    
    
        /**
         * 我们的目的为替换系统组件类加载器,
         * 通过Android源码得知mClassLoader在LoadedApk的mClassLoader中
         * 而LoadedApk在ActivityThread的mPackages中 key为包名,ActivityThread中currentActivityThread就是这个类的实例
         * 使用包名得到WeakReference再得到LoadedApk再得到mClassLoader并且替换之
         * 所以以下代码是如何去获得mClassLoader
         * @param classloader
         */
        public void replaceClassloader(ClassLoader classloader){
            try {
                //得到sCurrentActivityThread
                Class<?> ActivityThreadClazz=classloader.loadClass("android.app.ActivityThread");
                Method currentActivityThreadMethod= ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
                currentActivityThreadMethod.setAccessible(true);
                Object activityThreadObj=currentActivityThreadMethod.invoke(null);
                //得到mPackages 其定义如下
                //final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
                Field mPackagesField=ActivityThreadClazz.getDeclaredField("mPackages");
                mPackagesField.setAccessible(true);
                ArrayMap mPackagesObj= (ArrayMap) mPackagesField.get(activityThreadObj);
                WeakReference wr= (WeakReference) mPackagesObj.get(this.getPackageName());
                Object loadedApkObj=wr.get();
    
                Class LoadedApkClazz=classloader.loadClass("android.app.LoadedApk");
                //private ClassLoader mClassLoader;
                Field mClassLoaderField=LoadedApkClazz.getDeclaredField("mClassLoader");
                mClassLoaderField.setAccessible(true);
                //下面得到的同一个ClassLoader,也就是mClassLoader,所以上面创建指定的父节点就是老的mClassLoader
                ClassLoader temoClassLoader = (ClassLoader) mClassLoaderField.get(loadedApkObj);
                ClassLoader temoClassLoader2 = MainActivity.class.getClassLoader();
    
                mClassLoaderField.set(loadedApkObj,classloader);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    运行

    image.png

    可以看到结果正确得到了,我们将自己创建的DexClassLoader替换过去,因为双亲委派模式既不会影响原来的类加载器也可以将我们新的DexClassLoader在ActivityManagerService管理到

    方案二

    image.png

    使用第二种解决方案代码如下
    原理和第一种一样,只不过这个新的类加载器插到了中间

    //打破原有的双亲关系,在系统组件类加载器和BootClassLoader的中间插入我们自己的DexClassLoader
        public void startTestActivitySecondMethod(Context context, String dexfilepath) {
            File optfile = context.getDir("opt_dex", 0);
            File libfile = context.getDir("lib_path", 0);
            //得到当前MainActivity的ClassLoader即mClassLoader
            ClassLoader pathClassloader = MainActivity.class.getClassLoader();
            //得到mClassLoader的父节点
            ClassLoader bootClassloader = MainActivity.class.getClassLoader().getParent();
            //创建新的Classloader设置其父节点为mClassLoader的父节点
            DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath,
                    optfile.getAbsolutePath(), libfile.getAbsolutePath(), bootClassloader);
            try {
                //利用反射设置mClassLoader的父节点为我们新建的dexClassLoader
                Field parentField = ClassLoader.class.getDeclaredField("parent");
                parentField.setAccessible(true);
                parentField.set(pathClassloader, dexClassLoader);
            } catch (Exception e) {
                e.printStackTrace();
            }
            /**
             * 下面代码打印插入后的类加载器父子关系
             * I/SharkChilli: this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.shark.loaddex-2/base.apk"],nativeLibraryDirectories=[/data/app/com.shark.loaddex-2/lib/x86, /system/lib, /vendor/lib]]]--parent:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/3.dex"],nativeLibraryDirectories=[/data/user/0/com.shark.loaddex/app_lib_path, /system/lib, /vendor/lib]]]
             * I/SharkChilli: this:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/3.dex"],nativeLibraryDirectories=[/data/user/0/com.shark.loaddex/app_lib_path, /system/lib, /vendor/lib]]]--parent:java.lang.BootClassLoader@57e43f5
             *
             * 其父子关系如下:
             * PathClassLoader->DexClassLoader(我们新建的类加载器)->BootClassLoader
             */
            ClassLoader tmpClassloader = pathClassloader;
            ClassLoader parentClassloader = pathClassloader.getParent();
            while (parentClassloader != null) {
                Log.i("SharkChilli", "this:" + tmpClassloader + "--parent:" + parentClassloader);
                tmpClassloader = parentClassloader;
                parentClassloader = parentClassloader.getParent();
            }
            Class<?> clazz = null;
            try {
                clazz = dexClassLoader.loadClass("com.shark.testdex.TestActivity");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            context.startActivity(new Intent(context, clazz));
        }
    

    运行

    image.png

    相关文章

      网友评论

          本文标题:Android的加壳与脱壳 之 Android类加载器(二)

          本文链接:https://www.haomeiwen.com/subject/aylxkktx.html