騰訊熱修復框架tinker

Tinker分析:

 

什麼是tinker?

Tinker是騰訊出的一款熱修復框架,可以修復代碼,資源文件,so庫,但不能新增四大組件。

熱修復與增量更新的本質區別:增量更新是根據new.apk和old.apk按照bsdiff算法,生成一個patch,然後將patch通過服務端推送,推送給客戶端,客戶端下載patch,再使用bsdiff算法,將patch和old.apk生成新的apk,完成升級。需要重新安裝。

熱修復,是不需要進行重新安裝,所以這就導致了熱修復是不能新增四大組件的。

 

Tinker使用:

目前是2種,一種是直接使用tencent提供的gradleproject依賴的方式,直接項目依賴;另一種是使用命令行手動生成patch.下面就說明本地測試的使用命令行的方式進行的demo:

…………後面再說

 

 

Tinker源碼分析:分2步,首先是生成patch的過程。克隆tencenttinker github源碼,目錄下的module:tinker-patch-cli就是patch工具的代碼。

使用該jar工具的方式,命令行輸入:

java -jar tinker-patch-cli-1.7.7.jar -oldold.apk -new new.apk -config tinker_config.xml -out output

 

private void run(String[] args) {

     …………

       try {

           ReadArgs readArgs = new ReadArgs(args).invoke();//就是生成patch時輸入的命令:java-jar tinker-patch-cli-1.7.7.jar -old old.apk -new new.apk -configtinker_config.xml -out output

           File configFile = readArgs.getConfigFile();// 配置文件,tinker_config.xml

           File outputFile = readArgs.getOutputFile();//

           File oldApkFile = readArgs.getOldApkFile();

           File newApkFile = readArgs.getNewApkFile();

 

           if (oldApkFile == null || newApkFile == null) {

               Logger.e("Missing old apk or new apk file argument");

               goToError();

           } else if (!oldApkFile.exists() || !newApkFile.exists()) {

               Logger.e("Old apk or new apk file does not exist");

               goToError();

           }

 

           if (outputFile == null) {

               outputFile = new File(mRunningLocation, TypedValue.PATH_DEFAULT_OUTPUT);

           }

這3個方法是關鍵,下面進行一一說明。

           loadConfigFromXml(configFile, outputFile,oldApkFile, newApkFile);

            Logger.initLogger(config);

            tinkerPatch();

        }catch (IOException e) {

           e.printStackTrace();

           goToError();

        }finally {

           Logger.closeLogger();

        }

}

 

loadConfigFromXml(configFile,outputFile, oldApkFile, newApkFile);

整個方法就是生成一個config對象,就相當於把tinker_config.xml轉化成一個對象。

 

下面開始具體的patch生成:tinkerPatch();

protected void tinkerPatch() {

       Logger.d("-----------------------Tinker patchbegin-----------------------");

 

       Logger.d(config.toString());

       try {

           //gen patch

           ApkDecoder decoder = new ApkDecoder(config);

           decoder.onAllPatchesStart();

           decoder.patch(config.mOldApkFile, config.mNewApkFile);

           decoder.onAllPatchesEnd();

 

           //gen meta file and version file

            PatchInfo info = new PatchInfo(config);

           info.gen();

 

           //build patch

           PatchBuilder builder = new PatchBuilder(config);

           builder.buildPatch();

 

        }catch (Throwable e) {

           e.printStackTrace();

           goToError();

        }

 

       Logger.d("Tinker patch done, total time cost: %fs",diffTimeFromBegin());

       Logger.d("Tinker patch done, you can go to file to find the output%s", config.mOutFolder);

       Logger.d("-----------------------Tinker patchend-------------------------");

}

 

ApkDecoder:

 publicApkDecoder(Configuration config) throws IOException {

       super(config);

       this.mNewApkDir = config.mTempUnzipNewDir;

       this.mOldApkDir = config.mTempUnzipOldDir;

 

       this.manifestDecoder = new ManifestDecoder(config);

 

       //put meta files in assets

       String prePath = TypedValue.FILE_ASSETS + File.separator;

       dexPatchDecoder = new UniqueDexDiffDecoder(config, prePath +TypedValue.DEX_META_FILE, TypedValue.DEX_LOG_FILE);

       soPatchDecoder = new BsDiffDecoder(config, prePath +TypedValue.SO_META_FILE, TypedValue.SO_LOG_FILE);

       resPatchDecoder = new ResDiffDecoder(config, prePath +TypedValue.RES_META_TXT, TypedValue.RES_LOG_FILE);

        resDuplicateFiles = newArrayList<>();

}

會發現,針對dex文件,so文件和res文件會生成相應的decoder,這些decoder都繼承自BaseDecoder,相當於文件解碼器。這些decoder的主要工作都在抽象方法patch()中實現。

 

 

 

decoder.onAllPatchesStart()和decoder.onAllPatchesEnd()都是空實現,不用分析,下面重點分析:

decoder.patch(config.mOldApkFile, config.mNewApkFile);

 

public boolean patch(File oldFile, File newFile)throws Exception {

       writeToLogFile(oldFile, newFile);//寫入log文件,忽略。

       //check manifest change first

//主要分析1

        manifestDecoder.patch(oldFile, newFile);

//主要分析2

        unzipApkFiles(oldFile, newFile);

//主要分析3

       Files.walkFileTree(mNewApkDir.toPath(), new ApkFilesVisitor(config,mNewApkDir.toPath(), mOldApkDir.toPath(), dexPatchDecoder, soPatchDecoder,resPatchDecoder));

 

       //get all duplicate resource file

       for (File duplicateRes : resDuplicateFiles) {

//           resPatchDecoder.patch(duplicateRes, null);

           Logger.e("Warning: res file %s is also match at dex or librarypattern, "

               + "we treat it as unchanged in the new resource_out.zip",getRelativePathStringToOldFile(duplicateRes));

        }

 

       soPatchDecoder.onAllPatchesEnd();//空實現

       dexPatchDecoder.onAllPatchesEnd();//非空實現,需要分析

       manifestDecoder.onAllPatchesEnd();//空實現

       resPatchDecoder.onAllPatchesEnd();//非空實現,需要分析

 

       //clean resources

       dexPatchDecoder.clean();

       soPatchDecoder.clean();

       resPatchDecoder.clean();

       return true;

    }


主要分析1:先看ManifestDecoder的patch():

