Androdi熱修復之路 ——熱修復框架Tinker 源碼分析

關於Tinker的使用和接入請讀者參考https://github.com/Tencent/tinker自行學習,本文主要從源碼的角度分析下Tinker的執行流程,讀本文前需要讀者瞭解Android加載機制和ClassLoader原理,另外這裏先介紹下Tinker的基本修復原理:

Android程序在啓動時會使用ClassLoader加載APK中已存在的類文件,並在內存中生成相應的類入口,程序員出現Bug說明某個類中的代碼存在問題,有兩種選擇修復的方式一種修改原來的類,這種很難實現另一種是用沒問題的類去覆蓋或替換有Bug的類文件,目前的修復框架基本使用次方案,具體詳解參見QQ超級補丁方案,下面帶着這些基礎知識進入Tinker的源碼分析。

1、Tinker初始化

  • Application
@Override
	public void onBaseContextAttached(Context base) {
		super.onBaseContextAttached(base);
		TinkerInstaller.install(this);
	}

public static Tinker install(ApplicationLike applicationLike) {
        Tinker tinker = new Tinker.Builder(applicationLike.getApplication()).build();
        Tinker.create(tinker);
        tinker.install(applicationLike.getTinkerResultIntent());
        return tinker;
    }

在Application中調用TinkerInstaller.install()初始化,在install()中使用Tinker.Builder()創建Tinker對象,然後調用create()設置Tinker對外單例提供,

  • Tinker.Builder
 
            this.context = context;
            this.mainProcess = TinkerServiceInternals.isInMainProcess(context);
            this.patchProcess = TinkerServiceInternals.isInTinkerPatchServiceProcess(context);
            this.patchDirectory = SharePatchFileUtil.getPatchDirectory(context);
            if (this.patchDirectory == null) {
                TinkerLog.e(TAG, "patchDirectory is null!");
                return;
            }
            this.patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath());
            this.patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory.getAbsolutePath());
        }

 public Tinker build() {
            if (loadReporter == null) {
                loadReporter = new DefaultLoadReporter(context);
            }
            if (patchReporter == null) {
                patchReporter = new DefaultPatchReporter(context);
            }
            if (listener == null) {
                listener = new DefaultPatchListener(context);
            }
            if (tinkerLoadVerifyFlag == null) {
                tinkerLoadVerifyFlag = false;
            }

            return new Tinker(context, status, loadReporter, patchReporter, listener, patchDirectory,
                patchInfoFile, patchInfoLockFile, mainProcess, patchProcess, tinkerLoadVerifyFlag);
        }

在TinkerBuiler的構造函數中主要完成了Tinker所需要的文件和文件夾的初始化,在build()中初始化loadReporter、patchReporter、listener,這些屬性在Tinker的加載過程中至關重要。

2、下載補丁合成Patch包

  • 在程序中獲取到修復補丁包後開始加載
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),
      Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch.patch");
  1. 通過網絡下載或本地保存的方式獲取補丁包
  2. 調用TinkerInstaller.onReceiveUpgradePatch()傳入補丁路徑,通知Tinker有新的補丁包需要合成
  • TinkerInstaller.onReceiveUpgradePatch()
public static void onReceiveUpgradePatch(Context context, String patchLocation) {
    Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
}
  1. TinkerInstaller方法中直接調用getPatcherListener()獲取Tinker中設置的PatchListener實例,此處獲得的是在TinkerBuilder中默認的DefaultPatchListener,具體的設置過程在加載補丁包時說明;
  • onPatchReceived(String path)
public int onPatchReceived(String path) {
    File patchFile = new File(path); //創建補丁包文件
    //(1)、
    int returnCode = patchCheck(path, SharePatchFileUtil.getMD5(patchFile)); // ————————檢查的細節
    //(2)
    if (returnCode == ShareConstants.ERROR_PATCH_OK) {
        TinkerPatchService.runPatchService(context, path);//啓動 TinkerPatchService
    } else { //(3)、校驗是失敗調用LoadReporter通知失敗
        Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
    }
    return returnCode;
}

