类加载器
在Android中,想要拿到一个类的class对象,通常有两种方式
1.Class.forName(name)
2.ClassLoad.loadclass(name)
这两种方式到底有什么区别呢,进到对应的方法里面,可以看到
第二个参数的initialize默认传的是true,根据方法的参数解释,当initialize为true这个类会被初始化。
在loadclass方法中,在调它的重载方法时,传的resolve 是false,根据方法参数解释,此时类不会进行链接,自然不会继续后面的初始化。
类的加载过程
加载--》链接(校验,准备,解析)--》初始化
- 加载:通过类加载器获取类的二进制流,并转换成方法区中的运行时的数据结构,在内存中生成class对象。
- 链接:
- 校验:检查加载的class的正确性和安全性
- 准备: 为类变量分配存储空间并设置类变量初始化
- 解析:将常量池中的符号引用转为直接引用(常量池变为运行时常量池,地址改成真实地址)
- 初始化:类的静态变量复制和执行代码块。
总结:这两个方法区别 在于一个进行了初始化,另一个没有。
实例
public class AClass {
public static int num1=20;
static {
System.out.println("静态代码块执行"+num1);
}
}
在调用 class.forname时,打印了 “静态代码块执行20“
try {
Class.forName("com.qiu.hotfix.AClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
在调用loadclass时,没有打印
try {
getClassLoader().loadClass("com.qiu.hotfix.AClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Android 如何加载类
Android的类加载器主要有三个
- BootClassLoader:主要加载Android framework内部的类
- pathClassLoader:主要加载应用的类
- DexClassLoader:支持加载APK、DEX和JAR,也可以从SD卡进行加载。
因此在热修复的时候,主要是用到pathClassLoader 来加载自己写的应用层的类。接下来就从pathClassLoader入手,看看类加载的代码。
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dalvik.system;
/**
* Provides a simple {@link ClassLoader} implementation that operates on a list
* of files and directories in the local file system, but does not attempt to
* load classes from the network. Android uses this class for its system class
* loader and for its application class loader(s).
*/
public class PathClassLoader extends BaseDexClassLoader {
/**
* Creates a {@code PathClassLoader} that operates on a given list of files
* and directories. This method is equivalent to calling
* {@link #PathClassLoader(String, String, ClassLoader)} with a
* {@code null} value for the second argument (see description there).
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
/**
* Creates a {@code PathClassLoader} that operates on two given
* lists of files and directories. The entries of the first list
* should be one of the following:
*
* <ul>
* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
* well as arbitrary resources.
* <li>Raw ".dex" files (not inside a zip file).
* </ul>
*
* The entries of the second list should be directories containing
* native library files.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
pathClassLoader,我们要找它的loadClass方法,但这个类很简单,没有这个方法,因此我们到它的父类BaseDexClassLoader去找。
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dalvik.system;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* Base class for common functionality between various dex-based
* {@link ClassLoader} implementations.
*/
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
/**
* Constructs an instance.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; may be {@code null}
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@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;
}
@Override
protected URL findResource(String name) {
return pathList.findResource(name);
}
@Override
protected Enumeration<URL> findResources(String name) {
return pathList.findResources(name);
}
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
/**
* Returns package information for the given package.
* Unfortunately, instances of this class don't really have this
* information, and as a non-secure {@code 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 {@code Package} object the first time it is being requested
* and fill most of the fields with dummy values. The {@code
* Package} object is then put into the {@code ClassLoader}'s
* package cache, so we see the same one next time. We don't
* create {@code Package} objects for {@code null} arguments or
* for the default package.
*
* <p>There is a limited chance that we end up with multiple
* {@code Package} objects representing the same package: It can
* happen when when a package is scattered across different JAR
* files which were loaded by different {@code ClassLoader}
* instances. This is rather unlikely, and given that this whole
* thing is more or less a workaround, probably not worth the
* effort to address.
*
* @param name the name of the class
* @return the package information for the class, or {@code null}
* if there is no package information available for it
*/
@Override
protected synchronized Package getPackage(String name) {
if (name != null && !name.isEmpty()) {
Package pack = super.getPackage(name);
if (pack == null) {
pack = definePackage(name, "Unknown", "0.0", "Unknown",
"Unknown", "0.0", "Unknown", null);
}
return pack;
}
return null;
}
/**
* @hide
*/
public String getLdLibraryPath() {
StringBuilder result = new StringBuilder();
for (File directory : pathList.getNativeLibraryDirectories()) {
if (result.length() > 0) {
result.append(':');
}
result.append(directory);
}
return result.toString();
}
@Override public String toString() {
return getClass().getName() + "[" + pathList + "]";
}
}
也没有,继续在它的父类中去找
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.lang;
import dalvik.system.PathClassLoader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.security.ProtectionDomain;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Loads classes and resources from a repository. One or more class loaders are
* installed at runtime. These are consulted whenever the runtime system needs a
* specific class that is not yet available in-memory. Typically, class loaders
* are grouped into a tree where child class loaders delegate all requests to
* parent class loaders. Only if the parent class loader cannot satisfy the
* request, the child class loader itself tries to handle it.
* <p>
* {@code ClassLoader} is an abstract class that implements the common
* infrastructure required by all class loaders. Android provides several
* concrete implementations of the class, with
* {@link dalvik.system.PathClassLoader} being the one typically used. Other
* applications may implement subclasses of {@code ClassLoader} to provide
* special ways for loading classes.
* </p>
* @see Class
*/
public abstract class ClassLoader {
/**
* The 'System' ClassLoader - the one that is responsible for loading
* classes from the classpath. It is not equal to the bootstrap class loader -
* that one handles the built-in classes.
*
* Because of a potential class initialization race between ClassLoader and
* java.lang.System, reproducible when using JDWP with "suspend=y", we defer
* creation of the system class loader until first use. We use a static
* inner class to get synchronization at init time without having to sync on
* every access.
*
* @see #getSystemClassLoader()
*/
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
/**
* The parent ClassLoader.
*/
private ClassLoader parent;
/**
* The packages known to the class loader.
*/
private Map<String, Package> packages = new HashMap<String, Package>();
/**
* To avoid unloading individual classes, {@link java.lang.reflect.Proxy}
* only generates one class for each set of interfaces. This maps sets of
* interfaces to the proxy class that implements all of them. It is declared
* here so that these generated classes can be unloaded with their class
* loader.
*
* @hide
*/
public final Map<List<Class<?>>, Class<?>> proxyCache =
new HashMap<List<Class<?>>, Class<?>>();
/**
* Create the system class loader. Note this is NOT the bootstrap class
* loader (which is managed by the VM). We use a null value for the parent
* to indicate that the bootstrap loader is our parent.
*/
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
// String[] paths = classPath.split(":");
// URL[] urls = new URL[paths.length];
// for (int i = 0; i < paths.length; i++) {
// try {
// urls[i] = new URL("file://" + paths[i]);
// }
// catch (Exception ex) {
// ex.printStackTrace();
// }
// }
//
// return new java.net.URLClassLoader(urls, null);
// TODO Make this a java.net.URLClassLoader once we have those?
return new PathClassLoader(classPath, BootClassLoader.getInstance());
}
/**
* Returns the system class loader. This is the parent for new
* {@code ClassLoader} instances and is typically the class loader used to
* start the application.
*/
public static ClassLoader getSystemClassLoader() {
return SystemClassLoader.loader;
}
/**
* Finds the URL of the resource with the specified name. The system class
* loader's resource lookup algorithm is used to find the resource.
*
* @return the {@code URL} object for the requested resource or {@code null}
* if the resource can not be found.
* @param resName
* the name of the resource to find.
* @see Class#getResource
*/
public static URL getSystemResource(String resName) {
return SystemClassLoader.loader.getResource(resName);
}
/**
* Returns an enumeration of URLs for the resource with the specified name.
* The system class loader's resource lookup algorithm is used to find the
* resource.
*
* @return an enumeration of {@code URL} objects containing the requested
* resources.
* @param resName
* the name of the resource to find.
* @throws IOException
* if an I/O error occurs.
*/
public static Enumeration<URL> getSystemResources(String resName) throws IOException {
return SystemClassLoader.loader.getResources(resName);
}
/**
* Returns a stream for the resource with the specified name. The system
* class loader's resource lookup algorithm is used to find the resource.
* Basically, the contents of the java.class.path are searched in order,
* looking for a path which matches the specified resource.
*
* @return a stream for the resource or {@code null}.
* @param resName
* the name of the resource to find.
* @see Class#getResourceAsStream
*/
public static InputStream getSystemResourceAsStream(String resName) {
return SystemClassLoader.loader.getResourceAsStream(resName);
}
/**
* Constructs a new instance of this class with the system class loader as
* its parent.
*/
protected ClassLoader() {
this(getSystemClassLoader(), false);
}
/**
* Constructs a new instance of this class with the specified class loader
* as its parent.
*
* @param parentLoader
* The {@code ClassLoader} to use as the new class loader's
* parent.
*/
protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
/*
* constructor for the BootClassLoader which needs parent to be null.
*/
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
if (parentLoader == null && !nullAllowed) {
throw new NullPointerException("parentLoader == null && !nullAllowed");
}
parent = parentLoader;
}
/**
* Constructs a new class from an array of bytes containing a class
* definition in class file format.
*
* @param classRep
* the memory image of a class file.
* @param offset
* the offset into {@code classRep}.
* @param length
* the length of the class file.
* @return the {@code Class} object created from the specified subset of
* data in {@code classRep}.
* @throws ClassFormatError
* if {@code classRep} does not contain a valid class.
* @throws IndexOutOfBoundsException
* if {@code offset < 0}, {@code length < 0} or if
* {@code offset + length} is greater than the length of
* {@code classRep}.
* @deprecated Use {@link #defineClass(String, byte[], int, int)}
*/
@Deprecated
protected final Class<?> defineClass(byte[] classRep, int offset, int length)
throws ClassFormatError {
throw new UnsupportedOperationException("can't load this type of class file");
}
/**
* Constructs a new class from an array of bytes containing a class
* definition in class file format.
*
* @param className
* the expected name of the new class, may be {@code null} if not
* known.
* @param classRep
* the memory image of a class file.
* @param offset
* the offset into {@code classRep}.
* @param length
* the length of the class file.
* @return the {@code Class} object created from the specified subset of
* data in {@code classRep}.
* @throws ClassFormatError
* if {@code classRep} does not contain a valid class.
* @throws IndexOutOfBoundsException
* if {@code offset < 0}, {@code length < 0} or if
* {@code offset + length} is greater than the length of
* {@code classRep}.
*/
protected final Class<?> defineClass(String className, byte[] classRep, int offset, int length)
throws ClassFormatError {
throw new UnsupportedOperationException("can't load this type of class file");
}
/**
* Constructs a new class from an array of bytes containing a class
* definition in class file format and assigns the specified protection
* domain to the new class. If the provided protection domain is
* {@code null} then a default protection domain is assigned to the class.
*
* @param className
* the expected name of the new class, may be {@code null} if not
* known.
* @param classRep
* the memory image of a class file.
* @param offset
* the offset into {@code classRep}.
* @param length
* the length of the class file.
* @param protectionDomain
* the protection domain to assign to the loaded class, may be
* {@code null}.
* @return the {@code Class} object created from the specified subset of
* data in {@code classRep}.
* @throws ClassFormatError
* if {@code classRep} does not contain a valid class.
* @throws IndexOutOfBoundsException
* if {@code offset < 0}, {@code length < 0} or if
* {@code offset + length} is greater than the length of
* {@code classRep}.
* @throws NoClassDefFoundError
* if {@code className} is not equal to the name of the class
* contained in {@code classRep}.
*/
protected final Class<?> defineClass(String className, byte[] classRep, int offset, int length,
ProtectionDomain protectionDomain) throws java.lang.ClassFormatError {
throw new UnsupportedOperationException("can't load this type of class file");
}
/**
* Defines a new class with the specified name, byte code from the byte
* buffer and the optional protection domain. If the provided protection
* domain is {@code null} then a default protection domain is assigned to
* the class.
*
* @param name
* the expected name of the new class, may be {@code null} if not
* known.
* @param b
* the byte buffer containing the byte code of the new class.
* @param protectionDomain
* the protection domain to assign to the loaded class, may be
* {@code null}.
* @return the {@code Class} object created from the data in {@code b}.
* @throws ClassFormatError
* if {@code b} does not contain a valid class.
* @throws NoClassDefFoundError
* if {@code className} is not equal to the name of the class
* contained in {@code b}.
*/
protected final Class<?> defineClass(String name, ByteBuffer b,
ProtectionDomain protectionDomain) throws ClassFormatError {
byte[] temp = new byte[b.remaining()];
b.get(temp);
return defineClass(name, temp, 0, temp.length, protectionDomain);
}
/**
* Overridden by subclasses, throws a {@code ClassNotFoundException} by
* default. This method is called by {@code loadClass} after the parent
* {@code ClassLoader} has failed to find a loaded class of the same name.
*
* @param className
* the name of the class to look for.
* @return the {@code Class} object that is found.
* @throws ClassNotFoundException
* if the class cannot be found.
*/
protected Class<?> findClass(String className) throws ClassNotFoundException {
throw new ClassNotFoundException(className);
}
/**
* Returns the class with the specified name if it has already been loaded
* by the VM or {@code null} if it has not yet been loaded.
*
* @param className
* the name of the class to look for.
* @return the {@code Class} object or {@code null} if the requested class
* has not been loaded.
*/
protected final Class<?> findLoadedClass(String className) {
ClassLoader loader;
if (this == BootClassLoader.getInstance())
loader = null;
else
loader = this;
return VMClassLoader.findLoadedClass(loader, className);
}
/**
* Finds the class with the specified name, loading it using the system
* class loader if necessary.
*
* @param className
* the name of the class to look for.
* @return the {@code Class} object with the requested {@code className}.
* @throws ClassNotFoundException
* if the class can not be found.
*/
protected final Class<?> findSystemClass(String className) throws ClassNotFoundException {
return Class.forName(className, false, getSystemClassLoader());
}
/**
* Returns this class loader's parent.
*
* @return this class loader's parent or {@code null}.
*/
public final ClassLoader getParent() {
return parent;
}
/**
* Returns the URL of the resource with the specified name. This
* implementation first tries to use the parent class loader to find the
* resource; if this fails then {@link #findResource(String)} is called to
* find the requested resource.
*
* @param resName
* the name of the resource to find.
* @return the {@code URL} object for the requested resource or {@code null}
* if the resource can not be found
* @see Class#getResource
*/
public URL getResource(String resName) {
URL resource = parent.getResource(resName);
if (resource == null) {
resource = findResource(resName);
}
return resource;
}
/**
* Returns an enumeration of URLs for the resource with the specified name.
* This implementation first uses this class loader's parent to find the
* resource, then it calls {@link #findResources(String)} to get additional
* URLs. The returned enumeration contains the {@code URL} objects of both
* find operations.
*
* @return an enumeration of {@code URL} objects for the requested resource.
* @param resName
* the name of the resource to find.
* @throws IOException
* if an I/O error occurs.
*/
@SuppressWarnings("unchecked")
public Enumeration<URL> getResources(String resName) throws IOException {
Enumeration<URL> first = parent.getResources(resName);
Enumeration<URL> second = findResources(resName);
return new TwoEnumerationsInOne(first, second);
}
/**
* Returns a stream for the resource with the specified name. See
* {@link #getResource(String)} for a description of the lookup algorithm
* used to find the resource.
*
* @return a stream for the resource or {@code null} if the resource can not be found
* @param resName
* the name of the resource to find.
* @see Class#getResourceAsStream
*/
public InputStream getResourceAsStream(String resName) {
try {
URL url = getResource(resName);
if (url != null) {
return url.openStream();
}
} catch (IOException ex) {
// Don't want to see the exception.
}
return null;
}
/**
* Loads the class with the specified name. Invoking this method is
* equivalent to calling {@code loadClass(className, false)}.
* <p>
* <strong>Note:</strong> In the Android reference implementation, the
* second parameter of {@link #loadClass(String, boolean)} is ignored
* anyway.
* </p>
*
* @return the {@code Class} object.
* @param className
* the name of the class to look for.
* @throws ClassNotFoundException
* if the class can not be found.
*/
public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, false);
}
/**
* Loads the class with the specified name, optionally linking it after
* loading. The following steps are performed:
* <ol>
* <li> Call {@link #findLoadedClass(String)} to determine if the requested
* class has already been loaded.</li>
* <li>If the class has not yet been loaded: Invoke this method on the
* parent class loader.</li>
* <li>If the class has still not been loaded: Call
* {@link #findClass(String)} to find the class.</li>
* </ol>
* <p>
* <strong>Note:</strong> In the Android reference implementation, the
* {@code resolve} parameter is ignored; classes are never linked.
* </p>
*
* @return the {@code Class} object.
* @param className
* the name of the class to look for.
* @param resolve
* Indicates if the class should be resolved after loading. This
* parameter is ignored on the Android reference implementation;
* classes are not resolved.
* @throws ClassNotFoundException
* if the class can not be found.
*/
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
/**
* Forces a class to be linked (initialized). If the class has already been
* linked this operation has no effect.
* <p>
* <strong>Note:</strong> In the Android reference implementation, this
* method has no effect.
* </p>
*
* @param clazz
* the class to link.
*/
protected final void resolveClass(Class<?> clazz) {
// no-op, doesn't make sense on android.
}
/**
* Finds the URL of the resource with the specified name. This
* implementation just returns {@code null}; it should be overridden in
* subclasses.
*
* @param resName
* the name of the resource to find.
* @return the {@code URL} object for the requested resource.
*/
protected URL findResource(String resName) {
return null;
}
/**
* Finds an enumeration of URLs for the resource with the specified name.
* This implementation just returns an empty {@code Enumeration}; it should
* be overridden in subclasses.
*
* @param resName
* the name of the resource to find.
* @return an enumeration of {@code URL} objects for the requested resource.
* @throws IOException
* if an I/O error occurs.
*/
@SuppressWarnings( {
"unchecked", "unused"
})
protected Enumeration<URL> findResources(String resName) throws IOException {
return Collections.emptyEnumeration();
}
/**
* Returns the absolute path of the native library with the specified name,
* or {@code null}. If this method returns {@code null} then the virtual
* machine searches the directories specified by the system property
* "java.library.path".
* <p>
* This implementation always returns {@code null}.
* </p>
*
* @param libName
* the name of the library to find.
* @return the absolute path of the library.
*/
protected String findLibrary(String libName) {
return null;
}
/**
* Returns the package with the specified name. Package information is
* searched in this class loader.
*
* @param name
* the name of the package to find.
* @return the package with the requested name; {@code null} if the package
* can not be found.
*/
protected Package getPackage(String name) {
synchronized (packages) {
return packages.get(name);
}
}
/**
* Returns all the packages known to this class loader.
*
* @return an array with all packages known to this class loader.
*/
protected Package[] getPackages() {
synchronized (packages) {
Collection<Package> col = packages.values();
Package[] result = new Package[col.size()];
col.toArray(result);
return result;
}
}
/**
* Defines and returns a new {@code Package} using the specified
* information. If {@code sealBase} is {@code null}, the package is left
* unsealed. Otherwise, the package is sealed using this URL.
*
* @param name
* the name of the package.
* @param specTitle
* the title of the specification.
* @param specVersion
* the version of the specification.
* @param specVendor
* the vendor of the specification.
* @param implTitle
* the implementation title.
* @param implVersion
* the implementation version.
* @param implVendor
* the specification vendor.
* @param sealBase
* the URL used to seal this package or {@code null} to leave the
* package unsealed.
* @return the {@code Package} object that has been created.
* @throws IllegalArgumentException
* if a package with the specified name already exists.
*/
protected Package definePackage(String name, String specTitle, String specVersion,
String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase)
throws IllegalArgumentException {
synchronized (packages) {
if (packages.containsKey(name)) {
throw new IllegalArgumentException("Package " + name + " already defined");
}
Package newPackage = new Package(this, name, specTitle, specVersion, specVendor,
implTitle, implVersion, implVendor, sealBase);
packages.put(name, newPackage);
return newPackage;
}
}
/**
* Sets the signers of the specified class. This implementation does
* nothing.
*
* @param c
* the {@code Class} object for which to set the signers.
* @param signers
* the signers for {@code c}.
*/
protected final void setSigners(Class<?> c, Object[] signers) {
}
/**
* Sets the assertion status of the class with the specified name.
* <p>
* <strong>Note: </strong>This method does nothing in the Android reference
* implementation.
* </p>
*
* @param cname
* the name of the class for which to set the assertion status.
* @param enable
* the new assertion status.
*/
public void setClassAssertionStatus(String cname, boolean enable) {
}
/**
* Sets the assertion status of the package with the specified name.
* <p>
* <strong>Note: </strong>This method does nothing in the Android reference
* implementation.
* </p>
*
* @param pname
* the name of the package for which to set the assertion status.
* @param enable
* the new assertion status.
*/
public void setPackageAssertionStatus(String pname, boolean enable) {
}
/**
* Sets the default assertion status for this class loader.
* <p>
* <strong>Note: </strong>This method does nothing in the Android reference
* implementation.
* </p>
*
* @param enable
* the new assertion status.
*/
public void setDefaultAssertionStatus(boolean enable) {
}
/**
* Sets the default assertion status for this class loader to {@code false}
* and removes any package default and class assertion status settings.
* <p>
* <strong>Note:</strong> This method does nothing in the Android reference
* implementation.
* </p>
*/
public void clearAssertionStatus() {
}
}
/*
* Provides a helper class that combines two existing URL enumerations into one.
* It is required for the getResources() methods. Items are fetched from the
* first enumeration until it's empty, then from the second one.
*/
class TwoEnumerationsInOne implements Enumeration<URL> {
private final Enumeration<URL> first;
private final Enumeration<URL> second;
public TwoEnumerationsInOne(Enumeration<URL> first, Enumeration<URL> second) {
this.first = first;
this.second = second;
}
@Override
public boolean hasMoreElements() {
return first.hasMoreElements() || second.hasMoreElements();
}
@Override
public URL nextElement() {
if (first.hasMoreElements()) {
return first.nextElement();
} else {
return second.nextElement();
}
}
}
/**
* Provides an explicit representation of the boot class loader. It sits at the
* head of the class loader chain and delegates requests to the VM's internal
* class loading mechanism.
*/
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, true);
}
@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);
}
}
/**
* TODO Open issues - Missing / empty methods - Signer stuff - Protection
* domains - Assertions
*/
其中
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
解释下这段代码,首先如果这个类已经被加载过了,那么就从缓存里去找。如果没有,先让parent去加载,如果parent没有加载出来,那再自己去findclass。这里就是双亲委托机制,通过递归的方式,先委托parent去找,parent如果找不到,再让子加载器去找。
双亲委托机制的好处
1.避免类的重复加载,如果parent已经加载过,那自己就没必要再加载了。
2.避免核心类被篡改。通过委托的方式,保证核心.class不被篡改,即使被篡改也不会被加载,即使被加载也不会是同一个class对象,因为不同的加载器加载同一个.class也不是同一个Class对象。这样则保证了Class的执行安全。
注意:pathClassLoader的parent 是BootClassLoader,可以打印出来看
Log.e("TAG", "classLoader " + this.getClassLoader().getClass().getName());
this.getClassLoader().getParent().getClass().getName();
Log.e("TAG", "classLoader parent " + this.getClassLoader().getParent().getClass().getName());
输出结果
10-09 19:49:22.368 22177-22177/com.qiu.hotfix E/TAG: classLoader dalvik.system.PathClassLoader
10-09 19:49:22.368 22177-22177/com.qiu.hotfix E/TAG: classLoader parent java.lang.BootClassLoader
回到方法上来,如果parent找不到,会调用findclass方法,这个方法的实现在BaseDexClassLoader中
@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;
}
又通过了DexPathList对象去findclass ,具体看代码
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;
}
dexElements 通过循环,拿到DexFile对象,再通过DexFile对象去执行native方法,最终返回class对象。
热修复的方案,就是将自己修复后的dex包,加到dexElements数组里面,并且放在前面。这样在加载class的时,就能拿到新修复后的class。
热修复
步骤
1.拿到classloader对象
2.通过反射拿到DexpathList对象,再通过反射拿到该对象里面的dexElements属性。
3.将自己的dex包转换成Elements数组
注意:dex包转成Elements 数组,通过反射调用这个方法可以转换,切忌不用调里面的makeDexElements方法,因为有些系统版本中已经去掉了这个方法。最近踩了这个坑,用这个方法去反射,结果在6.0系统上直接崩溃了。
@SuppressWarnings("unused")
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}
4.整合成新的Elements数组,并重新赋值给DexpathList对象。
附上demo代码:
package com.qiu.hotfix;
import android.content.Context;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class Hotfix {
String dexFilePath;
public void install(Context context) {
dexFilePath="mnt/sdcard/patch.dex";
File dexFile=new File(dexFilePath);
if(!dexFile.exists()){
Log.d("TAG","dex file 不存在");
return;
}
//1.获取系统为我们创建好的classloader对象
ClassLoader classLoader = context.getClassLoader();
//2.获取到dexElements属性 路径 pathclassloader-》basedexclassloader(dexpathlist(Element[] dexElements))
Field field = InvokeUtils.getField(classLoader, "pathList");
field.setAccessible(true);
try {
Object dexpathList = field.get(classLoader);
Field dexelmentsfield = InvokeUtils.getField(dexpathList, "dexElements");
dexelmentsfield.setAccessible(true);
Object[] elements = (Object[]) dexelmentsfield.get(dexpathList);
//3.将自己的dex包 转成Object[] elements 调用Dexpathlist的方法
// private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
// List<IOException> suppressedExceptions, ClassLoader loader)
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
List<File> files=new ArrayList<>();
files.add(dexFile);
Field classload = InvokeUtils.getField(dexpathList, "definingContext");
classload.setAccessible(true);
Object clas=classload.get(dexpathList);
Method method=InvokeUtils.getMethod(dexpathList,"makePathElements",List.class,File.class,List.class);
method.setAccessible(true);
Object[] newElements= (Object[]) method.invoke(dexpathList,files,null,suppressedExceptions);
//组合elements;
Object[] dst= (Object[]) Array.newInstance(elements.getClass().getComponentType(),elements.length+newElements.length);
System.arraycopy(newElements,0,dst,0,newElements.length);
System.arraycopy(elements,0,dst,newElements.length,elements.length);
dexelmentsfield.set(dexpathList,dst);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.qiu.hotfix;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class InvokeUtils {
public static Field getField(Object obj, String fieldName) {
Class<?> clazz = obj.getClass();
Field field = null;
while (clazz != Object.class) {
try {
field = clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (field == null) {
clazz = clazz.getSuperclass();
} else {
return field;
}
}
throw new IllegalArgumentException("找不到对应属性");
}
public static Method getMethod(Object obj,String methodName,Class<?>... parameterTypes){
Class<?> clazz = obj.getClass();
Method method = null;
while (clazz != Object.class) {
try {
method = clazz.getDeclaredMethod(methodName,parameterTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
if (method == null) {
clazz = clazz.getSuperclass();
} else {
return method;
}
}
throw new IllegalArgumentException("找不到对应方法");
}
}
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Hotfix hotfix=new Hotfix();
hotfix.install(this);
}
}
注:class生成dex包 用dx --dex --output {生成dex的路径} {class文件所在路径 从包名开始}