梳理Tinker源码20230126

典型调用

1、依赖项

buildscript {
    
    repositories {
        google()
        jcenter()
        maven {
            url 'https://maven.google.com/'
            name 'Google'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.2'
        classpath("com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}") {
            changing = TINKER_VERSION?.endsWith("-SNAPSHOT")
            exclude group: 'com.android.tools.build', module: 'gradle'
        }
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
 
allprojects {
    repositories {
        google()
        jcenter()
        maven {
            url 'https://maven.google.com/'
            name 'Google'
        }
    }
}
 
task clean(type: Delete) {
    delete rootProject.buildDir
}
def is_gradle_3() {
    return hasProperty('GRADLE_3') && GRADLE_3.equalsIgnoreCase('TRUE')
}

2、依赖

apply plugin: 'com.android.application'
def javaVersion = JavaVersion.VERSION_1_7
 
android {
    compileSdkVersion 28
 
 
    defaultConfig {
        applicationId "com.example.tinkerproject"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "2.0"
        /**
         * you can use multiDex and install it in your ApplicationLifeCycle implement
         */
//        multiDexEnabled true
 
    }
    compileOptions {
        sourceCompatibility javaVersion
        targetCompatibility javaVersion
    }
    //recommend
    dexOptions {
        jumboMode = true
    }
 
    signingConfigs {
 
        release {
            storeFile file("sign/release.keystore")
            storePassword "123456"
            keyAlias "123456"
            keyPassword "123456"
        }
 
        debug {
            storeFile file("sign/release.keystore")
            storePassword "123456"
            keyAlias "123456"
            keyPassword "123456"
        }
    }
 
    buildTypes {
        release {
            minifyEnabled true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), project.file('proguard-rules.pro')
        }
        debug {
            debuggable true
            minifyEnabled true
            signingConfig signingConfigs.debug
            proguardFiles getDefaultProguardFile('proguard-android.txt'), project.file('proguard-rules.pro')
 
        }
    }
 
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}
def bakPath =  file("${buildDir}/bakApk/")
 
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    testImplementation 'junit:junit:4.12'
    implementation "com.android.support:appcompat-v7:28.0.0+"
    api("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
 
// Maven local cannot handle transist dependencies.
    implementation("com.tencent.tinker:tinker-android-loader:${TINKER_VERSION}") { changing = true }
 
    annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") {
        changing = true
    }
    compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
 
    implementation "com.android.support:multidex:1.0.1"
 
    implementation 'com.orhanobut:hawk:2.0.1'
}
 
/**
 * 使用Tinker的一些常量配置
 */
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-release-0317-18-54-47.apk"
    //proguard mapping file to build patch apk混淆文件
    tinkerApplyMappingPath = "${bakPath}/app-release-0317-18-54-47-mapping.txt"
    //resource R.txt to build patch apk, must input if there is resource changed资源路径
    tinkerApplyResourcePath = "${bakPath}/app-release-0317-18-54-47-R.txt"
 
    //only use for build all flavor, if not, just ignore this field
    tinkerBuildFlavorDirectory = "${bakPath}/"
 
 
 
 
}
/**
 * Tinker的全剧配置
 */