@Override

    publicboolean patch(File oldFile, File newFile) throws IOException,TinkerPatchException {

       try {

 //這2個方法涉及到解析編譯後的AndroidManifest.xml和resource.arsc文件,這是一個非常複雜的工程。就不詳細分析了。

           AndroidParser oldAndroidManifest = AndroidParser.getAndroidManifest(oldFile);

           AndroidParser newAndroidManifest =AndroidParser.getAndroidManifest(newFile);

 

           //check minSdkVersion

           int minSdkVersion =Integer.parseInt(oldAndroidManifest.apkMeta.getMinSdkVersion());

 

           if (minSdkVersion < TypedValue.ANDROID_40_API_LEVEL) {

               if (config.mDexRaw) {

                    final StringBuilder sb =new StringBuilder();

                    sb.append("your oldapk's minSdkVersion ")

                      .append(minSdkVersion)

                      .append(" is below14, you should set the dexMode to 'jar', ")

                      .append("otherwise,it will crash at some time");

                   announceWarningOrException(sb.toString());

               }

           }

 

           final String oldXml = oldAndroidManifest.xml.trim();

           final String newXml = newAndroidManifest.xml.trim();

           final boolean isManifestChanged = !oldXml.equals(newXml);

 

           if (!isManifestChanged) {

               Logger.d("\nManifest has no changes, skip rest decodeworks.");

               return false;

           }

 

           // check whether there is any new Android Component and get their names.

            // so far only Activity increment can passchecking.

//不支持新增四大組件。

           final Set<String> incActivities =getIncrementActivities(oldAndroidManifest.activities,newAndroidManifest.activities);

           final Set<String> incServices =getIncrementServices(oldAndroidManifest.services, newAndroidManifest.services);

           final Set<String> incReceivers =getIncrementReceivers(oldAndroidManifest.receivers,newAndroidManifest.receivers);

           final Set<String> incProviders =getIncrementProviders(oldAndroidManifest.providers,newAndroidManifest.providers);

 

           final boolean hasIncComponent = (!incActivities.isEmpty() ||!incServices.isEmpty()

                    || !incProviders.isEmpty()|| !incReceivers.isEmpty());

 

           if (!config.mSupportHotplugComponent && hasIncComponent) {

               announceWarningOrException("manifest was changed, while hot plugcomponent support mode is disabled. "

                        + "Such changeswill not take effect.");

           }

 

           // generate increment manifest.

           if (hasIncComponent) {

               final Document newXmlDoc =DocumentHelper.parseText(newAndroidManifest.xml);

               final Document incXmlDoc = DocumentHelper.createDocument();

 

               final Element newRootNode = newXmlDoc.getRootElement();

               final String packageName =newRootNode.attributeValue(XML_NODEATTR_PACKAGE);

               if (Utils.isNullOrNil(packageName)) {

                   throw newTinkerPatchException("Unable to find package name from manifest: " +newFile.getAbsolutePath());

               }

 

               final Element newAppNode =newRootNode.element(XML_NODENAME_APPLICATION);

 

               final Element incAppNode = incXmlDoc.addElement(newAppNode.getQName());

               copyAttributes(newAppNode, incAppNode);

 

               if (!incActivities.isEmpty()) {

                    final List<Element>newActivityNodes = newAppNode.elements(XML_NODENAME_ACTIVITY);

                    final List<Element>incActivityNodes = getIncrementActivityNodes(packageName, newActivityNodes,incActivities);

                    for (Element node :incActivityNodes) {

                       incAppNode.add(node.detach());

                    }

               }

 

               if (!incServices.isEmpty()) {

                    final List<Element>newServiceNodes = newAppNode.elements(XML_NODENAME_SERVICE);

                   final List<Element>incServiceNodes = getIncrementServiceNodes(packageName, newServiceNodes,incServices);

                    for (Element node :incServiceNodes) {

                       incAppNode.add(node.detach());

                    }

                }

 

               if (!incReceivers.isEmpty()) {

                    final List<Element>newReceiverNodes = newAppNode.elements(XML_NODENAME_RECEIVER);

                    final List<Element>incReceiverNodes = getIncrementReceiverNodes(packageName, newReceiverNodes,incReceivers);

                    for (Element node :incReceiverNodes) {

                       incAppNode.add(node.detach());

                    }

               }

 

               if (!incProviders.isEmpty()) {

                    final List<Element>newProviderNodes = newAppNode.elements(XML_NODENAME_PROVIDER);

                    final List<Element>incProviderNodes = getIncrementProviderNodes(packageName, newProviderNodes,incProviders);

                    for (Element node :incProviderNodes) {

                       incAppNode.add(node.detach());

                    }

               }

 

               final File incXmlOutput = new File(config.mTempResultDir,TypedValue.INCCOMPONENT_META_FILE);

               if (!incXmlOutput.exists()) {

                   incXmlOutput.getParentFile().mkdirs();

               }

               OutputStream os = null;

               try {

                    os = newBufferedOutputStream(new FileOutputStream(incXmlOutput));

                    final XMLWriter docWriter =new XMLWriter(os);

                    docWriter.write(incXmlDoc);

                    docWriter.close();

               } finally {

                    Utils.closeQuietly(os);

               }

           }

 

           if (isManifestChanged && !hasIncComponent) {

               Logger.d("\nManifest was changed, while there's no any newcomponents added."

                       + " Make sure ifsuch changes were all you expected.\n");

            }

 

        }catch (ParseException e) {

           e.printStackTrace();

           throw new TinkerPatchException("Parse android manifesterror!");

        }catch (DocumentException e) {

           e.printStackTrace();

           throw new TinkerPatchException("Parse android manifest by dom4jerror!");

        }catch (IOException e) {

           e.printStackTrace();

           throw new TinkerPatchException("Failed to generate incrementmanifest.", e);

        }

 

       return false;

}

主要分析2:unzipApkFiles(oldFile, newFile),

就是一個解壓新舊apk的過程。可以學習到的是,針對apk的解壓步驟。下面是解壓apk的主要代碼:

 publicstatic void unZipAPk(String fileName, String filePath) throws IOException {

       checkDirectory(filePath);//解壓前,先判斷destinationpath是否爲空,爲空的話就新建相關目錄。

 

        ZipFile zipFile = newZipFile(fileName);//apk其實也是一種zip壓縮格式的文件,下面就是java代碼如何解壓zip格式的文件。

第一步:zipfile.entries()得到該zip文件中所有的文件enum。

       Enumeration enumeration = zipFile.entries();

       try {

第二步:遍歷emum,類似於cursor遍歷。

           while (enumeration.hasMoreElements()) {

               ZipEntry entry = (ZipEntry) enumeration.nextElement();

第三步:如果是目錄的話,就需要新建一個目錄。

               if (entry.isDirectory()) {

                    new File(filePath,entry.getName()).mkdirs();

                    continue;

               }

第四步:如果不是目錄,那肯定就是文件,開始進行readwrite過程。無論是inputstream還是outputstream都需要用bufferedstream來裝飾下。

               BufferedInputStream bis = newBufferedInputStream(zipFile.getInputStream(entry));

 

               File file = new File(filePath + File.separator + entry.getName());

 

               File parentFile = file.getParentFile();

               if (parentFile != null && (!parentFile.exists())) {

                    parentFile.mkdirs();

               }

               FileOutputStream fos = null;

               BufferedOutputStream bos = null;

               try {

                    fos = newFileOutputStream(file);

                    bos = newBufferedOutputStream(fos, TypedValue.BUFFER_SIZE);

 

                    byte[] buf = newbyte[TypedValue.BUFFER_SIZE];

                    int len;

                    while ((len = bis.read(buf,0, TypedValue.BUFFER_SIZE)) != -1) {

                        fos.write(buf, 0, len);

                    }

                } finally {

                    if (bos != null) {

                        bos.flush();

                        bos.close();

                    }

                    if (bis != null) {

                        bis.close();

                    }

                }

           }

        }finally {

           if (zipFile != null) {

               zipFile.close();

           }

        }

    }

主要分析3:Files.walkFileTree(Path,FileVisitor)

該方法是NIO中的方法,用於對一個目錄進行遍歷操作,裏面的參數1是一個path,參數2是一個接口FileVisitor.該接口有四個抽象方法,具體使用方法可以查詢百度。

public interface FileVisitor<T> {

//訪問目錄前

   FileVisitResult preVisitDirectory(T var1, BasicFileAttributes var2)throws IOException;

//訪問文件

   FileVisitResult visitFile(T var1, BasicFileAttributes var2) throwsIOException;

//訪問文件失敗

   FileVisitResult visitFileFailed(T var1, IOException var2) throwsIOException;

//訪問目錄後

   FileVisitResult postVisitDirectory(T var1, IOException var2) throwsIOException;

}

在該方法中傳入的是ApkFilesVisitor對象。

class ApkFilesVisitor extendsSimpleFileVisitor<Path> {

       BaseDecoder     dexDecoder;

       BaseDecoder     soDecoder;

       BaseDecoder     resDecoder;

       Configuration   config;

       Path            newApkPath;

       Path            oldApkPath;

 

       ApkFilesVisitor(Configuration config, Path newPath, Path oldPath,BaseDecoder dex, BaseDecoder so, BaseDecoder resDecoder) {

           this.config = config;

           this.dexDecoder = dex;

           this.soDecoder = so;

           this.resDecoder = resDecoder;

           this.newApkPath = newPath;

           this.oldApkPath = oldPath;

        }

 

       @Override

       public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)throws IOException {

 

           Path relativePath = newApkPath.relativize(file);//relative方法就是p1到p2的相對路徑。這裏拿到的是文件到XXXapk這個路徑的相對路徑。

 

           Path oldPath = oldApkPath.resolve(relativePath);//如果relativepath是絕對路徑,那麼直接返回relativepath;否則,將relativepath添加到oldapkpath的後面。

 

            File oldFile = null;

           //is a new file?!

           if (oldPath.toFile().exists()) {如果這個成立,意味着這是一個新增文件。

               oldFile = oldPath.toFile();

           }

           String patternKey = relativePath.toString().replace("\\","/");

//判斷當前訪問的文件是不是classesN.dex文件,這個pattern是從tinker_config.xml中讀出來的。

Xml文件中的註釋

 <!--what dexes in apk are expected to dealwith tinkerPatch-->

        <!--it support * or ? pattern.-->

        <patternvalue="classes*.dex"/>

        <pattern value="assets/secondary-dex-?.jar"/>

 

           if (Utils.checkFileInPattern(config.mDexFilePattern, patternKey)) {

               //also treat duplicate file as unchanged

               if (Utils.checkFileInPattern(config.mResFilePattern, patternKey)&& oldFile != null) {

                   resDuplicateFiles.add(oldFile);

               }

 

               try {

                    dexDecoder.patch(oldFile,file.toFile());//這個就是dexdecoder的實際生成dex patch的操作。

               } catch (Exception e) {

//                    e.printStackTrace();

                    throw newRuntimeException(e);

               }

               return FileVisitResult.CONTINUE;

           }

           if (Utils.checkFileInPattern(config.mSoFilePattern, patternKey)) {

               //also treat duplicate file as unchanged

               if (Utils.checkFileInPattern(config.mResFilePattern, patternKey)&& oldFile != null) {

                   resDuplicateFiles.add(oldFile);

               }

               try {

                    soDecoder.patch(oldFile, file.toFile());//.so庫生成patch

               } catch (Exception e) {

//                    e.printStackTrace();

                    throw newRuntimeException(e);

               }

               return FileVisitResult.CONTINUE;

           }

           if (Utils.checkFileInPattern(config.mResFilePattern, patternKey)) {

               try {

                    resDecoder.patch(oldFile,file.toFile());//resource文件生成patch

               } catch (Exception e) {

//                    e.printStackTrace();

                    throw newRuntimeException(e);

               }

               return FileVisitResult.CONTINUE;

           }

           return FileVisitResult.CONTINUE;

        }

}

 

DexDiffDecoder.java:

主要方法patch()
public boolean patch(final File oldFile, final File newFile) throwsIOException, TinkerPatchException {

       final String dexName = getRelativeDexName(oldFile, newFile);

 

        //first of all, we should check input files if excluded classes were modified.

        Logger.d("Checkfor loader classes in dex: %s", dexName);

 

       try {

           主要分析1:將classes.dex文件轉化成Dex對象,dex對象是根據class.dex文件格式定義的一種數據格式
excludedClassModifiedChecker.checkIfExcludedClassWasModifiedInNewDex(oldFile,newFile);

        }catch (IOException e) {

           throw new TinkerPatchException(e);

        }catch (TinkerPatchException e) {

           if (config.mIgnoreWarning) {

               Logger.e("Warning:ignoreWarning is true, but we found %s",e.getMessage());

           } else {

               Logger.e("Warning:ignoreWarning is false, but we found %s",e.getMessage());

               throw e;

           }

        }catch (Exception e) {

           e.printStackTrace();

        }

 

        //If corresponding new dex was completely deleted, just return false.

        //don't process 0 length dex

        if(newFile == null || !newFile.exists() || newFile.length() == 0) {

           return false;

        }

 

       File dexDiffOut = getOutputPath(newFile).toFile();

 

       final String newMd5 = getRawOrWrappedDexMD5(newFile);

 

       //new add file

        if(oldFile == null || !oldFile.exists() || oldFile.length() == 0) {

           hasDexChanged = true;

//新增的classes.dex文件集合

            copyNewDexAndLogToDexMeta(newFile,newMd5, dexDiffOut);

           return true;

        }

//獲取文件的MD5值。可以學到的是求一個文件的md5的方法。

       final String oldMd5 = getRawOrWrappedDexMD5(oldFile);

 

        if((oldMd5 != null && !oldMd5.equals(newMd5)) || (oldMd5 == null&& newMd5 != null)) {

           hasDexChanged = true;

           if (oldMd5 != null) {

修改了的dex文件集合

               collectAddedOrDeletedClasses(oldFile, newFile);

           }

        }

 

       RelatedInfo relatedInfo = new RelatedInfo();

       relatedInfo.oldMd5 = oldMd5;

       relatedInfo.newMd5 = newMd5;

 

//把相對應的oldfile和newfile做成一個entry

        //collect current old dex file and corresponding new dex file for furtherprocessing.

       oldAndNewDexFilePairList.add(new AbstractMap.SimpleEntry<>(oldFile,newFile));

 

       dexNameToRelatedInfoMap.put(dexName, relatedInfo);

 

       return ;

}

excludedClassModifiedChecker.checkIfExcludedClassWasModifiedInNewDex(oldFile,newFile);

public void checkIfExcludedClassWasModifiedInNewDex(FileoldFile, File newFile) throws IOException, TinkerPatchException {

        if(oldFile == null && newFile == null) {

           throw new TinkerPatchException("both oldFile and newFile arenull.");

        }

 

        oldDex = (oldFile !=null ? new Dex(oldFile) : null);

       newDex = (newFile != null ? new Dex(newFile) : null);

 

       int stmCode = STMCODE_START;

 

       while (stmCode != STMCODE_END) {

           switch (stmCode) {

               /**

                * Check rule:

                * Loader classes must only appear in primary dex and each of them inprimary old dex should keep

                * completely consistent in new primary dex.

                *

                * An error is announced when any of these conditions below is fit:

                * 1. Primary old dex is missing.

                * 2. Primary new dex is missing.

                * 3. There are not any loader classes in primary old dex.

                * 4. There are some new loader classes added in new primary dex.

                * 5. Loader classes in old primary dex are modified, deleted in newprimary dex.

                * 6. Loader classes are found in secondary old dexes.

                * 7. Loader classes are found in secondary new dexes.

                */

               case STMCODE_START: {

//dex中的類是大部分不能做任何修改的,包括添加新類,刪除已有類。如果對類做了修改,但是該類在ignorechangewarning的名單中,那麼是允許的,否則不允許。還有一種錯誤情況是,在tinker_xml中用loader標籤的dex文件,被放在了非主dex中,這樣也會報錯。

                   boolean isPrimaryDex =isPrimaryDex((oldFile == null ? newFile : oldFile));

 

                    if (isPrimaryDex) {

                        if (oldFile == null) {

                            stmCode =STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING;

                        } else if (newFile == null) {

                            stmCode =STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING;

                        } else {

                           dexCmptor.startCheck(oldDex, newDex);//就是對new old dex包進行比較。

                            deletedClassInfos =dexCmptor.getDeletedClassInfos();//刪除的class

                            addedClassInfos =dexCmptor.getAddedClassInfos();//新增的class

                           changedClassInfosMap = new HashMap<>(dexCmptor.getChangedClassDescToInfosMap());//做了更改的class

 

                            // All loaderclasses are in new dex, while none of them in old one.

                            if(deletedClassInfos.isEmpty() && changedClassInfosMap.isEmpty()&& !addedClassInfos.isEmpty()) {

                                stmCode =STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX;

                            } else {

                                if(deletedClassInfos.isEmpty() && addedClassInfos.isEmpty()) {

                                    // classdescriptor is completely matches, see if any contents changes.

                                   ArrayList<String> removeClasses = new ArrayList<>();

                                    for (Stringclassname : changedClassInfosMap.keySet()) {

                                        if(Utils.checkFileInPattern(ignoreChangeWarning, classname)) {

                                           Logger.e("loader class pattern: " + classname + " haschanged, but it match ignore change pattern, just ignore!");

                                           removeClasses.add(classname);

                                        }

                                    }

                                   changedClassInfosMap.keySet().removeAll(removeClasses);

                                    if(changedClassInfosMap.isEmpty()) {

                                        stmCode= STMCODE_END;

                                    } else {

                                        stmCode= STMCODE_ERROR_LOADER_CLASS_CHANGED;

                                    }

                                } else {

                                    stmCode =STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH;

                                }

                            }

                        }

                    } else {

                        Set<Pattern>patternsOfClassDescToCheck = new HashSet<>();

                        for (String patternStr: config.mDexLoaderPattern) {

                           patternsOfClassDescToCheck.add(

                               Pattern.compile(

                                   PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr)

                                )

                           );

                        }

 

                        if (oldDex != null) {

                           oldClassesDescToCheck.clear();

//這裏就是判斷是否存在使用loader標註的class被放在了非主dex中。

                            for (ClassDefclassDef : oldDex.classDefs()) {

                                String desc =oldDex.typeNames().get(classDef.typeIndex);

                                if(Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) {

                                   oldClassesDescToCheck.add(desc);

                                }

                            }

                            if(!oldClassesDescToCheck.isEmpty()) {

                                stmCode =STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX;

                               break;

                            }

                        }

 

                        if (newDex != null) {

                           newClassesDescToCheck.clear();

                            for (ClassDefclassDef : newDex.classDefs()) {

                                String desc =newDex.typeNames().get(classDef.typeIndex);

                                if(Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) {

                                   newClassesDescToCheck.add(desc);

                                }

                            }

                            if(!newClassesDescToCheck.isEmpty()) {

                                stmCode =STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX;

                                break;

                            }

                        }

 

                        stmCode = STMCODE_END;

                   }

                    break;

               }

               case STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING: {

                    throw newTinkerPatchException("old primary dex is missing.");

               }

               case STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING: {

                    throw newTinkerPatchException("new primary dex is missing.");

               }

               case STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX: {

                    throw newTinkerPatchException("all loader classes don't appear in old primarydex.");

               }

               case STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH: {

                    throw newTinkerPatchException(

                        "loader classes inold primary dex are mismatched to those in new primary dex, \n"

                            + "if deletedclasses is not empty, check if your dex division strategy is fine. \n"

                            + "addedclasses: " + Utils.collectionToString(addedClassInfos) + "\n"

                            + "deletedclasses: " + Utils.collectionToString(deletedClassInfos)

                    );

               }

               case STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX: {

                    throw newTinkerPatchException("loader classes are found in old secondary dex. Foundclasses: " + Utils.collectionToString(oldClassesDescToCheck));

               }

               case STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX: {

                    throw newTinkerPatchException("loader classes are found in new secondary dex. Foundclasses: " + Utils.collectionToString(newClassesDescToCheck));

               }

               case STMCODE_ERROR_LOADER_CLASS_CHANGED: {

                    String msg =

                        "some loader classhas been changed in new dex."

                            + " Such thesechanges will not take effect!!"

                            + " relatedclasses: "

                            +Utils.collectionToString(changedClassInfosMap.keySet());

                    throw newTinkerPatchException(msg);

               }

               default: {

                   Logger.e("internal-error: unexpected stmCode.");

                    stmCode = STMCODE_END;

                    break;

               }

           }

        }

}

Dex.java

