您的位置:首页 > 其它

腾讯热修复框架tinker

2018-03-12 22:32 841 查看
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.manifestDeco
1ddc4
der = 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;
               }
第四步:如果不是目录,那肯定就是文件,开始进行read和write过程。无论是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()) {
//该文件刚好在ignorechange的pattern匹配格式中,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;
        }
//该文件刚好在ignorechange的pattern匹配格式中,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.java的executeAndSaveTo()方法,下面重点分析该方法。
        }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;
    }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: