Android 热更新 tinker 在Android 简单的使用 (替换class)

来源:互联网 发布:软件销售代理协议 编辑:程序博客网 时间:2024/06/07 00:39

Hello! 大家好.

前段时间比较忙,今天终于闲下来了.于是研究研究比较高大上的东西. 热更新!

网上一搜热更新,好家伙一点一大堆,各种框架让人应接不暇.最后综合来看选择了 微信的Tinker 热修复框架.

至于缺点就不多说了 ,网上一大堆,这里我贴一个官方的对比图





废话不多,直接进入正题.


一:  新建一个空的项目. TrustHotFix

里面就是两个Button 和一个TextView.


贴一下MainActivity代码  很干净 




运行.


ok 一个空的项目好了 .接下来进行相关配置


二: Tinker配置

1:在项目build.gradle 下面添加一下代码 


//热修复classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"



2:在目录gradle.properties 中  添加 下面代码   


TINKER_VERSION=1.7.11

注意  TIMKER_VERSIOM 最好用最新版的,我用的时候是直接按git上的Demo上的版本设置的.


3:在app  build.gradle中添加相应的依赖 


compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }compile "com.android.support:multidex:1.0.1"





4:接着配置其他参数

还是在app  build.gradle中添加相应


def gitSha() {    try {        String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim()        if (gitRev == null) {            throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")        }        return gitRev    } catch (Exception e) {        throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")    }}

def javaVersion = JavaVersion.VERSION_1_7

在 android {} 添加下面代码


//热修复compileOptions {    sourceCompatibility javaVersion    targetCompatibility javaVersion}//recommenddexOptions {    jumboMode = true}

在  signingConfigs{}添加签名配置 

release {    try {        storeFile file("./keystore/release.keystore")        storePassword "testres"        keyAlias "testres"        keyPassword "testres"    } catch (ex) {        throw new InvalidUserDataException(ex.toString())    }}debug {    storeFile file("./keystore/debug.keystore")}
上面这个签名文件是 官方Demo里面的

在 defaultConfig{}添加下面代码

  //热更新-----------        applicationId "tinker.sample.android"        minSdkVersion 10        targetSdkVersion 22        versionCode 1        versionName "1.0.0"        /**         * you can use multiDex and install it in your ApplicationLifeCycle implement         */        multiDexEnabled true        /**         * buildConfig can change during patch!         * we can use the newly value when patch         */        buildConfigField "String", "MESSAGE", "\"I am the base apk\""//        buildConfigField "String", "MESSAGE", "\"I am the patch apk\""        /**         * client version would update with patch         * so we can get the newly git version easily!         */        buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""        buildConfigField "String", "PLATFORM",  "\"all\""        //---------------------------------------------



然后添加代码

//热更新------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-release-0616-15-27-53.apk"    //proguard mapping file to build patch apk    tinkerApplyMappingPath = "${bakPath}/app-release-0616-15-27-53-mapping.txt"    //resource R.txt to build patch apk, must input if there is resource changed    tinkerApplyResourcePath = "${bakPath}/app-release-0616-15-27-53-R.txt"    //only use for build all flavor, if not, just ignore this field    tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"}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 : gitSha()}def buildWithTinker() {    return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled}def getTinkerBuildFlavorDirectory() {    return ext.tinkerBuildFlavorDirectory}if (buildWithTinker()) {    apply plugin: 'com.tencent.tinker.patch'    tinkerPatch {        /**         * necessary,default 'null'         * the old apk path, use to diff with the new apk to build         * add apk from the build/bakApk         */        oldApk = getOldApkPath()        /**         * optional,default 'false'         * there are some cases we may get some warnings         * if ignoreWarning is true, we would just assert the patch process         * case 1: minSdkVersion is below 14, but you are using dexMode with raw.         *         it must be crash when load.         * case 2: newly added Android Component in AndroidManifest.xml,         *         it must be crash when load.         * case 3: loader classes in dex.loader{} are not keep in the main dex,         *         it must be let tinker not work.         * case 4: loader classes in dex.loader{} changes,         *         loader classes is ues to load patch dex. it is useless to change them.         *         it won't crash, but these changes can't effect. you may ignore it         * case 5: resources.arsc has changed, but we don't use applyResourceMapping to build         */        ignoreWarning = false        /**         * optional,default 'true'         * whether sign the patch file         * if not, you must do yourself. otherwise it can't check success during the patch loading         * we will use the sign config with your build type         */        useSign = true        /**         * optional,default 'true'         * whether use tinker to build         */        tinkerEnable = buildWithTinker()        /**         * Warning, applyMapping will affect the normal android build!         */        buildConfig {            /**             * optional,default 'null'             * if we use tinkerPatch to build the patch apk, you'd better to apply the old             * apk mapping file if minifyEnabled is enable!             * Warning:             * you must be careful that it will affect the normal assemble build!             */            applyMapping = getApplyMappingPath()            /**             * optional,default 'null'             * It is nice to keep the resource id from R.txt file to reduce java changes             */            applyResourceMapping = getApplyResourceMappingPath()            /**             * necessary,default 'null'             * because we don't want to check the base apk with md5 in the runtime(it is slow)             * tinkerId is use to identify the unique base apk when the patch is tried to apply.             * we can use git rev, svn rev or simply versionCode.             * we will gen the tinkerId in your manifest automatic             */            tinkerId = getTinkerIdValue()            /**             * if keepDexApply is true, class in which dex refer to the old apk.             * open this can reduce the dex diff file size.             */            keepDexApply = false            /**             * optional, default 'false'             * Whether tinker should treat the base apk as the one being protected by app             * protection tools.             * If this attribute is true, the generated patch package will contain a             * dex including all changed classes instead of any dexdiff patch-info files.             *///            isProtectedApp = false        }        dex {            /**             * optional,default 'jar'             * only can be 'raw' or 'jar'. for raw, we would keep its original format             * for jar, we would repack dexes with zip format.             * if you want to support below 14, you must use jar             * or you want to save rom or check quicker, you can use raw mode also             */            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 '[]'             * what library in apk are expected to deal with tinkerPatch             * it support * or ? pattern.             * for library in assets, we would just recover them in the patch directory             * you can get them in TinkerLoadResult with Tinker             */            pattern = ["lib/*/*.so"]        }        res {            /**             * optional,default '[]'             * what resource in apk are expected to deal with tinkerPatch             * it support * or ? pattern.             * you must include all your resources in apk here,             * otherwise, they won't repack in the new apk resources.             */            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]            /**             * optional,default '[]'             * the resource file exclude patterns, ignore add, delete or modify resource change             * it support * or ? pattern.             * Warning, we can only use for files no relative with resources.arsc             */            ignoreChange = ["assets/sample_meta.txt"]            /**             * default 100kb             * for modify resource, if it is larger than 'largeModSize'             * we would like to use bsdiff algorithm to reduce patch file size             */            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.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")                        }                    }                }            }        }    }    project.afterEvaluate {        //sample use for build all flavor for one time        if (hasFlavors) {            task(tinkerPatchAllFlavorRelease) {                group = 'tinker'                def originOldPath = getTinkerBuildFlavorDirectory()                for (String flavor : flavors) {                    def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")                    dependsOn tinkerTask                    def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")                    preAssembleTask.doFirst {                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"                    }                }            }            task(tinkerPatchAllFlavorDebug) {                group = 'tinker'                def originOldPath = getTinkerBuildFlavorDirectory()                for (String flavor : flavors) {                    def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")                    dependsOn tinkerTask                    def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")                    preAssembleTask.doFirst {                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"                    }                }            }        }    }}


以上就是tinker所需要的配置,如果不知道这些配置对应的位置可以下载官方Demo查看 或者在文末连接下载Demo查看


Clean 我们的项目,这个时候会发现一个错误

不好意思的是这个错,我忘了截图了.抱歉啊各位

错误的指向是你的build.gradle文件里面的  




看到这里懂英文的大腿们,应该知道是什么原因了.

首先tinker是需要一个TINKER_ID的  这TINKER_ID可以是手写的(我没试过手写),也可以通过和git关联起来自动更新的.这个对以后的升级是很重要的.TINKER_ID必须是唯一的否则热更新的时候会加载之前的旧版的补丁


需要做的就是把本地的这项目,和git上的进行关联就行了.如果不会的话请看一下git的基本操作谢谢.


关联成功后 接下来我们要配置application文件了

首先创建一个class文件 名字随便起,这里我用的是和官方Demo一样的名字 SampleApplicationLike
extends DefaultApplicationLike
在上面添加下面代码
@SuppressWarnings("unused")@DefaultLifeCycle(application = "com.trust.trusthotfix.SampleApplication",                  flags = ShareConstants.TINKER_ENABLE_ALL,                  loadVerifyFlag = false)

注意:application 里面的包名不要随便乱起,前面的是你 SampleApplicationLike这个class文件的包名  后面的这个SampleApplication 是要在AndroidManifest.xml 文件里面使用的名字

这里没什么好说的都是直接复制的官方Demo 直接上整个类的代码  

SampleApplicationLike类 
@SuppressWarnings("unused")@DefaultLifeCycle(application = "com.trust.trusthotfix.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);    }}

下面还需要几个相关类   

BaseBuildInfo 
public class BaseBuildInfo {    public static String TEST_MESSAGE = "I won't change with tinker patch!";    public static String BASE_TINKER_ID = BuildConfig.TINKER_ID;}

BuildInfo类
public class BuildInfo {    /**     * they are not final, so they won't change with the BuildConfig values!     */    public static boolean DEBUG        = BuildConfig.DEBUG;    public static String VERSION_NAME = BuildConfig.VERSION_NAME;    public static int     VERSION_CODE = BuildConfig.VERSION_CODE;    public static String MESSAGE       = BuildConfig.MESSAGE;    public static String TINKER_ID     = BuildConfig.TINKER_ID;    public static String PLATFORM      = BuildConfig.PLATFORM;}

SampleApplicationContext类
public class SampleApplicationContext {    public static Application application = null;    public static Context context = null;}
.
TinkerManager类
public class TinkerManager {    private static final String TAG = "Tinker.TinkerManager";    private static ApplicationLike applicationLike;    private static SampleUncaughtExceptionHandler uncaughtExceptionHandler;    private static boolean isInstalled = false;    public static void setTinkerApplicationLike(ApplicationLike appLike) {        applicationLike = appLike;    }    public static ApplicationLike getTinkerApplicationLike() {        return applicationLike;    }    public static void initFastCrashProtect() {        if (uncaughtExceptionHandler == null) {            uncaughtExceptionHandler = new SampleUncaughtExceptionHandler();            Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);        }    }    public static void setUpgradeRetryEnable(boolean enable) {        UpgradePatchRetry.getInstance(applicationLike.getApplication()).setRetryEnable(enable);    }    /**     * all use default class, simply Tinker install method     */    public static void sampleInstallTinker(ApplicationLike appLike) {        if (isInstalled) {            TinkerLog.w(TAG, "install tinker, but has installed, ignore");            return;        }        TinkerInstaller.install(appLike);        isInstalled = true;    }    /**     * you can specify all class you want.     * sometimes, you can only install tinker in some process you want!     *     * @param appLike     */    public static void installTinker(ApplicationLike appLike) {        if (isInstalled) {            TinkerLog.w(TAG, "install tinker, but has installed, ignore");            return;        }        //or you can just use DefaultLoadReporter        LoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication());        //or you can just use DefaultPatchReporter        PatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication());        //or you can just use DefaultPatchListener        PatchListener patchListener = new SamplePatchListener(appLike.getApplication());        //you can set your own upgrade patch if you need        AbstractPatch upgradePatchProcessor = new UpgradePatch();        TinkerInstaller.install(appLike,            loadReporter, patchReporter, patchListener,            SampleResultService.class, upgradePatchProcessor);        isInstalled = true;    }}

Utils类
public class Utils {    private static final String TAG = "Tinker.Utils";    /**     * the error code define by myself     * should after {@code ShareConstants.ERROR_PATCH_INSERVICE     */    public static final int ERROR_PATCH_GOOGLEPLAY_CHANNEL      = -6;    public static final int ERROR_PATCH_ROM_SPACE               = -7;    public static final int ERROR_PATCH_MEMORY_LIMIT            = -8;    public static final int ERROR_PATCH_CRASH_LIMIT             = -9;    public static final int ERROR_PATCH_CONDITION_NOT_SATISFIED = -10;    public static final int ERROR_PATCH_ALREADY_APPLY           = -11;    public static final int ERROR_PATCH_RETRY_COUNT_LIMIT       = -12;    public static final String PLATFORM = "platform";    public static final int MIN_MEMORY_HEAP_SIZE = 45;    private static boolean background = false;    public static boolean isGooglePlay() {        return false;    }    public static boolean isBackground() {        return background;    }    public static void setBackground(boolean back) {        background = back;    }    public static int checkForPatchRecover(long roomSize, int maxMemory) {        if (Utils.isGooglePlay()) {            return Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL;        }        if (maxMemory < MIN_MEMORY_HEAP_SIZE) {            return Utils.ERROR_PATCH_MEMORY_LIMIT;        }        //or you can mention user to clean their rom space!        if (!checkRomSpaceEnough(roomSize)) {            return Utils.ERROR_PATCH_ROM_SPACE;        }        return ShareConstants.ERROR_PATCH_OK;    }    public static boolean isXposedExists(Throwable thr) {        StackTraceElement[] stackTraces = thr.getStackTrace();        for (StackTraceElement stackTrace : stackTraces) {            final String clazzName = stackTrace.getClassName();            if (clazzName != null && clazzName.contains("de.robv.android.xposed.XposedBridge")) {                return true;            }        }        return false;    }    @Deprecated    public static boolean checkRomSpaceEnough(long limitSize) {        long allSize;        long availableSize = 0;        try {            File data = Environment.getDataDirectory();            StatFs sf = new StatFs(data.getPath());            availableSize = (long) sf.getAvailableBlocks() * (long) sf.getBlockSize();            allSize = (long) sf.getBlockCount() * (long) sf.getBlockSize();        } catch (Exception e) {            allSize = 0;        }        if (allSize != 0 && availableSize > limitSize) {            return true;        }        return false;    }    public static String getExceptionCauseString(final Throwable ex) {        final ByteArrayOutputStream bos = new ByteArrayOutputStream();        final PrintStream ps = new PrintStream(bos);        try {            // print directly            Throwable t = ex;            while (t.getCause() != null) {                t = t.getCause();            }            t.printStackTrace(ps);            return toVisualString(bos.toString());        } finally {            try {                bos.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }    private static String toVisualString(String src) {        boolean cutFlg = false;        if (null == src) {            return null;        }        char[] chr = src.toCharArray();        if (null == chr) {            return null;        }        int i = 0;        for (; i < chr.length; i++) {            if (chr[i] > 127) {                chr[i] = 0;                cutFlg = true;                break;            }        }        if (cutFlg) {            return new String(chr, 0, i);        } else {            return src;        }    }    public static class ScreenState {        public interface IOnScreenOff {            void onScreenOff();        }        public ScreenState(final Context context, final IOnScreenOff onScreenOffInterface) {            IntentFilter filter = new IntentFilter();            filter.addAction(Intent.ACTION_SCREEN_OFF);            context.registerReceiver(new BroadcastReceiver() {                @Override                public void onReceive(Context context, Intent in) {                    String action = in == null ? "" : in.getAction();                    TinkerLog.i(TAG, "ScreenReceiver action [%s] ", action);                    if (Intent.ACTION_SCREEN_OFF.equals(action)) {                        if (onScreenOffInterface != null) {                            onScreenOffInterface.onScreenOff();                        }                    }                    context.unregisterReceiver(this);                }            }, filter);        }    }}


SampleResultService类
public class SampleResultService extends DefaultTinkerResultService {    private static final String TAG = "Tinker.SampleResultService";    @Override    public void onPatchResult(final PatchResult result) {        if (result == null) {            TinkerLog.e(TAG, "SampleResultService received null result!!!!");            return;        }        TinkerLog.i(TAG, "SampleResultService receive result: %s", result.toString());        //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!");            }        }    }    /**     * you can restart your process through service or broadcast     */    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());    }}


SampleLoadReporter类
public class SampleLoadReporter extends DefaultLoadReporter {    private final static String TAG = "Tinker.SampleLoadReporter";    public SampleLoadReporter(Context context) {        super(context);    }    @Override    public void onLoadPatchListenerReceiveFail(final File patchFile, int errorCode) {        super.onLoadPatchListenerReceiveFail(patchFile, errorCode);        SampleTinkerReport.onTryApplyFail(errorCode);    }    @Override    public void onLoadResult(File patchDirectory, int loadCode, long cost) {        super.onLoadResult(patchDirectory, loadCode, cost);        switch (loadCode) {            case ShareConstants.ERROR_LOAD_OK:                SampleTinkerReport.onLoaded(cost);                break;        }        Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() {            @Override            public boolean queueIdle() {                if (UpgradePatchRetry.getInstance(context).onPatchRetryLoad()) {                    SampleTinkerReport.onReportRetryPatch();                }                return false;            }        });    }    @Override    public void onLoadException(Throwable e, int errorCode) {        super.onLoadException(e, errorCode);        SampleTinkerReport.onLoadException(e, errorCode);    }    @Override    public void onLoadFileMd5Mismatch(File file, int fileType) {        super.onLoadFileMd5Mismatch(file, fileType);        SampleTinkerReport.onLoadFileMisMatch(fileType);    }    /**     * try to recover patch oat file     *     * @param file     * @param fileType     * @param isDirectory     */    @Override    public void onLoadFileNotFound(File file, int fileType, boolean isDirectory) {        super.onLoadFileNotFound(file, fileType, isDirectory);        SampleTinkerReport.onLoadFileNotFound(fileType);    }    @Override    public void onLoadPackageCheckFail(File patchFile, int errorCode) {        super.onLoadPackageCheckFail(patchFile, errorCode);        SampleTinkerReport.onLoadPackageCheckFail(errorCode);    }    @Override    public void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) {        super.onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile);        SampleTinkerReport.onLoadInfoCorrupted();    }    @Override    public void onLoadInterpret(int type, Throwable e) {        super.onLoadInterpret(type, e);        SampleTinkerReport.onLoadInterpretReport(type, e);    }    @Override    public void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) {        super.onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectoryFile, currentPatchName);    }}


SamplePatchListener类
public class SamplePatchListener extends DefaultPatchListener {    private static final String TAG = "Tinker.SamplePatchListener";    protected static final long NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN = 60 * 1024 * 1024;    private final int maxMemory;    public SamplePatchListener(Context context) {        super(context);        maxMemory = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();        TinkerLog.i(TAG, "application maxMemory:" + maxMemory);    }    /**     * because we use the defaultCheckPatchReceived method     * the error code define by myself should after {@code ShareConstants.ERROR_RECOVER_INSERVICE     *     * @param path     * @param newPatch     * @return     */    @Override    public int patchCheck(String path) {        File patchFile = new File(path);        TinkerLog.i(TAG, "receive a patch file: %s, file size:%d", path, SharePatchFileUtil.getFileOrDirectorySize(patchFile));        int returnCode = super.patchCheck(path);        if (returnCode == ShareConstants.ERROR_PATCH_OK) {            returnCode = Utils.checkForPatchRecover(NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN, maxMemory);        }        if (returnCode == ShareConstants.ERROR_PATCH_OK) {            String patchMd5 = SharePatchFileUtil.getMD5(patchFile);            SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS);            //optional, only disable this patch file with md5            int fastCrashCount = sp.getInt(patchMd5, 0);            if (fastCrashCount >= SampleUncaughtExceptionHandler.MAX_CRASH_COUNT) {                returnCode = Utils.ERROR_PATCH_CRASH_LIMIT;            } else {                //for upgrade patch, version must be not the same                //for repair patch, we won't has the tinker load flag                Tinker tinker = Tinker.with(context);                if (tinker.isTinkerLoaded()) {                    TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent();                    if (tinkerLoadResult != null && !tinkerLoadResult.useInterpretMode) {                        String currentVersion = tinkerLoadResult.currentVersion;                        if (patchMd5.equals(currentVersion)) {                            returnCode = Utils.ERROR_PATCH_ALREADY_APPLY;                        }                    }                }            }            //check whether retry so many times            if (returnCode == ShareConstants.ERROR_PATCH_OK) {                returnCode = UpgradePatchRetry.getInstance(context).onPatchListenerCheck(patchMd5)                    ? ShareConstants.ERROR_PATCH_OK : Utils.ERROR_PATCH_RETRY_COUNT_LIMIT;            }        }        // Warning, it is just a sample case, you don't need to copy all of these        // Interception some of the request        if (returnCode == ShareConstants.ERROR_PATCH_OK) {            Properties properties = ShareTinkerInternals.fastGetPatchPackageMeta(patchFile);            if (properties == null) {                returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;            } else {                String platform = properties.getProperty(Utils.PLATFORM);                TinkerLog.i(TAG, "get platform:" + platform);                // check patch platform require                if (platform == null || !platform.equals(BuildInfo.PLATFORM)) {                    returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;                }            }        }        SampleTinkerReport.onTryApply(returnCode == ShareConstants.ERROR_PATCH_OK);        return returnCode;    }}

SamplePatchReporter类 
public class SamplePatchReporter extends DefaultPatchReporter {    private final static String TAG = "Tinker.SamplePatchReporter";    public SamplePatchReporter(Context context) {        super(context);    }    @Override    public void onPatchServiceStart(Intent intent) {        super.onPatchServiceStart(intent);        SampleTinkerReport.onApplyPatchServiceStart();    }    @Override    public void onPatchDexOptFail(File patchFile, List<File> dexFiles, Throwable t) {        super.onPatchDexOptFail(patchFile, dexFiles, t);        SampleTinkerReport.onApplyDexOptFail(t);    }    @Override    public void onPatchException(File patchFile, Throwable e) {        super.onPatchException(patchFile, e);        SampleTinkerReport.onApplyCrash(e);    }    @Override    public void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion) {        super.onPatchInfoCorrupted(patchFile, oldVersion, newVersion);        SampleTinkerReport.onApplyInfoCorrupted();    }    @Override    public void onPatchPackageCheckFail(File patchFile, int errorCode) {        super.onPatchPackageCheckFail(patchFile, errorCode);        SampleTinkerReport.onApplyPackageCheckFail(errorCode);    }    @Override    public void onPatchResult(File patchFile, boolean success, long cost) {        super.onPatchResult(patchFile, success, cost);        SampleTinkerReport.onApplied(cost, success);    }    @Override    public void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType) {        super.onPatchTypeExtractFail(patchFile, extractTo, filename, fileType);        SampleTinkerReport.onApplyExtractFail(fileType);    }    @Override    public void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion) {        super.onPatchVersionCheckFail(patchFile, oldPatchInfo, patchFileVersion);        SampleTinkerReport.onApplyVersionCheckFail();    }}


SampleTinkerReport类
public class SampleTinkerReport {    private static final String TAG = "Tinker.SampleTinkerReport";    // KEY - PV    public static final int KEY_REQUEST                   = 0;    public static final int KEY_DOWNLOAD                  = 1;    public static final int KEY_TRY_APPLY                 = 2;    public static final int KEY_TRY_APPLY_SUCCESS         = 3;    public static final int KEY_APPLIED_START             = 4;    public static final int KEY_APPLIED                   = 5;    public static final int KEY_LOADED                    = 6;    public static final int KEY_CRASH_FAST_PROTECT        = 7;    public static final int KEY_CRASH_CAUSE_XPOSED_DALVIK = 8;    public static final int KEY_CRASH_CAUSE_XPOSED_ART    = 9;    public static final int KEY_APPLY_WITH_RETRY          = 10;    //Key -- try apply detail    public static final int KEY_TRY_APPLY_UPGRADE                 = 70;    public static final int KEY_TRY_APPLY_DISABLE                 = 71;    public static final int KEY_TRY_APPLY_RUNNING                 = 72;    public static final int KEY_TRY_APPLY_INSERVICE               = 73;    public static final int KEY_TRY_APPLY_NOT_EXIST               = 74;    public static final int KEY_TRY_APPLY_GOOGLEPLAY              = 75;    public static final int KEY_TRY_APPLY_ROM_SPACE               = 76;    public static final int KEY_TRY_APPLY_ALREADY_APPLY           = 77;    public static final int KEY_TRY_APPLY_MEMORY_LIMIT            = 78;    public static final int KEY_TRY_APPLY_CRASH_LIMIT             = 79;    public static final int KEY_TRY_APPLY_CONDITION_NOT_SATISFIED = 80;    public static final int KEY_TRY_APPLY_JIT                     = 81;    //Key -- apply detail    public static final int KEY_APPLIED_UPGRADE      = 100;    public static final int KEY_APPLIED_UPGRADE_FAIL = 101;    public static final int KEY_APPLIED_EXCEPTION                               = 120;    public static final int KEY_APPLIED_DEXOPT_OTHER                            = 121;    public static final int KEY_APPLIED_DEXOPT_EXIST                            = 122;    public static final int KEY_APPLIED_DEXOPT_FORMAT                           = 123;    public static final int KEY_APPLIED_INFO_CORRUPTED                          = 124;    //package check    public static final int KEY_APPLIED_PACKAGE_CHECK_SIGNATURE                 = 150;    public static final int KEY_APPLIED_PACKAGE_CHECK_DEX_META                  = 151;    public static final int KEY_APPLIED_PACKAGE_CHECK_LIB_META                  = 152;    public static final int KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND   = 153;    public static final int KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 154;    public static final int KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND            = 155;    public static final int KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL       = 156;    public static final int KEY_APPLIED_PACKAGE_CHECK_RES_META                  = 157;    public static final int KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT    = 158;    //version check    public static final int KEY_APPLIED_VERSION_CHECK      = 180;    //extract error    public static final int KEY_APPLIED_PATCH_FILE_EXTRACT = 181;    public static final int KEY_APPLIED_DEX_EXTRACT        = 182;    public static final int KEY_APPLIED_LIB_EXTRACT        = 183;    public static final int KEY_APPLIED_RESOURCE_EXTRACT   = 184;    //cost time    public static final int KEY_APPLIED_SUCC_COST_5S_LESS  = 200;    public static final int KEY_APPLIED_SUCC_COST_10S_LESS = 201;    public static final int KEY_APPLIED_SUCC_COST_30S_LESS = 202;    public static final int KEY_APPLIED_SUCC_COST_60S_LESS = 203;    public static final int KEY_APPLIED_SUCC_COST_OTHER    = 204;    public static final int KEY_APPLIED_FAIL_COST_5S_LESS  = 205;    public static final int KEY_APPLIED_FAIL_COST_10S_LESS = 206;    public static final int KEY_APPLIED_FAIL_COST_30S_LESS = 207;    public static final int KEY_APPLIED_FAIL_COST_60S_LESS = 208;    public static final int KEY_APPLIED_FAIL_COST_OTHER    = 209;    // KEY -- load detail    public static final int KEY_LOADED_UNKNOWN_EXCEPTION        = 250;    public static final int KEY_LOADED_UNCAUGHT_EXCEPTION       = 251;    public static final int KEY_LOADED_EXCEPTION_DEX            = 252;    public static final int KEY_LOADED_EXCEPTION_DEX_CHECK      = 253;    public static final int KEY_LOADED_EXCEPTION_RESOURCE       = 254;    public static final int KEY_LOADED_EXCEPTION_RESOURCE_CHECK = 255;    public static final int KEY_LOADED_MISMATCH_DEX       = 300;    public static final int KEY_LOADED_MISMATCH_LIB       = 301;    public static final int KEY_LOADED_MISMATCH_RESOURCE  = 302;    public static final int KEY_LOADED_MISSING_DEX        = 303;    public static final int KEY_LOADED_MISSING_LIB        = 304;    public static final int KEY_LOADED_MISSING_PATCH_FILE = 305;    public static final int KEY_LOADED_MISSING_PATCH_INFO = 306;    public static final int KEY_LOADED_MISSING_DEX_OPT    = 307;    public static final int KEY_LOADED_MISSING_RES        = 308;    public static final int KEY_LOADED_INFO_CORRUPTED     = 309;    //load package check    public static final int KEY_LOADED_PACKAGE_CHECK_SIGNATURE                 = 350;    public static final int KEY_LOADED_PACKAGE_CHECK_DEX_META                  = 351;    public static final int KEY_LOADED_PACKAGE_CHECK_LIB_META                  = 352;    public static final int KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND   = 353;    public static final int KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 354;    public static final int KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL       = 355;    public static final int KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND    = 356;    public static final int KEY_LOADED_PACKAGE_CHECK_RES_META                  = 357;    public static final int KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT    = 358;    public static final int KEY_LOADED_SUCC_COST_500_LESS  = 400;    public static final int KEY_LOADED_SUCC_COST_1000_LESS = 401;    public static final int KEY_LOADED_SUCC_COST_3000_LESS = 402;    public static final int KEY_LOADED_SUCC_COST_5000_LESS = 403;    public static final int KEY_LOADED_SUCC_COST_OTHER     = 404;    public static final int KEY_LOADED_INTERPRET_GET_INSTRUCTION_SET_ERROR = 450;    public static final int KEY_LOADED_INTERPRET_INTERPRET_COMMAND_ERROR   = 451;    public static final int KEY_LOADED_INTERPRET_TYPE_INTERPRET_OK         = 452;    interface Reporter {        void onReport(int key);        void onReport(String message);    }    private static Reporter reporter = null;    public void setReporter(Reporter reporter) {        this.reporter = reporter;    }    public static void onTryApply(boolean success) {        if (reporter == null) {            return;        }        reporter.onReport(KEY_TRY_APPLY);        reporter.onReport(KEY_TRY_APPLY_UPGRADE);        if (success) {            reporter.onReport(KEY_TRY_APPLY_SUCCESS);        }    }    public static void onTryApplyFail(int errorCode) {        if (reporter == null) {            return;        }        switch (errorCode) {            case ShareConstants.ERROR_PATCH_NOTEXIST:                reporter.onReport(KEY_TRY_APPLY_NOT_EXIST);                break;            case ShareConstants.ERROR_PATCH_DISABLE:                reporter.onReport(KEY_TRY_APPLY_DISABLE);                break;            case ShareConstants.ERROR_PATCH_INSERVICE:                reporter.onReport(KEY_TRY_APPLY_INSERVICE);                break;            case ShareConstants.ERROR_PATCH_RUNNING:                reporter.onReport(KEY_TRY_APPLY_RUNNING);                break;            case ShareConstants.ERROR_PATCH_JIT:                reporter.onReport(KEY_TRY_APPLY_JIT);                break;            case Utils.ERROR_PATCH_ROM_SPACE:                reporter.onReport(KEY_TRY_APPLY_ROM_SPACE);                break;            case Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL:                reporter.onReport(KEY_TRY_APPLY_GOOGLEPLAY);                break;            case Utils.ERROR_PATCH_ALREADY_APPLY:                reporter.onReport(KEY_TRY_APPLY_ALREADY_APPLY);                break;            case Utils.ERROR_PATCH_CRASH_LIMIT:                reporter.onReport(KEY_TRY_APPLY_CRASH_LIMIT);                break;            case Utils.ERROR_PATCH_MEMORY_LIMIT:                reporter.onReport(KEY_TRY_APPLY_MEMORY_LIMIT);                break;            case Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED:                reporter.onReport(KEY_TRY_APPLY_CONDITION_NOT_SATISFIED);                break;        }    }    public static void onLoadPackageCheckFail(int errorCode) {        if (reporter == null) {            return;        }        switch (errorCode) {            case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL:                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_SIGNATURE);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_DEX_META);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_LIB_META);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_RES_META);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);                break;        }    }    public static void onLoaded(long cost) {        if (reporter == null) {            return;        }        reporter.onReport(KEY_LOADED);        if (cost < 0L) {            TinkerLog.e(TAG, "hp_report report load cost failed, invalid cost");            return;        }        if (cost <= 500) {            reporter.onReport(KEY_LOADED_SUCC_COST_500_LESS);        } else if (cost <= 1000) {            reporter.onReport(KEY_LOADED_SUCC_COST_1000_LESS);        } else if (cost <= 3000) {            reporter.onReport(KEY_LOADED_SUCC_COST_3000_LESS);        } else if (cost <= 5000) {            reporter.onReport(KEY_LOADED_SUCC_COST_5000_LESS);        } else {            reporter.onReport(KEY_LOADED_SUCC_COST_OTHER);        }    }    public static void onLoadInfoCorrupted() {        if (reporter == null) {            return;        }        reporter.onReport(KEY_LOADED_INFO_CORRUPTED);    }    public static void onLoadFileNotFound(int fileType) {        if (reporter == null) {            return;        }        switch (fileType) {            case ShareConstants.TYPE_DEX_OPT:                reporter.onReport(KEY_LOADED_MISSING_DEX_OPT);                break;            case ShareConstants.TYPE_DEX:                reporter.onReport(KEY_LOADED_MISSING_DEX);                break;            case ShareConstants.TYPE_LIBRARY:                reporter.onReport(KEY_LOADED_MISSING_LIB);                break;            case ShareConstants.TYPE_PATCH_FILE:                reporter.onReport(KEY_LOADED_MISSING_PATCH_FILE);                break;            case ShareConstants.TYPE_PATCH_INFO:                reporter.onReport(KEY_LOADED_MISSING_PATCH_INFO);                break;            case ShareConstants.TYPE_RESOURCE:                reporter.onReport(KEY_LOADED_MISSING_RES);                break;        }    }    public static void onLoadInterpretReport(int type, Throwable e) {        if (reporter == null) {            return;        }        switch (type) {            case ShareConstants.TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR:                reporter.onReport(KEY_LOADED_INTERPRET_GET_INSTRUCTION_SET_ERROR);                reporter.onReport("Tinker Exception:interpret occur exception " + Utils.getExceptionCauseString(e));                break;            case ShareConstants.TYPE_INTERPRET_COMMAND_ERROR:                reporter.onReport(KEY_LOADED_INTERPRET_INTERPRET_COMMAND_ERROR);                reporter.onReport("Tinker Exception:interpret occur exception " + Utils.getExceptionCauseString(e));                break;            case ShareConstants.TYPE_INTERPRET_OK:                reporter.onReport(KEY_LOADED_INTERPRET_TYPE_INTERPRET_OK);                break;        }    }    public static void onLoadFileMisMatch(int fileType) {        if (reporter == null) {            return;        }        switch (fileType) {            case ShareConstants.TYPE_DEX:                reporter.onReport(KEY_LOADED_MISMATCH_DEX);                break;            case ShareConstants.TYPE_LIBRARY:                reporter.onReport(KEY_LOADED_MISMATCH_LIB);                break;            case ShareConstants.TYPE_RESOURCE:                reporter.onReport(KEY_LOADED_MISMATCH_RESOURCE);                break;        }    }    public static void onLoadException(Throwable throwable, int errorCode) {        if (reporter == null) {            return;        }        boolean isCheckFail = false;        switch (errorCode) {            case ShareConstants.ERROR_LOAD_EXCEPTION_DEX:                if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_INSTALL_FAIL)) {                    reporter.onReport(KEY_LOADED_EXCEPTION_DEX_CHECK);                    isCheckFail = true;                    TinkerLog.e(TAG, "tinker dex check fail:" + throwable.getMessage());                } else {                    reporter.onReport(KEY_LOADED_EXCEPTION_DEX);                    TinkerLog.e(TAG, "tinker dex reflect fail:" + throwable.getMessage());                }                break;            case ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE:                if (throwable.getMessage().contains(ShareConstants.CHECK_RES_INSTALL_FAIL)) {                    reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE_CHECK);                    isCheckFail = true;                    TinkerLog.e(TAG, "tinker res check fail:" + throwable.getMessage());                } else {                    reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE);                    TinkerLog.e(TAG, "tinker res reflect fail:" + throwable.getMessage());                }                break;            case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT:                reporter.onReport(KEY_LOADED_UNCAUGHT_EXCEPTION);                break;            case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN:                reporter.onReport(KEY_LOADED_UNKNOWN_EXCEPTION);                break;        }        //reporter exception, for dex check fail, we don't need to report stacktrace        if (!isCheckFail) {            reporter.onReport("Tinker Exception:load tinker occur exception " + Utils.getExceptionCauseString(throwable));        }    }    public static void onApplyPatchServiceStart() {        if (reporter == null) {            return;        }        reporter.onReport(KEY_APPLIED_START);    }    public static void onApplyDexOptFail(Throwable throwable) {        if (reporter == null) {            return;        }        if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_OAT_EXIST_FAIL)) {            reporter.onReport(KEY_APPLIED_DEXOPT_EXIST);        } else if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_OAT_FORMAT_FAIL)) {            reporter.onReport(KEY_APPLIED_DEXOPT_FORMAT);        } else {            reporter.onReport(KEY_APPLIED_DEXOPT_OTHER);            reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable));        }    }    public static void onApplyInfoCorrupted() {        if (reporter == null) {            return;        }        reporter.onReport(KEY_APPLIED_INFO_CORRUPTED);    }    public static void onApplyVersionCheckFail() {        if (reporter == null) {            return;        }        reporter.onReport(KEY_APPLIED_VERSION_CHECK);    }    public static void onApplyExtractFail(int fileType) {        if (reporter == null) {            return;        }        switch (fileType) {            case ShareConstants.TYPE_DEX:                reporter.onReport(KEY_APPLIED_DEX_EXTRACT);                break;            case ShareConstants.TYPE_LIBRARY:                reporter.onReport(KEY_APPLIED_LIB_EXTRACT);                break;            case ShareConstants.TYPE_PATCH_FILE:                reporter.onReport(KEY_APPLIED_PATCH_FILE_EXTRACT);                break;            case ShareConstants.TYPE_RESOURCE:                reporter.onReport(KEY_APPLIED_RESOURCE_EXTRACT);                break;        }    }    public static void onApplied(long cost, boolean success) {        if (reporter == null) {            return;        }        if (success) {            reporter.onReport(KEY_APPLIED);        }        if (success) {            reporter.onReport(KEY_APPLIED_UPGRADE);        } else {            reporter.onReport(KEY_APPLIED_UPGRADE_FAIL);        }        TinkerLog.i(TAG, "hp_report report apply cost = %d", cost);        if (cost < 0L) {            TinkerLog.e(TAG, "hp_report report apply cost failed, invalid cost");            return;        }        if (cost <= 5000) {            if (success) {                reporter.onReport(KEY_APPLIED_SUCC_COST_5S_LESS);            } else {                reporter.onReport(KEY_APPLIED_FAIL_COST_5S_LESS);            }        } else if (cost <= 10 * 1000) {            if (success) {                reporter.onReport(KEY_APPLIED_SUCC_COST_10S_LESS);            } else {                reporter.onReport(KEY_APPLIED_FAIL_COST_10S_LESS);            }        } else if (cost <= 30 * 1000) {            if (success) {                reporter.onReport(KEY_APPLIED_SUCC_COST_30S_LESS);            } else {                reporter.onReport(KEY_APPLIED_FAIL_COST_30S_LESS);            }        } else if (cost <= 60 * 1000) {            if (success) {                reporter.onReport(KEY_APPLIED_SUCC_COST_60S_LESS);            } else {                reporter.onReport(KEY_APPLIED_FAIL_COST_60S_LESS);            }        } else {            if (success) {                reporter.onReport(KEY_APPLIED_SUCC_COST_OTHER);            } else {                reporter.onReport(KEY_APPLIED_FAIL_COST_OTHER);            }        }    }    public static void onApplyPackageCheckFail(int errorCode) {        if (reporter == null) {            return;        }        TinkerLog.i(TAG, "hp_report package check failed, error = %d", errorCode);        switch (errorCode) {            case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL:                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_SIGNATURE);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_DEX_META);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_LIB_META);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_RES_META);                break;            case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);                break;        }    }    public static void onApplyCrash(Throwable throwable) {        if (reporter == null) {            return;        }        reporter.onReport(KEY_APPLIED_EXCEPTION);        reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable));    }    public static void onFastCrashProtect() {        if (reporter == null) {            return;        }        reporter.onReport(KEY_CRASH_FAST_PROTECT);    }    public static void onXposedCrash() {        if (reporter == null) {            return;        }        if (ShareTinkerInternals.isVmArt()) {            reporter.onReport(KEY_CRASH_CAUSE_XPOSED_ART);        } else {            reporter.onReport(KEY_CRASH_CAUSE_XPOSED_DALVIK);        }    }    public static void onReportRetryPatch() {        if (reporter == null) {            return;        }        reporter.onReport(KEY_APPLY_WITH_RETRY);    }}

MyLogImp类 
public class MyLogImp implements TinkerLog.TinkerLogImp {    private static final String TAG = "Tinker.MyLogImp";    public static final int LEVEL_VERBOSE = 0;    public static final int LEVEL_DEBUG   = 1;    public static final int LEVEL_INFO    = 2;    public static final int LEVEL_WARNING = 3;    public static final int LEVEL_ERROR   = 4;    public static final int LEVEL_NONE    = 5;    private static int level = LEVEL_VERBOSE;    public static int getLogLevel() {        return level;    }    public static void setLevel(final int level) {        MyLogImp.level = level;        android.util.Log.w(TAG, "new log level: " + level);    }    @Override    public void v(String s, String s1, Object... objects) {        if (level <= LEVEL_VERBOSE) {            final String log = objects == null ? s1 : String.format(s1, objects);            android.util.Log.v(s, log);        }    }    @Override    public void i(String s, String s1, Object... objects) {        if (level <= LEVEL_INFO) {            final String log = objects == null ? s1 : String.format(s1, objects);            android.util.Log.i(s, log);        }    }    @Override    public void w(String s, String s1, Object... objects) {        if (level <= LEVEL_WARNING) {            final String log = objects == null ? s1 : String.format(s1, objects);            android.util.Log.w(s, log);        }    }    @Override    public void d(String s, String s1, Object... objects) {        if (level <= LEVEL_DEBUG) {            final String log = objects == null ? s1 : String.format(s1, objects);            android.util.Log.d(s, log);        }    }    @Override    public void e(String s, String s1, Object... objects) {        if (level <= LEVEL_ERROR) {            final String log = objects == null ? s1 : String.format(s1, objects);            android.util.Log.e(s, log);        }    }    @Override    public void printErrStackTrace(String s, Throwable throwable, String s1, Object... objects) {        String log = objects == null ? s1 : String.format(s1, objects);        if (log == null) {            log = "";        }        log = log + "  " + Log.getStackTraceString(throwable);        android.util.Log.e(s, log);    }}


SampleUncaughtExceptionHandler类
public class SampleUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {    private static final String TAG = "Tinker.SampleUncaughtExHandler";    private final Thread.UncaughtExceptionHandler ueh;    private static final long   QUICK_CRASH_ELAPSE  = 10 * 1000;    public static final  int    MAX_CRASH_COUNT     = 3;    private static final String DALVIK_XPOSED_CRASH = "Class ref in pre-verified class resolved to unexpected implementation";    public SampleUncaughtExceptionHandler() {        ueh = Thread.getDefaultUncaughtExceptionHandler();    }    @Override    public void uncaughtException(Thread thread, Throwable ex) {        TinkerLog.e(TAG, "uncaughtException:" + ex.getMessage());        tinkerFastCrashProtect();        tinkerPreVerifiedCrashHandler(ex);        ueh.uncaughtException(thread, ex);    }    /**     * Such as Xposed, if it try to load some class before we load from patch files.     * With dalvik, it will crash with "Class ref in pre-verified class resolved to unexpected implementation".     * With art, it may crash at some times. But we can't know the actual crash type.     * If it use Xposed, we can just clean patch or mention user to uninstall it.     */    private void tinkerPreVerifiedCrashHandler(Throwable ex) {        ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike();        if (applicationLike == null || applicationLike.getApplication() == null) {            TinkerLog.w(TAG, "applicationlike is null");            return;        }        if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) {            TinkerLog.w(TAG, "tinker is not loaded");            return;        }        Throwable throwable = ex;        boolean isXposed = false;        while (throwable != null) {            if (!isXposed) {                isXposed = Utils.isXposedExists(throwable);            }            // xposed?            if (isXposed) {                boolean isCausedByXposed = false;                //for art, we can't know the actually crash type                //just ignore art                if (throwable instanceof IllegalAccessError && throwable.getMessage().contains(DALVIK_XPOSED_CRASH)) {                    //for dalvik, we know the actual crash type                    isCausedByXposed = true;                }                if (isCausedByXposed) {                    SampleTinkerReport.onXposedCrash();                    TinkerLog.e(TAG, "have xposed: just clean tinker");                    //kill all other process to ensure that all process's code is the same.                    ShareTinkerInternals.killAllOtherProcess(applicationLike.getApplication());                    TinkerApplicationHelper.cleanPatch(applicationLike);                    ShareTinkerInternals.setTinkerDisableWithSharedPreferences(applicationLike.getApplication());                    return;                }            }            throwable = throwable.getCause();        }    }    /**     * if tinker is load, and it crash more than MAX_CRASH_COUNT, then we just clean patch.     */    private boolean tinkerFastCrashProtect() {        ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike();        if (applicationLike == null || applicationLike.getApplication() == null) {            return false;        }        if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) {            return false;        }        final long elapsedTime = SystemClock.elapsedRealtime() - applicationLike.getApplicationStartElapsedTime();        //this process may not install tinker, so we use TinkerApplicationHelper api        if (elapsedTime < QUICK_CRASH_ELAPSE) {            String currentVersion = TinkerApplicationHelper.getCurrentVersion(applicationLike);            if (ShareTinkerInternals.isNullOrNil(currentVersion)) {                return false;            }            SharedPreferences sp = applicationLike.getApplication().getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS);            int fastCrashCount = sp.getInt(currentVersion, 0) + 1;            if (fastCrashCount >= MAX_CRASH_COUNT) {                SampleTinkerReport.onFastCrashProtect();                TinkerApplicationHelper.cleanPatch(applicationLike);                TinkerLog.e(TAG, "tinker has fast crash more than %d, we just clean patch!", fastCrashCount);                return true;            } else {                sp.edit().putInt(currentVersion, fastCrashCount).commit();                TinkerLog.e(TAG, "tinker has fast crash %d times", fastCrashCount);            }        }        return false;    }}



到这里需要的相关类已经完了,复制的好累.
接着是我们调用tinker的代码了 

在MainActivity中两个按钮点击里面添加调用代码 

加载需要更改的补丁
public void loadPath(View v){    TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),            Environment.getExternalStorageDirectory().getAbsolutePath()            + "/patch_signed_7zip.apk");}

删除补丁包已经杀死进程
public void kill (View v){    ShareTinkerInternals.killAllOtherProcess(getApplicationContext());    android.os.Process.killProcess(android.os.Process.myPid());}


在as上点击Gradle 






在build文件里面点击 assembleRelease   用assembleReleaseDebug也是可以的 
点击以后tinker就会进行编译,等待编译成功后在app目录build里面找到bakApk文件打开里面会有apk和R.txt文件




这个apk就可以直接使用经过签名的.把apk文件安装到手机上.正常运行这个时候点击加载Path按钮是不会有任何提示的.点击杀死自己 就会把自己杀了但是重新进来的时候还是一样的布局点击Path还是没反应.









接下来

在APP build.gradle 里面找到下面代码 

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-0616-15-27-53.apk"    //proguard mapping file to build patch apk    tinkerApplyMappingPath = "${bakPath}/app-release-0616-15-27-53-mapping.txt"    //resource R.txt to build patch apk, must input if there is resource changed    tinkerApplyResourcePath = "${bakPath}/app-release-0616-15-27-53-R.txt"    //only use for build all flavor, if not, just ignore this field    tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"}