public Dex(File file) throws IOException {

        if(file == null) {

           throw new IllegalArgumentException("file is null.");

        }

 

        if(FileUtils.hasArchiveSuffix(file.getName())) {

           ZipFile zipFile = null;

           try {

               zipFile = new ZipFile(file);

這種情況是指對dex文件進行了jar打包操作。

               ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME);

               if (entry != null) {

                    InputStream inputStream =null;

                    try {

                        inputStream =zipFile.getInputStream(entry);

                        loadFrom(inputStream,(int) entry.getSize());

                    } finally {

                        if (inputStream !=null) {

                           inputStream.close();

                        }

                    }

               } else {

                    throw newDexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in" + file);

               }

           } finally {

               if (zipFile != null) {

                    try {

                        zipFile.close();

                    } catch (Exception e) {

                        // ignored.

                    }

               }

           }

這種就是未對.dex文件進行jar打包操作的。

        }else if (file.getName().endsWith(".dex")) {

           InputStream in = null;

           try {

               in = new BufferedInputStream(new FileInputStream(file));

               loadFrom(in, (int) file.length());

           } catch (Exception e) {

               throw new DexException(e);

           } finally {

               if (in != null) {

                    try {

                        in.close();

                    } catch (Exception e) {

                        // ignored.

                    }

               }

           }

        } else {

           throw new DexException("unknown output extension: " + file);

        }

}

loadFrom(in, (int)file.length());

這個就是將dex文件以字節流的方式讀入內存中。這個需要理解dex文件解析內容,不做深究。

private void loadFrom(InputStream in, intinitSize) throws IOException {

        byte[] rawData = FileUtils.readStream(in,initSize);

       this.data = ByteBuffer.wrap(rawData);

       this.data.order(ByteOrder.LITTLE_ENDIAN);//大小端問題,dex文件中都是以小端方式放置數據的。

       this.tableOfContents.readFrom(this);

}

 

getRawOrWrappedDexMD5(oldFile)

最終的實現獲取md5的代碼:

/**

     * Getthe md5 for inputStream.

     *This method cost less memory. It read bufLen bytes from the FileInputStreamonce.

     *

     *@param is

     *@param bufLen bytes number read from the stream once.

    *               The less bufLen isthe more times getMD5() method takes. Also the less bufLen is the less memorycost.

     */

    publicstatic String getMD5(final InputStream is, final int bufLen) {

        if(is == null || bufLen <= 0) {

            return null;

        }

       try {

           MessageDigest md = MessageDigest.getInstance("MD5");

           StringBuilder md5Str = new StringBuilder(32);

 

           byte[] buf = new byte[bufLen];

           int readCount = 0;

           while ((readCount = is.read(buf)) != -1) {

               md.update(buf, 0, readCount);

           }

 

           byte[] hashValue = md.digest();

 

           for (int i = 0; i < hashValue.length; i++) {

               md5Str.append(Integer.toString((hashValue[i] & 0xff) + 0x100,16).substring(1));

           }

           return md5Str.toString();

        }catch (Exception e) {

           return null;

        }

}

 

soDecoder.patch(oldFile,file.toFile());//.so庫生成patch

Sodecoder實際類型是BsDiffDecoder,下面看下它的patch()方法

@Override

    publicboolean patch(File oldFile, File newFile) throws IOException,TinkerPatchException {

       //first of all, we should check input files

        if(newFile == null || !newFile.exists()) {

           return false;

        }

       //new add file

       String newMd5 = MD5.getMD5(newFile);

       File bsDiffFile = getOutputPath(newFile).toFile();

 

        if(oldFile == null || !oldFile.exists()) {

           FileOperation.copyFileUsingStream(newFile, bsDiffFile);

           writeLogFiles(newFile, null, null, newMd5);

           return true;

        }

 

       //both file length is 0

        if(oldFile.length() == 0 && newFile.length() == 0) {

           return false;

        }

        if(oldFile.length() == 0 || newFile.length() == 0) {

           FileOperation.copyFileUsingStream(newFile, bsDiffFile);

           writeLogFiles(newFile, null, null, newMd5);

           return true;

        }

 

       //new add file

        String oldMd5 = MD5.getMD5(oldFile);

 

        if(oldMd5.equals(newMd5)) {

           return false;

        }

 

        if(!bsDiffFile.getParentFile().exists()) {

           bsDiffFile.getParentFile().mkdirs();

        }

//直接使用java版的bsdiff算法,生成so庫的patch文件

       BSDiff.bsdiff(oldFile, newFile, bsDiffFile);

//如果文件太大,則直接新增一個file,否則還是按照patch文件製作。

        if(Utils.checkBsDiffFileSize(bsDiffFile, newFile)) {

           writeLogFiles(newFile, oldFile, bsDiffFile, newMd5);

        }else {

           FileOperation.copyFileUsingStream(newFile, bsDiffFile);

           writeLogFiles(newFile, null, null, newMd5);

        }

       return true;

    }

 

resDecoder.patch(oldFile,file.toFile());//resource文件生成patch

@Override

    publicboolean patch(File oldFile, File newFile) throws IOException,TinkerPatchException {

       String name = getRelativePathStringToNewFile(newFile);

 

       //actually, it won't go below

        if(newFile == null || !newFile.exists()) {

            String relativeStringByOldDir =getRelativePathStringToOldFile(oldFile);

           if (Utils.checkFileInPattern(config.mResIgnoreChangePattern,relativeStringByOldDir)) {

               Logger.e("found delete resource: " + relativeStringByOldDir +" ,but it match ignore change pattern, just ignore!");

               return false;

           }

           deletedSet.add(relativeStringByOldDir);

           writeResLog(newFile, oldFile, TypedValue.DEL);

           return true;

        }

 

        FileoutputFile = getOutputPath(newFile).toFile();

 

        if(oldFile == null || !oldFile.exists()) {

//該文件剛好在ignorechangepattern匹配格式中,so 忽略它。

           if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) {

               Logger.e("found add resource: " + name + " ,but it matchignore change pattern, just ignore!");

               return false;

           }

           FileOperation.copyFileUsingStream(newFile, outputFile);

           addedSet.add(name);

           writeResLog(newFile, oldFile, TypedValue.ADD);

           return true;

        }

       //both file length is 0

        if(oldFile.length() == 0 && newFile.length() == 0) {

           return false;

        }

       //new add file

        StringnewMd5 = MD5.getMD5(newFile);

       String oldMd5 = MD5.getMD5(oldFile);

 

       //oldFile or newFile may be 0b length

        if(oldMd5 != null && oldMd5.equals(newMd5)) {

           return false;

        }

//該文件剛好在ignorechangepattern匹配格式中,so 忽略它。

        if(Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) {

           Logger.d("found modify resource: " + name + ", but itmatch ignore change pattern, just ignore!");

           return false;

        }

//如果該文件是manifest文件,也需要忽略,因爲manifest有專門的decoder.

        if(name.equals(TypedValue.RES_MANIFEST)) {

           Logger.d("found modify resource: " + name + ", but it isAndroidManifest.xml, just ignore!");

           return false;

        }

//arsc文件

        if(name.equals(TypedValue.RES_ARSC)) {

           if (AndroidParser.resourceTableLogicalChange(config)) {//這裏面又涉及到arsc文件格式的解析,忽略。

               Logger.d("found modify resource: " + name + ", but it islogically the same as original new resources.arsc, just ignore!");

               return false;

           }

        }

//處理被修改的文件。

       dealWithModifyFile(name, newMd5, oldFile, newFile, outputFile);

       return true;

}

dexPatchDecoder.onAllPatchesEnd()
該方法就是根據前面比對的new apk和old apk結果,把新增的或者修改的.class文件(就是dex文件描述的class文件,對應的數據對象的名稱是Dexclassinfo),寫到一個changed_classes.dex中,這裏面涉及到dex文件格式,dex文件寫入,還有虛擬機指令,非常深入,下面只簡單地過一下流程。

@Override

    publicvoid onAllPatchesEnd() throws Exception {

        if(!hasDexChanged) {

           Logger.d("No dexes were changed, nothing needs to be donenext.");

           return;

        }

        if(config.mIsProtectedApp) {

           generateChangedClassesDexFile();

        }else {

           generatePatchInfoFile();//主要分析這個方法

        }

 

       addTestDex();

}

 

 

 generatePatchInfoFile();
 @SuppressWarnings("NewApi")

   private void generatePatchInfoFile() throws IOException {

       generatePatchedDexInfoFile();

 

        //generateSmallPatchedDexInfoFile is blocked by issue we found in ART environment

        // which indicates that if inline optimizationis done on patched class, some error

        //such as crash, ClassCastException, mistaken string fetching, etc. would happen.

        //

        //Instead, we will log all classN dexes as 'copy directly' in dex-meta, so that

        //tinker patch applying procedure will copy them out and load them in ARTenvironment.

 

       //generateSmallPatchedDexInfoFile();

 

       logDexesToDexMeta();

 

       checkCrossDexMovingClasses();

}

 

generatePatchedDexInfoFile();

