Android ClassLoader流程解读并简单方式实现热更新

ClassLoader在启动Activity的时候会调用loadClass方法,我们就从这里入手:

public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }

然后我们点击进入进入了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;
    }

看到源码是调用了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;
        }
    }

得知这个ClassLoaderPathClassLoader,点进去查看

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;
    }

可以发现其实是一个dexElement[]数组,通过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

你可能感兴趣的:(Android ClassLoader流程解读并简单方式实现热更新)