if (buildWithTinker()) {
    apply plugin: 'com.tencent.tinker.patch'
 
    tinkerPatch {
        /**
         * 第一次生成的基线包
         */
        oldApk = getOldApkPath()
        /**
         * default 'false'
         * 如果出现以下的情况,并且ignoreWarning为false,我们将中断编译。因为这些情况可能会导致编译出来的patch包带来风险:
         1. minSdkVersion小于14,但是dexMode的值为"raw";
         2. 新编译的安装包出现新增的四大组件(Activity, BroadcastReceiver...);
         3. 定义在dex.loader用于加载补丁的类不在main dex中;
         4. 定义在dex.loader用于加载补丁的类出现修改;
         5. resources.arsc改变,但没有使用applyResourceMapping编译。
         */
        ignoreWarning = false
 
        /**
         * 在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。
         */
        useSign = true
 
        /**
         * optional,default 'true'
         * 是否打开Tinker功能
         */
        tinkerEnable = buildWithTinker()
 
        /**
         * 编译相关的配置项
         */
        buildConfig {
            /**
             * optional,default 'null'
             * 在编译新的apk时候,我们希望通过保持旧apk的proguard混淆方式,
             * 从而减少补丁包的大小。这个只是推荐设置,
             * 不设置applyMapping也不会影响任何的assemble编译。
             */
//            applyMapping = getApplyMappingPath()
            /**
             * optional,default 'null'
             * 可选参数;在编译新的apk时候,我们希望通过旧apk的R.txt文件保持ResId的分配
             * ,这样不仅可以减少补丁包的大小,同时也避免由于ResId改变导致remote view异常
             */
            applyResourceMapping = getApplyResourceMappingPath()
 
            /**
             * necessary,default 'null'
             * 在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId。
             * 这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号
             * 、versionName等等。
             */
            tinkerId = getTinkerIdValue()
 
            /**
             *     如果我们有多个dex,编译补丁时可能会由于类的移动导致变更增多。
             *     若打开keepDexApply模式,补丁包将根据基准包的类分布来编译
             */
            keepDexApply = true
 
            /**
             * optional, default 'false'
             * 是否使用加固模式,仅仅将变更的类合成补丁。注意,这种模式仅仅可以用于加固应用中。
             */
            isProtectedApp = false
 
            /**
             * optional, default 'false'
             * 是否支持新增非export的Activity
             * <b>Notice that currently this feature is incubating and only support NON-EXPORTED Activity</b>
             */
            supportHotplugComponent = false
        }
 
        dex {
            /**
             * optional,default 'jar'
             *     只能是'raw'或者'jar'。
             对于'raw'模式,我们将会保持输入dex的格式。
             对于'jar'模式,我们将会把输入dex重新压缩封装到jar。
             如果你的minSdkVersion小于14,你必须选择‘jar’模式,而且它更省存储空间,
             但是验证md5时比'raw'模式耗时。默认我们并不会去校验md5,一般情况下选择jar模式即可。
             */
            dexMode = "jar"
 
            /**
             * necessary,default '[]'
             * what dexes in apk are expected to deal with tinkerPatch
             * it support * or ? pattern.
             */
            pattern = ["classes*.dex",
                       "assets/secondary-dex-?.jar"]
            /**
             * necessary,default '[]'
             * Warning, it is very very important, loader classes can't change with patch.
             * thus, they will be removed from patch dexes.
             * you must put the following class into main dex.
             * Simply, you should add your own application {@code tinker.sample.android.SampleApplication}
             * own tinkerLoader, and the classes you use in them
             *
             */
            loader = [
                    //use sample, let BaseBuildInfo unchangeable with tinker
                    "tinker.sample.android.app.BaseBuildInfo"
            ]
        }
 
        lib {
            /**
             * optional,default '[]'
             * 需要处理lib路径,
             * 支持*、?通配符,必须使用'/'分割。与dex.pattern一致,
             * 路径是相对安装包的,例如assets/...
             */
            pattern = ["lib/*/*.so"]
        }
 
        res {
            /**
             * optional,default '[]'
             * 需要处理res路径,支持*、?通配符,必须使用'/'分割。
             * 与dex.pattern一致, 路径是相对安装包的,例如assets/...,务必注意的是,
             * 只有满足pattern的资源才会放到合成后的资源包
             */
            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
 
            /**
             * optional,default '[]'
             * 支持*、?通配符,必须使用'/'分割。若满足ignoreChange的pattern,
             * 在编译时会忽略该文件的新增、删除与修改。 最极端的情况,
             * ignoreChange与上面的pattern一致,即会完全忽略所有资源的修改。
             ignoreChange = ["assets/sample_meta.txt"]
 
             /**
             * default 100kb
             * 如果大于largeModSize,我们将使用bsdiff算法。
             * 这可以降低补丁包的大小,但是会增加合成时的复杂度。默认大小为100kb
             */
            largeModSize = 100
        }
 
        packageConfig {
            /**
             * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE'
             * package meta file gen. path is assets/package_meta.txt in patch file
             * you can use securityCheck.getPackageProperties() in your ownPackageCheck method
             * or TinkerLoadResult.getPackageConfigByName
             * we will get the TINKER_ID from the old apk manifest for you automatic,
             * other config files (such as patchMessage below)is not necessary
             */
            configField("patchMessage", "tinker is sample to use")
            /**
             * just a sample case, you can use such as sdkVersion, brand, channel...
             * you can parse it in the SamplePatchListener.
             * Then you can use patch conditional!
             */
            configField("platform", "all")
            /**
             * patch version via packageConfig
             */
            configField("patchVersion", "1.0")
        }
        //or you can add config filed outside, or get meta value from old apk
        //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test"))
        //project.tinkerPatch.packageConfig.configField("test2", "sample")
 
        /**
         * if you don't use zipArtifact or path, we just use 7za to try
         */
        sevenZip {
            /**
             * optional,default '7za'
             * the 7zip artifact path, it will use the right 7za with your platform
             */
            zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
            /**
             * optional,default '7za'
             * you can specify the 7za path yourself, it will overwrite the zipArtifact value
             */
//        path = "/usr/local/bin/7za"
        }
    }
 
    List<String> flavors = new ArrayList<>();
    project.android.productFlavors.each { flavor ->
        flavors.add(flavor.name)
    }
    boolean hasFlavors = flavors.size() > 0
    def date = new Date().format("MMdd-HH-mm-ss")
 
    /**
     * bak apk and mapping
     */
    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
                        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"
                        into destPath
                        rename { String fileName ->
                            fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
                        }
                    }
                }
            }
        }
    }
 
}
 
def getOldApkPath() {
    return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
}
 
def getApplyMappingPath() {
    return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
}
 
def getApplyResourceMappingPath() {
    return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
}
 
def getTinkerIdValue() {
    return hasProperty("TINKER_ID") ? TINKER_ID : android.defaultConfig.versionName
}
 
def buildWithTinker() {
    return hasProperty("TINKER_ENABLE") ? Boolean.parseBoolean(TINKER_ENABLE) : ext.tinkerEnabled
}
 
def getTinkerBuildFlavorDirectory() {
    return ext.tinkerBuildFlavorDirectory
}
 

3、初始化

@SuppressWarnings("unused")
@DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication",
                  flags = ShareConstants.TINKER_ENABLE_ALL,
                  loadVerifyFlag = false)
public class SampleApplicationLike extends DefaultApplicationLike {
    private static final String TAG = "Tinker.SampleApplicationLike";