@SuppressWarnings("NewApi")

   private void generatePatchedDexInfoFile() {

        //Generate dex diff out and full patched dex if a pair of dex is different.

這個oldAndNewDexFilePairList就是patch()方法中得到的有變化的classesN.dex新舊文件。

       for (AbstractMap.SimpleEntry<File, File> oldAndNewDexFilePair :oldAndNewDexFilePairList) {

           File oldFile = oldAndNewDexFilePair.getKey();

           File newFile = oldAndNewDexFilePair.getValue();

           final String dexName = getRelativeDexName(oldFile, newFile);

           RelatedInfo relatedInfo = dexNameToRelatedInfoMap.get(dexName);

           if (!relatedInfo.oldMd5.equals(relatedInfo.newMd5)) {

               diffDexPairAndFillRelatedInfo(oldFile,newFile, relatedInfo);

           } else {

               // In this case newDexFile is the same as oldDexFile, but we still

               // need to treat it as patched dex file so that the SmallPatchGenerator

                // can analyze which class ofthis dex should be kept in small patch.

               relatedInfo.newOrFullPatchedFile = newFile;

               relatedInfo.newOrFullPatchedMd5 = relatedInfo.newMd5;

           }

        }

}

 

diffDexPairAndFillRelatedInfo(oldFile,newFile, relatedInfo);

private void diffDexPairAndFillRelatedInfo(FileoldDexFile, File newDexFile, RelatedInfo relatedInfo) {

       File tempFullPatchDexPath = new File(config.mOutFolder + File.separator+ TypedValue.DEX_TEMP_PATCH_DIR);

       final String dexName = getRelativeDexName(oldDexFile, newDexFile);

 

       File dexDiffOut = getOutputPath(newDexFile).toFile();

       ensureDirectoryExist(dexDiffOut.getParentFile());

 

       try {

           DexPatchGenerator dexPatchGen = new DexPatchGenerator(oldDexFile,newDexFile);

           dexPatchGen.setAdditionalRemovingClassPatterns(config.mDexLoaderPattern);

 

           logWriter.writeLineToInfoFile(

                    String.format(

                            "Start diffbetween [%s] as old and [%s] as new:",

                           getRelativeStringBy(oldDexFile, config.mTempUnzipOldDir),

                           getRelativeStringBy(newDexFile, config.mTempUnzipNewDir)

                    )

           );

 

           dexPatchGen.executeAndSaveTo(dexDiffOut);最終會調用DexPatchGenerator.javaexecuteAndSaveTo()方法,下面重點分析該方法。

        }catch (Exception e) {

           throw new TinkerPatchException(e);

        }

 

        if(!dexDiffOut.exists()) {

           throw new TinkerPatchException("can not find the diff file:" +dexDiffOut.getAbsolutePath());

        }

 

       relatedInfo.dexDiffFile = dexDiffOut;

       relatedInfo.dexDiffMd5 = MD5.getMD5(dexDiffOut);

       Logger.d("\nGen %s patch file:%s, size:%d, md5:%s", dexName,relatedInfo.dexDiffFile.getAbsolutePath(), relatedInfo.dexDiffFile.length(),relatedInfo.dexDiffMd5);

 

       File tempFullPatchedDexFile = new File(tempFullPatchDexPath, dexName);

        if(!tempFullPatchedDexFile.exists()) {

           ensureDirectoryExist(tempFullPatchedDexFile.getParentFile());

        }

 

       try {

           new DexPatchApplier(oldDexFile,dexDiffOut).executeAndSaveTo(tempFullPatchedDexFile);

 

           Logger.d(

                    String.format("Verifyingif patched new dex is logically the same as original new dex: %s ...",getRelativeStringBy(newDexFile, config.mTempUnzipNewDir))

           );

 

           Dex origNewDex = new Dex(newDexFile);

           Dex patchedNewDex = new Dex(tempFullPatchedDexFile);

           checkDexChange(origNewDex, patchedNewDex);

 

           relatedInfo.newOrFullPatchedFile = tempFullPatchedDexFile;

           relatedInfo.newOrFullPatchedMd5 = MD5.getMD5(tempFullPatchedDexFile);

        }catch (Exception e) {

           e.printStackTrace();

           throw new TinkerPatchException(

                    "Failed to generatetemporary patched dex, which makes MD5 generating procedure of new dex failed,either.", e

           );

        }

 

        if(!tempFullPatchedDexFile.exists()) {

           throw new TinkerPatchException("can not find the temporary fullpatched dex file:" + tempFullPatchedDexFile.getAbsolutePath());

        }

       Logger.d("\nGen %s for dalvik full dex file:%s, size:%d,md5:%s", dexName, tempFullPatchedDexFile.getAbsolutePath(),tempFullPatchedDexFile.length(), relatedInfo.newOrFullPatchedMd5);

}

executeAndSaveTo(OutputStream out):