onPatchReceived()中執行以下操作:

  1. 獲取補丁包文件後,檢查Tinker的配置和path的合法性,主要檢測是否開啓Tinker、Tinker的服務是否可用、服務是否運行;
  2. 檢查通過則啓動服務,否則調用默認的LoadReporter通知檢車失敗
  • TinkerPatchService.runPatchService()
public static void runPatchService(Context context, String path) {
       TinkerLog.i(TAG, "run patch service...");
		Intent intent = new Intent(context, TinkerPatchService.class);
		intent.putExtra(PATCH_PATH_EXTRA, path);
		intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
		try {
			enqueueWork(context, TinkerPatchService.class, JOB_ID, intent);
		} catch (Throwable thr) {
			TinkerLog.e(TAG, "run patch service fail, exception:" + thr);
		}
    }

在runPatchService()中創建Intent保存補丁路徑,調用enqueueWork()方法啓動TinkerPatchService服務,enqueueWork()在TinkerJobIntentService類中,enqueueWork()中根據Android版本的不同分別使用JobSchedule和PowerManager執行服務

public static void enqueueWork(@NonNull Context context, @NonNull ComponentName component,
                                   int jobId, @NonNull Intent work) {
        synchronized (sLock) {
            WorkEnqueuer we = getWorkEnqueuer(context, component, true, jobId);
            we.ensureJobId(jobId);
            we.enqueueWork(work);
        }
    }

 static WorkEnqueuer getWorkEnqueuer(Context context, ComponentName cn, boolean hasJobId,
                                        int jobId) {
        WorkEnqueuer we = sClassWorkEnqueuer.get(cn); // 從緩存中獲取任務
        if (we == null) {
            if (Build.VERSION.SDK_INT >= 26) {//SDK > 26 使用JobSchedule執行服務
                we = new JobWorkEnqueuer(context, cn, jobId);
            } else {
                we = new CompatWorkEnqueuer(context, cn);
            }
            sClassWorkEnqueuer.put(cn, we);
        }
        return we;
    }

使用JobSchedule或PowerManager啓動TinkerPatchService後,程序最終執行將調用TinkerPatchService.onHandleWork()

@Override
protected void onHandleWork(Intent intent) {
   //(1)進程保活
   increasingPriority();
   doApplyPatch(this, intent); //(2)執行解析
}

onHandleWork()中做了兩件大事:

  1. 進程保活,爲了讓程序能在後臺加載補丁完成,必須讓進程在後臺保留一段時間
  2. 調用doApplyPatch()解析補丁包,獲取補丁包中的代碼和資源;
  • doApplyPatch()
private static void doApplyPatch(Context context, Intent intent) {
   Tinker tinker = Tinker.with(context);
   tinker.getPatchReporter().onPatchServiceStart(intent); //(1)
   String path = getPatchPathExtra(intent); //(2)
   
   File patchFile = new File(path); //(3)創建patch文件
   //開始時間
   long begin = SystemClock.elapsedRealtime();
   //(3)
   PatchResult patchResult = new PatchResult();
   try {
      //(4)執行Patch文件解析,此處的upgradePatchProcessor是UpgradePatch的實例
      //tryPath中執行文件的複製並返回結果,主要工作都在這
      result = upgradePatchProcessor.tryPatch(context, path, patchResult);
   } catch (Throwable throwable) {
      tinker.getPatchReporter().onPatchException(patchFile, e); //通知異常
   }
   cost = SystemClock.elapsedRealtime() - begin;
   tinker.getPatchReporter().onPatchResult(patchFile, result, cost); //通知解析花費的時長
   //(5)保存結果信息在patchResult中
   patchResult.isSuccess = result;
   patchResult.rawPatchFilePath = path;
   patchResult.costTime = cost;
   patchResult.e = e;
   //(3)
   AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
   sIsPatchApplying.set(false);
}

