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

2020-07-19  本文已影响0人  Sharkchilli

前言

上一章我们讲述了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
上一篇下一篇

猜你喜欢

热点阅读