public void executeAndSaveTo(OutputStream out)throws IOException {

        //Firstly, collect information of items we want to remove additionally

        //in new dex and set them to corresponding diff algorithm implementations.

// 哪些文件的變化應該忽略,不應該添加到patch中。

       Pattern[] classNamePatterns = new Pattern[this.additionalRemovingClassPatternSet.size()];

       int classNamePatternCount = 0;

       for (String regExStr : this.additionalRemovingClassPatternSet) {

           classNamePatterns[classNamePatternCount++] = Pattern.compile(regExStr);

        }

 

       List<Integer> typeIdOfClassDefsToRemove = newArrayList<>(classNamePatternCount);

       List<Integer> offsetOfClassDatasToRemove = newArrayList<>(classNamePatternCount);

       for (ClassDef classDef : this.newDex.classDefs()) {

//拿到dex文件中的所有類定義,判斷是否在上面得到的忽略list

           String typeName = this.newDex.typeNames().get(classDef.typeIndex);

           for (Pattern pattern : classNamePatterns) {

               if (pattern.matcher(typeName).matches()) {

                    typeIdOfClassDefsToRemove.add(classDef.typeIndex);

                   offsetOfClassDatasToRemove.add(classDef.classDataOffset);

                    break;

               }

           }

        }

 

       ((ClassDefSectionDiffAlgorithm) this.classDefSectionDiffAlg)

                .setTypeIdOfClassDefsToRemove(typeIdOfClassDefsToRemove);

       ((ClassDataSectionDiffAlgorithm) this.classDataSectionDiffAlg)

               .setOffsetOfClassDatasToRemove(offsetOfClassDatasToRemove);

 

//下面開始就是根據dex數據格式,比較新的dex文件和舊的dex文件,根據區別生成dex patch,算法複雜,需要對dex文件格式非常精通。

        //Then, run diff algorithms according to sections' dependencies.

 

        //Use size calculated by algorithms above or from dex file definition to

        //calculate sections' offset and patched dex size.

 

        //Calculate header and id sections size, so that we can work out

        //the base offset of typeLists Section.

       int patchedheaderSize = SizeOf.HEADER_ITEM;

       int patchedStringIdsSize = newDex.getTableOfContents().stringIds.size *SizeOf.STRING_ID_ITEM;

       int patchedTypeIdsSize = newDex.getTableOfContents().typeIds.size *SizeOf.TYPE_ID_ITEM;

 

        //Although simulatePatchOperation can calculate this value, since protoIdssection

        //depends on typeLists section, we can't run protoIds Section'ssimulatePatchOperation

        //method so far. Instead we calculate protoIds section's size using informationin newDex

        //directly.

       int patchedProtoIdsSize = newDex.getTableOfContents().protoIds.size *SizeOf.PROTO_ID_ITEM;

 

       int patchedFieldIdsSize = newDex.getTableOfContents().fieldIds.size *SizeOf.MEMBER_ID_ITEM;

       int patchedMethodIdsSize = newDex.getTableOfContents().methodIds.size *SizeOf.MEMBER_ID_ITEM;

       int patchedClassDefsSize = newDex.getTableOfContents().classDefs.size *SizeOf.CLASS_DEF_ITEM;

 

       int patchedIdSectionSize =

               patchedStringIdsSize

                        + patchedTypeIdsSize

                        + patchedProtoIdsSize

                        + patchedFieldIdsSize

                        + patchedMethodIdsSize

                        + patchedClassDefsSize;

 

       this.patchedHeaderOffset = 0;

 

        //The diff works on each sections obey such procedure:

       //  1. Execute diff algorithms tocalculate indices of items we need to add, del and replace.

       //  2. Execute patch algorithmsimulation to calculate indices and offsets mappings that is

       //  necessary to next section'sdiff works.

 

        //Immediately do the patch simulation so that we can know:

       //  1. Indices and offsets mappingbetween old dex and patched dex.

       //  2. Indices and offsets mappingbetween new dex and patched dex.

        //These information will be used to do next diff works.

       this.patchedStringIdsOffset = patchedHeaderOffset + patchedheaderSize;

        if(this.oldDex.getTableOfContents().stringIds.isElementFourByteAligned) {

           this.patchedStringIdsOffset

                    = SizeOf.roundToTimesOfFour(this.patchedStringIdsOffset);

        }

       this.stringDataSectionDiffAlg.execute();

       this.patchedStringDataItemsOffset = patchedheaderSize +patchedIdSectionSize;

        if(this.oldDex.getTableOfContents().stringDatas.isElementFourByteAligned) {

           this.patchedStringDataItemsOffset

                    =SizeOf.roundToTimesOfFour(this.patchedStringDataItemsOffset);

        }

       this.stringDataSectionDiffAlg.simulatePatchOperation(this.patchedStringDataItemsOffset);

 

       this.typeIdSectionDiffAlg.execute();

       this.patchedTypeIdsOffset = this.patchedStringIdsOffset +patchedStringIdsSize;

        if(this.oldDex.getTableOfContents().typeIds.isElementFourByteAligned) {

           this.patchedTypeIdsOffset

                    =SizeOf.roundToTimesOfFour(this.patchedTypeIdsOffset);

        }

       this.typeIdSectionDiffAlg.simulatePatchOperation(this.patchedTypeIdsOffset);

 

       this.typeListSectionDiffAlg.execute();

       this.patchedTypeListsOffset

               = patchedheaderSize

               + patchedIdSectionSize

               + this.stringDataSectionDiffAlg.getPatchedSectionSize();

        if(this.oldDex.getTableOfContents().typeLists.isElementFourByteAligned) {

           this.patchedTypeListsOffset

                    =SizeOf.roundToTimesOfFour(this.patchedTypeListsOffset);

        }

       this.typeListSectionDiffAlg.simulatePatchOperation(this.patchedTypeListsOffset);

 

       this.protoIdSectionDiffAlg.execute();

       this.patchedProtoIdsOffset = this.patchedTypeIdsOffset +patchedTypeIdsSize;

        if(this.oldDex.getTableOfContents().protoIds.isElementFourByteAligned) {

           this.patchedProtoIdsOffset = SizeOf.roundToTimesOfFour(this.patchedProtoIdsOffset);

        }

       this.protoIdSectionDiffAlg.simulatePatchOperation(this.patchedProtoIdsOffset);

 

       this.fieldIdSectionDiffAlg.execute();

       this.patchedFieldIdsOffset = this.patchedProtoIdsOffset +patchedProtoIdsSize;

        if(this.oldDex.getTableOfContents().fieldIds.isElementFourByteAligned) {

           this.patchedFieldIdsOffset =SizeOf.roundToTimesOfFour(this.patchedFieldIdsOffset);

        }

       this.fieldIdSectionDiffAlg.simulatePatchOperation(this.patchedFieldIdsOffset);

 

       this.methodIdSectionDiffAlg.execute();

       this.patchedMethodIdsOffset = this.patchedFieldIdsOffset +patchedFieldIdsSize;

        if(this.oldDex.getTableOfContents().methodIds.isElementFourByteAligned) {

           this.patchedMethodIdsOffset =SizeOf.roundToTimesOfFour(this.patchedMethodIdsOffset);

        }

       this.methodIdSectionDiffAlg.simulatePatchOperation(this.patchedMethodIdsOffset);

 

       this.annotationSectionDiffAlg.execute();

       this.patchedAnnotationItemsOffset

               = this.patchedTypeListsOffset

               + this.typeListSectionDiffAlg.getPatchedSectionSize();

        if(this.oldDex.getTableOfContents().annotations.isElementFourByteAligned) {

           this.patchedAnnotationItemsOffset

                    =SizeOf.roundToTimesOfFour(this.patchedAnnotationItemsOffset);

        }

       this.annotationSectionDiffAlg.simulatePatchOperation(this.patchedAnnotationItemsOffset);

 

       this.annotationSetSectionDiffAlg.execute();

       this.patchedAnnotationSetItemsOffset

               = this.patchedAnnotationItemsOffset

               + this.annotationSectionDiffAlg.getPatchedSectionSize();

        if(this.oldDex.getTableOfContents().annotationSets.isElementFourByteAligned) {

           this.patchedAnnotationSetItemsOffset

                    =SizeOf.roundToTimesOfFour(this.patchedAnnotationSetItemsOffset);

        }

       this.annotationSetSectionDiffAlg.simulatePatchOperation(

               this.patchedAnnotationSetItemsOffset

        );

 

       this.annotationSetRefListSectionDiffAlg.execute();

       this.patchedAnnotationSetRefListItemsOffset

               = this.patchedAnnotationSetItemsOffset

               + this.annotationSetSectionDiffAlg.getPatchedSectionSize();

        if(this.oldDex.getTableOfContents().annotationSetRefLists.isElementFourByteAligned){

           this.patchedAnnotationSetRefListItemsOffset

                    =SizeOf.roundToTimesOfFour(this.patchedAnnotationSetRefListItemsOffset);

        }

       this.annotationSetRefListSectionDiffAlg.simulatePatchOperation(

               this.patchedAnnotationSetRefListItemsOffset

        );

 

        this.annotationsDirectorySectionDiffAlg.execute();

       this.patchedAnnotationsDirectoryItemsOffset

               = this.patchedAnnotationSetRefListItemsOffset

               + this.annotationSetRefListSectionDiffAlg.getPatchedSectionSize();

        if(this.oldDex.getTableOfContents().annotationsDirectories.isElementFourByteAligned){

           this.patchedAnnotationsDirectoryItemsOffset

                    =SizeOf.roundToTimesOfFour(this.patchedAnnotationsDirectoryItemsOffset);

        }

        this.annotationsDirectorySectionDiffAlg.simulatePatchOperation(

               this.patchedAnnotationsDirectoryItemsOffset

        );

 

       this.debugInfoSectionDiffAlg.execute();

       this.patchedDebugInfoItemsOffset

               = this.patchedAnnotationsDirectoryItemsOffset

               + this.annotationsDirectorySectionDiffAlg.getPatchedSectionSize();

        if(this.oldDex.getTableOfContents().debugInfos.isElementFourByteAligned) {

           this.patchedDebugInfoItemsOffset

                    =SizeOf.roundToTimesOfFour(this.patchedDebugInfoItemsOffset);

        }

       this.debugInfoSectionDiffAlg.simulatePatchOperation(this.patchedDebugInfoItemsOffset);

 

       this.codeSectionDiffAlg.execute();

       this.patchedCodeItemsOffset

               = this.patchedDebugInfoItemsOffset

               + this.debugInfoSectionDiffAlg.getPatchedSectionSize();

        if(this.oldDex.getTableOfContents().codes.isElementFourByteAligned) {

           this.patchedCodeItemsOffset =SizeOf.roundToTimesOfFour(this.patchedCodeItemsOffset);

        }

       this.codeSectionDiffAlg.simulatePatchOperation(this.patchedCodeItemsOffset);

 

       this.classDataSectionDiffAlg.execute();

       this.patchedClassDataItemsOffset

               = this.patchedCodeItemsOffset

               + this.codeSectionDiffAlg.getPatchedSectionSize();

        if(this.oldDex.getTableOfContents().classDatas.isElementFourByteAligned) {

           this.patchedClassDataItemsOffset

                    =SizeOf.roundToTimesOfFour(this.patchedClassDataItemsOffset);

        }

       this.classDataSectionDiffAlg.simulatePatchOperation(this.patchedClassDataItemsOffset);

 

       this.encodedArraySectionDiffAlg.execute();

       this.patchedEncodedArrayItemsOffset

               = this.patchedClassDataItemsOffset

               + this.classDataSectionDiffAlg.getPatchedSectionSize();

        if(this.oldDex.getTableOfContents().encodedArrays.isElementFourByteAligned) {

           this.patchedEncodedArrayItemsOffset

                    =SizeOf.roundToTimesOfFour(this.patchedEncodedArrayItemsOffset);

        }

       this.encodedArraySectionDiffAlg.simulatePatchOperation(this.patchedEncodedArrayItemsOffset);

 

       this.classDefSectionDiffAlg.execute();

       this.patchedClassDefsOffset = this.patchedMethodIdsOffset +patchedMethodIdsSize;

        if(this.oldDex.getTableOfContents().classDefs.isElementFourByteAligned) {

           this.patchedClassDefsOffset =SizeOf.roundToTimesOfFour(this.patchedClassDefsOffset);

        }

 

        //Calculate any values we still know nothing about them.

       this.patchedMapListOffset

               = this.patchedEncodedArrayItemsOffset

               + this.encodedArraySectionDiffAlg.getPatchedSectionSize();

        if(this.oldDex.getTableOfContents().mapList.isElementFourByteAligned) {

           this.patchedMapListOffset =SizeOf.roundToTimesOfFour(this.patchedMapListOffset);

        }

       int patchedMapListSize = newDex.getTableOfContents().mapList.byteCount;

 

       this.patchedDexSize

               = this.patchedMapListOffset

               + patchedMapListSize;

 

        //Finally, write results to patch file.

       writeResultToStream(out);

}

 

雲裏霧裏總算粗略地過了一遍,其實還有resdecoder.onpatchEnd()沒有分析,實在頭大,等把patch合成過程分析完後,再回過頭來看看patch的生成過程,或許會好一些。

 

上面就是patch的生成過程,接下來需要分析patch的合成過程。。。。。。

 

客戶端用法,就不詳述了,需要重寫application,使用tinker提供的applicationlike模板,好吧這又涉及到了一個知識點,gradle模板動態生成代碼,有時間再看。。。。然後在合適的時機調用啓動合成patch的方法:

