Android加壳脱壳学习—动态加载和类加载机制详解

发表于 2年以前  | 总阅读数:1961 次

前言

最近一直在学习Android 加壳和脱壳,在进行Android加壳和脱壳的学习中,第一步便是深入理解类加载器和动态加载二者之间的关系。

本文详细的介绍了类加载器和动态加载之间的关系和原理,之所以详细的讲解两者之间的关系,一是学习脱壳和加壳的需要,二是为后面Android插件化漏洞挖掘的讲解做铺垫。

类加载器

Android中的类加载器机制与JVM一样遵循双亲委派模式。

1.双亲委派模式

(1)双亲委派模式定义

(1)加载.class文件时,以递归的形式逐级向上委托给父加载器ParentClassLoader加载,如果加载过了,就不用再加载一遍
(2)如果父加载器没有加载过,继续委托给父加载器去加载,一直到这条链路的顶级,顶级ClassLoader如果没有加载过,则尝试加载,加载失败,则逐级向下交还调用者加载

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
           //1.先检查是否已经加载过--findLoaded
           Class<?> c = findLoadedClass(name);
           if (c == null) {
               try {
                   //2.如果自己没加载过,存在父类,则委托父类
                   if (parent != null) {
                       c = parent.loadClass(name, false);
                   } else {
                       c = findBootstrapClassOrNull(name);
                   }
               } catch (ClassNotFoundException e) {
               }

               if (c == null) {
                   //3.如果父类也没加载过,则尝试本级classLoader加载
                   c = findClass(name);
               }
           }
          return c;
   }

代码解释:

① 先检查自己是否已经加载过class文件,用findLoadedClass方法,如果已经加载了直接返。

② 如果自己没有加载过,存在父类,则委派父类去加载,用parent.loadClass(name,false)方法,此时会向上传递,然后去父加载器中循环第1步,一直到顶级ClassLoader。

③ 如果父类没有加载,则尝试本级classLoader加载,如果加载失败了就会向下传递,交给调用方式实现.class文件的加载。

(2)双亲委派模式加载流程

(3)双亲委派的作用

① 防止同一个.class文件重复加载。 ② 对于任意一个类确保在虚拟机中的唯一性。由加载它的类加载器和这个类的全类名一同确立其在Java虚拟机中的唯一性。 ③ 保证.class文件不被篡改,通过委派方式可以保证系统类的加载逻辑不被篡改。

2. Android中类加载机制

(1)Android基本类预加载

我们了解Android基本类预加载,首先我们回顾上文的Dalvik虚拟机启动相关:

我们执行app_process程序,进入main函数里面,然后进行AndroidRuntime::start:

Zygote native 进程主要工作:

① 创建虚拟机–startVM

② 注册JNI函数–startReg

③ 通过JNI知道Java层的com.android.internal.os.ZygoteInit 类,调用main 函数,进入java 世界

然后进入Java层:

Zygote总结:

① 解析init.zygote.rc中的参数,创建AppRuntime并调用AppRuntime.start()方法。

② 调用AndroidRuntime的startVM()方法创建虚拟机,再调用startReg()注册JNI函数。

③ 通过JNI方式调用ZygoteInit.main(),第一次进入Java世界。

④ registerZygoteSocket()建立socket通道,zygote作为通信的服务端,用于响应客户端请求。

⑤ preload()预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率。

⑥ 通过startSystemServer(),fork得力帮手system_server进程,也是Java Framework的运行载体(下面讲到system server再详细讲解)。

⑦ 调用runSelectLoop(),随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作。

Android的类加载机制和JVM一样遵循双亲委派模式,在dalvik/art启动时将所有Java基本类和Android系统框架的基本类加载进来,预加载的类记录在/frameworks/base/config/preloaded-classes中。


android.R$styleable
android.accessibilityservice.AccessibilityServiceInfo$1
android.accessibilityservice.AccessibilityServiceInfo
android.accessibilityservice.IAccessibilityServiceClient$Stub$Proxy
android.accessibilityservice.IAccessibilityServiceClient$Stub
android.accessibilityservice.IAccessibilityServiceClient
android.accounts.AbstractAccountAuthenticator$Transport
android.accounts.AbstractAccountAuthenticator
android.accounts.Account$1
android.accounts.Account
...

