TinkerGradlePluginSource

keyTitle_decoder类设计

keyTitle_dexsectiondiffalgorithm类设计

keyTitle_dexsectionpatchalgorithm类设计

build.gradle层

1:构建基准apk(仅仅发版时需要打开tinkerEnabled,开发自己debug时不需要打开)

1.1: Copy apk,map,R文件到bakApk目录下

1.2: 创建tinkerPatchDebug和tinkerPatchRelease的task执行的必要参数

//tinker/tinker-sample-android/app/build.gradle
def bakPath = file("${buildDir}/bakApk/")

/**
 \* you can use assembleRelease to build you base apk
 \* use tinkerPatchRelease -POLD_APK= -PAPPLY_MAPPING= -PAPPLY_RESOURCE= to build patch
 \* add apk from the build/bakApk
 */
 ext {
   //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
   tinkerEnabled = true

   //for normal build
   //old apk file to build patch apk
   tinkerOldApkPath = "${bakPath}/app-debug-0917-16-24-41.apk"
   //proguard mapping file to build patch apk
   tinkerApplyMappingPath = "${bakPath}/app-debug-1018-17-32-47-mapping.txt"
   //resource R.txt to build patch apk, must input if there is resource changed
   tinkerApplyResourcePath = "${bakPath}/app-debug-0424-15-02-56-R.txt"

   //only use for build all flavor, if not, just ignore this field
   tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
 }

if (buildWithTinker()) {
   apply plugin: 'com.tencent.tinker.patch'

   tinkerPatch {
     oldApk = getOldApkPath()
     ignoreWarning = false
     useSign = true
     tinkerEnable = buildWithTinker()
     buildConfig {
       applyMapping = getApplyMappingPath()
       applyResourceMapping = getApplyResourceMappingPath()
 
       tinkerId = getTinkerIdValue()
 
       keepDexApply = false
 
       isProtectedApp = false
 
       supportHotplugComponent = false
     }
 
     dex {
       dexMode = "jar"
       pattern = ["classes*.dex",
            "assets/secondary-dex-?.jar"]
       loader = [
            "tinker.sample.android.app.BaseBuildInfo"
       ]
     }
 
     lib {
       pattern = ["lib/*/*.so"]
     }
 
     res {
       pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
       ignoreChange = ["assets/sample_meta.txt"]
       largeModSize = 100
     }
 
     packageConfig {
       configField("patchMessage", "tinker is sample to use")
       configField("platform", "all")
       configField("patchVersion", "1.0")
     }
     sevenZip {
       zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
     }
   }


android.applicationVariants.all { variant ->
   /**
   \* task type, you want to bak
   */
   def taskName = variant.name

   tasks.all {
     if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
       it.doLast {
         copy {
           def fileNamePrefix = "${project.name}-${variant.baseName}"
           def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"

           def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath

           if (variant.metaClass.hasProperty(variant, 'packageApplicationProvider')) {
             def packageAndroidArtifact = variant.packageApplicationProvider.get()
             if (packageAndroidArtifact != null) {
               try {
                 from new File(packageAndroidArtifact.outputDirectory.getAsFile().get(), variant.outputs.first().apkData.outputFileName)
               } catch (Exception e) {
                 from new File(packageAndroidArtifact.outputDirectory, variant.outputs.first().apkData.outputFileName)
               }
             } else {
               from variant.outputs.first().mainOutputFile.outputFile
             }
           } else {
             from variant.outputs.first().outputFile
           }

           into destPath
           rename { String fileName ->
             fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
           }

           from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
           into destPath
           rename { String fileName ->
             fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
           }

           from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
           from "${buildDir}/intermediates/symbol_list/${variant.dirName}/R.txt"
           into destPath
           rename { String fileName ->
             fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
           }
         }
       }
     }
   }
 }

TinkerPatchPlugin