doApplyPatch()中幹了跟多重要的事情:

  1. 調用PathReporter通知服務開始執行
  2. 從intent中獲取傳入的Patch路徑並獲取補丁包文件
  3. 創建PatchResult實例,保存執行解析的狀態和結果
  4. 調用tryPatch()執行文件的檢查和複製等操作,此處的upgradePatchProcessor是UpgradePatch的實例
  5. 在保tryPatch()中解析的結果信息都會保存在patchResult中
  6. 從Intent中獲取設置的DefaultTinkerResultService類名,使用JobSchedule執行服務
  • tryPatch():真正執行patch的地方,會在dataDir文件夾下創建dataDir/tinker/patch-xxx/patch-xxx.apk,並將要加載的Path文件內容複製到其中
 @Override
    public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
        Tinker manager = Tinker.with(context);
        final File patchFile = new File(tempPatchPath);
        //(1)創建檢查器檢查補丁的簽名和TinkerId
        ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context);
        int returnCode = ShareTinkerInternals.checkTinkerPackage(context, manager.getTinkerFlags(), patchFile, signatureCheck);
        //(2)從File文件中讀取字節流,並將字節流轉換成MD5字符串
        String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
        //(3)保存Md5 的字符串作爲本次補丁文件的版本代號
        patchResult.patchVersion = patchMd5;
         //獲取的是dataDir/tinker文件路徑(/data/data/com.xxxx.sample/tinker)
        final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();
        ////(4)在tinker文件夾下創建info.lock文件
        File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory);   
        //(5)在tinker文件夾下創建patch.info文件 
        File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory);
        //(6)讀取文件信息,判斷當前是否存在舊的補丁程序,(每次添加補丁包則會在patch.info和info.lock保存補丁信息)
        SharePatchInfo oldInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
        SharePatchInfo newInfo; //(7)創建SharePatchInfo保存新、舊的補丁版本信息
        if (oldInfo != null) { //存在舊的補丁包,處理
            //在新的newInfo中保存舊的版本信息
            newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5, false, Build.FINGERPRINT, finalOatDir);
        } else {//第一次執行補丁包
            newInfo = new SharePatchInfo("", patchMd5, false, Build.FINGERPRINT, ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH);
        }
        final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5); //創建 patch-xxx 文件名
        final String patchVersionDirectory = patchDirectory + "/" + patchName; // 創建tinker/patch-xxx 文件
         //(8)創建 dataDir/tinker/patch-xxx/patch-xxx.apk文件,將patch文件複製其中
        File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5));
        try {
            // 校驗patch-xxx.apk中生成和的MD5和 patchMd5是否一致,側面檢查內容是否已經複製過此補丁包
            if (!patchMd5.equals(SharePatchFileUtil.getMD5(destPatchFile))) {
                //(9)執行文件的複製
                SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);
            }
        }
        return true;
    }

ShareTinkerInternals.checkTinkerPackage()中主要執行和校驗一下內容:

  1. 檢驗patch的簽名文件,將patch包下的以meta.txt結尾的文件(dex_meta.txt、package_meta.txt、res_meta.txt),以文件名爲Key、內容中的字符串爲Value 保存在ShareSecurityCheck的metaContentMap中
public boolean verifyPatchMetaSignature(File path) {
    JarFile jarFile = null;
    try {
        jarFile = new JarFile(path);
        final Enumeration<JarEntry> entries = jarFile.entries(); //獲取JarFile中的所有文件
        while (entries.hasMoreElements()) { // 遍歷文件
            JarEntry jarEntry = entries.nextElement();
            final String name = jarEntry.getName();
            if (name.startsWith("META-INF/")) {
                continue;
            }
            if (!name.endsWith(ShareConstants.META_SUFFIX)) { // 查找後綴爲meta.txt文件
                continue;
            }
            metaContentMap.put(name, SharePatchFileUtil.loadDigestes(jarFile, jarEntry)); //讀取meta.txt中的內容保存
            Certificate[] certs = jarEntry.getCertificates();
        }
    }
    return true;
}
  1. 讀取package_meta.text中的Tinker和Patch的版本信息,並將屬性保存在ShareSecurityCheck的packageProperties中
