ClassLoader抽象类,主要实现:系统类加载器和自定义加载器。
ClassLoader
https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/lang/ClassLoader.java
BootClassLoader :用于加载FrameWork层Class文件。
PathClassLoader/DexClassLoader:均继承与BaseDexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
唯一区别:String optimizedDirectory,在PathClassLoader直接为NULL。
BaseDexClassLoader :https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
}
/**
* @hide
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent, boolean isTrusted) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
if (reporter != null) {
reportClassLoaderChain();
}
}
DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);//这里这里去加载DexElements
// Native libraries may exist in both the system and
// application library paths, and we use this search order:
//
// 1. This class loader's library path for application libraries (librarySearchPath):
// 1.1. Native library directories
// 1.2. Path to libraries in apk-files
// 2. The VM's library path from the system property for system libraries
// also known as java.library.path
//
// This order was reversed prior to Gingerbread; see http://b/2933456.
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
makeDexElements 去加载[line:373]
dex = loadDexFile(file, optimizedDirectory, loader, elements);
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
Element[] elements)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}
/*
* Private version with class loader argument.
*
* @param fileName
* the filename of the DEX file //DEX文件
* @param loader
* the class loader creating the DEX file object
* @param elements
* the temporary dex path list elements from DexPathList.makeElements
*/
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
mCookie = openDexFile(fileName, null, 0, loader, elements);
mInternalCookie = mCookie;
mFileName = fileName;
//System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
}
DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
最后会执行
/**
* Opens a DEX file from a given filename, using a specified file
* to hold the optimized data.
*
* @param sourceName
* Jar or APK file with "classes.dex". //jar或者APK文件,包含 dex文件
* @param outputName
* File that will hold the optimized form of the DEX data.
* @param flags
* Enable optional features.
* @param loader
* The class loader creating the DEX file object.
* @param elements
* The temporary dex path list elements from DexPathList.makeElements
*/
private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
DexPathList.Element[] elements) throws IOException {
if (outputName != null) {
try {
String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
throw new IllegalArgumentException("Optimized data directory " + parent
+ " is not owned by the current user. Shared storage cannot protect"
+ " your application from code injection attacks.");
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
mInternalCookie = mCookie;
mFileName = sourceName;
//System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
}
看注释鸭,最后openDexFile执行的是native方法。
所以如果是null的话,即PathClassLoad,只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。
class MainActivit : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var cs=classLoader
while (cs!=null){
Log.d("test",cs.toString())
cs=cs.parent
}
}
}
输出:
test: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.kalerm.oldtest-z8F5ZCS9v9fD8p1w23LCnQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.kalerm.oldtest-z8F5ZCS9v9fD8p1w23LCnQ==/lib/x86, /system/lib, /vendor/lib]]]
test: java.lang.BootClassLoader@48e10c5
可以看到应用程序类是由PathClassLoader加载的。
双亲委派机制的loadclass 由ClassLoader 中的loadClass实现,
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
首先会 findLoadedClass,如果为空,则会使用parent 的 loadClass
打印得知,应用最后的parent的最后 是 BootClassLoader
BootClassLoader 定义在ClassLoader中:https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/lang/ClassLoader.java
Line:1400+ 最后
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
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 URL findResource(String name) {
return VMClassLoader.getResource(name);
}
@SuppressWarnings("unused")
@Override
protected Enumeration<URL> findResources(String resName) throws IOException {
return Collections.enumeration(VMClassLoader.getResources(resName));
}
/**
* Returns package information for the given package. Unfortunately, the
* Android BootClassLoader doesn't really have this information, and as a
* non-secure ClassLoader, it isn't even required to, according to the spec.
* Yet, we want to provide it, in order to make all those hopeful callers of
* {@code myClass.getPackage().getName()} happy. Thus we construct a Package
* object the first time it is being requested and fill most of the fields
* with dummy values. The Package object is then put into the ClassLoader's
* Package cache, so we see the same one next time. We don't create Package
* objects for null arguments or for the default package.
* <p>
* There a limited chance that we end up with multiple Package objects
* representing the same package: It can happen when when a package is
* scattered across different JAR files being loaded by different
* ClassLoaders. Rather unlikely, and given that this whole thing is more or
* less a workaround, probably not worth the effort.
*/
@Override
protected Package getPackage(String name) {
if (name != null && !name.isEmpty()) {
synchronized (this) {
Package pack = super.getPackage(name);
if (pack == null) {
pack = definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0",
"Unknown", null);
}
return pack;
}
}
return null;
}
@Override
public URL getResource(String resName) {
return findResource(resName);
}
@Override
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
clazz = findClass(className);
}
return clazz;
}
@Override
public Enumeration<URL> getResources(String resName) throws IOException {
return findResources(resName);
}
}
上述可见BootClassLoader的loadclass 会终结递归,如果没找到,直接调用findclass。
BootClassLoader没加载成功,又会向下到BaseDexClassLoader调用findClass
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
// TODO We should support giving this a library search path maybe.
super(parent);
this.pathList = new DexPathList(this, dexFiles);
}
DexPathList: https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private Element[] dexElements;
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
Class对象就是从Element中获得的。每一个Element就对应一个dex文件,
可以通过反射Elemet数组。把插件的element 复制到宿主中,实现插件化,动态加载类。
反射CODE
class TestInvoke {
fun loadClass(context: Context, apkPath: String) {
val baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader")
val pathListField = baseDexClassLoaderClass.getDeclaredField("pathList")
pathListField.isAccessible = true
val dexPathListClass = Class.forName("dalvik.system.DexPathList")
val dexElementsField = dexPathListClass.getDeclaredField("dexElements")
dexElementsField.isAccessible = true
//host
val pathClassLoaderInstance = context.classLoader as PathClassLoader
val pathListInstance = pathListField.get(pathClassLoaderInstance)
val dexElementsInstance = dexElementsField.get(pathListInstance) as Array<*>
//visitor
val dexClassLoader =
DexClassLoader(apkPath, context.cacheDir.absolutePath, null, context.classLoader)
val visitorPathListInstance = pathListField.get(dexClassLoader)
val visitorElementsInstance = dexElementsField.get(visitorPathListInstance) as Array<*>
val newHostElementsInstance =
java.lang.reflect.Array.newInstance(
visitorElementsInstance.javaClass.componentType,
dexElementsInstance.size + visitorElementsInstance.size
) as Array<*>
System.arraycopy(
dexElementsInstance,
0,
newHostElementsInstance,
0,
dexElementsInstance.size
)
System.arraycopy(
visitorElementsInstance,
0,
newHostElementsInstance,
dexElementsInstance.size,
visitorElementsInstance.size
)
dexElementsField.set(pathListInstance, newHostElementsInstance)
}
}
测试代码:
val path = Environment.getExternalStorageDirectory().path + "/${Environment.DIRECTORY_DCIM}"
val file = File(path, "11111.apk")
TestInvoke().loadClass(this, file.absolutePath)
val noClass = Class.forName("com.kalerm.extend.ClassA")
val con = noClass.getDeclaredConstructor(String::class.java, String::class.java)
con.isAccessible = true
val noClassInstance= con.newInstance("11","13")
val method=noClass.getDeclaredMethod("do1")
method.isAccessible=true
val rs= method.invoke(noClassInstance)
反射ClassA 代码
class ClassA
(
var value1: String? = null,
var value2: String? = null
) {
var value3:String?=null
override fun toString(): String {
return "$value1 $value2"
}
fun do1() {
println("this is classa do1 ${toString()}")
}
fun do2() {
println("this is classa do2")
}
}
execute result:
image.png
网友评论