美文网首页Android常用功能
Android热修复Tinker集成

Android热修复Tinker集成

作者: 龙儿筝 | 来源:发表于2017-08-02 17:50 被阅读19次

    1.配置Project下的build.gradle

    buildscript {
      repositories {
        ...
        mavenLocal()
      }
      dependencies {
        ...
        classpath "com.tencent.tinker:tinker-patch-gradle-plugin:1.7.11"
      }
    }
    
    allprojects {
      repositories {
        ...
        mavenLocal()
        }
    }
    

    2.配置Module下的build.gradle

    apply plugin: 'com.android.application'
    apply plugin: 'com.tencent.tinker.patch'
    android {
      ...
      defaultConfig {
        ...
        multiDexEnabled true
        buildConfigField "String", "MESSAGE", "\"I am the base apk\""
        buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""
        buildConfigField "String", "PLATFORM", "\"all\""
      }
    }
    
    dependencies {
      ...
      compile "com.android.support:multidex:1.0.1"
      provided('com.tencent.tinker:tinker-android-anno:1.7.11')
      compile('com.tencent.tinker:tinker-android-lib:1.7.11')
    }
    //定义基准apk构建的位置
    def bakPath = file("${buildDir}/bakApk/")
    
    //额外属性,实际上这些也是可以不用写的,腾讯真是良心,可以支持Gradle脚本自定义,这些值实际上都可以在gradle.properites中自定义
    ext {
      //是否支持tinker构建
      tinkerEnabled = true
      //如果需要构建patch包,这里需要天上同一个其基准包地址
      tinkerOldApkPath = "${bakPath}/app-debug-05-27.apk"
      //如果需要构建patch包,这里需要天上同一个其基准包的混淆文件
      tinkerApplyMappingPath = "${bakPath}/app-debug-05-27-mapping.txt"
      //如果差分包有修改资源文件,则必须需要输入以前基准包的R文件,主要是为了固定id来用的。
      tinkerApplyResourcePath = "${bakPath}/app-debug-05-27-R.txt"
    }
    
    if (ext.tinkerEnabled) {
      //引用patch插件
      apply plugin: 'com.tencent.tinker.patch'
      //tinkerPath任务,补丁关键任务,源码也有,后面我会有详细撰述,这里只知道怎么用即可
      //直接使用基准apk包与新编译出来的apk包做差异,得到最终的补丁包
      tinkerPatch {
        oldApk = getTinkerOldApkPath()
        /**
          * 是否忽略警告
          * 值为false将中断编译。因为这些情况可能会导致编译出来的patch包带来风险:
          * 1. minSdkVersion小于14,但是dexMode的值为"raw";  dexmode下面会有介绍
          * 2. 新编译的安装包出现新增的四大组件(Activity, BroadcastReceiver...);
          * 3. 定义在dex.loader用于加载补丁的类不在main dex中;
          * 4. 定义在dex.loader用于加载补丁的类出现修改;
          * 5. resources.arsc改变,但没有使用applyResourceMapping编译。
          */
        ignoreWarning = false
        //是否签名,保证基准包和补丁包的签名一致,代码里有判断逻辑
        useSign = true
        /**
          * 编译相关配置项
          */
        buildConfig {
          //在编译新的apk时候,通过保持旧apk的proguard混淆方式,从而减少补丁包的大小。这个只是推荐设置,不设置applyMapping也不会影响任何的assemble编译
          applyMapping = getTinkerApplyMappingPath()
          //在编译新的apk时候,通过旧apk的R.txt文件保持ResId的分配,这样不仅可以减少补丁包的大小,同时也避免由于ResId改变导致remote view异常。
          applyResourceMapping = getTinkerApplyResourcePath()
          //tinkerID
          tinkerId = getTinkerIdValue()
          //如果有多个dex,编译补丁时可能会由于类的移动导致变更增多。若打开keepDexApply模式,补丁包将根据基准包的类分布来编译。
          keepDexApply = false
        }
        dex {
          //只能是'raw'或者'jar'。
          //对于'raw'模式,将会保持输入dex的格式。
          //对于'jar'模式,将会把输入dex重新压缩封装到jar。如果你的minSdkVersion小于14,你必须选择‘jar’模式,而且它更省存储空间,但是验证md5时比'raw'模式耗时。默认我们并不会去校验md5,一般情况下选择jar模式即可。
          dexMode = "jar"
          //需要处理dex路径,支持*、?通配符,必须使用'/'分割。路径是相对安装包的,例如assets/...
          pattern = ["classes*.dex", "assets/secondary-dex-?.jar"]
          /**
            * 这一项非常重要,它定义了哪些类在加载补丁包的时候会用到。这些类是通过Tinker无法修改的类,也是一定要放在main dex的类。
              这里需要定义的类有:
              1. 你自己定义的Application类;
              2. Tinker库中用于加载补丁包的部分类,即com.tencent.tinker.loader.*;
              3. 如果你自定义了TinkerLoader,需要将它以及它引用的所有类也加入loader中;
              4. 其他一些你不希望被更改的类,例如Sample中的BaseBuildInfo类。这里需要注意的是,这些类的直接引用类也需要加入到loader中。或者你需要将这个类变成非preverify。
              5. 使用1.7.6版本之后版本,参数1、2会自动填写。
            */
          loader = [
            //use sample, let BaseBuildInfo unchangeable with tinker
            //                    "tinker.sample.android.app.BaseBuildInfo"
          ]
        }
        lib {
          //需要处理lib路径,支持*、?通配符,必须使用'/'分割。与dex.pattern一致, 路径是相对安装包的,例如assets/...
          pattern = ["lib/armeabi/*.so"]
      }
      res {
        //需要处理res路径,支持*、?通配符,必须使用'/'分割。与dex.pattern一致, 路径是相对安装包的,例如assets/...,务必注意的是,只有满足pattern的资源才会放到合成后的资源包。
                pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
    
                //支持*、?通配符,必须使用'/'分割。若满足ignoreChange的pattern,在编译时会忽略该文件的新增、删除与修改。 最极端的情况,ignoreChange与上面的pattern一致,即会完全忽略所有资源的修改。
                ignoreChange = ["assets/sample_meta.txt"]
    
                //对于修改的资源,如果大于largeModSize,我们将使用bsdiff算法。这可以降低补丁包的大小,但是会增加合成时的复杂度。默认大小为100kb
                largeModSize = 100
            }
    
            packageConfig {
                //configField("key", "value"), 默认我们自动从基准安装包与新安装包的Manifest中读取tinkerId,并自动写入configField。在这里,你可以定义其他的信息,在运行时可以通过TinkerLoadResult.getPackageConfigByName得到相应的数值。但是建议直接通过修改代码来实现,例如BuildConfig。
                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")
            }
    
            //7zip路径配置项,执行前提是useSign为true
            sevenZip {
                //例如"com.tencent.mm:SevenZip:1.1.10",将自动根据机器属性获得对应的7za运行文件,推荐使用
                zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
            }
        }
    
        List<String> flavors = new ArrayList<>();
        project.android.productFlavors.each { flavor ->
            flavors.add(flavor.name)
        }
        boolean hasFlavors = flavors.size() > 0
        //bak apk and mapping, 渠道包相关配置。
        android.applicationVariants.all { variant ->
            //task type, you want to bak
            def taskName = variant.name
            //def date = new Date().format("MMdd-HH-mm-ss")
            def date = new Date().format("mm-ss")
            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")
                            }
                        }
                    }
                }
            }
        }
    }
    
    def getTinkerApplyResourcePath() {
        return ext.tinkerApplyResourcePath
    }
    
    def getTinkerApplyMappingPath() {
        return ext.tinkerApplyMappingPath
    }
    
    def getTinkerOldApkPath() {
        return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
    }
    
    def getTinkerIdValue() {
        return hasProperty("TINKER_ID") ? TINKER_ID : 1
    }
    

    3.配置Application

    @SuppressWarnings("unused")
    @DefaultLifeCycle(application = ".BaseApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false)
    public class SampleApplication extends DefaultApplicationLike {
      private static final String TAG = "Tinker.SampleApplicationLike";
    
      public SampleApplication(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
       */
      @Override
      public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        //you must install multiDex whatever tinker is installed!
        MultiDex.install(base);
    
        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());
        TinkerInstaller.setLogIml(TinkerLog.getImpl());
    
        //installTinker after load multiDex
        //or you can put com.tencent.tinker.** to main dex
        TinkerManager.installTinker(this);
        Tinker tinker = Tinker.with(getApplication());
      }
    }
    

    其中注解参数BaseApplication才是真正的Application,需要在AndroidManifest.xml文件中进行配置


    4.相关文件如下

    1.TinkerManager.java

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

    2.Utils.java

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

    3.SampleLoadReporter.java

    public class SampleLoadReporter extends DefaultLoadReporter {
    
      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);
      }
    
    }
    

    4.SamplePatchListener.java

    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(BuildConfig.PLATFORM)) {
              returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
            }
          }
        }
    
        SampleTinkerReport.onTryApply(returnCode == ShareConstants.ERROR_PATCH_OK);
        return returnCode;
      }
    }
    

    5.SamplePatchReporter.java

    public class SamplePatchReporter extends DefaultPatchReporter {
      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();
      }
    }
    

    6.SampleResultService.java

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

    7.SampleTinkerReport.java

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

    8.SampleUncaughtExceptionHandler.java

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

    5.生成补丁文件
    1.生成安装包:
    打开终端Terminal执行gradlew assembleDebug命令后,会在build/bakApk目录下生成一个安装包和文本文件。

    2017-08-02_173659.png
    在build.gradle文件中将后面的编号给为一致,如图所示
    image.png
    2.生成补丁包:
    打开终端Terminal执行gradlew tinkerPatchDebug命令后,在build/outputs/tinkerPatch/debug目录下会生成补丁文件,有签名和不签名的,
    image.png
    我们将补丁包patch_signed_7zip.apk模拟从服务器下载到手机本地,放在SDcard目录下
    6.加载补丁
    TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
    

    运行上面代码即可加载补丁。
    在打补丁之前,我们点击按钮弹个Toast,在补丁包中更改Toast内容。打完补丁后,再点击按钮即可看见Toast内容已改变为Patch

    image.png

    相关文章

      网友评论

      本文标题:Android热修复Tinker集成

      本文链接:https://www.haomeiwen.com/subject/hmfylxtx.html