java.lang.Short
java.lang.StackOverflowError
java.lang.StackTraceElement
java.lang.StrictMath
java.lang.String$1
java.lang.String$CaseInsensitiveComparator
java.lang.String
java.lang.StringBuffer
java.lang.StringBuilder
java.lang.StringFactory
java.lang.StringIndexOutOfBoundsException
java.lang.System$PropertiesWithNonOverrideableDefaults
java.lang.System
java.lang.Thread$1
...

这些类只需要在Zygote进程启动时加载一遍就可以了,后续没一个APP或Android运行时环境的进程,都是从Zygote中fork出来,天然保留了加载过的类缓存。

ZygoteInit.preload()

static void preload(TimingsTraceLog bootTimingsTraceLog) {
    // ...省略
    preloadClasses();
    // ...省略
}

private static void preloadClasses() {
    final VMRuntime runtime = VMRuntime.getRuntime();

    // 读取 preloaded_classes 文件
    InputStream is;
    try {
        is = new FileInputStream(PRELOADED_CLASSES);
    } catch (FileNotFoundException e) {
        Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
        return;
    }

    // ...省略

    try {
        BufferedReader br =
                new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);

        int count = 0;
        String line;
        while ((line = br.readLine()) != null) {
            // Skip comments and blank lines.
            line = line.trim();
            if (line.startsWith("#") || line.equals("")) {
                continue;
            }

            Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
            try {
                // 逐行加载基本类
                Class.forName(line, true, null);
                count++;
                // ...省略
            } catch (Throwable t) {
                // ...省略
            }
        }

        // ...省略
    } catch (IOException e) {
        Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
    } finally {
        // ...省略
    }
}

(2)Android类加载器层级关系及分析

Android中的ClassLoader类型分为系统ClassLoader和自定义ClassLoader。其中系统ClassLoader包括3种是BootClassLoader、DexClassLoader、PathClassLoader。

① BootClassLoader:Android平台上所有Android系统启动时会使用BootClassLoader来预加载常用的类。

② BaseDexClassLoader:实际应用层类文件的加载,而真正的加载委托给pathList来完成。

③ DexClassLoader:可以加载dex文件以及包含dex的压缩文件(apk,dex,jar,zip),可以安装一个未安装的apk文件,一般为自定义类加载器。

④ PathClassLoader:可以加载系统类和应用程序的类,通常用来加载已安装的apk的dex文件。

补充:Android 提供的原生加载器叫做基础类加载器,包括:BootClassLoader,PathClassLoader,DexClassLoader,InMemoryDexClassLoader(Android 8.0 引入),DelegateLastClassLoader(Android 8.1 引入)。

<1> BootClassLoader

启动类加载器,用于加载 Zygote 进程已经预加载的基本类,可以推测它只需从缓存中加载。这是基类 ClassLoader 的一个内部类,是包访问权限,所以应用程序无权直接访问。

public abstract class ClassLoader {
    // ...省略

    class BootClassLoader extends ClassLoader {
        private static BootClassLoader instance;

        public static synchronized BootClassLoader getInstance() {
            if (instance == null) {
                instance = new BootClassLoader();
            }

            return instance;
        }

        public BootClassLoader() {
            super(null);
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            return Class.classForName(name, false, null);
        }

        // ...省略

        @Override
        protected Class<?> loadClass(String className, boolean resolve)
               throws ClassNotFoundException {
            Class<?> clazz = findLoadedClass(className);

            if (clazz == null) {
                clazz = findClass(className);
            }

            return clazz;
        }

        // ...省略
    }
}

源码分析: 我们可以看见,BootClassLoader没有父加载器,在缓存取不到类是直接调用自己的findClass()方法。

findClass()方法调用Class.classForName()方法,而ZygoteInit.preloadClasses()中,加载基本类是Class.forName()。

ublic final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    // ...省略

    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName(className, true, ClassLoader.getClassLoader(caller));
    }

    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        if (loader == null) {
            loader = BootClassLoader.getInstance();
        }
        Class<?> result;
        try {
            result = classForName(name, initialize, loader);
        } catch (ClassNotFoundException e) {
            Throwable cause = e.getCause();
            if (cause instanceof LinkageError) {
                throw (LinkageError) cause;
            }
            throw e;
        }
        return result;
    }

    // 本地方法
    static native Class<?> classForName(String className, boolean shouldInitialize,
            ClassLoader classLoader) throws ClassNotFoundException;

    // ...省略
}

我们可以发现,预加载时,ZygoteInit.preloadClasses()中调用Class.forName(),实际是指定BootClassLoader为类加载器,且只需要在预加载的时候进行类初始化,只需要一次。

