Android項(xiàng)目復(fù)盤2

個(gè)人主頁(yè):https://chengang.plus/

文章將會(huì)同步到個(gè)人微信公眾號(hào):Android部落格

2、系統(tǒng)數(shù)據(jù)檢查

2.1 dex更新

我們開發(fā)的java代碼通過(guò)編譯生成.class文件,然后通過(guò)dx工具生成機(jī)器可以識(shí)別的dex文件。

Android中采用ClassLoader加載dex文件,加載完成之后可以通過(guò)反射調(diào)用其中的方法,適合那些不依賴文件等資源的業(yè)務(wù),而打點(diǎn)恰好比較適合使用dex加載的方式。

Android中有PathClassLoader和DexClassLoader ,他們都繼承自ClassLoader。他們的繼承關(guān)系如下:

image
  • DexClassLoader:可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk;
  • PathClassLoader:要傳入系統(tǒng)中apk的存放Path,所以只能加載已經(jīng)安裝的apk文件。
  • BaseDexClassLoader是DexClassLoader和PathClassLoader的父類,針對(duì)傳入不同的參數(shù)做差異化處理。

看看三個(gè)類的源碼:

2.1.1 PathClassLoader

dalvik/system/PathClassLoader.java

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

2.1.2 DexClassLoader

dalvik/system/DexClassLoader.java

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

2.1.3 BaseDexClassLoader

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
    }
    
    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();
        }
    }
    
    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);
    }

在BaseDexClassLoader中通過(guò)DexPathList類具體的處理Dex,他的構(gòu)造函數(shù)如下:

2.1.4 DexPathList

private Element[] dexElements;
DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
    this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted);
}
            
private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
    Element[] elements = new Element[files.size()];
    int elementsPos = 0;
    for (File file : files) {
        if (file.isDirectory()) {
            elements[elementsPos++] = new Element(file);
        } else if (file.isFile()) {
            String name = file.getName();
            
            DexFile dex = null;
            dex = loadDexFile(file, optimizedDirectory, loader, elements);
        }
    }
    retrun 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);
    }
}

最終通過(guò)DexFile加載loadDex方法在native層實(shí)現(xiàn)對(duì)dex的加載和處理。

而分析PathClassLoaderDexClassLoader的構(gòu)造函數(shù)可以看到Android 9.0中的DexClassLoader構(gòu)造函數(shù)的optimizedDirectory參數(shù)默認(rèn)是null。所以這里要針對(duì)版本的不同做差異化處理。

我們一般加載Dex的方式是:

classLoader = context.getClassLoader();
classLoader.loadClass("you class path");

這里做一下差異化:

public ClassLoader load(Context context,String dexName) {
    mLoaded = false;
    ClassLoader classLoader = null;
    File dexOutputDir = context.getDir("dex", 0);
    File dexFile = new File(dexOutputDir.getAbsolutePath(), dexName);
    Log.d(TAG, "load start");
    if (!dexFile.exists()) {
        return null;
    }
    String dexPath = dexFile.getAbsolutePath();
    Log.d(TAG, "dPath = " + dexPath);
    int version = android.os.Build.VERSION.SDK_INT;
    if (version >= 25) {
        BaseDexClassLoader parent = (BaseDexClassLoader) context.getClassLoader();
        Class<BaseDexClassLoader> c = BaseDexClassLoader.class;
        Method method;
        try {
            method = c.getMethod("addDexPath", String.class);
            method.invoke(parent, dexPath);
            mLoaded = true;
            classLoader = parent;
            return classLoader;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    Log.d(TAG, "mLoaded1 = " + mLoaded);
    if (!mLoaded) {
        ArrayList<File> files = new ArrayList<File>();
        files.add(dexFile);

        classLoader = context.getClassLoader();
        classLoader.loadClass("you class path");
        try {
            Field pathListField = findField(classLoader, "pathList");
            Object dexPathList = pathListField.get(classLoader);
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            if (version < 19) {
                expandFieldArray(dexPathList, "dexElements",
                        makeDexElements(dexPathList, files, null));
            } else if (version < 23) {
                expandFieldArray(dexPathList, "dexElements",
                        makeDexElements(dexPathList, files, null, suppressedExceptions));
            } else {
                expandFieldArray(dexPathList, "dexElements",
                        makePathElements(dexPathList, files, null, suppressedExceptions));
            }

        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }
    Log.d(TAG, "mLoaded2 = " + mLoaded);
    if (mLoaded) {
        return classLoader;
    }
    return null;
}
  • SDK_INI大于等于25

當(dāng)sdk_int大于等于25時(shí),通過(guò)反射BaseDexClassLoader的addDexPath方法直接添加dex文件到DexPathList的Element[]數(shù)組中,而后續(xù)findClass方法的邏輯就是遍歷這個(gè)數(shù)據(jù)找到對(duì)應(yīng)的dex文件。

  • SDK_INI小于25

這種情況下,先反射獲取ClassLoader的pathList對(duì)象,這里的ClassLoader實(shí)際是PathClassLoader,但是最終都會(huì)到BaseDexClassLoader的pathList。

獲取到這個(gè)變量之后,先調(diào)用makeDexElements方法將生成的dex對(duì)象放到一個(gè)數(shù)組中,接著在expandFieldArray方法中將就的dex數(shù)組和新的dex數(shù)組合并:

private Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    Method makeDexElements = findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);

    return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
}

這里的makeDexElements方法對(duì)應(yīng)的是DexPathListmakeDexElements方法,最終目的是將dex對(duì)象添加到Element[]數(shù)組中,作為新的數(shù)組返回。

expandFieldArray對(duì)應(yīng)的是DexPathListaddDexPath方法,將新舊Element[]數(shù)組合到一個(gè)數(shù)組中,舊的數(shù)組在前面。這樣就導(dǎo)致了相同文件名的dex文件,最新修復(fù)了bug的dex不能立即生效。

private void expandFieldArray(Object instance, String fieldName, Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
    Field jlrField = findField(instance, fieldName);
    Object[] original = (Object[]) jlrField.get(instance);
    Object[] combined = (Object[]) Array.newInstance(original.getClass()
            .getComponentType(), original.length + extraElements.length);
    System.arraycopy(original, 0, combined, 0, original.length);
    System.arraycopy(extraElements, 0, combined, original.length,
            extraElements.length);
    jlrField.set(instance, combined);
    mLoaded = true;
    Log.d(TAG, "expandFieldArray");
}

對(duì)比看下DexPathList中的addDexPath方法:

public void addDexPath(String dexPath, File optimizedDirectory, boolean isTrusted) {
    final List<IOException> suppressedExceptionList = new ArrayList<IOException>();
    final Element[] newElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
            suppressedExceptionList, definingContext, isTrusted);
    
    if (newElements != null && newElements.length > 0) {
        final Element[] oldElements = dexElements;
        dexElements = new Element[oldElements.length + newElements.length];
        System.arraycopy(
                oldElements, 0, dexElements, 0, oldElements.length);
        System.arraycopy(
                newElements, 0, dexElements, oldElements.length, newElements.length);
    }
}

可見我們自己的操作對(duì)應(yīng)著DexPathListaddDexPath方法。

在我們自己的expandFieldArray方法最后通過(guò)執(zhí)行jlrField.set(instance, combined);,將合并后的Element[]數(shù)組賦值給DexPathListElement[] dexElements

當(dāng)上述操作完成之后,就要調(diào)用loadClass方法加載dex文件中的類了,這個(gè)方法在ClassLoader類中定義:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
    Class<?> c = findLoadedClass(name);
    if (parent != null) {
        c = parent.loadClass(name, false);
    } else {
        c = findBootstrapClassOrNull(name);
    }
    if (c == null) {
        c = findClass(name);
    }
    return c;            
}

parentContext.getClassLoader所屬的PathClassLoader傳遞,一直從BaseDexClassLoaderClassLoader。

到這里要熟悉應(yīng)用被創(chuàng)建初始化的流程了,這里先不引申過(guò)去,只需要知道這個(gè)parent是BootClassLoader類型。

@Override
protected Class<?> loadClass(String className, boolean resolve)
       throws ClassNotFoundException {
    Class<?> clazz = findLoadedClass(className);

    if (clazz == null) {
        clazz = findClass(className);
    }

    return clazz;
}

如果還找不到,就調(diào)用BaseDexClassLoaderfindClass方法了。

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    Class c = pathList.findClass(name, suppressedExceptions);
    return c;
}

到這里如果還找不到就拋異常出來(lái)了,ClassNotFoundException。

上邊的流程就是各大博客上面說(shuō)的雙親委派機(jī)制,父類先從已經(jīng)加載的類里面找,找不到的話,再?gòu)淖约?code>BaseDexClassLoader的findClass方法里面去找。

2.1.5 問(wèn)題復(fù)盤

到這里,基本就將Dex加載的流程搞清楚了,但是這樣的加載會(huì)導(dǎo)致新加載的Dex不能立即生效,必須重新啟動(dòng)應(yīng)用之后才能生效。針對(duì)這種問(wèn)題,可以將Dex熱更新模塊放到一個(gè)單獨(dú)的進(jìn)程中,當(dāng)Dex加載完畢之后,調(diào)用killProcess方法自殺,然后由另一個(gè)進(jìn)程拉活重啟。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容