// package_meta.txt 中的信息
* #base package config field
* #Tue Jan 22 18:16:52 CST 2019
* platform=all
* NEW_TINKER_ID=tinker_id_1.0
* TINKER_ID=tinker_id_1.0
* patchMessage=tinker is sample to use
* patchVersion=1.0

public HashMap<String, String> getPackagePropertiesIfPresent() {
    String property = metaContentMap.get(ShareConstants.PACKAGE_META_FILE); //從metaContentMap中獲取package_meta.txt
    String[] lines = property.split("\n");
    for (final String line : lines) {
        final String[] kv = line.split("=", 2);  //Key、Value保存屬性信息
        packageProperties.put(kv[0].trim(), kv[1].trim());
    }
    return packageProperties;
}
  1. 執行校驗patch 的TinkerId和 註冊清單中的Id是否一致,不一致拋出異常
  2. 檢驗是否支持dex、so、resource修復

在執行checkTinkerPackage()檢查OK後繼續執行以下流程(具體見代碼註釋):
5. 從File文件中讀取字節流,並將字節流轉換成MD5字符串,此MD5字符串就作爲補丁的版本號
6. 在dataDir/tinker文件路徑下創建info.lock文件、patch.info文件
7. 讀取文件信息,判斷當前是否存在舊的補丁程序,創建SharePatchInfo保存新、舊的補丁版本信息
8. 創建 dataDir/tinker/patch-xxx/patch-xxx.apk文件,將patch文件即補丁包複製其中
9. 校驗patch-xxx.apk中生成和的MD5和 patchMd5是否一致,側面檢查內容是否已經複製過此補丁包

  • 複製好補丁包後對補丁包的處理(dex、Resource、SO),此處以dex的修復加載爲例,調用DexDiffPatchInternal.tryRecoverDexFiles()加載dex文件
if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
    return false;
}
  • DexDiffPatchInternal.tryRecoverDexFiles()
protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context,
                                            String patchVersionDirectory, File patchFile) {
    String dexMeta = checker.getMetaContentMap().get(DEX_META_FILE); //
    boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile);
    return result;
}

首先從checker的metaContentMa中獲取assets/dex_meta.txt中內容,由上面的分析知道,metaContentMap中保存着meta.txt文件中的信息,這裏獲取其中的assets/dex_meta.txt中內容,dex_meta中記載着此次修改的補丁中包好的.dex文件

  • 解析meta中的dex文件,並執行對比合並
private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile) {
    String dir = patchVersionDirectory + "/" + DEX_PATH + "/;   //創建tinker/patch-xxxx/dex/
   
    if (!extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)) {//解析assets/dex_meta.txt信息
      return false;
    }
    File dexFiles = new File(dir);
    File[] files = dexFiles.listFiles(); //遍歷patch-xxxx/dex/中修復文件列表,找出其中dex、jar、apk的文件 
    List<File> legalFiles = new ArrayList<>();
    if (files != null) {
        for (File file : files) {
            final String fileName = file.getName();
            if (file.isFile()
                &&  (fileName.endsWith(ShareConstants.DEX_SUFFIX)
                  || fileName.endsWith(ShareConstants.JAR_SUFFIX)
                  || fileName.endsWith(ShareConstants.PATCH_SUFFIX))
            ) {
                legalFiles.add(file); //legalFiles中保存所有的修復文件
            }
        }
    }
    final String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/; //創建tinker/patch-xxxx/odex/
    return dexOptimizeDexFiles(context, legalFiles, optimizeDexDirectory, patchFile); ????
}

在patchDexExtractViaDexDiff()中,首先創建tinker/patch-xxxx/dex/文件夾,然後解析meta中的信息保存在此文件中,然後遍歷dex文件找出其中的dex、jar、apk文件保存在legalFiles集合中,然後執行dexOptimizeDexFiles()方法,在此先看一下如何解析meta信息的;

  • extractDexDiffInternals()