总之,通过 Class.forName() 或者 Class.classForName() 可以且仅可以直接加载基本类,一旦基本类预加载后,对于应用程序而言,我们虽然不能直接访问BootClassLoader,但可以通过Class.forName/Class.classForName加载。

无论是系统类加载器(PathClassLoader)还是自定义的类加载器(DexClassLoader),最顶层的祖先加载器默认是 BootClassLoader,与 JVM 一样,保证了基本类的类型安全。

Class文件加载:

① 通过Class.forName()方法动态加载

② 通过ClassLoader.loadClass()方法动态加载 类的加载分为3个步骤:1.装载(Load),2.链接(Link),3.初始化(Intialize) 类加载时机:

1.隐式加载:

① 创建类的实例,也就是new一个对象

② 访问某个类或接口的静态变量,或者对该静态变量赋值

③ 调用类的静态方法

④ 反射Class.forName("android.app.ActivityThread")

⑤ 初始化一个类的子类(会首先初始化子类的父类)

2.显示加载:

① 使用LoadClass()加载

② 使用forName()加载

<2> PathClassLoader

主要用于系统和app的类加载器,其中optimizedDirectory为null, 采用默认目录/data/dalvik-cache/。

PathClassLoader 是作为应用程序的系统类加载器,也是在 Zygote 进程启动的时候初始化的(基本流程为:ZygoteInit.main() -> ZygoteInit.forkSystemServer() -> ZygoteInit.handleSystemServerProcess() -> ZygoteInit.createPathClassLoader()。在预加载基本类之后执行),所以每一个 APP 进程从 Zygote 中 fork 出来之后都自动携带了一个 PathClassLoader,它通常用于加载 apk 里面的 .dex 文件。

<3> DexClassLoader

可以从包含classes.dex的jar或者apk中,加载类的类加载器, 可用于执行动态加载, 但必须是app私有可写目录来缓存odex文件. 能够加载系统没有安装的apk或者jar文件, 因此很多热修复和插件化方案都是采用DexClassLoader。


public class
DexClassLoader extends BaseDexClassLoader {

   public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

总结:

我们可以发现DexClassLoader与PathClassLoader都继承于BaseDexClassLoader,这两个类只是提供了自己的构造函数,没有额外的实现。

区别:

DexClassLoader提供了optimizedDirectory,而PathClassLoader则没有,optimizedDirectory正是用来存放odex文件的地方,所以可以利用DexClassLoader实现动态加载。

<4> BaseDexClassLoader

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;  //记录dex文件路径信息

    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
}

dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用:分割;optimizedDirectory: 优化后dex文件存在的目录, 可以为null;libraryPath: native库所在路径列表;当有多个路径则采用:分割;ClassLoader:父类的类加载器。

BaseDexClassLoader会初始化dexPathList,收集dex文件和Native文件动态库。

初始化:

DexPathList:

该类主要用来查找Dex、SO库的路径,并这些路径整体呈一个数组。

final class DexPathList {
    private Element[] dexElements;
    private final List<File> nativeLibraryDirectories;
    private final List<File> systemNativeLibraryDirectories;

    final class DexPathList {
    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        ...
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();

        //记录所有的dexFile文件
        this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);

        //app目录的native库
        this.nativeLibraryDirectories = splitPaths(libraryPath, false);
        //系统目录的native库
        this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
        //记录所有的Native动态库
        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions);
        ...
    }
}

DexPathList初始化过程,主要收集以下两个变量信息:

(1)dexElements: 根据多路径的分隔符“;”将dexPath转换成File列表,记录所有的dexFile

(2)nativeLibraryPathElements: 记录所有的Native动态库, 包括app目录的native库和系统目录的native库

makePathElements:

private static Element[] makePathElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions) {
    return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}

makeDexElements:

makeDexElements方法的作用是获取一个包含dex文件的元素集合。

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions, ClassLoader loader) {
    return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
}

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
  Element[] elements = new Element[files.size()];  //获取文件个数
  int elementsPos = 0;
  for (File file : files) {
      if (file.isDirectory()) {
          elements[elementsPos++] = new Element(file);
      } else if (file.isFile()) {
          String name = file.getName();
          DexFile dex = null;
          //匹配以.dex为后缀的文件
          if (name.endsWith(DEX_SUFFIX)) {
              dex = loadDexFile(file, optimizedDirectory, loader, elements);
              if (dex != null) {
                  elements[elementsPos++] = new Element(dex, null);
              }
          } else {
              dex = loadDexFile(file, optimizedDirectory, loader, elements);             
              if (dex == null) {
                  elements[elementsPos++] = new Element(file);
              } else {
                  elements[elementsPos++] = new Element(dex, file);
              }
          }
          if (dex != null && isTrusted) {
            dex.setTrusted();
          }
      } else {
          System.logW("ClassLoader referenced unknown path: " + file);
      }
  }
  if (elementsPos != elements.length) {
      elements = Arrays.copyOf(elements, elementsPos);
  }

  return elements;
}

该方法的主要功能是创建Element数组。

loadDexFile:

加载DexFile文件,而且会把优化后的dex文件缓存到对应目录。

private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
                                   Element[] elements)
        throws IOException {
    if (optimizedDirectory == null) {
        return new DexFile(file, loader, elements);  //创建DexFile对象
    } else {
        String optimizedPath = optimizedPathFor(file, optimizedDirectory);
        return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
    }
}

DexFile:

用来描述Dex文件,Dex的加载以及Class的查找都是由该类调用它的native方法完成的。

DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
        throws IOException {
    this(file.getPath(), loader, elements);
}

DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    mCookie = openDexFile(fileName, null, 0, loader, elements);
    mInternalCookie = mCookie;
    mFileName = fileName;
}

openDexFile:

private static Object openDexFile(String sourceName, String outputName, int flags,
        ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    return openDexFileNative(new File(sourceName).getAbsolutePath(),
                             (outputName == null) ? null : new File(outputName).getAbsolutePath(),
                             flags,
                             loader,
                             elements);
}
此时参数取值说明:
sourceName为PathClassLoader构造函数传递的dexPath中以分隔符划分之后的文件名;
outputName为null;
flags = 0
loader为null;
elements为makeDexElements()过程生成的Element数组;

openDexFileNative:

static jobject DexFile_openDexFileNative(JNIEnv* env,
                                         jclass,
                                         jstring javaSourceName,
                                         jstring javaOutputName ATTRIBUTE_UNUSED,
                                         jint flags ATTRIBUTE_UNUSED,
                                         jobject class_loader,
                                         jobjectArray dex_elements) {
  ScopedUtfChars sourceName(env, javaSourceName);
  if (sourceName.c_str() == nullptr) {
    return 0;
  }
  Runtime* const runtime = Runtime::Current();
  ClassLinker* linker = runtime->GetClassLinker();
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  std::vector<std::string> error_msgs;
  const OatFile* oat_file = nullptr;

  dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
                                                               class_loader,
                                                               dex_elements,
                                                               /*out*/ &oat_file,
                                                               /*out*/ &error_msgs);

  if (!dex_files.empty()) {
    jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
    ...
    return array;
  } else {
    ...
    return nullptr;
  }
}

这样就完成了dex的加载过程,而BaseDexClassLoader派生出两个子类加载器:PathClassLoader和DexClassLoader。

Android中如果parent类加载器加载不到类,最终还是会调用ClassLoader对象自己的findClass()方法。

loadClass()加载:

public abstract class ClassLoader {

    public Class<?> loadClass(String className) throws ClassNotFoundException {
        return loadClass(className, false);
    }

    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        //判断当前类加载器是否已经加载过指定类,若已加载则直接返回
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
            //如果没有加载过,则调用parent的类加载递归加载该类,若已加载则直接返回
            clazz = parent.loadClass(className, false);

            if (clazz == null) {
                //还没加载,则调用当前类加载器来加载
                clazz = findClass(className);
            }
        }
        return clazz;
    }
}

该方法的加载流程如下:

① 判断当前类加载器是否已经加载过指定类,若已加载则直接返回,否则继续执行;

② 调用parent的类加载递归加载该类,检测是否加载,若已加载则直接返回,否则继续执行;

③ 调用当前类加载器,通过findClass加载。

findLoadedClass:

[-> ClassLoader.java]


protected final Class<?> findLoadedClass(String name) {
    ClassLoader loader;
    if (this == BootClassLoader.getInstance())
        loader = null;
    else
        loader = this;
    return VMClassLoader.findLoadedClass(loader, name);
}

findClass:

[-> BaseDexClassLoader.java]


public class BaseDexClassLoader extends ClassLoader {
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class c = pathList.findClass(name, suppressedExceptions);
        ...
        return c;
    }
}

