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

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
}

//recommend
dexOptions {
    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文件介绍.



END

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值