private static boolean extractDexDiffInternals(Context context, String dir, String meta, File patchFile, int type) {
    patchList.clear();
    ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, patchList); // 遍歷meta中的信息,並封裝在patchList集合中
    // 創建 tinker/patch-xxxx/dex/ 目錄
    File directory = new File(dir);
    
    Tinker manager = Tinker.with(context);
    try {
        ApplicationInfo applicationInfo = context.getApplicationInfo();
        String apkPath = applicationInfo.sourceDir;   //獲取apk的路徑
        apk = new ZipFile(apkPath);   //創建apk的ZipFile
        patch = new ZipFile(patchFile); //創建補丁包 patch-xxx.apk的Zip文件
        
        for (ShareDexDiffPatchInfo info : patchList) {  //遍歷patchFile中的文件
            if (infoPath.equals("")) {
                patchRealPath = info.rawName;
            } else {
                patchRealPath = info.path + "/" + info.rawName;
            }
            File extractedFile = new File(dir + info.realName); //在dex目錄下創建每個dexFile
            ZipEntry patchFileEntry = patch.getEntry(patchRealPath); //從補丁包中取出dex文件
            ZipEntry rawApkFileEntry = apk.getEntry(patchRealPath); //從apk中取出dex文件
            patchDexFile(apk, patch, rawApkFileEntry, patchFileEntry, info, extractedFile);//合併dex
   }
}
//dex_meta.txt
classes.dex,,d4a8261c0b1ee8b309ac869fba20e1e1,d4a8261c0b1ee8b309ac869fba20e1e1,dac09804b9bf6728bcca1da866e3e8a4,2491048140,4231045191,jar
test.dex,,56900442eb5b7e1de45449d0685e6e00,56900442eb5b7e1de45449d0685e6e00,0,0,0,jar
//解析dex_meta.txt
public static void parseDexDiffPatchInfo(String meta, ArrayList<ShareDexDiffPatchInfo> dexList) {
    String[] lines = meta.split("\n");
    for (final String line : lines) {
        final String name = kv[0].trim(); //class.dex 
        final String path = kv[1].trim();
        final String destMd5InDvm = kv[2].trim();
        final String destMd5InArt = kv[3].trim();
        final String dexDiffMd5 = kv[4].trim();
        final String oldDexCrc = kv[5].trim();
        final String newDexCrc = kv[6].trim();
        final String dexMode = kv[7].trim();
        ShareDexDiffPatchInfo dexInfo = new ShareDexDiffPatchInfo(name, path, destMd5InDvm, destMd5InArt,
            dexDiffMd5, oldDexCrc, newDexCrc, dexMode);
        dexList.add(dexInfo);
    }
}

總結一下:

  1. 首先執行parseDexDiffPatchInfo()從meta字符串中解析出補丁包的dex文件及其版本信息,將信息封裝在ShareDexDiffPatchInfo的實例中並保存在pathList的集合中
  2. 獲取apk原來的ZipFile和patch的ZipFile
  3. 遍歷pathList集合,對其中每個dex文件在 tinker/patch-xxxx/dex/目錄下創建對應的目標文件
  4. 分別從apk和patch的ZipFile中取出對應的dex文件,執行patchDexFile()通過算法合併寫入目標文件中,此文件即爲加載的修復文件
  • patchDexFile():根據apk和補丁包中的dex文件通過算法對比合並生成修復文件