參數就傳patch文件所在路徑

TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),

               Environment.getExternalStorageDirectory().getAbsolutePath() +"/patch_signed_7zip.apk");

初始化一些基本參數:

public Builder(Context context) {

           if (context == null) {

               throw new TinkerRuntimeException("Context must not be null.");

           }

           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());

           TinkerLog.w(TAG, "tinker patch directory: %s",patchDirectory);

        }

接下來調用DefaultPatchListener.java的onPatchReceived(Stringpath):

@Override

    publicint onPatchReceived(String path) {

需要分析1

        int returnCode = patchCheck(path);

 

        if(returnCode == ShareConstants.ERROR_PATCH_OK) {

需要分析2

           TinkerPatchService.runPatchService(context, path);

        }else {

           Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(newFile(path), returnCode);

        }

       return returnCode;

 

    }

patchCheck():主要做配置檢查。代碼略。

最終的調用會在UpgradePatch.java的tryPatch()
@Override

    publicboolean tryPatch(Context context, String tempPatchPath, PatchResultpatchResult) {

       Tinker manager = Tinker.with(context);

 

        finalFile patchFile = new File(tempPatchPath);

 

        if(!manager.isTinkerEnabled() ||!ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) {

           TinkerLog.e(TAG, "UpgradePatch tryPatch:patch is disabled, justreturn");

           return false;

        }

 

        if(!SharePatchFileUtil.isLegalFile(patchFile)) {

           TinkerLog.e(TAG, "UpgradePatch tryPatch:patch file is not found,just return");

           return false;

        }

簽名檢查,可以學習下

       //check the signature, we should create a new checker

        ShareSecurityChecksignatureCheck = new ShareSecurityCheck(context);

//簽名驗證,還必須有tinkerId,否則會直接報錯。

       int returnCode = ShareTinkerInternals.checkTinkerPackage(context,manager.getTinkerFlags(), patchFile, signatureCheck);

        if(returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) {

           TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchPackageCheckFail");

           manager.getPatchReporter().onPatchPackageCheckFail(patchFile,returnCode);

           return false;

        }

 

       String patchMd5 = SharePatchFileUtil.getMD5(patchFile);

        if(patchMd5 == null) {

           TinkerLog.e(TAG, "UpgradePatch tryPatch:patch md5 is null, justreturn");

           return false;

        }

       //use md5 as version

       patchResult.patchVersion = patchMd5;

 

       TinkerLog.i(TAG, "UpgradePatch tryPatch:patchMd5:%s",patchMd5);

 

        //check ok, we can real recover a newpatch

       final String patchDirectory =manager.getPatchDirectory().getAbsolutePath();

//file

       File patchInfoLockFile =SharePatchFileUtil.getPatchInfoLockFile(patchDirectory);

       File patchInfoFile =SharePatchFileUtil.getPatchInfoFile(patchDirectory);

 

       SharePatchInfo oldInfo =SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);

 

       //it is a new patch, so we should not find a exist

       SharePatchInfo newInfo;

 

        //already have patch

        if(oldInfo != null) {

           if (oldInfo.oldVersion == null || oldInfo.newVersion == null ||oldInfo.oatDir == null) {

               TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchInfoCorrupted");

               manager.getPatchReporter().onPatchInfoCorrupted(patchFile,oldInfo.oldVersion, oldInfo.newVersion);

               return false;

           }

 

           if (!SharePatchFileUtil.checkIfMd5Valid(patchMd5)) {

               TinkerLog.e(TAG,"UpgradePatch tryPatch:onPatchVersionCheckFail md5 %s is valid",patchMd5);

               manager.getPatchReporter().onPatchVersionCheckFail(patchFile, oldInfo,patchMd5);

               return false;

           }

           // if it is interpret now, use changing flag to wait main process

           final String finalOatDir =oldInfo.oatDir.equals(ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH)

               ? ShareConstants.CHANING_DEX_OPTIMIZE_PATH : oldInfo.oatDir;

           newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5,Build.FINGERPRINT, finalOatDir);

        }else {

           newInfo = new SharePatchInfo("", patchMd5, Build.FINGERPRINT,ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH);

        }

 

        //it is a new patch, we first delete if thereis any files

       //don't delete dir for faster retry

//       SharePatchFileUtil.deleteDir(patchVersionDirectory);

       final String patchName =SharePatchFileUtil.getPatchVersionDirectory(patchMd5);

 

       final String patchVersionDirectory = patchDirectory + "/" +patchName;

 

       TinkerLog.i(TAG, "UpgradePatchtryPatch:patchVersionDirectory:%s", patchVersionDirectory);

 

       //copy file

       File destPatchFile = new File(patchVersionDirectory + "/" +SharePatchFileUtil.getPatchVersionFile(patchMd5));

 

       try {

           // check md5 first

           if (!patchMd5.equals(SharePatchFileUtil.getMD5(destPatchFile))) {

               SharePatchFileUtil.copyFileUsingStream(patchFile,destPatchFile);

               TinkerLog.w(TAG, "UpgradePatch copy patch file, src file: %s size:%d, dest file: %s size:%d", patchFile.getAbsolutePath(),patchFile.length(),

                    destPatchFile.getAbsolutePath(),destPatchFile.length());

           }

        }catch (IOException e) {

//           e.printStackTrace();

           TinkerLog.e(TAG, "UpgradePatch tryPatch:copy patch file fail from%s to %s", patchFile.getPath(), destPatchFile.getPath());

           manager.getPatchReporter().onPatchTypeExtractFail(patchFile,destPatchFile, patchFile.getName(), ShareConstants.TYPE_PATCH_FILE);

           return false;

        }

 

        //we use destPatchFile instead of patchFile, because patchFile maybe deleted during the patch process

//開始合併修復dex文件。

tryRecoverDexFiles()→patchDexExtractViaDexDiff()→extractDexDiffInternals()→dexOptimizeDexFiles()

        if(!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context,patchVersionDirectory, destPatchFile)) {

           TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, trypatch dex failed");

           return false;

        }

 

 

 

        if(!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context,patchVersionDirectory, destPatchFile)) {

           TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, trypatch library failed");

           return false;

        }

 

        if(!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck,context, patchVersionDirectory, destPatchFile)) {

           TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, trypatch resource failed");

           return false;

        }

 

        //check dex opt file at last, some phone such as VIVO/OPPO like to change dex2oatto interpreted

        if(!DexDiffPatchInternal.waitAndCheckDexOptFile(patchFile, manager)) {

           TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, checkdex opt file failed");

           return false;

        }

 

        if(!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo,patchInfoLockFile)) {

           TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewritepatch info failed");

           manager.getPatchReporter().onPatchInfoCorrupted(patchFile,newInfo.oldVersion, newInfo.newVersion);

           return false;

        }

 

       TinkerLog.w(TAG, "UpgradePatch tryPatch: done, it is ok");

       return true;

}

獲取安裝包的MD5簽名:

@SuppressLint("PackageManagerGetSignatures")

   private void init(Context context) {

       ByteArrayInputStream stream = null;

       try {

           PackageManager pm = context.getPackageManager();

           String packageName = context.getPackageName();

           PackageInfo packageInfo = pm.getPackageInfo(packageName,PackageManager.GET_SIGNATURES);

           mPublicKeyMd5 =SharePatchFileUtil.getMD5(packageInfo.signatures[0].toByteArray());

           if (mPublicKeyMd5 == null) {

               throw new TinkerRuntimeException("get public key md5 isnull");

           }

        }catch (Exception e) {

           throw new TinkerRuntimeException("ShareSecurityCheck init publickey fail", e);

        }finally {

           SharePatchFileUtil.closeQuietly(stream);

        }

}

 

 

 

 

可以使用jdk中的properties類,實現hashtable和xml文件之間的相互轉換,非常方便。