    public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
                                 long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
    }

    /**
     * install multiDex before install tinker
     * so we don't need to put the tinker lib classes in the main dex
     *
     * @param base
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        //you must install multiDex whatever tinker is installed!
        MultiDex.install(base);

        SampleApplicationContext.application = getApplication();
        SampleApplicationContext.context = getApplication();
        TinkerManager.setTinkerApplicationLike(this);

        TinkerManager.initFastCrashProtect();
        //should set before tinker is installed
        TinkerManager.setUpgradeRetryEnable(true);

        //optional set logIml, or you can use default debug log
        TinkerInstaller.setLogIml(new MyLogImp());

        //installTinker after load multiDex
        //or you can put com.tencent.tinker.** to main dex
        TinkerManager.installTinker(this);
        Tinker tinker = Tinker.with(getApplication());
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
        getApplication().registerActivityLifecycleCallbacks(callback);
    }

}

4、加载patch包

Button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                  TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), getPatchName());
                            
            }
        });

调用流程

1、我们从TinkerInstaller.onReceiveUpgradePatch()方法开始

public class TinkerInstaller {
    ...
    public static void onReceiveUpgradePatch(Context context, String patchLocation) {
        Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
    }
    ...
}

2、 SampleApplicationLike中的Tinker在onBaseContextAttached()方法初始化了,实际上是回调的DefaultPatchListener

public class SampleApplicationLike extends DefaultApplicationLike {
   ...

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        ...
        Tinker tinker = Tinker.with(getApplication());
    }
}
public class Tinker {
    public static Tinker with(Context context) {
        if (!sInstalled) {
            throw new TinkerRuntimeException("you must install tinker before get tinker sInstance");
        }
        synchronized (Tinker.class) {
            if (sInstance == null) {
                sInstance = new Builder(context).build();
            }
        }
        return sInstance;
    }
}

3、在 Builder(context).build();的时候给了默认的DefaultPatchListener

 public static class Builder {
  
        private PatchListener listener;

        /**
         * Start building a new {@link Tinker} instance.
         */
        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);
        }

        ...

        public Builder listener(PatchListener listener) {
            if (listener == null) {
                throw new TinkerRuntimeException("listener must not be null.");
            }
            if (this.listener != null) {
                throw new TinkerRuntimeException("listener is already set.");
            }
            this.listener = listener;
            return this;
        }

        public Tinker build() {
            ...

            if (listener == null) {
                listener = new DefaultPatchListener(context);
            }
            ....

            return new Tinker(context, status, loadReporter, patchReporter, listener, patchDirectory,
                patchInfoFile, patchInfoLockFile, mainProcess, patchProcess, tinkerLoadVerifyFlag);
        }
    }

4、这样我们来到了DefaultPatchListener类的onPatchReceived()方法

public class DefaultPatchListener implements PatchListener {
    protected final Context context;

    public DefaultPatchListener(Context context) {
        this.context = context;
    }

    @Override
    public int onPatchReceived(String path) {
        File patchFile = new File(path);

        int returnCode = patchCheck(path, SharePatchFileUtil.getMD5(patchFile));

        if (returnCode == ShareConstants.ERROR_PATCH_OK) {
            TinkerPatchService.runPatchService(context, path);
        } else {
            Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
        }
        return returnCode;
    }
}

5、调用了TinkerPatchService.runPatchService();方法

public class TinkerPatchService {
     ...

    public static void runPatchService(final Context context, final String path) {
        try {
            if (Build.VERSION.SDK_INT < MIN_SDKVER_TO_USE_JOBSCHEDULER) {
                runPatchServiceByIntentService(context, path);
            } else {
                try {
                    runPatchServiceByJobScheduler(context, path);
                } catch (Throwable ignored) {
                    // ignored.
                }
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        TinkerLog.i(TAG, "check if patch service is running.");
                       ...
                     
                    }
                }, TimeUnit.SECONDS.toMillis(5));
            }
        } catch (Throwable throwable) {
            TinkerLog.e(TAG, "start patch service fail, exception:" + throwable);
        }
    }
}

6、无论是runPatchServiceByIntentService()还是runPatchServiceByJobScheduler(),最终他们都是调用的doApplyPatch()

  • runPatchServiceByIntentService()
private static void runPatchServiceByIntentService(Context context, String path) {
        TinkerLog.i(TAG, "run patch service by intent service.");
        Intent intent = new Intent(context, IntentServiceRunner.class);
        intent.putExtra(PATCH_PATH_EXTRA, path);
        intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
        context.startService(intent);
    }

public static class IntentServiceRunner extends IntentService {
        @Override
        protected void onHandleIntent(@Nullable Intent intent) {
            increasingPriority();
            doApplyPatch(getApplicationContext(), intent);
        }
}
  • runPatchServiceByJobScheduler()
 @TargetApi(21)
    private static boolean runPatchServiceByJobScheduler(Context context, String path) {
        TinkerLog.i(TAG, "run patch service by job scheduler.");
        final JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(
                0xF0F1F2F3, new ComponentName(context, JobServiceRunner.class)
        );
        final PersistableBundle extras = new PersistableBundle();
        extras.putString(PATCH_PATH_EXTRA, path);
        extras.putString(RESULT_CLASS_EXTRA, resultServiceClass.getName());
        jobInfoBuilder.setExtras(extras);
        jobInfoBuilder.setOverrideDeadline(5);
        final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
        if (jobScheduler == null) {
            TinkerLog.e(TAG, "jobScheduler is null.");
            return false;
        }
        return (jobScheduler.schedule(jobInfoBuilder.build()) == JobScheduler.RESULT_SUCCESS);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static class JobServiceRunner extends JobService {
        private JobAsyncTask mTask = null;

        @Override
        public boolean onStartJob(JobParameters params) {
            mTask = new JobAsyncTask(this);
            mTask.execute(params);
            return true;
        }

       ...

        private static class JobAsyncTask extends AsyncTask<JobParameters, Void, Void> {
            private final WeakReference<JobService> mHolderRef;

            JobAsyncTask(JobService holder) {
                mHolderRef = new WeakReference<>(holder);
            }

            @Override
            protected Void doInBackground(JobParameters... paramsList) {
                ...
                doApplyPatch(holder.getApplicationContext(), paramIntent);
                ...
                return null;
            }
            ...
        }
    }

7、在doApplyPatch()方法中,调用了一个重要的方法upgradePatchProcessor.tryPatch()

private static void doApplyPatch(Context context, Intent intent) {
        ...
        boolean result;
        ... 
        try {
            result = upgradePatchProcessor.tryPatch(context, path, patchResult);
        } catch (Throwable throwable) {
            ...
        }

        ...
    }

7、upgradePatchProcessor顺藤摸瓜,我们发现它在TinkerManager类中传入了UpgradePatch()实例

public class SampleApplicationLike extends DefaultApplicationLike {

    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        ...
        TinkerManager.installTinker(this);
        ...
    }
}
这里传入了UpgradePatch()实例

