1. 程式人生 > >騰訊熱修復框架tinker

騰訊熱修復框架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);

                   &nbs