DexPathList.findClass:


public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        if (dex != null) {
            //找到目标类,则直接返回
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    return null;
}

代码解释:

一个Classloader可以包含多个dex文件,每个dex文件被封装到一个Element对象,这些Element对象排列成有序的数组 dexElements。当查找某个类时,会遍历所有的dex文件,如果找到则直接返回,不再继续遍历dexElements。也就是说当两个类不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心精髓,将需要修复的类所打包的dex文件插入到dexElements前面。

热修复原理:

现在很多热修复技术就是把修复的dex文件放在DexPathList中Element[]数组的前面,这样就实现了修复后的Class抢先加载了,达到了修改bug的目的。

DexFile.loadClassBinaryName:

public final class DexFile {

    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, suppressed); 
    }

    private static Class defineClass(String name, ClassLoader loader, Object cookie, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie); 
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
}

defineClassNative()这是native方法。

defineClassNative:

[-> dalvik_system_DexFile.cc]

static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,
                                        jobject cookie) {
  std::unique_ptr<std::vector<const DexFile*>> dex_files = ConvertJavaArrayToNative(env, cookie);
  if (dex_files.get() == nullptr) {
    return nullptr; //dex文件为空, 则直接返回
  }

  ScopedUtfChars class_name(env, javaName);
  if (class_name.c_str() == nullptr) {
    return nullptr; //类名为空, 则直接返回
  }

  const std::string descriptor(DotToDescriptor(class_name.c_str()));
  const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str())); //将类名转换为hash码
  for (auto& dex_file : *dex_files) {
    const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash);
    if (dex_class_def != nullptr) {
      ScopedObjectAccess soa(env);
      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
      class_linker->RegisterDexFile(*dex_file);
      StackHandleScope<1> hs(soa.Self());
      Handle<mirror::ClassLoader> class_loader(
          hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
      //获取目标类
      mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash,
                                                        class_loader, *dex_file, *dex_class_def);
      if (result != nullptr) {
        // 找到目标对象
        return soa.AddLocalReference<jclass>(result);
      }
    }
  }
  return nullptr; //没有找到目标类
}

在native层创建目标类的对象并添加到虚拟机列表。 我们继续分析Native层可以发现:

DexFile.defineClassNative() 的实现在 /art/runtime/native/dalvik_system_DexFile.cc,最终由 ClassLinker.DefineClass() 实现
Class.classForName() 的实现在 /art/runtime/native/java_lang_Class.cc,最终由 ClassLinker.FindClass() 实现

ClassLinker核心原理:

先从已加载类的 class_table 中查询,若找到则直接返回;若找不到则说明该类是第一次加载,则执行加载流程,其中可能需要穿插加载依赖的类,加载完成后将其缓存到 class_table 中。

在 ClassLinker 中,会维护两类 class_table,一类针对基本类,一类针对其它的类。class_table 是作为缓存已经加载过的类的缓冲池。不管以什么样的方式去加载类,都需要先从 class_table 中先进行查询以提高加载性能。

ClassLinker 在加载类的时候遇到该类依赖的类,进行穿插加载依赖类:

我们总结BaseDexClassLoader初始化和加载原理:

Android类加载详细流程:

3.案例

(1)验证类加载器

我们验证App中的MainActivity类加载器和系统类String类的类加载器:

ClassLoader thisclassloader = MainActivity.class.getClassLoader();
ClassLoader StringClassloader = String.class.getClassLoader();
Log.e("ClassLoader1","MainActivity is in" + thisclassloader.toString());
Log.e("ClassLoader1","String is in" + StringClassloader.toString());

我们可以明显发现PathClassLoader加载已安装的APK类加载器,而BootClassLoader加载系统预安装的类。

(2)遍历父类加载器

public static  void printClassLoader(ClassLoader classLoader) {
       Log.e("printClassLoader","this->"+ classLoader.toString());
       ClassLoader parent = classLoader.getParent();
       while (parent!=null){
           Log.i("printClassLoader","parent->"+parent.toString());
           parent = parent.getParent();
       }
   }

(3)验证双亲委派机制


try {
            Class StringClass = thisclassloader.loadClass("java.lang.String");
            Log.e("ClassLoader1","load StringClass!"+thisclassloader.toString());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            Log.e("ClassLoader1","load MainActivity fail!"+thisclassloader.toString());
        }

我们使用PathClassLoader去加载 String.class类,还是可以加载成功,因为双亲委派的机制。