public class TinkerManager {
 public static void installTinker(ApplicationLike appLike) {
         ...
         //you can set your own upgrade patch if you need
        AbstractPatch upgradePatchProcessor = new UpgradePatch();

        TinkerInstaller.install(appLike,
            loadReporter, patchReporter, patchListener,
            SampleResultService.class, upgradePatchProcessor);

        ...
    }
}
public class TinkerInstaller {
     public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter,
                                 PatchListener listener, Class<? extends AbstractResultService> resultServiceClass,
                                 AbstractPatch upgradePatchProcessor) {

        ...

        Tinker.create(tinker);
        tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor);
        return tinker;
    }
}
public class Tinker {
     ...

     public void install(Intent intentResult, Class<? extends AbstractResultService> serviceClass,
                        AbstractPatch upgradePatch) {
        ...
        TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass);
        ...
    }
}
public class TinkerPatchService {
     public static void setPatchProcessor(AbstractPatch upgradePatch, Class<? extends AbstractResultService> serviceClass) {
        upgradePatchProcessor = upgradePatch;
        ...
    }

   private static void doApplyPatch(Context context, Intent intent) {
        ...
        result = upgradePatchProcessor.tryPatch(context, path, patchResult);
        ...
    }
}

8、紧接着我们来到UpgradePatch类的tryPatch()方法,方法的前部分做了很多准备工作

完成全部工作后,返回true

public class UpgradePatch extends AbstractPatch {
    private static final String TAG = "Tinker.UpgradePatch";

    @Override
    public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
        Tinker manager = Tinker.with(context);

        // 1、这里是补丁包文件
        final File patchFile = new File(tempPatchPath);

        if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:patch is disabled, just return");
            return false;
        }

        if (!SharePatchFileUtil.isLegalFile(patchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:patch file is not found, just return");
            return false;
        }
        // 2、签名校验工具
        //check the signature, we should create a new checker
        ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context);

        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;
        }

        // 3、取得补丁包的MD5
        String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
        if (patchMd5 == null) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:patch md5 is null, just return");
            return false;
        }
        // 4、用补丁包的MD5作为版本号
        //use md5 as version
        patchResult.patchVersion = patchMd5;

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

        //  5、当前安装包的路径
        //check ok, we can real recover a new patch
        final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();

        // 6、这两个文件是安装包的信息(记录包的md5 crc)
        File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory);// "info.lock"; 一个锁
        File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory);// "patch.info";版本信息

        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 there is 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, "UpgradePatch tryPatch:patchVersionDirectory:%s", patchVersionDirectory);

        // 复制一份补丁包的apk(最后真正要用的)
        //copy file
        File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5));

        try {
            // 因为是空文件,所以md5不相等
            // check md5 first
            if (!patchMd5.equals(SharePatchFileUtil.getMD5(destPatchFile))) {
                // 复制补丁包的内容到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;
        }

        // 我们使用上一段复制的destPatchFile来热修复,因为为patchFile可能在打补丁的过程中人为删掉
        //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
        if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
            return false;
        }

        if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed");
            return false;
        }

        if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed");
            return false;
        }

        // check dex opt file at last, some phone such as VIVO/OPPO like to change dex2oat to interpreted
        if (!DexDiffPatchInternal.waitAndCheckDexOptFile(patchFile, manager)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, check dex opt file failed");
            return false;
        }

        if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, patchInfoLockFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed");
            manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion);
            return false;
        }

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

}

9、我们找到一个典型的调用

  • 我们使用目标的补丁包替换当前包
 //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
            return false;
}

10、真正的热修复开始了