private static void patchDexFile(ZipFile baseApk, ZipFile patchPkg, ZipEntry oldDexEntry, ZipEntry patchFileEntry,
    ShareDexDiffPatchInfo patchInfo, File patchedDexFile) throws IOException {
    try {
    //獲取apk中要修復文件的輸入流
    InputStream  oldDexStream = new BufferedInputStream(baseApk.getInputStream(oldDexEntry));
    //獲取補丁包中修復文件的輸入流
    InputStream  patchFileStream = (patchFileEntry != null ? new BufferedInputStream(patchPkg.getInputStream(patchFileEntry)) : null);
        final boolean isRawDexFile = SharePatchFileUtil.isRawDexFile(patchInfo.rawName); //判斷patch是否是.dex文件
        if (!isRawDexFile || patchInfo.isJarMode) { //處理.jar文件
            ZipOutputStream zos = null;
            try {
                zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(patchedDexFile)));
                zos.putNextEntry(new ZipEntry(ShareConstants.DEX_IN_JAR));
                if (!isRawDexFile) {
                    ZipInputStream zis = null;
                    try {
                        zis = new ZipInputStream(oldDexStream);
                        ZipEntry entry;
                        while ((entry = zis.getNextEntry()) != null) {
                            if (ShareConstants.DEX_IN_JAR.equals(entry.getName())) break;
                        }
                        new DexPatchApplier(zis, patchFileStream).executeAndSaveTo(zos); 
                    } 
                } else {
                    new DexPatchApplier(oldDexStream, patchFileStream).executeAndSaveTo(zos);
                }
            }
        } else {
            new DexPatchApplier(oldDexStream, patchFileStream).executeAndSaveTo(patchedDexFile); //處理.dex文件
        }
    } 
}
  • 合併下發的pathch後,執行DefaultTinkerResultService服務殺死進程在重啓時加載使用補丁
//從Intent中獲取設置的DefaultTinkerResultService類名,使用JibSchedule執行服務
AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));//從Intent中獲取
  • DefaultTinkerResultService
public void onPatchResult(PatchResult result) {
   //停止TinkerPatchService服務進程
   TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
  
   if (result.isSuccess) {
      deleteRawPatchFile(new File(result.rawPatchFilePath)); //rawPatchFilePath爲保存的是傳入時的Patch路徑
      if (checkIfNeedKill(result)) {
         android.os.Process.killProcess(android.os.Process.myPid()); //殺死當前進程
      } 
   }
}

3、重啓時加載補丁包

在獲取和解析補丁包後系統會重啓,在重啓時會執行Application的onBaseContextAttached(),在TinkerApplication的onBaseContextAttached中調用loadTinker(),loadTinker中通過反射執行TinkerLoader的tryLoad()方法:

  • TinkerApplication
 private void onBaseContextAttached(Context base) {
            loadTinker();
    }

private static final String TINKER_LOADER_METHOD   = "tryLoad";
private void loadTinker() {
        try {
            Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader());
            Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class);
            Constructor<?> constructor = tinkerLoadClass.getConstructor();
            tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this);
        } 
    }
  • tryLoad():直接調用tryLoadPatchFilesInternal()
public Intent tryLoad(TinkerApplication app) {
   tryLoadPatchFilesInternal(app, resultIntent);
   return resultIntent;
}
  • tryLoadPatchFilesInternal()
//(1)檢查並獲取工作目錄dataDir/tinker文件
File patchDirectoryFile = SharePatchFileUtil.getPatchDirectory(app);
String patchDirectoryPath = patchDirectoryFile.getAbsolutePath();
//(2)檢查獲取tinker/patch.info 和 info.lock文件
File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath);
File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectoryPath);
//(3)從patch.info 和 info.lock兩個文件中讀取保存的PatchInfo信息
patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
//patch-641e634c
String patchName = SharePatchFileUtil.getPatchVersionDirectory(version);
//獲取 tinker/patch-641e634c
String patchVersionDirectory = patchDirectoryPath + "/" + patchName;
//(4)檢查獲取補丁包目錄 tinker/patch-641e634c
File patchVersionDirectoryFile = new File(patchVersionDirectory);
//(5)檢查獲取保存拷貝內容的 tinker/patch-641e634c/patch-641e634c.apk
final String patchVersionFileRelPath = SharePatchFileUtil.getPatchVersionFile(version);
File patchVersionFile = (patchVersionFileRelPath != null ? new File(patchVersionDirectoryFile.getAbsolutePath(), patchVersionFileRelPath) : null);
//(6) 校驗patch-641e634c.apk文件 (重點 :檢驗簽名)
ShareSecurityCheck securityCheck = new ShareSecurityCheck(app); ////和下發時校驗一樣校驗patch-641e634c.apk
int returnCode = ShareTinkerInternals.checkTinkerPackage(app, tinkerFlag, patchVersionFile, securityCheck); 
//(7)檢查meta文件中記錄的dex文件是否存在,並將結果保存在loadDexList集合中
boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, oatDex, resultIntent);
//處理DexLoader
if (isEnabledForDex) {
   //執行dex文件的加載
   boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA);
 }
}

