ClassLoader
在启动Activity
的时候会调用loadClass
方法,我们就从这里入手:
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
然后我们点击进入进入了ClassLoade
r的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) {
long t0 = System.nanoTime();
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.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
}
}
return c;
}
看到源码是调用了findClass
方法:
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
发现这个是抛出了一个异常,就没办法继续阅读了。这个时候发现ClassLoader是一个抽象类,应该是子类重写了这个方法,然后通过启动StartActivity
的源码可以得到:
public ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
String librarySearchPath, String libraryPermittedPath,
ClassLoader parent) {
/*
* This is the parent we use if they pass "null" in. In theory
* this should be the "system" class loader; in practice we
* don't use that and can happily (and more efficiently) use the
* bootstrap class loader.
*/
ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();
synchronized (mLoaders) {
if (parent == null) {
parent = baseParent;
}
/*
* If we're one step up from the base class loader, find
* something in our cache. Otherwise, we create a whole
* new ClassLoader for the zip archive.
*/
if (parent == baseParent) {
ClassLoader loader = mLoaders.get(zip);
if (loader != null) {
return loader;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(
zip,
librarySearchPath,
libraryPermittedPath,
parent,
targetSdkVersion,
isBundled);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupVulkanLayerPath");
setupVulkanLayerPath(pathClassloader, librarySearchPath);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
mLoaders.put(zip, pathClassloader);
return pathClassloader;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return pathClassloader;
}
}
得知这个ClassLoader
是PathClassLoader
,点进去查看
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!");
}
}
发现其实是BaseDexClassLoader里面的findClass()生效了,我们通过http://androidxref.com/ 查看源码:
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 suppressedExceptions = new ArrayList();
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 pathList
这个字段得到Class>
,我们继续查看DexPathList
里面源码
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private final Element[] dexElements;
/**
* * Finds the named class in one of the dex files pointed at by
* * this instance. This will find the one in the earliest listed
* * path element. If the class is found but has not yet been
* * defined, then this method will define it in the defining
* * context that this instance was constructed with.
* *
* * @param name of class to find
* * @param suppressed exceptions encountered whilst finding the class
* * @return the named class or {@code null} if the class is not
* * found in any of the dex files
*
*/
public Class findClass(String name, List suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
可以发现其实是一个dex
的Element[]
数组,通过for
循环得到相应的值,所以我们就可以把改变的class
打成dex
格式的文件,通过反射把这个dex
文件里面的Element[] dexElements
值插入到原APP的Element[] dexElements
前就可以了。
下面是具体代码:
package com.zzw.baselibray.fixBug;
import android.content.Context;
import android.util.Log;
import com.zzw.baselibray.util.FileUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import dalvik.system.BaseDexClassLoader;
/**
* Created by zzw on 2017/5/5.
* 热修复管理
*/
public class FixBugManager {
private static final String TAG = "FixBugManager";
private Context mContext;
private File mDexDir;//应用可以访问的dex目录
public FixBugManager(Context context) {
this.mContext = context;
//获取到应用可以访问的dex目录
this.mDexDir = context.getDir("odex", Context.MODE_PRIVATE);
}
/**
* 设置新的dexElements到applicationClassLoader里面
*
* @param classLoader
* @param dexElements
*/
private void setElementsToClassLoader(ClassLoader classLoader, Object dexElements) throws NoSuchFieldException, IllegalAccessException {
//1.先获取ClassLoader里面的pathList
Field pathListFiled = BaseDexClassLoader.class.getDeclaredField("pathList");
pathListFiled.setAccessible(true);
Object pathList = pathListFiled.get(classLoader);
//2.获取pathList里面的dexElements字段并设置新的值
Field dexElementsField = pathList.getClass().getField("dexElements");
dexElementsField.setAccessible(true);
dexElementsField.set(pathList, dexElements);
}
/**
* 从ClassLoader里面获取dexElements
*
* @param applicationClassLoader
* @return
*/
private Object getElementsByClassLoader(ClassLoader applicationClassLoader) throws NoSuchFieldException, IllegalAccessException {
//1.先获取ClassLoader里面的pathList
Field pathListFiled = BaseDexClassLoader.class.getDeclaredField("pathList");
pathListFiled.setAccessible(true);
Object pathList = pathListFiled.get(applicationClassLoader);
//2.获取pathList里面的dexElements
Field dexElementsField = pathList.getClass().getField("dexElements");
dexElementsField.setAccessible(true);
Object dexElements = dexElementsField.get(pathList);
return dexElements;
}
/**
* 合并两个dexElements数组
*
* @param arrayLhs
* @param arrayRhs
* @return
*/
private static Object combineArray(Object arrayLhs, Object arrayRhs) {
Class> localClass = arrayLhs.getClass().getComponentType();
int i = Array.getLength(arrayLhs);
int j = i + Array.getLength(arrayRhs);
Object result = Array.newInstance(localClass, j);
for (int k = 0; k < j; ++k) {
if (k < i) {
Array.set(result, k, Array.get(arrayLhs, k));
} else {
Array.set(result, k, Array.get(arrayRhs, k - i));
}
}
return result;
}
/**
* 加载所有的修复包
*/
public void loadFixDex() throws Exception {
File[] files = mDexDir.listFiles();
List fixDexFiles = new ArrayList<>();
for (File dexFile : files) {
if (dexFile.getName().endsWith(".dex")) {
fixDexFiles.add(dexFile);
}
}
fixDexFiles(fixDexFiles);
}
/**
* 修复dex包
*
* @param fixDexPath dexDex修复路径
*/
public void fixDex(String fixDexPath) throws Exception {
File srcFile = new File(fixDexPath);
if (!srcFile.exists()) {
throw new FileNotFoundException(fixDexPath);
}
File destFile = new File(mDexDir, srcFile.getName());
if (destFile.exists()) {
Log.d(TAG, "patch [" + fixDexPath + "] has be loaded.");
return;
}
FileUtil.copyFile(srcFile, destFile);// copy to patch's directory
// FileUtil.deleteFile(srcFile);//copy完成后删除
//2.2 ClassLoader读取fixDex路径 为什么加入到集合?-->可能已启动就可能要修复
List fixDexFiles = new ArrayList<>();
fixDexFiles.add(destFile);
fixDexFiles(fixDexFiles);
}
/**
* 修复dex 已经修复过的dex文件全部copy在mContext里面,application初始化的时候将这些多个dex文件一起修复
*
* @param fixDexFiles
*/
private void fixDexFiles(List fixDexFiles) throws Exception {
//1.先获取applicationClassLoader的pathList字段的dexElements值
ClassLoader applicationClassLoader = mContext.getClassLoader();
Object applicationDexElements = getElementsByClassLoader(applicationClassLoader);
//2.获取下载好的补丁的dexElements
//2.1 移动到系统能够访问的dex目录下 --> ClassLoader
File optimizedDirectory = new File(mDexDir, "oder");
if (!optimizedDirectory.exists())
optimizedDirectory.mkdirs();
//修复
for (File fixDexFile : fixDexFiles) {
//dexPath 加载的dex路径
//optimizedDirectory 解压路径
//librarySearchPath so文件位置
//parent 父ClassLoader
ClassLoader fixClassLoader = new BaseDexClassLoader(
fixDexFile.getAbsolutePath(), //dexPath 加载的dex路径
optimizedDirectory,// 解压文件
null,
applicationClassLoader);
Object fixDexElements = getElementsByClassLoader(fixClassLoader);
//3.把补丁的dexElements插到已经已经运行的dexElements前面
//合并完成 fixDexElements插入dexElements之前
applicationDexElements = combineArray(fixDexElements, applicationDexElements);
//把合并的数组注入到原来的applicationClassLoader类中
setElementsToClassLoader(applicationClassLoader, applicationDexElements);
}
}
}
/*
*
* Copyright (c) 2015, alipay.com
*
* 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 com.zzw.baselibray.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
/**
* file utility
*
* @author [email protected]
*/
public class FileUtil {
/**
* copy file
*
* @param src source file
* @param dest target file
* @throws IOException
*/
public static void copyFile(File src, File dest) throws IOException {
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
if (!dest.exists()) {
dest.createNewFile();
}
inChannel = new FileInputStream(src).getChannel();
outChannel = new FileOutputStream(dest).getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
} finally {
if (inChannel != null) {
inChannel.close();
}
if (outChannel != null) {
outChannel.close();
}
}
}
/**
* delete file
*
* @param file file
* @return true if delete success
*/
public static boolean deleteFile(File file) {
if (!file.exists()) {
return true;
}
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
deleteFile(f);
}
}
return file.delete();
}
}
学习来源:红橙Darren