public class DexDiffPatchInternal extends BasePatchInternal {
// 这里开始真正做热修复了
    protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context,
                                                String patchVersionDirectory, File patchFile) {
        if (!manager.isEnabledForDex()) {
            TinkerLog.w(TAG, "patch recover, dex is not enabled");
            return true;
        }
        String dexMeta = checker.getMetaContentMap().get(DEX_META_FILE);

        if (dexMeta == null) {
            TinkerLog.w(TAG, "patch recover, dex is not contained");
            return true;
        }

        long begin = SystemClock.elapsedRealtime();

        // 开始根据dex的不同提取补丁包
        boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile);
        long cost = SystemClock.elapsedRealtime() - begin;
        TinkerLog.i(TAG, "recover dex result:%b, cost:%d", result, cost);
        return result;
    }

    private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile) {
        // patchVersionDirectory:复制的补丁包的路径
        // patchFile:复制的补丁包
        String dir = patchVersionDirectory + "/" + DEX_PATH + "/";//一个新的地址

        // 开始提取内部不同的dex
        // meta应该是一些文件信息
        if (!extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)) {
            TinkerLog.w(TAG, "patch recover, extractDiffInternals fail");
            return false;
        }

        File dexFiles = new File(dir);
        File[] files = dexFiles.listFiles();
        List<File> legalFiles = new ArrayList<>();
        // may have directory in android o
        if (files != null) {
            for (File file : files) {
                final String fileName = file.getName();
                if (file.isFile()
                    &&  (fileName.endsWith(ShareConstants.DEX_SUFFIX)
                      || fileName.endsWith(ShareConstants.JAR_SUFFIX)
                      || fileName.endsWith(ShareConstants.PATCH_SUFFIX))
                ) {
                    legalFiles.add(file);
                }
            }
        }

        TinkerLog.i(TAG, "legal files to do dexopt: " + legalFiles);

        // 在处理后优化所有的dex
        final String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/";
        return dexOptimizeDexFiles(context, legalFiles, optimizeDexDirectory, patchFile);

    }

    private static boolean extractDexDiffInternals(Context context, String dir, String meta, File patchFile, int type) {
        // 解析meta里面的数据添加到patchList
        // 这个数据是所有包的信息,包括md5 (vm art)crc 路径  名字
        //parse
        patchList.clear();
        ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, patchList);

        if (patchList.isEmpty()) {
            TinkerLog.w(TAG, "extract patch list 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 without patching.
                TinkerLog.w(TAG, "applicationInfo == null!!!!");
                return false;
            }

            // 旧的apk的路径
            String apkPath = applicationInfo.sourceDir;
            apk = new ZipFile(apkPath); // 旧的apk
            patch = new ZipFile(patchFile); // 补丁apk
            if (checkClassNDexFiles(dir)) {
                TinkerLog.w(TAG, "class n dex file %s is already exist, and md5 match, just continue", ShareConstants.CLASS_N_APK_NAME);
                return true;
            }
            // patchList该包括了所有的dex包的信息(名字,路径,crc,md5),我们用它来做对比
            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;

                // 不是有效的dvm虚拟机
                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, "meta file md5 invalid, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.rawName, extractedFileMd5);
                    manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type));
                    return false;
                }

                // 新旧apk最后合起来的包
                File extractedFile = new File(dir + info.realName);

                //check file whether already exist
                if (extractedFile.exists()) {
                    if (SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {
                        //it is ok, just continue
                        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();
                }

                // 补丁apk
                ZipEntry patchFileEntry = patch.getEntry(patchRealPath);
                // 旧的apk
                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;
                    }

                    // 1、补丁包 -> 新文件
                    //it is a new file, but maybe 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 for real 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 instead of 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;
                    }

                    // 2、旧的包  -> 新文件
                    // Small patched dex generating 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 instead of 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;
    }

     private static void patchDexFile(
        ZipFile baseApk, ZipFile patchPkg, ZipEntry oldDexEntry, ZipEntry patchFileEntry,
        ShareDexDiffPatchInfo patchInfo, File patchedDexFile) throws IOException {
        InputStream oldDexStream = null;
        InputStream patchFileStream = null;
        try {
            oldDexStream = new BufferedInputStream(baseApk.getInputStream(oldDexEntry));
            patchFileStream = (patchFileEntry != null ? new BufferedInputStream(patchPkg.getInputStream(patchFileEntry)) : null);

            final boolean isRawDexFile = SharePatchFileUtil.isRawDexFile(patchInfo.rawName);
            if (!isRawDexFile || patchInfo.isJarMode) {
                // jar模式
                ZipOutputStream zos = null;
                try {
                    zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(patchedDexFile)));
                    zos.putNextEntry(new ZipEntry(ShareConstants.DEX_IN_JAR));
                    // Old dex is not a raw dex file.
                    if (!isRawDexFile) {
                        ZipInputStream zis = null;
                        try {
                            zis = new ZipInputStream(oldDexStream);
                            ZipEntry entry;
                            while ((entry = zis.getNextEntry()) != null) {
                                if (ShareConstants.DEX_IN_JAR.equals(entry.getName())) break;
                            }
                            if (entry == null) {
                                throw new TinkerRuntimeException("can't recognize zip dex format file:" + patchedDexFile.getAbsolutePath());
                            }
                            new DexPatchApplier(zis, patchFileStream).executeAndSaveTo(zos);
                        } finally {
                            StreamUtil.closeQuietly(zis);
                        }
                    } else {
                        new DexPatchApplier(oldDexStream, patchFileStream).executeAndSaveTo(zos);
                    }
                    zos.closeEntry();
                } finally {
                    StreamUtil.closeQuietly(zos);
                }
            } else {
                // dex模式,patchedDexFile为新旧两个包合起来的包,这里用到了bsdiff算法,二分归并
                // 先把用到的数据类型排序,然后一个一个类型的对比
                new DexPatchApplier(oldDexStream, patchFileStream).executeAndSaveTo(patchedDexFile);
            }
        } finally {
            StreamUtil.closeQuietly(oldDexStream);
            StreamUtil.closeQuietly(patchFileStream);
        }
    }
}
public class DexPatchApplier {
     public void executeAndSaveTo(OutputStream out) throws IOException {
        // Before executing, we should check if this patch can be applied to
        // old dex we passed in.
        byte[] oldDexSign = this.oldDex.computeSignature(false);
        if (oldDexSign == null) {
            throw new IOException("failed to compute old dex's signature.");
        }
        if (this.patchFile == null) {
            throw new IllegalArgumentException("patch file is null.");
        }
        byte[] oldDexSignInPatchFile = this.patchFile.getOldDexSignature();
        if (CompareUtils.uArrCompare(oldDexSign, oldDexSignInPatchFile) != 0) {
            throw new IOException(
                    String.format(
                            "old dex signature mismatch! expected: %s, actual: %s",
                            Arrays.toString(oldDexSign),
                            Arrays.toString(oldDexSignInPatchFile)
                    )
            );
        }

        // 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.typeIdSectionPatchAlg = new TypeIdSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.protoIdSectionPatchAlg = new ProtoIdSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.fieldIdSectionPatchAlg = new FieldIdSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.methodIdSectionPatchAlg = new MethodIdSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.classDefSectionPatchAlg = new ClassDefSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.typeListSectionPatchAlg = new TypeListSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.annotationSetRefListSectionPatchAlg = new AnnotationSetRefListSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.annotationSetSectionPatchAlg = new AnnotationSetSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.classDataSectionPatchAlg = new ClassDataSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.codeSectionPatchAlg = new CodeSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.debugInfoSectionPatchAlg = new DebugInfoItemSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.annotationSectionPatchAlg = new AnnotationSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.encodedArraySectionPatchAlg = new StaticValueSectionPatchAlgorithm(
                patchFile, oldDex, patchedDex, oldToPatchedIndexMap
        );
        this.annotationsDirectorySectionPatchAlg = new AnnotationsDirectorySectionPatchAlgorithm(
                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);
    }

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

二分归并算法 时间复杂度为O(nlogn)。

11、第8步完成工作后返回true,相当于热修复成功了,回调结果

  • resultServiceClass是 SampleResultService.class
public class SampleApplicationLike extends DefaultApplicationLike {

    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        ...
        TinkerManager.installTinker(this);
        ...
    }
}
这里传入了resultServiceClass是 SampleResultService.class

public class TinkerManager {
 public static void installTinker(ApplicationLike appLike) {
         ...
        TinkerInstaller.install(appLike,
            loadReporter, patchReporter, patchListener,
            SampleResultService.class, upgradePatchProcessor);

        ...
    }
}
public class TinkerInstaller {
     public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter,
                                 PatchListener listener, Class<? extends AbstractResultService> resultServiceClass,
                                 AbstractPatch upgradePatchProcessor) {

        ...

        Tinker.create(tinker);
        tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor);
        return tinker;
    }
}
public class Tinker {
     ...

     public void install(Intent intentResult, Class<? extends AbstractResultService> serviceClass,
                        AbstractPatch upgradePatch) {
        ...
        TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass);
        ...
    }
}
public class TinkerPatchService {
     private static Class<? extends AbstractResultService> resultServiceClass = null;

     public static void setPatchProcessor(AbstractPatch upgradePatch, Class<? extends AbstractResultService> serviceClass) {
        ...
      resultServiceClass = serviceClass;
        //try to load
        try {
            Class.forName(serviceClass.getName());
        } catch (ClassNotFoundException e) {
//            e.printStackTrace();
        }
    }

    public static void runPatchService(final Context context, final String path) {
        try {
            if (Build.VERSION.SDK_INT < MIN_SDKVER_TO_USE_JOBSCHEDULER) {
                runPatchServiceByIntentService(context, path);
            } else {
                  ...
                  runPatchServiceByJobScheduler(context, path);
                  ...
               
            }
        } catch (Throwable throwable) {
            TinkerLog.e(TAG, "start patch service fail, exception:" + throwable);
        }
    }

    private static void runPatchServiceByIntentService(Context context, String path) {
        TinkerLog.i(TAG, "run patch service by intent service.");
        Intent intent = new Intent(context, IntentServiceRunner.class);
        intent.putExtra(PATCH_PATH_EXTRA, path);
        intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
        context.startService(intent);
    }
   
    public static class IntentServiceRunner extends IntentService {

        public IntentServiceRunner() {
            super("TinkerPatchService");
        }

        @Override
        protected void onHandleIntent(@Nullable Intent intent) {
            increasingPriority();
            doApplyPatch(getApplicationContext(), intent);
        }
    }
 
    private static boolean runPatchServiceByJobScheduler(Context context, String path) {
        TinkerLog.i(TAG, "run patch service by job scheduler.");
        final JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(
                0xF0F1F2F3, new ComponentName(context, JobServiceRunner.class)
        );
        final PersistableBundle extras = new PersistableBundle();
        extras.putString(PATCH_PATH_EXTRA, path);
        extras.putString(RESULT_CLASS_EXTRA, resultServiceClass.getName());
        jobInfoBuilder.setExtras(extras);
        jobInfoBuilder.setOverrideDeadline(5);
        final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
        if (jobScheduler == null) {
            TinkerLog.e(TAG, "jobScheduler is null.");
            return false;
        }
        return (jobScheduler.schedule(jobInfoBuilder.build()) == JobScheduler.RESULT_SUCCESS);
    }
   
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static class JobServiceRunner extends JobService {
        private JobAsyncTask mTask = null;

        @Override
        public boolean onStartJob(JobParameters params) {
            mTask = new JobAsyncTask(this);
            mTask.execute(params);
            return true;
        }

       ...

        private static class JobAsyncTask extends AsyncTask<JobParameters, Void, Void> {
            private final WeakReference<JobService> mHolderRef;

            JobAsyncTask(JobService holder) {
                mHolderRef = new WeakReference<>(holder);
            }

            @Override
            protected Void doInBackground(JobParameters... paramsList) {
                ...
                doApplyPatch(holder.getApplicationContext(), paramIntent);
                ...
                return null;
            }
            ...
        }
    }

    private static void doApplyPatch(Context context, Intent intent) {
        ...
        result = upgradePatchProcessor.tryPatch(context, path, patchResult);
        ...
          AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
    }

    public static String getPatchResultExtra(Intent intent) {
        if (intent == null) {
            throw new TinkerRuntimeException("getPatchResultExtra, but intent is null");
        }
        return ShareIntentUtil.getStringExtra(intent, RESULT_CLASS_EXTRA);
    }
}

12、在AbstractResultService类

  • 我们取出intent的RESULT_CLASS_EXTRA跳转到SampleResultService
  • SampleResultService类继承自DefaultTinkerResultService类,DefaultTinkerResultService继承自AbstractResultService类
  • 实际又回到了AbstractResultService的onHandleIntent()方法
public abstract class AbstractResultService extends IntentService {
    
    public static void runResultService(Context context, PatchResult result, String resultServiceClass) {
        if (resultServiceClass == null) {
            throw new TinkerRuntimeException("resultServiceClass is null.");
        }
        try {
            Intent intent = new Intent();
            intent.setClassName(context, resultServiceClass);
            intent.putExtra(RESULT_EXTRA, result);

            context.startService(intent);
        } catch (Throwable throwable) {
            TinkerLog.e(TAG, "run result service fail, exception:" + throwable);
        }
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null) {
            TinkerLog.e(TAG, "AbstractResultService received a null intent, ignoring.");
            return;
        }
        PatchResult result = (PatchResult) ShareIntentUtil.getSerializableExtra(intent, RESULT_EXTRA);

        onPatchResult(result);
    }
}
public class SampleResultService extends DefaultTinkerResultService {}
public class DefaultTinkerResultService extends AbstractResultService {}

14、然后调用onPatchResult(result)方法,回调结果

  • 杀tinker服务进程
  • 删掉原始文件
  • 杀当前app进程
public class DefaultTinkerResultService extends AbstractResultService {
    
    @Override
    public void onPatchResult(PatchResult result) {
        ...
        TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
        ...
        if (result.isSuccess) {
            deleteRawPatchFile(new File(result.rawPatchFilePath));
                ...
                android.os.Process.killProcess(android.os.Process.myPid());
                ..
        }
    }
}
public class SampleResultService extends DefaultTinkerResultService {
    private static final String TAG = "Tinker.SampleResultService";


    @Override
    public void onPatchResult(final PatchResult result) {
       ...

        //first, we want to kill the recover process
        TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());

        Handler handler = new Handler(Looper.getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (result.isSuccess) {
                    Toast.makeText(getApplicationContext(), "patch success, please restart process", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(getApplicationContext(), "patch fail, please check reason", Toast.LENGTH_LONG).show();
                }
            }
        });
        // is success and newPatch, it is nice to delete the raw file, and restart at once
        // for old patch, you can't delete the patch file
        if (result.isSuccess) {
            deleteRawPatchFile(new File(result.rawPatchFilePath));

            //not like TinkerResultService, I want to restart just when I am at background!
            //if you have not install tinker this moment, you can use TinkerApplicationHelper api
            if (checkIfNeedKill(result)) {
                if (Utils.isBackground()) {
                    TinkerLog.i(TAG, "it is in background, just restart process");
                    restartProcess();
                } else {
                    //we can wait process at background, such as onAppBackground
                    //or we can restart when the screen off
                    TinkerLog.i(TAG, "tinker wait screen to restart process");
                    new Utils.ScreenState(getApplicationContext(), new Utils.ScreenState.IOnScreenOff() {
                        @Override
                        public void onScreenOff() {
                            restartProcess();
                        }
                    });
                }
            } else {
                TinkerLog.i(TAG, "I have already install the newly patch version!");
            }
        }
    }

    private void restartProcess() {
        TinkerLog.i(TAG, "app is background now, i can kill quietly");
        //you can send service or broadcast intent to restart your process
        android.os.Process.killProcess(android.os.Process.myPid());
    }

}

加载新包

1、在抽象类TinkerApplication中

public abstract class TinkerApplication extends Application {
    private static final String TINKER_LOADER_METHOD   = "tryLoad";

    protected TinkerApplication(int tinkerFlags) {
        this(tinkerFlags, "com.tencent.tinker.entry.DefaultApplicationLike", TinkerLoader.class.getName(), false);
    }

    protected TinkerApplication(int tinkerFlags, String delegateClassName,
                                String loaderClassName, boolean tinkerLoadVerifyFlag) {
        this.tinkerFlags = tinkerFlags;
        this.delegateClassName = delegateClassName;
        this.loaderClassName = loaderClassName;
        this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag;
    }

    private void onBaseContextAttached(Context base) {
        ...
        loadTinker();
        ...
    }
   
    private void loadTinker() {
        try {
            //reflect tinker loader, because loaderClass may be define by user!
            Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader());
            Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class);
            Constructor<?> constructor = tinkerLoadClass.getConstructor();
            tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this);
        } catch (Throwable e) {
            //has exception, put exception error code
            tinkerResultIntent = new Intent();
            ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
            tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
        }
    }
}

2、然后调用TinkerLoader类tryLoad方()法,然后调用tryLoadPatchFilesInternal()方法

public class TinkerLoader extends AbstractTinkerLoader {
    private static final String TAG = "Tinker.TinkerLoader";

    /**
     * the patch info file
     */
    private SharePatchInfo patchInfo;

    /**
     * only main process can handle patch version change or incomplete
     */
    @Override
    public Intent tryLoad(TinkerApplication app) {
        Intent resultIntent = new Intent();

        long begin = SystemClock.elapsedRealtime();
        tryLoadPatchFilesInternal(app, resultIntent);
        long cost = SystemClock.elapsedRealtime() - begin;
        ShareIntentUtil.setIntentPatchCostTime(resultIntent, cost);
        return resultIntent;
    }

    private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
         ...

        //now we can load patch jar
        if (isEnabledForDex) {
            boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA);