上面的整個流程主要執行拿到上面解析保存的patch文件,經過系列檢驗後加載合併所有的dex文件,具體細節如下:

  1. 檢查並獲取工作目錄dataDir/tinker文件,並從中獲取tinker/patch.info 和 info.lock文件
  2. 從tinker的信息文件中讀取信息保存在PatchInfo中,獲取插件的版本
  3. 根據補丁版本獲取到前面複製的補丁apk文件
  4. 校驗補丁apk和宿主apk的簽名是否一致
  5. 調用TinkerDexLoader.checkComplete()檢車meta文件中記錄的dex文件,並將結果保存在loadDexList集合中
public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, String oatDir, Intent intentResult) {
        //(1)從metaContentMap中獲取assets/dex_meta.txt文件中的字符串 
        String meta = securityCheck.getMetaContentMap().get(DEX_MEAT_FILE);
        ArrayList<ShareDexDiffPatchInfo> allDexInfo = new ArrayList<>();
        ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, allDexInfo);// (2)解析meta中的信息保存在allDexInfo集合中
        HashMap<String, String> dexes = new HashMap<>();
        for (ShareDexDiffPatchInfo info : allDexInfo) { //(3)遍歷allDexInfo集合
            if (isJustArtSupportDex(info)) {
                continue;
            }
            if (isVmArt && info.rawName.startsWith(ShareConstants.TEST_DEX_NAME)) {
                testInfo = info; //獲取test.dex
            } else if (isVmArt && ShareConstants.CLASS_N_PATTERN.matcher(info.realName).matches()) {
                classNDexInfo.add(info); //保存.jar文件
            } else {
                dexes.put(info.realName, getInfoMd5(info));
                loadDexList.add(info); //保存補丁的文件信息
            }
        }
        if (isVmArt
            && (testInfo != null || !classNDexInfo.isEmpty())) {
            if (testInfo != null) {
                classNDexInfo.add(ShareTinkerInternals.changeTestDexToClassN(testInfo, classNDexInfo.size() + 1));
            }
            dexes.put(ShareConstants.CLASS_N_APK_NAME, "");
        }
        return true;
    }
  1. 執行所有補丁包中dex文件的加載、合併
public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA) {
    //獲取系統加載的ClassLoader
    PathClassLoader classLoader = (PathClassLoader) TinkerDexLoader.class.getClassLoader();
    //在tinker/patch-641e634c/下創建dex文件夾
    String dexPath = directory + "/" + DEX_PATH + "/";
    ArrayList<File> legalFiles = new ArrayList<>();
    for (ShareDexDiffPatchInfo info : loadDexList) {
        //忽略僅支持art的文件
        if (isJustArtSupportDex(info)) {
            continue;
        }
     //遍歷文件集合,
     String path = dexPath + info.realName;
     File file = new File(path);
     legalFiles.add(file); //遍歷集合找到dex/目錄下要加載的File集合
     }
   }
    SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles); //執行ClassLoader加載
}

installDexes()方法中根據不同的版本,調用對應的install()方法執行加載,在install()中使用ClassLoader加載每個文件中的dex文件,然後將獲取到的Elements合併在原本程序類的前面,達到替換原來類文件的目的,關於ClassLoader的合併和加載參見Android熱修復之路(一)——ClassLoader

private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                            File optimizedDirectory) {
    Field pathListField = ShareReflectUtil.findField(loader, "pathList");
    Object dexPathList = pathListField.get(loader); //獲取loader中的pathList
    ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
    //先反射調用dexPathList中的makePathElements()將List<File>解析成Elements數組
    //將獲取到的解析成Elements數組合並原來的數組,然後設置爲dexPathList
    ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
        new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
        suppressedExceptions));
}

整個Tinker的執行流程就到此結束了,也讓我從源碼的角度理解了Tinker的強大,希望本篇能對想深入學習Tinker的同學有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章