#tinker/tinker-build/tinker-patch-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.tencent.tinker.patch.properties
implementation-class=com.tencent.tinker.build.gradle.TinkerPatchPlugin
class TinkerPatchPlugin implements Plugin<Project> {
  @Override
 public void apply(Project project) {
 mProject = project
 mProject.extensions.create('tinkerPatch', TinkerPatchExtension)
 mProject.tinkerPatch.extensions.create('buildConfig', TinkerBuildConfigExtension, mProject)
 
 //mProject.tinkerPatch.extensions
 mProject.tinkerPatch.extensions.create('dex', TinkerDexExtension, mProject)
 mProject.tinkerPatch.extensions.create('lib', TinkerLibExtension)
 mProject.tinkerPatch.extensions.create('res', TinkerResourceExtension)
 mProject.tinkerPatch.extensions.create("arkHot", TinkerArkHotExtension)
 mProject.tinkerPatch.extensions.create('packageConfig', TinkerPackageConfigExtension, mProject)
 mProject.tinkerPatch.extensions.create('sevenZip', TinkerSevenZipExtension, mProject)

android.applicationVariants.all { variant ->
   def variantOutput = variant.outputs.first()
   def variantName = variant.name.capitalize()
   def variantData = variant.variantData

    //创建task: tinkerPatchDebug或tinkerPatchRelease, main
    TinkerPatchSchemaTask tinkerPatchBuildTask = mProject.tasks.create("tinkerPatch${variantName}", TinkerPatchSchemaTask)

    //创建task: tinkerProcessDebugManifest,main
    // Create a task to add a build TINKER_ID to AndroidManifest.xml
    // This task must be called after "process${variantName}Manifest", since it
    // requires that an AndroidManifest.xml exists in `build/intermediates`.
    TinkerManifestTask manifestTask = mProject.tasks.create("tinkerProcess${variantName}Manifest", TinkerManifestTask)
    tinkerPatchBuildTask.signConfig = variantData.variantConfiguration.signingConfig
 
    variant.outputs.each { output ->
       setPatchNewApkPath(configuration, output, variant, tinkerPatchBuildTask)
       setPatchOutputFolder(configuration, output, variant, tinkerPatchBuildTask)
    }

    manifestTask.mustRunAfter variantOutput.processManifest
    variantOutput.processResources.dependsOn manifestTask
    //main
    TinkerResourceIdTask applyResourceTask = mProject.tasks.create("tinkerProcess${variantName}ResourceId", TinkerResourceIdTask)
    applyResourceTask.resDir = variantOutput.processResources.inputResourcesDir.getFiles().first()
    applyResourceTask.mustRunAfter manifestTask

    variantOutput.processResources.dependsOn applyResourceTask
    applyResourceTask.dependsOn mergeResourcesTask


    if (multiDexEnabled) {//main
       TinkerMultidexConfigTask multidexConfigTask = mProject.tasks.create("tinkerProcess${variantName}MultidexKeep",     TinkerMultidexConfigTask)
       multidexConfigTask.applicationVariant = variant
       multidexConfigTask.multiDexKeepProguard = getManifestMultiDexKeepProguard(variant)
       multidexConfigTask.mustRunAfter manifestTask

    multidexConfigTask.mustRunAfter variantOutput.processResources
    multidexTask.dependsOn multidexConfigTask

    if (configuration.buildConfig.keepDexApply
         && FileOperation.isLegalFile(mProject.tinkerPatch.oldApk)) {
       com.tencent.tinker.build.gradle.transform.ImmutableDexTransform.inject(mProject, variant)//main
     }
}
void setPatchNewApkPath(configuration, output, variant, tinkerPatchBuildTask) {
    tinkerPatchBuildTask.dependsOn variant.assemble//assembleDebug
}

image-20210301174211724

ImmutableDexTransform

@Override
 void transform(TransformInvocation transformInvocation) throws TransformException, IOException, InterruptedException {}

TinkerPatchSchemaTask

TinkerPatchExtension configuration
 def android
 String buildApkPath
 String outputFolder
 def signConfig

public TinkerPatchSchemaTask() {
   description = 'Assemble Tinker Patch'
   group = 'tinker'
   outputs.upToDateWhen { false }
   configuration = project.tinkerPatch

   android = project.extensions.android
 }
@TaskAction
 def tinkerPatch() {
 configuration.checkParameter()
 configuration.buildConfig.checkParameter()
 configuration.res.checkParameter()
 configuration.dex.checkDexMode()
 configuration.sevenZip.resolveZipFinalPath()

builder.setOldApk(configuration.oldApk)
    .setNewApk(buildApkPath)
    .setOutBuilder(outputFolder)
    .setIgnoreWarning(configuration.ignoreWarning)
    .setAllowLoaderInAnyDex(configuration.allowLoaderInAnyDex)
    .setRemoveLoaderForAllDex(configuration.removeLoaderForAllDex)
    .setDexFilePattern(new ArrayList<String>(configuration.dex.pattern))
    .setIsProtectedApp(configuration.buildConfig.isProtectedApp)
    .setIsComponentHotplugSupported(configuration.buildConfig.supportHotplugComponent)
    .setDexLoaderPattern(new ArrayList<String>(configuration.dex.loader))
    .setDexIgnoreWarningLoaderPattern(new ArrayList<String>(configuration.dex.ignoreWarningLoader))
    .setDexMode(configuration.dex.dexMode)
    .setSoFilePattern(new ArrayList<String>(configuration.lib.pattern))
    .setResourceFilePattern(new ArrayList<String>(configuration.res.pattern))
    .setResourceIgnoreChangePattern(new ArrayList<String>(configuration.res.ignoreChange))
    .setResourceIgnoreChangeWarningPattern(new ArrayList<String>(configuration.res.ignoreChangeWarning))
    .setResourceLargeModSize(configuration.res.largeModSize)
    .setUseApplyResource(configuration.buildConfig.usingResourceMapping)
    .setConfigFields(new HashMap<String, String>(configuration.packageConfig.getFields()))
    .setSevenZipPath(configuration.sevenZip.path)
    .setUseSign(configuration.useSign)
    .setArkHotPath(configuration.arkHot.path)
    .setArkHotName(configuration.arkHot.name)

 InputParam inputParam = builder.create()
 Runner.gradleRun(inputParam);

Runner.thinkerPatch

public static void gradleRun(InputParam inputParam) {
   mBeginTime = System.currentTimeMillis();
   Runner m = new Runner();
   m.run(inputParam);
 }

private void run(InputParam inputParam) {
   loadConfigFromGradle(inputParam);
   try {
    //tinker-sample-android\app\build\outputs\apk/tinkerPatch/debug\log.txt
     Logger.initLogger(config);
     tinkerPatch();//main
   } catch (IOException e) {
     e.printStackTrace();
     goToError();
   } finally {
     Logger.closeLogger();
   }
 }

protected void tinkerPatch() {
   Logger.d("-----------------------Tinker patch begin-----------------------");
   Logger.d(config.toString());
   try {
     //gen patch
     ApkDecoder decoder = new ApkDecoder(config);
     decoder.onAllPatchesStart();
     decoder.patch(config.mOldApkFile, config.mNewApkFile);//main
     decoder.onAllPatchesEnd();
 
     //gen meta file and version file
     PatchInfo info = new PatchInfo(config);
     info.gen();//输出assets/package_meta.txt文件记录版本信息

     //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 patch end-------------------------");
 }

Decoder类设计

DexDiffDecoder

ExcludedClassModifiedChecker

public void checkIfExcludedClassWasModifiedInNewDex(File oldFile, File newFile) throws IOException, TinkerPatchException {
 int stmCode = STMCODE_START;
 while (stmCode != STMCODE_END) {
   switch (stmCode) {
   	case STMCODE_START: {
      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);//main

DexClassesComparator.startCheck

public void startCheck(Dex oldDex, Dex newDex) {
   startCheck(DexGroup.wrap(oldDex), DexGroup.wrap(newDex));
 }

public void startCheck(DexGroup oldDexGroup, DexGroup newDexGroup) {
//add, delete, change三个列表必须都是empty,才算是逻辑上一致
Set<String> deletedClassDescs = new HashSet<>(oldDescriptorOfClassesToCheck);
deletedClassDescs.removeAll(newDescriptorOfClassesToCheck);

Set<String> addedClassDescs = new HashSet<>(newDescriptorOfClassesToCheck);
addedClassDescs.removeAll(oldDescriptorOfClassesToCheck);

Set<String> mayBeChangedClassDescs = new HashSet<>(oldDescriptorOfClassesToCheck);
mayBeChangedClassDescs.retainAll(newDescriptorOfClassesToCheck);

for (String desc : mayBeChangedClassDescs) {
   DexClassInfo oldClassInfo = oldClassDescriptorToClassInfoMap.get(desc);
   DexClassInfo newClassInfo = newClassDescriptorToClassInfoMap.get(desc);
   switch (compareMode) {
     case COMPARE_MODE_NORMAL: {
       if (!isSameClass(//main
           oldClassInfo.owner,
           newClassInfo.owner,
           oldClassInfo.classDef,
           newClassInfo.classDef
       )) {
         if (Utils.isStringMatchesPatterns(desc, patternsOfIgnoredRemovedClassDesc)) {
           logger.i(TAG, "Ignored changed class: %s", desc);
         } else {
           logger.i(TAG, "Changed class: %s", desc);
           changedClassDescToClassInfosMap.put(
               desc, new DexClassInfo[]{oldClassInfo, newClassInfo}
           );
         }

isSameClass

private boolean isSameClass(
     Dex oldDex,
     Dex newDex,
     ClassDef oldClassDef,
     ClassDef newClassDef
 ) {//内部会有各种界别same的判断,内部还会调用isSameMethod,isSameCode等等,判断是否逻辑一致
  if (oldClassDef.accessFlags != newClassDef.accessFlags) {
     return false;
   }
 
   if (!isSameClassDesc(
       oldDex, newDex, oldClassDef.supertypeIndex, newClassDef.supertypeIndex
   )) {
     return false;
   }
 
   short[] oldInterfaceIndices = oldDex.interfaceTypeIndicesFromClassDef(oldClassDef);
   short[] newInterfaceIndices = newDex.interfaceTypeIndicesFromClassDef(newClassDef);
   if (oldInterfaceIndices.length != newInterfaceIndices.length) {
     return false;
   } else {
     for (int i = 0; i < oldInterfaceIndices.length; ++i) {
       if (!isSameClassDesc(oldDex, newDex, oldInterfaceIndices[i], newInterfaceIndices[i])) {
         return false;
       }
     }
   }
 
   if (!isSameName(oldDex, newDex, oldClassDef.sourceFileIndex, newClassDef.sourceFileIndex)) {
     return false;
   }
 
   if (!isSameAnnotationDirectory(
       oldDex,
       newDex,
       oldClassDef.annotationsOffset,
       newClassDef.annotationsOffset
   )) {
     return false;
   }
 
   if (!isSameClassData(
       oldDex,
       newDex,
       oldClassDef.classDataOffset,
       newClassDef.classDataOffset
   )) {
     return false;
   }
 
   return isSameStaticValue(
       oldDex,
       newDex,
       oldClassDef.staticValuesOffset,
       newClassDef.staticValuesOffset
   );
 }

Dex差分

dexPatchGen.executeAndSaveTo(dexDiffOut);//Dex差分

DexSectionDiffAlgorithm类设计

二路归并算法是较小的指针+1,从而判断是删除old还是新增new

DexPatchGenerator

 private DexSectionDiffAlgorithm<StringData> stringDataSectionDiffAlg;
 private DexSectionDiffAlgorithm<Integer> typeIdSectionDiffAlg;
 private DexSectionDiffAlgorithm<ProtoId> protoIdSectionDiffAlg;
 private DexSectionDiffAlgorithm<FieldId> fieldIdSectionDiffAlg;
 private DexSectionDiffAlgorithm<MethodId> methodIdSectionDiffAlg;
 private DexSectionDiffAlgorithm<ClassDef> classDefSectionDiffAlg;
 private DexSectionDiffAlgorithm<TypeList> typeListSectionDiffAlg;
 private DexSectionDiffAlgorithm<AnnotationSetRefList> annotationSetRefListSectionDiffAlg;
 private DexSectionDiffAlgorithm<AnnotationSet> annotationSetSectionDiffAlg;
 private DexSectionDiffAlgorithm<ClassData> classDataSectionDiffAlg;
 private DexSectionDiffAlgorithm<Code> codeSectionDiffAlg;
 private DexSectionDiffAlgorithm<DebugInfoItem> debugInfoSectionDiffAlg;
 private DexSectionDiffAlgorithm<Annotation> annotationSectionDiffAlg;
 private DexSectionDiffAlgorithm<EncodedValue> encodedArraySectionDiffAlg;
 private DexSectionDiffAlgorithm<AnnotationsDirectory> annotationsDirectorySectionDiffAlg;

public DexPatchGenerator(File oldDexFile, File newDexFile) throws IOException {
   this(new Dex(oldDexFile), new Dex(newDexFile));
 }

public DexPatchGenerator(Dex oldDex, Dex newDex) {
   this.oldDex = oldDex;
   this.newDex = newDex;
   SparseIndexMap oldToNewIndexMap = new SparseIndexMap();
   SparseIndexMap oldToPatchedIndexMap = new SparseIndexMap();
   SparseIndexMap newToPatchedIndexMap = new SparseIndexMap();
   SparseIndexMap selfIndexMapForSkip = new SparseIndexMap();

this.stringDataSectionDiffAlg = new StringDataSectionDiffAlgorithm(
     oldDex, newDex,
     oldToNewIndexMap,
     oldToPatchedIndexMap,
     newToPatchedIndexMap,
     selfIndexMapForSkip
 );
 this.typeIdSectionDiffAlg = new TypeIdSectionDiffAlgorithm(
     oldDex, newDex,
     oldToNewIndexMap,
     oldToPatchedIndexMap,
     newToPatchedIndexMap,
     selfIndexMapForSkip
 );

executeAndSaveTo

public void executeAndSaveTo(File file) throws IOException {
   OutputStream os = null;
   try {
     os = new BufferedOutputStream(new FileOutputStream(file));
     executeAndSaveTo(os);
   } finally {
     StreamUtil.closeQuietly(os);
   }
 }
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.

//首先移除需要ignore变更的class,如loader包中的,SampleApplication和BaseBuildInfo,这些类的差异无法通过tinker修复
......

((ClassDefSectionDiffAlgorithm) this.classDefSectionDiffAlg)
     .setTypeIdOfClassDefsToRemove(typeIdOfClassDefsToRemove);
 ((ClassDataSectionDiffAlgorithm) this.classDataSectionDiffAlg)
     .setOffsetOfClassDatasToRemove(offsetOfClassDatasToRemove);

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

// The diff works on each sections obey such procedure:
 // 1. Execute diff algorithms to calculate indices of items we need to add, del and replace.
 // 2. Execute patch algorithm simulation to calculate indices and offsets mappings that is
 // necessary to next section's diff works.

 // Immediately do the patch simulation so that we can know:
 // 1. Indices and offsets mapping between old dex and patched dex.
 // 2. Indices and offsets mapping between 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();//执行二路归并算法,结果存储到三种(add/del/replace)map中//main
...

this.stringDataSectionDiffAlg.simulatePatchOperation(this.patchedStringDataItemsOffset);//main
......

// Finally, write results to patch file.
 writeResultToStream(out);//main
private void writeResultToStream(OutputStream os) throws IOException {
DexDataBuffer buffer = new DexDataBuffer();
 buffer.write(DexPatchFile.MAGIC);
 buffer.writeShort(DexPatchFile.CURRENT_VERSION);
 buffer.writeInt(this.patchedDexSize);
 // we will return here to write firstChunkOffset later.
 int posOfFirstChunkOffsetField = buffer.position();
 buffer.writeInt(0);
 buffer.writeInt(this.patchedStringIdsOffset);
 buffer.writeInt(this.patchedTypeIdsOffset);
 buffer.writeInt(this.patchedProtoIdsOffset);
 buffer.writeInt(this.patchedFieldIdsOffset);
 buffer.writeInt(this.patchedMethodIdsOffset);
 buffer.writeInt(this.patchedClassDefsOffset);
 buffer.writeInt(this.patchedMapListOffset);
 buffer.writeInt(this.patchedTypeListsOffset);
 buffer.writeInt(this.patchedAnnotationSetRefListItemsOffset);
 buffer.writeInt(this.patchedAnnotationSetItemsOffset);
 buffer.writeInt(this.patchedClassDataItemsOffset);
 buffer.writeInt(this.patchedCodeItemsOffset);
 buffer.writeInt(this.patchedStringDataItemsOffset);
 buffer.writeInt(this.patchedDebugInfoItemsOffset);
 buffer.writeInt(this.patchedAnnotationItemsOffset);
 buffer.writeInt(this.patchedEncodedArrayItemsOffset);
 buffer.writeInt(this.patchedAnnotationsDirectoryItemsOffset);

 buffer.write(this.oldDex.computeSignature(false));
 int firstChunkOffset = buffer.position();
 buffer.position(posOfFirstChunkOffsetField);
 buffer.writeInt(firstChunkOffset);
 buffer.position(firstChunkOffset);
 
 writePatchOperations(buffer, this.stringDataSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.typeIdSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.typeListSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.protoIdSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.fieldIdSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.methodIdSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.annotationSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.annotationSetSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.annotationSetRefListSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.annotationsDirectorySectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.debugInfoSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.codeSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.classDataSectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.encodedArraySectionDiffAlg.getPatchOperationList());
 writePatchOperations(buffer, this.classDefSectionDiffAlg.getPatchOperationList());
 
 byte[] bufferData = buffer.array();
 os.write(bufferData);
 os.flush();
}
private <T extends Comparable<T>> void writePatchOperations(
     DexDataBuffer buffer, List<PatchOperation<T>> patchOperationList
 ) {
......

buffer.writeUleb128(delOpIndexList.size());
 int lastIndex = 0;
 for (Integer index : delOpIndexList) {
   buffer.writeSleb128(index - lastIndex);
   lastIndex = index;
 }
 
 buffer.writeUleb128(addOpIndexList.size());
 lastIndex = 0;
 for (Integer index : addOpIndexList) {
   buffer.writeSleb128(index - lastIndex);
   lastIndex = index;
 }
 
 buffer.writeUleb128(replaceOpIndexList.size());
 lastIndex = 0;
 for (Integer index : replaceOpIndexList) {
   buffer.writeSleb128(index - lastIndex);
   lastIndex = index;
 }

for (T newItem : newItemList) {
   if (newItem instanceof StringData) {
     buffer.writeStringData((StringData) newItem);
   } else

......

Dex差分合成

new DexPatchApplier(oldDexFile, dexDiffOut).executeAndSaveTo(tempFullPatchedDexFile);//Dex差分合成

DexSectionPatchAlgorithm类设计

public DexPatchApplier(File oldDexIn, File patchFileIn) throws IOException {
   this(new Dex(oldDexIn), new DexPatchFile(patchFileIn));
 }

public DexPatchApplier(Dex oldDexIn, DexPatchFile patchFileIn) {
   this.oldDex = oldDexIn;
   this.patchFile = patchFileIn;
   this.patchedDex = new Dex(patchFileIn.getPatchedDexSize());
   this.oldToPatchedIndexMap = new SparseIndexMap();
 }

DexPatchApplier.executeAndSaveTo

public void executeAndSaveTo(File file) throws IOException {
   OutputStream os = null;
   try {
     os = new BufferedOutputStream(new FileOutputStream(file));
     executeAndSaveTo(os);
   } finally {
     StreamUtil.closeQuietly(os);
   }
 }

public void executeAndSaveTo(OutputStream out) throws IOException {
   // Before executing, we should check if this patch can be applied to
   // old dex we passed in.

 // Firstly, set sections' offset after patched, sort according to their offset so that
 // the dex lib of aosp can calculate section size.
 TableOfContents patchedToc = this.patchedDex.getTableOfContents();
 
 patchedToc.header.off = 0;
 patchedToc.header.size = 1;
 patchedToc.mapList.size = 1;
 
 patchedToc.stringIds.off = this.patchFile.getPatchedStringIdSectionOffset();
 patchedToc.typeIds.off = this.patchFile.getPatchedTypeIdSectionOffset();
 patchedToc.typeLists.off = this.patchFile.getPatchedTypeListSectionOffset();
 patchedToc.protoIds.off = this.patchFile.getPatchedProtoIdSectionOffset();
 patchedToc.fieldIds.off = this.patchFile.getPatchedFieldIdSectionOffset();
 patchedToc.methodIds.off = this.patchFile.getPatchedMethodIdSectionOffset();
 patchedToc.classDefs.off = this.patchFile.getPatchedClassDefSectionOffset();
 patchedToc.mapList.off = this.patchFile.getPatchedMapListSectionOffset();
 patchedToc.stringDatas.off = this.patchFile.getPatchedStringDataSectionOffset();
 patchedToc.annotations.off = this.patchFile.getPatchedAnnotationSectionOffset();
 patchedToc.annotationSets.off = this.patchFile.getPatchedAnnotationSetSectionOffset();
 patchedToc.annotationSetRefLists.off = this.patchFile.getPatchedAnnotationSetRefListSectionOffset();
 patchedToc.annotationsDirectories.off = this.patchFile.getPatchedAnnotationsDirectorySectionOffset();
 patchedToc.encodedArrays.off = this.patchFile.getPatchedEncodedArraySectionOffset();
 patchedToc.debugInfos.off = this.patchFile.getPatchedDebugInfoSectionOffset();
 patchedToc.codes.off = this.patchFile.getPatchedCodeSectionOffset();
 patchedToc.classDatas.off = this.patchFile.getPatchedClassDataSectionOffset();
 patchedToc.fileSize = this.patchFile.getPatchedDexSize();
 
 Arrays.sort(patchedToc.sections);
 
 patchedToc.computeSizesFromOffsets();
// Secondly, run patch algorithms according to sections' dependencies.
 this.stringDataSectionPatchAlg = new StringDataSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
....
 this.stringDataSectionPatchAlg.execute();
 this.typeIdSectionPatchAlg.execute();
 this.typeListSectionPatchAlg.execute();
 this.protoIdSectionPatchAlg.execute();
 this.fieldIdSectionPatchAlg.execute();
 this.methodIdSectionPatchAlg.execute();
 this.annotationSectionPatchAlg.execute();
 this.annotationSetSectionPatchAlg.execute();
 this.annotationSetRefListSectionPatchAlg.execute();
 this.annotationsDirectorySectionPatchAlg.execute();
 this.debugInfoSectionPatchAlg.execute();
 this.codeSectionPatchAlg.execute();
 this.classDataSectionPatchAlg.execute();
 this.encodedArraySectionPatchAlg.execute();
 this.classDefSectionPatchAlg.execute();

// Thirdly, write header, mapList. Calculate and write patched dex's sign and checksum.
 Dex.Section headerOut = this.patchedDex.openSection(patchedToc.header.off);
 patchedToc.writeHeader(headerOut);

 Dex.Section mapListOut = this.patchedDex.openSection(patchedToc.mapList.off);
 patchedToc.writeMap(mapListOut);

 this.patchedDex.writeHashes();

 // Finally, write patched dex to file.
 this.patchedDex.writeTo(out);
}

DexPatchFile

public DexPatchFile(File file) throws IOException {
   this.buffer = new DexDataBuffer(ByteBuffer.wrap(FileUtils.readFile(file)));
   init();
 }

private void init() {
   byte[] magic = this.buffer.readByteArray(MAGIC.length);
   if (CompareUtils.uArrCompare(magic, MAGIC) != 0) {
     throw new IllegalStateException("bad dex patch file magic: " + Arrays.toString(magic));
   }

   this.version = this.buffer.readShort();
   if (CompareUtils.uCompare(this.version, CURRENT_VERSION) != 0) {
     throw new IllegalStateException("bad dex patch file version: " + this.version + ", expected: " + CURRENT_VERSION);
   }

   this.patchedDexSize = this.buffer.readInt();
   this.firstChunkOffset = this.buffer.readInt();
   this.patchedStringIdSectionOffset = this.buffer.readInt();
   this.patchedTypeIdSectionOffset = this.buffer.readInt();
   this.patchedProtoIdSectionOffset = this.buffer.readInt();
   this.patchedFieldIdSectionOffset = this.buffer.readInt();
   this.patchedMethodIdSectionOffset = this.buffer.readInt();
   this.patchedClassDefSectionOffset = this.buffer.readInt();
   this.patchedMapListSectionOffset = this.buffer.readInt();
   this.patchedTypeListSectionOffset = this.buffer.readInt();
   this.patchedAnnotationSetRefListSectionOffset = this.buffer.readInt();
   this.patchedAnnotationSetSectionOffset = this.buffer.readInt();
   this.patchedClassDataSectionOffset = this.buffer.readInt();
   this.patchedCodeSectionOffset = this.buffer.readInt();
   this.patchedStringDataSectionOffset = this.buffer.readInt();
   this.patchedDebugInfoSectionOffset = this.buffer.readInt();
   this.patchedAnnotationSectionOffset = this.buffer.readInt();
   this.patchedEncodedArraySectionOffset = this.buffer.readInt();
   this.patchedAnnotationsDirectorySectionOffset = this.buffer.readInt();
   this.oldDexSignature = this.buffer.readByteArray(SizeOf.SIGNATURE);

   this.buffer.position(firstChunkOffset);
 }

public DexDataBuffer getBuffer() {
   return buffer;
 }

checkDexChange

checkDexChange(origNewDex, patchedNewDex);//检查是否patchedNewDex(打完补丁的new dex)和原本的newDex在逻辑上保持一致

addTestDex

tinker-build/tinker-patch-lib/src/main/resources/test.dex代码中固定的文件通过getResourceAsStream复制到当前patch的输出目录mTempResultDir

private void addTestDex() throws IOException {
    //write test dex
    String dexMode = "jar";
    if (config.mDexRaw) {
        dexMode = "raw";
    }

    final InputStream is = DexDiffDecoder.class.getResourceAsStream("/" + TEST_DEX_NAME);
    String md5 = MD5.getMD5(is, 1024);
    is.close();

    String meta = TEST_DEX_NAME + "," + "" + "," + md5 + "," + md5 + "," + 0 + "," + 0 + "," + 0 + "," + dexMode;

    File dest = new File(config.mTempResultDir + "/" + TEST_DEX_NAME);
    FileOperation.copyResourceUsingStream(TEST_DEX_NAME, dest);
    Logger.d("\nAdd test install result dex: %s, size:%d", dest.getAbsolutePath(), dest.length());
    Logger.d("DexDecoder:write test dex meta file data: %s", meta);

    metaWriter.writeLineToInfoFile(meta);
}

PatchBuilder.buildPatch

public void buildPatch() throws Exception {
   final File resultDir = config.mTempResultDir;
   generateUnsignedApk(unSignedApk);//将tinker_result文件夹下的内容打包到patch apk
   signApk(unSignedApk, signedApk);执行jarsigner命令进行sign
   use7zApk(signedApk, signedWith7ZipApk, sevenZipOutPutDir);
}

Dex解析

aosp-dexutils-1.9.14.jar中包含dex字节码的处理库如Code类

Dex

/**
 \* Creates a new dex buffer from the dex file {@code file}.
 */
public Dex(File file) throws IOException {
 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);
   }
 }
}

private void loadFrom(InputStream in, int initSize) throws IOException {
   byte[] rawData = FileUtils.readStream(in, initSize);//rawData表示dex文件的byte数组

   this.data = ByteBuffer.wrap(rawData);
   this.data.order(ByteOrder.LITTLE_ENDIAN);
   this.tableOfContents.readFrom(this);//main
 }

public void writeTo(OutputStream out) throws IOException {
   byte[] rawData = data.array();
   out.write(rawData);
   out.flush();
 }

TableOfContents

/**
 \* The file header and map.
 */

TableOfContents {
public final Section header = new Section(SECTION_TYPE_HEADER, true);//无Algorithm对应
public final Section stringIds = new Section(SECTION_TYPE_STRINGIDS, true);//ref StringDataSectionDiffAlgorithm
public final Section typeIds = new Section(SECTION_TYPE_TYPEIDS, true);//ref TypeIdSectionDiffAlgorithm
public final Section protoIds = new Section(SECTION_TYPE_PROTOIDS, true);//ref ProtoIdSectionDiffAlgorithm
public final Section fieldIds = new Section(SECTION_TYPE_FIELDIDS, true);//ref FieldIdSectionDiffAlgorithm
public final Section methodIds = new Section(SECTION_TYPE_METHODIDS, true);//ref MethodIdSectionDiffAlgorithm
public final Section classDefs = new Section(SECTION_TYPE_CLASSDEFS, true);//ref ClassDefSectionDiffAlgorithm
public final Section mapList = new Section(SECTION_TYPE_MAPLIST, true);//无Algorithm对应
public final Section typeLists = new Section(SECTION_TYPE_TYPELISTS, true);//TypeListSectionDiffAlgorithm
public final Section annotationSetRefLists = new Section(SECTION_TYPE_ANNOTATIONSETREFLISTS, true);//ref ...
public final Section annotationSets = new Section(SECTION_TYPE_ANNOTATIONSETS, true);ref...
public final Section classDatas = new Section(SECTION_TYPE_CLASSDATA, false);ref...
public final Section codes = new Section(SECTION_TYPE_CODES, true);ref...
public final Section stringDatas = new Section(SECTION_TYPE_STRINGDATAS, false);ref...
public final Section debugInfos = new Section(SECTION_TYPE_DEBUGINFOS, false);ref...
public final Section annotations = new Section(SECTION_TYPE_ANNOTATIONS, false);ref...
public final Section encodedArrays = new Section(SECTION_TYPE_ENCODEDARRAYS, false);//无Algorithm对应
public final Section annotationsDirectories = new Section(SECTION_TYPE_ANNOTATIONSDIRECTORIES, true);ref...

//18个section,对应map size = 18,其中15个都一一对应一个SectionDiffAlgorithm类进行差分,在Algorithm子类的getTocSection方法中返回对应的Section
public final Section[] sections = {
     header, stringIds, typeIds, protoIds, fieldIds, methodIds, classDefs, mapList,
     typeLists, annotationSetRefLists, annotationSets, classDatas, codes, stringDatas,
     debugInfos, annotations, encodedArrays, annotationsDirectories
 };

 public int checksum;
 public byte[] signature;
 public int fileSize;
 public int linkSize;
 public int linkOff;
 public int dataSize;
 public int dataOff;

}

readFromDex

public void readFrom(Dex dex) throws IOException {
   readHeader(dex.openSection(header));
   // special case, since mapList.byteCount is available only after
   // computeSizesFromOffsets() was invoked, so here we can't use
   // dex.openSection(mapList) to get dex section. Or
   // an {@code java.nio.BufferUnderflowException} will be thrown.
   readMap(dex.openSection(mapList.off));
   computeSizesFromOffsets();
 }
//Dex
public Section openSection(TableOfContents.Section tocSec) {
   int position = tocSec.off;
   if (position < 0 || position >= data.capacity()) {
     throw new IllegalArgumentException(
         "position=" + position + " length=" + data.capacity()
     );
   }
   ByteBuffer sectionData = data.duplicate();
   sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary?
   sectionData.position(position);//设定指定section的起始地址position和大小byteCount

   sectionData.limit(position + tocSec.byteCount);
   return new Section("section", sectionData);
 }
readHeader
private void readHeader(Dex.Section headerIn) throws UnsupportedEncodingException {
   byte[] magic = headerIn.readByteArray(8);
   int apiTarget = DexFormat.magicToApi(magic);
 
   if (apiTarget != DexFormat.API_NO_EXTENDED_OPCODES) {
     throw new DexException("Unexpected magic: " + Arrays.toString(magic));
   }
 
   checksum = headerIn.readInt();
   signature = headerIn.readByteArray(20);
   fileSize = headerIn.readInt();

......
 stringIds.size = headerIn.readInt();
 stringIds.off = headerIn.readInt();
 typeIds.size = headerIn.readInt();
 typeIds.off = headerIn.readInt();
 protoIds.size = headerIn.readInt();
 protoIds.off = headerIn.readInt();
......
 dataSize = headerIn.readInt();
 dataOff = headerIn.readInt();
readMap
private void readMap(Dex.Section in) throws IOException {
   int mapSize = in.readInt();
   Section previous = null;
   for (int i = 0; i < mapSize; i++) {
     short type = in.readShort();
     in.readShort(); // unused
     Section section = getSection(type);
     int size = in.readInt();
     int offset = in.readInt();
     if ((section.size != 0 && section.size != size)
         || (section.off != Section.UNDEF_OFFSET && section.off != offset)) {//末尾map中的size和offset应该和header中的size,offset一致
       throw new DexException("Unexpected map value for 0x" + Integer.toHexString(type));
     }
     section.size = size;
     section.off = offset;

     if (previous != null && previous.off > section.off) {
       throw new DexException("Map is unsorted at " + previous + ", " + section);
     }
     previous = section;
   }

   header.off = 0;
   Arrays.sort(sections);
   // Skip header section, since its offset must be zero.
   for (int i = 1; i < sections.length; ++i) {
     if (sections[i].off == Section.UNDEF_OFFSET) {
       sections[i].off = sections[i - 1].off;
     }
   }
 }
computeSizesFromOffsets
public void computeSizesFromOffsets() {
   int end = fileSize;
   for (int i = sections.length - 1; i >= 0; i--) {//18个Section反向迭代,计算出每个Section的byteCount

     Section section = sections[i];
     if (section.off == Section.UNDEF_OFFSET) {
       continue;
     }
     if (section.off > end) {
       throw new DexException("Map is unsorted at " + section);
     }
     section.byteCount = end - section.off;
     end = section.off;
   }
 
   dataOff = header.byteCount
       \+ stringIds.byteCount
       \+ typeIds.byteCount
       \+ protoIds.byteCount
       \+ fieldIds.byteCount
       \+ methodIds.byteCount
       \+ classDefs.byteCount;
 
   dataSize = fileSize - dataOff;
 }

TableOfContents.Section

public static class Section implements Comparable<Section> {
 public final short type;
 public boolean isElementFourByteAligned;
 public int size = 0;//数组大小

 public int off = UNDEF_OFFSET;//起始地址
 public int byteCount = 0;//整个Section所占区域大小

public Section(int type, boolean isElementFourByteAligned) {
   this.type = (short) type;
   this.isElementFourByteAligned = isElementFourByteAligned;
   if (type == SECTION_TYPE_HEADER) {
     off = 0;
     size = 1;
     byteCount = SizeOf.HEADER_ITEM;
   } else
   if (type == SECTION_TYPE_MAPLIST) {
     size = 1;
   }
 }

  public static abstract class Item<T> implements Comparable<T> {
     public int off;
 
     public Item(int off) {
       this.off = off;
     }
}

Dex.Section

public final class Section extends DexDataBuffer {
   private final String name;

   private Section(String name, ByteBuffer data) {
     super(data);
     this.name = name;
   }
}

class DexDataBuffer {
  public DexDataBuffer(ByteBuffer data) {
   this.data = data;
   this.data.order(ByteOrder.LITTLE_ENDIAN);
   this.dataBound = data.limit();
   this.isResizeAllowed = false;
 }
}

AbstractIndexMap.adjust(Code)

public Code adjust(Code code) {
   int adjustedDebugInfoOffset = adjustDebugInfoItemOffset(code.debugInfoOffset);
   short[] adjustedInstructions = adjustInstructions(code.instructions);//main
   Code.CatchHandler[] adjustedCatchHandlers = adjustCatchHandlers(code.catchHandlers);
   return new Code(
       code.off, code.registersSize, code.insSize, code.outsSize,
       adjustedDebugInfoOffset, adjustedInstructions, code.tries, adjustedCatchHandlers
   );
 }

private short[] adjustInstructions(short[] instructions) {
   if (instructions == null || instructions.length == 0) {
     return instructions;
   }
   InstructionTransformer insTrans = new InstructionTransformer(this);
   return insTrans.transform(instructions);
 }

InstructionTransformer

public short[] transform(short[] encodedInstructions) throws DexException {
   ShortArrayCodeOutput out = new ShortArrayCodeOutput(encodedInstructions.length);//因为每个指令的长度是u1 也就是0~255

  InstructionPromoter ipmo = new InstructionPromoter();//地址转换,应对类似const-string 到const-string/jumbo的地址扩展情况

  InstructionWriter iw = new InstructionWriter(out, ipmo);
   InstructionReader ir = new InstructionReader(new ShortArrayCodeInput(encodedInstructions));
 
   try {
     // First visit, we collect mappings from original target address to promoted target address.
     ir.accept(new InstructionTransformVisitor(ipmo));//main
 
     // Then do the real transformation work.
     ir.accept(new InstructionTransformVisitor(iw));//main
   } catch (EOFException e) {
     throw new DexException(e);
   }
 
   return out.getArray();
 }

InstructionReader(被访问者)

private final ShortArrayCodeInput codeIn;

 public InstructionReader(ShortArrayCodeInput in) {
   this.codeIn = in;
 }

public void accept(InstructionVisitor iv) throws EOFException {
   codeIn.reset();
   while (codeIn.hasMore()) {
     int currentAddress = codeIn.cursor();
     int opcodeUnit = codeIn.read();
     int opcodeForSwitch = Opcodes.extractOpcodeFromUnit(opcodeUnit);//main
     switch (opcodeForSwitch) {
       case Opcodes.SPECIAL_FORMAT: {
         iv.visitZeroRegisterInsn(currentAddress, opcodeUnit, 0, InstructionCodec.INDEX_TYPE_NONE, 0, 0L);
         break;
       }
       case Opcodes.GOTO: {
         int opcode = InstructionCodec.byte0(opcodeUnit);//main
         int target = (byte) InstructionCodec.byte1(opcodeUnit); // sign-extend
         iv.visitZeroRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, currentAddress + target, 0L);
         break;
       }

......

case Opcodes.INVOKE_STATIC:
 case Opcodes.INVOKE_INTERFACE: {
   int opcode = InstructionCodec.byte0(opcodeUnit);
   int e = InstructionCodec.nibble2(opcodeUnit);
   int registerCount = InstructionCodec.nibble3(opcodeUnit);
   int index = codeIn.read();
   int abcd = codeIn.read();
   int a = InstructionCodec.nibble0(abcd);
   int b = InstructionCodec.nibble1(abcd);
   int c = InstructionCodec.nibble2(abcd);
   int d = InstructionCodec.nibble3(abcd);
   int indexType = InstructionCodec.getInstructionIndexType(opcode);
 
   switch (registerCount) {
     case 0: {
       iv.visitZeroRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L);
       break;
     }
     case 1: {
       iv.visitOneRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a);
       break;
     }
     case 2: {
       iv.visitTwoRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b);
       break;
     }
     case 3: {
       iv.visitThreeRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b, c);
       break;
     }
     case 4: {
       iv.visitFourRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b, c, d);
       break;
     }
     case 5: {
       iv.visitFiveRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b, c, d, e);
       break;
     }
     default: {
       throw new DexException("bogus registerCount: " + Hex.uNibble(registerCount));
     }

InstructionVisitor类设计

classDiagram
class InstructionVisitor {
+visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) void
+visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) void
}

InstructionVisitor<|--InstructionTransformVisitor
InstructionTransformVisitor--*InstructionPromoter:delegate
InstructionVisitor<|--InstructionPromoter

InstructionVisitor<|--InstructionWriter
InstructionTransformVisitor--*InstructionWriter:delegate

InstructionVisitor

private final InstructionVisitor prevIv;
 public InstructionVisitor(InstructionVisitor iv) {
   this.prevIv = iv;
 }

public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) {
   if (prevIv != null) {
     prevIv.visitZeroRegisterInsn(currentAddress, opcode, index, indexType, target, literal);
   }
 }

public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) {
   if (prevIv != null) {
     prevIv.visitOneRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a);
   }
 }

InstructionTransformVisitor

@Override
 public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) {
   int mappedIndex = transformIndexIfNeeded(index, indexType);
   super.visitZeroRegisterInsn(currentAddress, opcode, mappedIndex, indexType, target, literal);
 }

@Override
 public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) {
   int mappedIndex = transformIndexIfNeeded(index, indexType);//这里index为old index, mappedIndex为new dex中的 index
  super.visitOneRegisterInsn(currentAddress, opcode, mappedIndex, indexType, target, literal, a);
 }

InstructionWriter

public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) {
}
public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) {
 switch (opcode) {

  case Opcodes.CONST_STRING: {
   if (this.hasPromoter) {
     if (index > 0xFFFF) {
       codeOut.write(
           InstructionCodec.codeUnit(Opcodes.CONST_STRING_JUMBO, a),
           InstructionCodec.unit0(index),
           InstructionCodec.unit1(index)
       );
     } else {
       short indexUnit = (short) index;
       codeOut.write(InstructionCodec.codeUnit(opcode, a), indexUnit);
 //写入到out字节码中的字符串 index 是new dex中的索引,这里把旧的 Code 字节码强行改变,所以在做对比的时候这两个的字节码是完全一样的,这个方法也不会将该方法打入补丁包中,这个过程对补丁包的大小影响很大,能减少好多原本没有改变的方法打入补丁包中   
     }

参考

https://www.zybuluo.com/dodola/note/554061