            if (isSystemOTA) {
                // update fingerprint after load success
                patchInfo.fingerPrint = Build.FINGERPRINT;
                patchInfo.oatDir = loadTinkerJars ? ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH : ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH;
                // reset to false
                oatModeChanged = false;

                if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) {
                    ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL);
                    Log.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted");
                    return;
                }
                // update oat dir
                resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OAT_DIR, patchInfo.oatDir);
            }
            if (!loadTinkerJars) {
                Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail");
                return;
            }
        }

        //now we can load patch resource
        if (isEnabledForResource) {
            boolean loadTinkerResources = TinkerResourceLoader.loadTinkerResources(app, patchVersionDirectory, resultIntent);
            if (!loadTinkerResources) {
                Log.w(TAG, "tryLoadPatchFiles:onPatchLoadResourcesFail");
                return;
            }
        }

        // Init component hotplug support.
        if (isEnabledForDex && isEnabledForResource) {
            ComponentHotplug.install(app, securityCheck);
        }

        //all is ok!
        ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK);
        Log.i(TAG, "tryLoadPatchFiles: load end, ok!");
        return;
    }
}

3、最后调用SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);找到pathlist,找到dexelements,makeDexElements使用数组复制加载

public class TinkerDexLoader {
     public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA) {
        if (loadDexList.isEmpty() && classNDexInfo.isEmpty()) {
            Log.w(TAG, "there is no dex to load");
            return true;
        }

        PathClassLoader classLoader = (PathClassLoader) TinkerDexLoader.class.getClassLoader();
        if (classLoader != null) {
            Log.i(TAG, "classloader: " + classLoader.toString());
        } else {
            Log.e(TAG, "classloader is null");
            ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL);
            return false;
        }
        String dexPath = directory + "/" + DEX_PATH + "/";

        ArrayList<File> legalFiles = new ArrayList<>();

        for (ShareDexDiffPatchInfo info : loadDexList) {
            //for dalvik, ignore art support dex
            if (isJustArtSupportDex(info)) {
                continue;
            }

            String path = dexPath + info.realName;
            File file = new File(path);

            if (application.isTinkerLoadVerifyFlag()) {
                long start = System.currentTimeMillis();
                String checkMd5 = getInfoMd5(info);
                if (!SharePatchFileUtil.verifyDexFileMd5(file, checkMd5)) {
                    //it is good to delete the mismatch file
                    ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH);
                    intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH,
                        file.getAbsolutePath());
                    return false;
                }
                Log.i(TAG, "verify dex file:" + file.getPath() + " md5, use time: " + (System.currentTimeMillis() - start));
            }
            legalFiles.add(file);
        }
        // verify merge classN.apk
        if (isVmArt && !classNDexInfo.isEmpty()) {
            File classNFile = new File(dexPath + ShareConstants.CLASS_N_APK_NAME);
            long start = System.currentTimeMillis();

            if (application.isTinkerLoadVerifyFlag()) {
                for (ShareDexDiffPatchInfo info : classNDexInfo) {
                    if (!SharePatchFileUtil.verifyDexFileMd5(classNFile, info.rawName, info.destMd5InArt)) {
                        ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH);
                        intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH,
                            classNFile.getAbsolutePath());
                        return false;
                    }
                }
            }
            Log.i(TAG, "verify dex file:" + classNFile.getPath() + " md5, use time: " + (System.currentTimeMillis() - start));

            legalFiles.add(classNFile);
        }
        File optimizeDir = new File(directory + "/" + oatDir);

        if (isSystemOTA) {
            final boolean[] parallelOTAResult = {true};
            final Throwable[] parallelOTAThrowable = new Throwable[1];
            String targetISA;
            try {
                targetISA = ShareTinkerInternals.getCurrentInstructionSet();
            } catch (Throwable throwable) {
                Log.i(TAG, "getCurrentInstructionSet fail:" + throwable);
//                try {
//                    targetISA = ShareOatUtil.getOatFileInstructionSet(testOptDexFile);
//                } catch (Throwable throwable) {
                // don't ota on the front
                deleteOutOfDateOATFile(directory);

                intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_INTERPRET_EXCEPTION, throwable);
                ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_GET_OTA_INSTRUCTION_SET_EXCEPTION);
                return false;
//                }
            }

            deleteOutOfDateOATFile(directory);

            Log.w(TAG, "systemOTA, try parallel oat dexes, targetISA:" + targetISA);
            // change dir
            optimizeDir = new File(directory + "/" + INTERPRET_DEX_OPTIMIZE_PATH);

            TinkerDexOptimizer.optimizeAll(
                legalFiles, optimizeDir, true, targetISA,
                new TinkerDexOptimizer.ResultCallback() {
                    long start;

                    @Override
                    public void onStart(File dexFile, File optimizedDir) {
                        start = System.currentTimeMillis();
                        Log.i(TAG, "start to optimize dex:" + dexFile.getPath());
                    }

                    @Override
                    public void onSuccess(File dexFile, File optimizedDir, File optimizedFile) {
                        // Do nothing.
                        Log.i(TAG, "success to optimize dex " + dexFile.getPath() + ", use time " + (System.currentTimeMillis() - start));
                    }

                    @Override
                    public void onFailed(File dexFile, File optimizedDir, Throwable thr) {
                        parallelOTAResult[0] = false;
                        parallelOTAThrowable[0] = thr;
                        Log.i(TAG, "fail to optimize dex " + dexFile.getPath() + ", use time " + (System.currentTimeMillis() - start));
                    }
                }
            );


            if (!parallelOTAResult[0]) {
                Log.e(TAG, "parallel oat dexes failed");
                intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_INTERPRET_EXCEPTION, parallelOTAThrowable[0]);
                ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_OTA_INTERPRET_ONLY_EXCEPTION);
                return false;
            }
        }
        try {
            SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);
        } catch (Throwable e) {
            Log.e(TAG, "install dexes failed");
//            e.printStackTrace();
            intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
            ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION);
            return false;
        }

        return true;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容