Defining difference plug-ins

  • Gradle directory and build directory, build. Gradle directory and build directory, build. Gradle file contents are as follows: And generate the corresponding plug-in, such as apply Plugin: “groovy”, our new file bsdiffplugin.groovy will take effect
buildscript { repositories { google() jcenter() mavenCentral() } dependencies { classpath 'com. Android. Tools. Build: gradle: 4.1.2'}} / / identify groovy plugin apply plugin: "groovy" apply the plugin: "java" repositories { mavenCentral() } dependencies { }Copy the code
  • 2. Create a Groovy directory and define bsDiffplugin.groovy

The plugin is paired with the Task, the plugin is responsible for building the task and initializing its parameters, and the task is responsible for the actual task execution.

Class BsDiffPlugin Plugin<Project>{@override void apply(Project Project) {project.afterevaluate { Will not execute, /gradlew bsDiff project.task(type:BsDiffTask, "bsDiffWrapper"){ inputs.files "${project.rootDir.absolutePath}/temp/testold.txt","${project.rootDir.absolutePath}/temp/testnew.txt" outputs.files "${project.rootDir.absolutePath}/temp/testdiff.patch" } } } }Copy the code

When we add it in build.gralde under app

apply plugin: BsDiffPlugin
Copy the code

In gradle configuration, the apply method of BsDiffPlugin is executed, while the task defined in BsDiffTask is not executed, it is simply initialized

Task (type:BsDiffTask, “bsDiffWrapper”) the second parameter changes the name of the task

  • 3. Perform tasks

There are two ways to execute a task

./gradlew tasks 
Copy the code

Make a list of tasks and execute

./gradlew bsDiffWrapper
Copy the code

Or check a default build task execution

project.getTasksByName("assembleDebug",false).getAt(0).finalizedBy 'bsDiffWrapper'
project.getTasksByName("bsDiffWrapper",false).getAt(0).finalizedBy 'bsPatchWrapper'
Copy the code

Define the id for the plug-in

  • 1. Build resources/META_INF/gradle-plugins under buildSrc /main
  • 2. Create two new properties files whose names are the ids we will rely on

com.hch.plugin.bsDiff.properties com.hch.plugin.bsPatch.properties

  • 3. Add a line to the file that represents the plug-in’s implementation classpath
implementation-class=com.hch.plugin.BsPatchPlugin
Copy the code
  • 4. Use id dependencies in gradle where you need them, such as build.gradle in your app. Plugins must be placed at the beginning of the file and must not be preceded by other scripts
plugins{
    id 'com.hch.plugin.bsDiff'
    id 'com.hch.plugin.bsPatch'
    id 'com.android.application'
}
Copy the code

Dex file format analysis

Format viewing tool 010Editor

The number of methods cannot exceed 65535

In dex, the description id of the class pointed to by the method was USHORT and the length was 65535. Therefore, if the number of classes exceeded 65535, too much classes would be prompted.

Defects and improvements of bsDiff/dexDiff comparison algorithm

Check the method of Tinker comparison algorithm and copy the implementation class by relying on Tinker’s Patch plug-in

CompileOnly "com. Tencent. Tinker: tinker - patch - gradle - plugin: 1.9.1." "Copy the code

The structure of dex

Dex is structured data that contains headers, maps, and data sections.

The header contains:

  • Magic string [dex. 035.],
  • Checksum Checksum data
  • Other section types and length descriptions, such as string, ID, prototype,method,class initial position, length, etc. String, ID, prototype,method,class each structure is a structured data

Compared with header, MAplist more comprehensively describes the entire block information of DEX

Datasection Information about the actual contents of each Secion

Dex algorithm implementation

Therefore, the basic flow of the comparison algorithm can be determined as follows:

  • Dex file Reads and converts dex memory objects
  • For structural comparison of 15 data structures in DEX, the approximate implementation process is as follows: Read item of each structured list and sort it, generate oldList and newList, circulate cursor of list according to current cursor, obtain oldItem and newItem respectively, and compare their value:

    • If <0, the oldItem is considered to be deleted and recorded as patchoperation. OP_DEL. The oldItem index is recorded in the PatchOperation object and added to the patchOperationList.
    • If the value is greater than 0, the newItem is considered to be new and recorded as patchoperation. OP_ADD. The newItem index and value are added to the PatchOperation object and added to the patchOperationList.
    • If =0, PatchOperation will not be generated.
  • Get the result map, items to be deleted from oldDex, items to be added to newDex, and items to be replaced
// 2. Collect patch operations while (oldCursor < enclosing oldItemCount | | newCursor < enclosing newItemCount) {if (oldCursor > = this. OldItemCount) { // Rest item are all newitem. while (newCursor < this.newItemCount) {} else if (newCursor >= NewItemCount) {// Rest item are all olditem. while (oldCursor < oldItemCount) {// DEL the rest oldItem}} else {}}Copy the code

Dex Diff’s flaws

We can use array + linked list to store the key values (int) generated according to the hashcode of the item. We can compare the key values with o(1) complexity to get the comparison result

BsDiff principle