tinkerOldApkPath 是你apk的路径 
tinkerApplyMappingPath  如果你没混淆的话这个可以仿照我写的那样 ,你混淆以后就会在bakApk 会生成mapping文件
tinkerApplyResourcePath 这是生成R.txt文件路径
配置好了以后

对MainActivity里面的代码以及布局做一些修改  

显示布局文件  

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="com.trust.trusthotfix.MainActivity">    <ImageView        android:src="@mipmap/ic_launcher"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <TextView        android:text="BUG 已经修复!!!!!1"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <EditText        android:id="@+id/ed"        android:layout_width="match_parent"        android:layout_height="48dp" />    <Button        android:onClick="click"        android:text="确定"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <Button        android:visibility="gone"        android:onClick="loadPath"        android:text="加载path"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <Button        android:visibility="gone"        android:onClick="kill"        android:text="杀死自己"        android:layout_width="wrap_content"        android:layout_height="wrap_content" /></LinearLayout>





添加一个系统图片  添加一个 输入框   把之前的两个按钮隐藏    添加一个确定按钮


逻辑代码  


public class MainActivity extends AppCompatActivity {    EditText ed;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Log.d("lhh", "onCreate: BUG is  nothing");        ed = (EditText) findViewById(R.id.ed);    }    public void loadPath(View v){        TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),                Environment.getExternalStorageDirectory().getAbsolutePath()                + "/patch_signed_7zip.apk");    }    public void kill (View v){        ShareTinkerInternals.killAllOtherProcess(getApplicationContext());        android.os.Process.killProcess(android.os.Process.myPid());    }    public void click(View v){        String msg = ed.getText().toString().trim();        if(msg != null && !msg.equals("")){            Toast.makeText(this,"你输入的是:"+msg,Toast.LENGTH_LONG).show();        }else{            Toast.makeText(this,"不能为空!"+msg,Toast.LENGTH_LONG).show();        }    }



只是简单的用toast 显示输入的文字  

ok 继续 Gradle  ---- >tinker---->tinkerPathRelease 双击 




等待编译成功后,在build文件 ------>outputs------>tinkerPath----->patch_signed_7zip.apk





patch_signed_7zip.apk这个文件就是我们需要的更新补丁了.把这个补丁直接放入手机里面的根目录中,打开手机上的app.

点击 加载Path 成的话 会有一个toast 提示 success  ,然后点击杀死自己,重新进入APP就会发现布局已经改变了











ok到这里 tinker 热更新 替换class 文件已经写完了.接下来时 更新lib文件介绍.


Demo地址

END