代碼如下:

 Properties properties = new Properties();

           FileInputStream inputStream = null;

           try {

               inputStream = new FileInputStream(pathInfoFile);

               properties.load(inputStream);//加載文件

               oldVer = properties.getProperty(OLD_VERSION);//根據key獲取相應的值。

               newVer = properties.getProperty(NEW_VERSION);

               lastFingerPrint = properties.getProperty(FINGER_PRINT);

               oatDIr = properties.getProperty(OAT_DIR);

 

 

parseDexDiffPatchInfo():把生成的patch文件中,與dex文件修改相關的信息提取出來。

public static void parseDexDiffPatchInfo(Stringmeta, ArrayList<ShareDexDiffPatchInfo> dexList) {

        if(meta == null || meta.length() == 0) {

           return;

        }

       String[] lines = meta.split("\n");

       for (final String line : lines) {

           if (line == null || line.length() <= 0) {

               continue;

           }

           final String[] kv = line.split(",", 8);

           if (kv == null || kv.length < 8) {

               continue;

           }

 

           // key

            final String name = kv[0].trim();

            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 StringdexMode = kv[7].trim();

 

           ShareDexDiffPatchInfo dexInfo = new ShareDexDiffPatchInfo(name, path,destMd5InDvm, destMd5InArt,

               dexDiffMd5, oldDexCrc, newDexCrc, dexMode);

           dexList.add(dexInfo);

        }

 

    }

 

extractDexDiffInternals()
private static boolean extractDexDiffInternals(Context context, String dir,String meta, File patchFile, int type) {

       //parse

       patchList.clear();

//根據patch生成時的記錄將每一個改動對應的封裝對象加入到patchList中,封裝對象字段包括:

this.rawName= name;

        this.path = path;

        this.destMd5InDvm = destMd5InDvm;

        this.destMd5InArt = destMd5InArt;

        this.dexDiffMd5 = dexDiffMd5;

        this.oldDexCrC = oldDexCrc;

        this.newDexCrC = newDexCrC;

        this.dexMode = dexMode;

        if(dexMode.equals(ShareConstants.DEXMODE_JAR)) {

            this.isJarMode = true;

            if(SharePatchFileUtil.isRawDexFile(name)) {

                realName = name +ShareConstants.JAR_SUFFIX;

            } else {

                realName = name;

            }

        } else if(dexMode.equals(ShareConstants.DEXMODE_RAW)) {

            this.isJarMode = false;

            this.realName = name;

        } else {

            throw newTinkerRuntimeException("can't recognize dex mode:" + dexMode);

        }

       ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, patchList);

 

        if(patchList.isEmpty()) {

            TinkerLog.w(TAG, "extract patchlist is empty! type:%s:", ShareTinkerInternals.getTypeString(type));

           return true;

        }

 

       File directory = new File(dir);

        if(!directory.exists()) {

           directory.mkdirs();

        }

       //I think it is better to extract the raw files from apk

       Tinker manager = Tinker.with(context);

       ZipFile apk = null;

       ZipFile patch = null;

       try {

           ApplicationInfo applicationInfo = context.getApplicationInfo();

           if (applicationInfo == null) {

               // Looks like running on a test Context, so just return withoutpatching.

               TinkerLog.w(TAG, "applicationInfo == null!!!!");

               return false;

           }

 

           String apkPath = applicationInfo.sourceDir;

           apk = new ZipFile(apkPath);

           patch = new ZipFile(patchFile);

           //dir:patch/dex/

           if (checkClassNDexFiles(dir)) {

               TinkerLog.w(TAG, "class n dex file %s is already exist, and md5match, just continue", ShareConstants.CLASS_N_APK_NAME);

               return true;

           }

           for (ShareDexDiffPatchInfo info : patchList) {

               long start = System.currentTimeMillis();

 

                final String infoPath =info.path;

               String patchRealPath;

               if (infoPath.equals("")) {

                    patchRealPath =info.rawName;

               } else {

                    patchRealPath = info.path +"/" + info.rawName;

               }

 

               String dexDiffMd5 = info.dexDiffMd5;

               String oldDexCrc = info.oldDexCrC;

 

               if (!isVmArt && info.destMd5InDvm.equals("0")) {

                    TinkerLog.w(TAG,"patch dex %s is only for art, just continue", patchRealPath);

                    continue;

               }

               String extractedFileMd5 = isVmArt ? info.destMd5InArt :info.destMd5InDvm;

 

               if (!SharePatchFileUtil.checkIfMd5Valid(extractedFileMd5)) {

                    TinkerLog.w(TAG, "metafile md5 invalid, type:%s, name: %s, md5: %s",ShareTinkerInternals.getTypeString(type), info.rawName, extractedFileMd5);

                   manager.getPatchReporter().onPatchPackageCheckFail(patchFile,BasePatchInternal.getMetaCorruptedCode(type));

                    return false;

               }

 

               File extractedFile = new File(dir + info.realName);

 

               //check file whether already exist

               if (extractedFile.exists()) {

                    if(SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {

                        //it is ok, justcontinue

                        TinkerLog.w(TAG,"dex file %s is already exist, and md5 match, just continue",extractedFile.getPath());

                        continue;

                    } else {

                        TinkerLog.w(TAG,"have a mismatch corrupted dex " + extractedFile.getPath());

                        extractedFile.delete();

                    }

               } else {

                   extractedFile.getParentFile().mkdirs();

               }

 

               ZipEntry patchFileEntry = patch.getEntry(patchRealPath);

                ZipEntry rawApkFileEntry =apk.getEntry(patchRealPath);

 

               if (oldDexCrc.equals("0")) {

                    if (patchFileEntry == null){

                        TinkerLog.w(TAG,"patch entry is null. path:" + patchRealPath);

                       manager.getPatchReporter().onPatchTypeExtractFail(patchFile,extractedFile, info.rawName, type);

                        return false;

                    }

 

                    //it is a new file, butmaybe we need to repack the dex file

                    if (!extractDexFile(patch,patchFileEntry, extractedFile, info)) {

                        TinkerLog.w(TAG,"Failed to extract raw patch file " + extractedFile.getPath());

                       manager.getPatchReporter().onPatchTypeExtractFail(patchFile,extractedFile, info.rawName, type);

                        return false;

                    }

               } else if (dexDiffMd5.equals("0")) {

                    // skip process old dex forreal dalvik vm

                   if (!isVmArt) {

                        continue;

                    }

 

                    if (rawApkFileEntry ==null) {

                        TinkerLog.w(TAG,"apk entry is null. path:" + patchRealPath);

                        manager.getPatchReporter().onPatchTypeExtractFail(patchFile,extractedFile, info.rawName, type);

                        return false;

                    }

 

                    //check source crc insteadof md5 for faster

                    String rawEntryCrc =String.valueOf(rawApkFileEntry.getCrc());

                    if(!rawEntryCrc.equals(oldDexCrc)) {

                        TinkerLog.e(TAG,"apk entry %s crc is not equal, expect crc: %s, got crc: %s",patchRealPath, oldDexCrc, rawEntryCrc);

                       manager.getPatchReporter().onPatchTypeExtractFail(patchFile,extractedFile, info.rawName, type);

                        return false;

                    }

 

                    // Small patched dexgenerating strategy was disabled, we copy full original dex directly now.

                    //patchDexFile(apk, patch,rawApkFileEntry, null, info, smallPatchInfoFile, extractedFile);

                    extractDexFile(apk,rawApkFileEntry, extractedFile, info);

 

                    if(!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {

                        TinkerLog.w(TAG,"Failed to recover dex file when verify patched dex: " + extractedFile.getPath());

                       manager.getPatchReporter().onPatchTypeExtractFail(patchFile,extractedFile, info.rawName, type);

                       SharePatchFileUtil.safeDeleteFile(extractedFile);

                        return false;

                    }

               } else {

                    if (patchFileEntry == null){

                        TinkerLog.w(TAG,"patch entry is null. path:" + patchRealPath);

                       manager.getPatchReporter().onPatchTypeExtractFail(patchFile,extractedFile, info.rawName, type);

                        return false;

                    }

 

                    if(!SharePatchFileUtil.checkIfMd5Valid(dexDiffMd5)) {

                        TinkerLog.w(TAG,"meta file md5 invalid, type:%s, name: %s, md5: %s",ShareTinkerInternals.getTypeString(type), info.rawName, dexDiffMd5);

                       manager.getPatchReporter().onPatchPackageCheckFail(patchFile,BasePatchInternal.getMetaCorruptedCode(type));

                        return false;

                    }

 

                    if (rawApkFileEntry ==null) {

                        TinkerLog.w(TAG,"apk entry is null. path:" + patchRealPath);

                       manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile,info.rawName, type);

                        return false;

                    }

                    //check source crc insteadof md5 for faster

                    String rawEntryCrc =String.valueOf(rawApkFileEntry.getCrc());

                    if(!rawEntryCrc.equals(oldDexCrc)) {

                        TinkerLog.e(TAG,"apk entry %s crc is not equal, expect crc: %s, got crc: %s",patchRealPath, oldDexCrc, rawEntryCrc);

                       manager.getPatchReporter().onPatchTypeExtractFail(patchFile,extractedFile, info.rawName, type);

                        return false;

                    }

 

                    patchDexFile(apk, patch,rawApkFileEntry, patchFileEntry, info, extractedFile);

 

                    if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile,extractedFileMd5)) {

                        TinkerLog.w(TAG,"Failed to recover dex file when verify patched dex: " +extractedFile.getPath());

                       manager.getPatchReporter().onPatchTypeExtractFail(patchFile,extractedFile, info.rawName, type);

                       SharePatchFileUtil.safeDeleteFile(extractedFile);

                        return false;

                    }

 

                    TinkerLog.w(TAG,"success recover dex file: %s, size: %d, use time: %d",

                           extractedFile.getPath(), extractedFile.length(),(System.currentTimeMillis() - start));

               }

           }

           if (!mergeClassNDexFiles(context, patchFile, dir)) {

               return false;

           }

        }catch (Throwable e) {

           throw new TinkerRuntimeException("patch " +ShareTinkerInternals.getTypeString(type) + " extract failed (" +e.getMessage() + ").", e);

        }finally {

           SharePatchFileUtil.closeZip(apk);

           SharePatchFileUtil.closeZip(patch);

        }

       return true;

    }


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