ClassLoader在啟動(dòng)Activity的時(shí)候會(huì)調(diào)用loadClass方法,我們就從這里入手:
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
然后我們點(diǎn)擊進(jìn)入進(jìn)入了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) {
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;
}
看到源碼是調(diào)用了findClass方法:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
發(fā)現(xiàn)這個(gè)是拋出了一個(gè)異常,就沒(méi)辦法繼續(xù)閱讀了。這個(gè)時(shí)候發(fā)現(xiàn)ClassLoader是一個(gè)抽象類,應(yīng)該是子類重寫了這個(gè)方法,然后通過(guò)啟動(dòng)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;
}
}
得知這個(gè)ClassLoader是PathClassLoader,點(diǎn)進(jìn)去查看
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!");
}
}
發(fā)現(xiàn)其實(shí)是BaseDexClassLoader里面的findClass()生效了,我們通過(guò)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<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;
}
其實(shí)就是DexPathList pathList這個(gè)字段得到Class<?>,我們繼續(xù)查看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<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;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
可以發(fā)現(xiàn)其實(shí)是一個(gè)dex的Element[]數(shù)組,通過(guò)for循環(huán)得到相應(yīng)的值,所以我們就可以把改變的class打成dex格式的文件,通過(guò)反射把這個(gè)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.
* 熱修復(fù)管理
*/
public class FixBugManager {
private static final String TAG = "FixBugManager";
private Context mContext;
private File mDexDir;//應(yīng)用可以訪問(wèn)的dex目錄
public FixBugManager(Context context) {
this.mContext = context;
//獲取到應(yīng)用可以訪問(wèn)的dex目錄
this.mDexDir = context.getDir("odex", Context.MODE_PRIVATE);
}
/**
* 設(shè)置新的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字段并設(shè)置新的值
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;
}
/**
* 合并兩個(gè)dexElements數(shù)組
*
* @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;
}
/**
* 加載所有的修復(fù)包
*/
public void loadFixDex() throws Exception {
File[] files = mDexDir.listFiles();
List<File> fixDexFiles = new ArrayList<>();
for (File dexFile : files) {
if (dexFile.getName().endsWith(".dex")) {
fixDexFiles.add(dexFile);
}
}
fixDexFiles(fixDexFiles);
}
/**
* 修復(fù)dex包
*
* @param fixDexPath dexDex修復(fù)路徑
*/
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路徑 為什么加入到集合?-->可能已啟動(dòng)就可能要修復(fù)
List<File> fixDexFiles = new ArrayList<>();
fixDexFiles.add(destFile);
fixDexFiles(fixDexFiles);
}
/**
* 修復(fù)dex 已經(jīng)修復(fù)過(guò)的dex文件全部copy在mContext里面,application初始化的時(shí)候?qū)⑦@些多個(gè)dex文件一起修復(fù)
*
* @param fixDexFiles
*/
private void fixDexFiles(List<File> fixDexFiles) throws Exception {
//1.先獲取applicationClassLoader的pathList字段的dexElements值
ClassLoader applicationClassLoader = mContext.getClassLoader();
Object applicationDexElements = getElementsByClassLoader(applicationClassLoader);
//2.獲取下載好的補(bǔ)丁的dexElements
//2.1 移動(dòng)到系統(tǒng)能夠訪問(wèn)的dex目錄下 --> ClassLoader
File optimizedDirectory = new File(mDexDir, "oder");
if (!optimizedDirectory.exists())
optimizedDirectory.mkdirs();
//修復(fù)
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.把補(bǔ)丁的dexElements插到已經(jīng)已經(jīng)運(yùn)行的dexElements前面
//合并完成 fixDexElements插入dexElements之前
applicationDexElements = combineArray(fixDexElements, applicationDexElements);
//把合并的數(shù)組注入到原來(lái)的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 sanping.li@alipay.com
*/
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();
}
}
學(xué)習(xí)來(lái)源:紅橙Darren