(4)动态加载

这里我借用网上寒冰大佬动态加载的案例,来进一步讲述使用DexClassLoader类实现简单的动态加载插件dex,并验证ClassLoader的继承关系。

我们先编写一个测试类文件,然后生成dex文件。

我们先将dex文件放到模拟器的sdcard/下。

我们新建一个程序,然后编写主程序的代码,并授权sd读取权限。

Context appContext = this.getApplication();
testDexClassLoader(appContext,"/sdcard/classes.dex");
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

然后我们编写类加载器代码。

private void testDexClassLoader(Context context, String dexfilepath) {
        //构建文件路径:/data/data/com.emaxple.test02/app_opt_dex,存放优化后的dex,lib库
        File optfile = context.getDir("opt_dex",0);
        File libfile = context.getDir("lib_dex",0);

        ClassLoader parentclassloader = MainActivity.class.getClassLoader();
        ClassLoader tmpclassloader = context.getClassLoader();
    //可以为DexClassLoader指定父类加载器
        DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),parentclassloader);

        Class clazz = null;
        try {
            clazz = dexClassLoader.loadClass("com.example.test.TestClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if(clazz!=null){
            try {
                Method testFuncMethod = clazz.getDeclaredMethod("test02");
                Object obj = clazz.newInstance();
                testFuncMethod.invoke(obj);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }

    }

(5)获得类列表

我们通过getClassNameList来获取类列表

private static native String[] getClassNameList(Object cookie);
public static void getClassListInClassLoader(ClassLoader classLoader){
        //先拿到BaseDexClassLoader
        try {
            //拿到pathList
            Class BaseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListField = BaseDexClassLoader.getDeclaredField("pathList");
            pathListField.setAccessible(true);
            Object pathListObj = pathListField.get(classLoader);

            //拿到dexElements
            Class DexElementClass = Class.forName("dalvik.system.DexPathList");
            Field DexElementFiled = DexElementClass.getDeclaredField("dexElements");
            DexElementFiled.setAccessible(true);
            Object[]  dexElementObj = (Object[]) DexElementFiled.get(pathListObj);
            //拿到dexFile
            Class Element = Class.forName("dalvik.system.DexPathList$Element");
            Field dexFileField = Element.getDeclaredField("dexFile");
            dexFileField.setAccessible(true);
            Class DexFile =Class.forName("dalvik.system.DexFile");
            Field mCookieField = DexFile.getDeclaredField("mCookie");
            mCookieField.setAccessible(true);
            Field mFiledNameField = DexFile.getDeclaredField("mFileName");
            mFiledNameField.setAccessible(true);
            //拿到getClassNameList
            Method getClassNameListMethod = DexFile.getDeclaredMethod("getClassNameList",Object.class);
            getClassNameListMethod.setAccessible(true);

            for(Object dexElement:dexElementObj){
                Object dexfileObj = dexFileField.get(dexElement);
                Object mCookiedobj = mCookieField.get(dexfileObj);
                String mFileNameobj = (String) mFiledNameField.get(dexfileObj);
                String[] classlist = (String[]) getClassNameListMethod.invoke(null,mCookiedobj);
                for(String classname:classlist){
                    Log.e("classlist",classLoader.toString()+"-----"+mFileNameobj+"-----"+classname);
                }
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

实验总结

花了一段时间,断断续续总算把这篇类加载器和动态加载的帖子写完了,从中学习到了很多,这里如果有什么错误,就请各位大佬指正了。

参考文献:

http://gityuan.com/2017/03/19/android-classloader/

https://www.jianshu.com/p/7193600024e7

https://www.jianshu.com/p/ff489696ada2

https://www.jianshu.com/p/363a4ad0489d

https://github.com/huanzhiyazi/articles/issues/30

https://juejin.cn/post/6844903940094427150#heading-12

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/tf0wJxcrmcfZB2UngbCN1w

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:6月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:6月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:6月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:6月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:6月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:6月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:7月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:6月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:6月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:6月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:6月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:6月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:6月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:6月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:6月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:7月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:7月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:6月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:6月以前  |  398次阅读  |  详细内容 »
 相关文章
简化Android的UI开发 4年以前  |  520651次阅读
Android 深色模式适配原理分析 3年以前  |  28546次阅读
Android阴影实现的几种方案 1年以前  |  10655次阅读
Android 样式系统 | 主题背景覆盖 3年以前  |  9521次阅读
 目录