前言

公司最近项目上线后总是遇见各种问题或bug,而我最近就一直在背黑锅,幸亏最近不用上Google Play了,赶紧加上热更新来脱离苦海吧,谁知道接入Tinker的过程中也踩了将近一周的坑,哎...一言难尽

为什么选择Tinker和对比了也懒得说了(其实是因为隔壁阿里收费)

而且需要注意,中间很多版本不要使用最新的,要使用我写的,不然有问题了找都找不到(我在这中间查,试,测,中间用了不知道多长时间)

正文

此次接入Tinker是直接用的Tinker,没有使用Bugly的方式

官方地址

不多感慨了,直接上步骤吧

1.项目的gradle->buildscript.dependencies中加入

classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')//版本号不能改

2.module的gradle->dependencies中加入

implementation('com.tencent.tinker:tinker-android-lib:1.9.9')//版本号不能改

3.改造Application,Tinker有两种改造Application的方法:

第一种手动改造,兼容性最高

第二种使用注解自动改造

本来嫌费事用的第二种,然后接入后各种问题(但也不知这的问题),然后我又改成第一种了

首先写一个类

public class SampleApplicationLike extends DefaultApplicationLike {public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);}@Overridepublic void onCreate() {super.onCreate();//在这里把之前Application中的初始化搬到这里}@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)@Overridepublic void onBaseContextAttached(Context base) {super.onBaseContextAttached(base);MultiDex.install(base);//这里加上分dex,我用的androidx的TinkerManager.setTinkerApplicationLike(this);TinkerManager.setUpgradeRetryEnable(true);TinkerManager.installTinker(this);Tinker.with(getApplication());}

然后改造你的Application,让其"真"·什么都不做,改成我这个样子

public class BaseApplication extends TinkerApplication {public BaseApplication() {super(ShareConstants.TINKER_ENABLE_ALL, SampleApplicationLike.class.getName());}
}

4.加上以下工具类(在上面的代码中用到了)

/*** Created by zhangshaowen on 16/6/30.* we use BuildInfo instead of {@link BuildInfo} to make less change*/
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;}
import android.content.Context;
import android.os.Looper;
import android.os.MessageQueue;import com.tencent.tinker.lib.reporter.DefaultLoadReporter;
import com.tencent.tinker.lib.util.UpgradePatchRetry;
import com.tencent.tinker.loader.shareutil.ShareConstants;import java.io.File;/*** optional, you can just use DefaultLoadReporter* Created by zhangshaowen on 16/4/13.*/
public class SampleLoadReporter extends DefaultLoadReporter {private final static String TAG = "Tinker.SampleLoadReporter";public SampleLoadReporter(Context context) {super(context);}@Overridepublic void onLoadPatchListenerReceiveFail(final File patchFile, int errorCode) {super.onLoadPatchListenerReceiveFail(patchFile, errorCode);SampleTinkerReport.onTryApplyFail(errorCode);}@Overridepublic 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() {@Overridepublic boolean queueIdle() {if (UpgradePatchRetry.getInstance(context).onPatchRetryLoad()) {SampleTinkerReport.onReportRetryPatch();}return false;}});}@Overridepublic void onLoadException(Throwable e, int errorCode) {super.onLoadException(e, errorCode);SampleTinkerReport.onLoadException(e, errorCode);}@Overridepublic 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*/@Overridepublic void onLoadFileNotFound(File file, int fileType, boolean isDirectory) {super.onLoadFileNotFound(file, fileType, isDirectory);SampleTinkerReport.onLoadFileNotFound(fileType);}@Overridepublic void onLoadPackageCheckFail(File patchFile, int errorCode) {super.onLoadPackageCheckFail(patchFile, errorCode);SampleTinkerReport.onLoadPackageCheckFail(errorCode);}@Overridepublic void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) {super.onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile);SampleTinkerReport.onLoadInfoCorrupted();}@Overridepublic void onLoadInterpret(int type, Throwable e) {super.onLoadInterpret(type, e);SampleTinkerReport.onLoadInterpretReport(type, e);}@Overridepublic void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) {super.onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectoryFile, currentPatchName);}}
import android.app.ActivityManager;
import android.content.Context;
import android.content.SharedPreferences;import com.tencent.tinker.lib.listener.DefaultPatchListener;
import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.loader.shareutil.ShareConstants;
import com.tencent.tinker.loader.shareutil.SharePatchFileUtil;
import com.tencent.tinker.loader.shareutil.ShareTinkerInternals;import java.io.File;
import java.util.Properties;/*** Created by zhangshaowen on 16/4/30.* optional, you can just use DefaultPatchListener* we can check whatever you want whether we actually send a patch request* such as we can check rom space or apk channel*/
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*/@Overridepublic int patchCheck(String path, String patchMd5) {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, patchMd5);if (returnCode == ShareConstants.ERROR_PATCH_OK) {returnCode = Utils.checkForPatchRecover(NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN, maxMemory);}if (returnCode == ShareConstants.ERROR_PATCH_OK) {SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS);//optional, only disable this patch file with md5int fastCrashCount = sp.getInt(patchMd5, 0);if (fastCrashCount >= SampleUncaughtExceptionHandler.MAX_CRASH_COUNT) {returnCode = Utils.ERROR_PATCH_CRASH_LIMIT;}}// Warning, it is just a sample case, you don't need to copy all of these// Interception some of the requestif (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 requireif (platform == null || !platform.equals(BuildInfo.PLATFORM)) {returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;}}}SampleTinkerReport.onTryApply(returnCode == ShareConstants.ERROR_PATCH_OK);return returnCode;}
}
import android.content.Context;
import android.content.Intent;import com.tencent.tinker.lib.reporter.DefaultPatchReporter;
import com.tencent.tinker.loader.shareutil.SharePatchInfo;import java.io.File;
import java.util.List;/*** optional, you can just use DefaultPatchReporter* Created by zhangshaowen on 16/4/8.*/
public class SamplePatchReporter extends DefaultPatchReporter {private final static String TAG = "Tinker.SamplePatchReporter";public SamplePatchReporter(Context context) {super(context);}@Overridepublic void onPatchServiceStart(Intent intent) {super.onPatchServiceStart(intent);SampleTinkerReport.onApplyPatchServiceStart();}@Overridepublic void onPatchDexOptFail(File patchFile, List<File> dexFiles, Throwable t) {super.onPatchDexOptFail(patchFile, dexFiles, t);SampleTinkerReport.onApplyDexOptFail(t);}@Overridepublic void onPatchException(File patchFile, Throwable e) {super.onPatchException(patchFile, e);SampleTinkerReport.onApplyCrash(e);}@Overridepublic void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion) {super.onPatchInfoCorrupted(patchFile, oldVersion, newVersion);SampleTinkerReport.onApplyInfoCorrupted();}@Overridepublic void onPatchPackageCheckFail(File patchFile, int errorCode) {super.onPatchPackageCheckFail(patchFile, errorCode);SampleTinkerReport.onApplyPackageCheckFail(errorCode);}@Overridepublic void onPatchResult(File patchFile, boolean success, long cost) {super.onPatchResult(patchFile, success, cost);SampleTinkerReport.onApplied(cost, success);}@Overridepublic void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType) {super.onPatchTypeExtractFail(patchFile, extractTo, filename, fileType);SampleTinkerReport.onApplyExtractFail(fileType);}@Overridepublic void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion) {super.onPatchVersionCheckFail(patchFile, oldPatchInfo, patchFileVersion);SampleTinkerReport.onApplyVersionCheckFail();}
}
import java.io.File;/*** optional, you can just use DefaultTinkerResultService* we can restart process when we are at background or screen off* Created by zhangshaowen on 16/4/13.*/
public class SampleResultService extends DefaultTinkerResultService {private static final String TAG = "Tinker.SampleResultService";private static boolean isUploaded = false;@Overridepublic 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 processTinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());//在这里自定义处理加载成功和失败String log = result.isSuccess ? "patch success, please restart process  补丁加载成功,请重启应用" : "patch fail, please check reason  补丁加载失败!!!!!!!!!!!";try {if (!isUploaded) {StringExtendFunKt.showDebugToast(log);StringExtendFunKt.w(log, "lllttt_tinker");UmengDiyEventUtilKt.actionEvent(SampleApplicationLike.getApp(),"updateHotFix",new UmengEventBean("fixSuccess", result.isSuccess),new UmengEventBean("systemInfo", JSONObject.toJSONString(YxUtil.getInstance().getInputDevice())));}isUploaded = true;} catch (Exception e) {AnyExtendFunKt.upload(e);}// 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 fileif (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 apiif (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 offTinkerLog.i(TAG, "tinker wait screen to restart process");new Utils.ScreenState(getApplicationContext(), new Utils.ScreenState.IOnScreenOff() {@Overridepublic 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 processandroid.os.Process.killProcess(android.os.Process.myPid());}}
import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.loader.shareutil.ShareConstants;
import com.tencent.tinker.loader.shareutil.ShareTinkerInternals;/*** a simple tinker data reporter* Created by zhangshaowen on 16/9/17.*/
public class SampleTinkerReport {private static final String TAG = "Tinker.SampleTinkerReport";// KEY - PVpublic 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 detailpublic 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 detailpublic 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 checkpublic 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 checkpublic static final int KEY_APPLIED_VERSION_CHECK      = 180;//extract errorpublic 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 timepublic 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 detailpublic 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 checkpublic 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 ShareConstants.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 stacktraceif (!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);}}
import android.content.Context;
import android.content.SharedPreferences;
import android.os.SystemClock;import com.tencent.tinker.entry.ApplicationLike;
import com.tencent.tinker.lib.tinker.TinkerApplicationHelper;
import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.loader.shareutil.ShareConstants;
import com.tencent.tinker.loader.shareutil.ShareTinkerInternals;/*** optional, use dynamic configuration is better way* for native crash,* <p/>* Created by zhangshaowen on 16/7/3.* tinker's crash is caught by {@code LoadReporter.onLoadException}* use {@code TinkerApplicationHelper} api, no need to install tinker!*/
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();}@Overridepublic 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 artif (throwable instanceof IllegalAccessError && throwable.getMessage().contains(DALVIK_XPOSED_CRASH)) {//for dalvik, we know the actual crash typeisCausedByXposed = 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 apiif (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;}
}
import com.tencent.tinker.entry.ApplicationLike;
import com.tencent.tinker.lib.listener.PatchListener;
import com.tencent.tinker.lib.patch.AbstractPatch;
import com.tencent.tinker.lib.patch.UpgradePatch;
import com.tencent.tinker.lib.reporter.LoadReporter;
import com.tencent.tinker.lib.reporter.PatchReporter;
import com.tencent.tinker.lib.tinker.TinkerInstaller;
import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.lib.util.UpgradePatchRetry;/*** Created by zhangshaowen on 16/7/3.*/
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 DefaultLoadReporterLoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication());//or you can just use DefaultPatchReporterPatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication());//or you can just use DefaultPatchListenerPatchListener patchListener = new SamplePatchListener(appLike.getApplication());//you can set your own upgrade patch if you needAbstractPatch upgradePatchProcessor = new UpgradePatch();TinkerInstaller.install(appLike,loadReporter, patchReporter, patchListener,SampleResultService.class, upgradePatchProcessor);isInstalled = true;}
}
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.os.StatFs;import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.loader.shareutil.ShareConstants;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;/*** Created by zhangshaowen on 16/4/7.*/
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      = -20;public static final int ERROR_PATCH_ROM_SPACE               = -21;public static final int ERROR_PATCH_MEMORY_LIMIT            = -22;public static final int ERROR_PATCH_CRASH_LIMIT             = -23;public static final int ERROR_PATCH_CONDITION_NOT_SATISFIED = -24;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;}@Deprecatedpublic 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 directlyThrowable 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() {@Overridepublic 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);}}
}

5.配置清单文件

Application配置上刚才写的那个BaseApplication

然后配置service

        <serviceandroid:name=".app.tinker.SampleResultService"android:exported="false" />

6.版本号配置

gradle版本改为3.5.3

classpath 'com.android.tools.build:gradle:3.5.3'

gradle.zip版本修改(在gradle-wrapper.properties文件内)

distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

app.gradle中修改版本号

compileSdkVersion 29 //推荐
buildToolsVersion '29.0.3' //推荐
minSdkVersion 19//必须小于等于19,因为大于19之后就改了生成dex的方式,这个东西坑了我好久
minifyEnabled false//公司项目里没有用混淆,用的是加固,这里如果打开会有很多配置不相同,没有试过

7.app.gradle中增加如下代码(标记edit this的是可以需要更改的)

defaultConfig {...javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }buildConfigField "String", "MESSAGE", "\"I am the base apk\""buildConfigField "String", "TINKER_ID", "\"${getTINKER_ID()}\""buildConfigField "String", "PLATFORM", "\"all\""}dexOptions {jumboMode = true}
def bakPath = file("${buildDir}/bakApk/")def getTINKER_ID() {//todo 每次打包使用build->assembleAli命令,并保存release apk和release R.txt//todo 每次发布热更新先配置本文件,在使用tinker->tinkerPatchAliRelease命令,然后找到对应文件修改文件名为path.aaareturn android.defaultConfig.versionName+"_1" //todo edit this,这里经我测试每次打基础包和增量包都要修改,所以我设置的是版本名称和追加热更版本
}ext {//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?tinkerEnabled = true//基准apk路径tinkerOldApkPath = "C:\\Users\\a\\Desktop\\oldApks\\ali-release_v0.8.6.apk"//todo edit this//未开启混淆,则不需要填写tinkerApplyMappingPath = "C:\\Users\\a\\Desktop\\oldApks"//基准apk中的R文件路径tinkerApplyResourcePath = "C:\\Users\\a\\Desktop\\oldApks\\app-ali-release-R.txt"//todo edit this//如果你修复了res文件,需要指定你bug版本的R.txt文件tinkerBuildFlavorDirectory = "C:\\Users\\a\\Desktop\\oldApks\\app-ali-release-R.txt"//todo edit this}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 buildWithTinker() {return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
}def getTinkerBuildFlavorDirectory() {return ext.tinkerBuildFlavorDirectory
}if (buildWithTinker()) {apply plugin: 'com.tencent.tinker.patch'tinkerPatch {/*** 默认为null* 将旧的apk和新的apk建立关联* 从build / bakApk添加apk*/oldApk = getOldApkPath()/*** 可选,默认'false'* 有些情况下我们可能会收到一些警告* 如果ignoreWarning为true,我们只是断言补丁过程* case 1:minSdkVersion低于14,但是你使用dexMode与raw。* case 2:在AndroidManifest.xml中新添加Android组件,* case 3:装载器类在dex.loader {}不保留在主要的dex,*          它必须让tinker不工作。* case 4:在dex.loader {}中的loader类改变,*          加载器类是加载补丁dex。改变它们是没有用的。*          它不会崩溃,但这些更改不会影响。你可以忽略它* case 5:resources.arsc已经改变,但是我们不使用applyResourceMapping来构建*/ignoreWarning = true/*** 可选,默认为“true”* 是否签名补丁文件* 如果没有,你必须自己做。否则在补丁加载过程中无法检查成功* 我们将使用sign配置与您的构建类型*/useSign = true/*** 可选,默认为“true”* 是否使用tinker构建*/tinkerEnable = buildWithTinker()/*** 警告,applyMapping会影响正常的android build!*/buildConfig {/*** 可选,默认为'null'* 如果我们使用tinkerPatch构建补丁apk,你最好应用旧的* apk映射文件如果minifyEnabled是启用!* 警告:你必须小心,它会影响正常的组装构建!*/applyMapping = getApplyMappingPath()/*** 可选,默认为'null'* 最好保留R.txt文件中的资源id,以减少java更改*/applyResourceMapping = getApplyResourceMappingPath()/*** 必需,默认'null'* 因为我们不想检查基地apk与md5在运行时(它是慢)* tinkerId用于在试图应用补丁时标识唯一的基本apk。* 我们可以使用git rev,svn rev或者简单的versionCode。* 我们将在您的清单中自动生成tinkerId*/tinkerId = getTINKER_ID()/*** 如果keepDexApply为true,则表示dex指向旧apk的类。* 打开这可以减少dex diff文件大小。*/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 = true //todo 注意这里,如果是debug测试或者app不进行加固,请关闭/*** optional, default 'false'* Whether tinker should support component hotplug (add new component dynamically).* If this attribute is true, the component added in new apk will be available after* patch is successfully loaded. Otherwise an error would be announced when generating patch* on compile-time.** <b>Notice that currently this feature is incubating and only support NON-EXPORTED Activity</b>*/supportHotplugComponent = false}dex {/*** 可选,默认'jar'* 只能是'raw'或'jar'。对于原始,我们将保持其原始格式* 对于jar,我们将使用zip格式重新包装dexes。* 如果你想支持下面14,你必须使用jar* 或者你想保存rom或检查更快,你也可以使用原始模式*/dexMode = "jar"/*** 必需,默认'[]'* apk中的dexes应该处理tinkerPatch* 它支持*或?模式。*/pattern = ["classes*.dex","assets/secondary-dex-?.jar"]/*** 必需,默认'[]'* 警告,这是非常非常重要的,加载类不能随补丁改变。* 因此,它们将从补丁程序中删除。* 你必须把下面的类放到主要的dex。* 简单地说,你应该添加自己的应用程序{@code tinker.sample.android.SampleApplication}* 自己的tinkerLoader,和你使用的类*/loader = [//use sample, let BaseBuildInfo unchangeable with tinker"tinker.sample.android.app.BaseBuildInfo","com.xx.xx.view.BaseApplication"//todo 这里加上你的Application]}lib {/*** 可选,默认'[]'* apk中的图书馆应该处理tinkerPatch* 它支持*或?模式。* 对于资源库,我们只是在补丁目录中恢复它们* 你可以得到他们在TinkerLoadResult与Tinker*/pattern = ["lib/*/*.so"]}res {/*** 可选,默认'[]'* apk中的什么资源应该处理tinkerPatch* 它支持*或?模式。* 你必须包括你在这里的所有资源,* 否则,他们不会重新包装在新的apk资源。*/pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]/***  可选,默认'[]'*  资源文件排除模式,忽略添加,删除或修改资源更改* *它支持*或?模式。* *警告,我们只能使用文件没有relative与resources.arsc*/ignoreChange = ["assets/sample_meta.txt"]/***  默认100kb* *对于修改资源,如果它大于'largeModSize'* *我们想使用bsdiff算法来减少补丁文件的大小*/largeModSize = 100}packageConfig {/*** 可选,默认'TINKER_ID,TINKER_ID_VALUE','NEW_TINKER_ID,NEW_TINKER_ID_VALUE'* 包元文件gen。路径是修补程序文件中的assets / package_meta.txt* 你可以在您自己的PackageCheck方法中使用securityCheck.getPackageProperties()* 或TinkerLoadResult.getPackageConfigByName* 我们将从旧的apk清单为您自动获取TINKER_ID,* 其他配置文件(如下面的patchMessage)不是必需的*/configField("patchMessage", "tinker is sample to use")/*** 只是一个例子,你可以使用如sdkVersion,品牌,渠道...* 你可以在SamplePatchListener中解析它。* 然后你可以使用补丁条件!*/configField("platform", "all")/*** 补丁版本通过packageConfig*/configField("patchVersion", "1.0.2")}//或者您可以添加外部的配置文件,或从旧apk获取元值//project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test"))//project.tinkerPatch.packageConfig.configField("test2", "sample")/*** 如果你不使用zipArtifact或者path,我们只是使用7za来试试*/sevenZip {/*** 可选,默认'7za'* 7zip工件路径,它将使用正确的7za与您的平台*/zipArtifact = "com.tencent.mm:SevenZip:1.1.10"/*** 可选,默认'7za'* 你可以自己指定7za路径,它将覆盖zipArtifact值*/
//        path = "/usr/local/bin/7za"}}List<String> flavors = new ArrayList<>();project.android.productFlavors.each { flavor ->flavors.add(flavor.name)}boolean hasFlavors = flavors.size() > 0def 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.nametasks.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}") : bakPathif (variant.metaClass.hasProperty(variant, 'packageApplicationProvider')) {def packageAndroidArtifact = variant.packageApplicationProvider.get()if (packageAndroidArtifact != null) {try {from new File(packageAndroidArtifact.outputDirectory.getAsFile().get(), variant.outputs.first().apkData.outputFileName)} catch (Exception e) {from new File(packageAndroidArtifact.outputDirectory, variant.outputs.first().apkData.outputFileName)}} else {from variant.outputs.first().mainOutputFile.outputFile}} else {from variant.outputs.first().outputFile}into destPathrename { String fileName ->fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")}from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"into destPathrename { String fileName ->fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")}from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"from "${buildDir}/intermediates/symbol_list/${variant.dirName}/R.txt"from "${buildDir}/intermediates/runtime_symbol_list/${variant.dirName}/R.txt"into destPathrename { String fileName ->fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")}}}}}}project.afterEvaluate {//sample use for build all flavor for one timeif (hasFlavors) {task(tinkerPatchAllFlavorRelease) {group = 'tinker'def originOldPath = getTinkerBuildFlavorDirectory()for (String flavor : flavors) {def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")dependsOn tinkerTaskdef 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"project.tinkerPatch.oldApk = getOldApkPath()System.out.println("lllttt oldApk=" + project.tinkerPatch.oldApk)project.tinkerPatch.buildConfig.applyMapping = ""project.tinkerPatch.buildConfig.applyResourceMapping = originOldPathSystem.out.println("lllttt old R=" + project.tinkerPatch.buildConfig.applyResourceMapping)}}}task(tinkerPatchAllFlavorDebug) {group = 'tinker'def originOldPath = getTinkerBuildFlavorDirectory()for (String flavor : flavors) {def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")dependsOn tinkerTaskdef 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"project.tinkerPatch.oldApk = getOldApkPath()System.out.println("lllttt oldApk=" + project.tinkerPatch.oldApk)project.tinkerPatch.buildConfig.applyMapping = ""project.tinkerPatch.buildConfig.applyResourceMapping = originOldPathSystem.out.println("lllttt old R=" + project.tinkerPatch.buildConfig.applyResourceMapping)}}}}}
}

8.经过上面的配置后就可以用了,这一节我们说说怎么使用

双击该位置生成基准包,如果没有多渠道就点击assemble,这时会生成debug和release包和对应的R.txt文件

具体生成位置是:build-bakApk

然后修改app.gradle中的tinkerOldApkPath,tinkerApplyResourcePath,tinkerBuildFlavorDirectory三项配置和getTINKER_ID()方法中的后缀自增,并修改bug或者修改ui或逻辑

然后按照下面生成debug或者release包

生成的位置是:build-outputs-apk-tinkerPatch-patch_signed_7zip.apk(这个就是生成的差量包)

我们可以放到存储目录,也可以放到cache目录

然后调用下面的api来安装增量包,并且会在上面配置的SampleResultService中有回调,成功后重启app或者锁屏后就可以了

9.各种api

//安装增量包
//TinkerInstaller.onReceiveUpgradePatch(getContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk")
//安装增量so(未测试)
//TinkerLoadLibrary.installNavitveLibraryABI(getApplicationContext(), "armeabi");
//                System.loadLibrary("stlport_shared");
//清除增量包
//Tinker.with(getApplicationContext()).cleanPatch();
//是否安装了增量包
//Tinker.with(getApplicationContext()).isTinkerLoaded();
//兼容google play
//https://github.com/Tencent/tinker/issues/1314

然后就可以难过的玩耍了(其实听说bugly接入方式更简单,问题更少,不过这是后话了)

结语

第一次写文章不想写结语

完成这次接入并测试完成,我大概用了三个这样的浏览器tab(同时打开,因为都有用,没用的都关了)

如果有问题或者有问题(没有语句bug)请提交评论

end

接入Tinker热修复和踩坑相关推荐

  1. 【错误记录】集成 Tinker 热修复报错 ( No such property: variantConfiguration for class: .ApplicationVariantData )

    文章目录 一.报错信息 二.解决方案 一.报错信息 接入 Tinker 热修复 , 使用如下 Gradle 插件 , // Tinker 的 tinker-patch-gradle-plugin 插件 ...

  2. android Tinker 热修复 乐固加固后友盟打多渠道包之后的补丁失效

    继上一篇 android tinker 热修复使用及注意事项  生成了热修复的补丁; 现在的需求是这样的,我想把这个包用腾讯乐固加固,然后生成多渠道包,希望这个补丁能修复所有这些渠道的包,经过测试,直 ...

  3. 微信tinker 热修复

    Tinker 是微信官方的Android热补丁解决方案,它支持动态下发代码.So库以及资源,让应用能够在不需要重新安装的情况下实现更新.当然,你也可以使用Tinker来更新你的插件. github:h ...

  4. Tinker热修复初探

    听说热修复已经很久了,但这是第一次尝试去应用它.所以我对其它各种热修复也没什么了解,这里仅仅记下如何使用Tinker热修复. 对于Tinker热修复的介绍和问题这里也不写了,因为官方文档已经有了,戳这 ...

  5. 腾讯Tinker 热修复 Andriod studio 3.0 配置和集成(二)多渠道打包和补丁发布

    腾讯Tinker 热修复 Andriod studio 3.0 多渠道打包和发布补丁方式推荐 本文说明 在之前我已经分享了Tinker 热修复的 Andriod studio3.0 初次配置和集成,时 ...

  6. (十四)Tinker 热修复原理及手写实现

    版权声明:本文为博主原创文章,未经博主允许不得转载. 本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下. 一.热修复 热补丁方案有很多,其中比较出名的有阿里的 AndFix.美团的 ...

  7. 【错误记录】Tinker 热修复示例运行报错 ( patch receive fail: /storage/emulated/0/patch_signed_7zip.apk, code: -2)

    文章目录 一.报错信息 二.解决方案 参考 [Android 热修复]运行 Tinker 官方示例 博客 ; 一.报错信息 Tinker 热修复中 , 将生成的 patch 包 app-debug-p ...

  8. tinker热修复gradle接入

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 2021-04-29更新,最近在复习热修复,发现我的老代码不行了,所以把这篇文章更新一下 本篇是gradle接入 tinker的gi ...

  9. 腾讯Tinker 热修复 Andriod studio 3.0 配置和集成(一)

    本文说明 面试的时候经常问我有没有用过热修复?用谁的?能说下原理吗?当时我回答得不好,毕竟以前的项目都没有用,又不敢装逼,mmp,但是基本流程还是知道的,所以我们来初探下Tinker 这个热修复,如果 ...

最新文章

  1. Go 分布式学习利器(14)-- Go语言的错误处理
  2. ant Table td 溢出隐藏(省略号)
  3. 自测之Lesson6:文件I/O
  4. 【BZOJ 1486】 [HNOI2009]最小圈
  5. 北京奥运会闭幕式落下帷幕
  6. sublime text3安装js提示的插件
  7. 计算机应用基础学生自查报告,计算机应用基础(专科).docx
  8. BZOJ 1597 [Usaco2008 Mar]土地购买 (斜率优化dp)
  9. SkeyePlayer rtsp、rtmp低延迟播放器源码解析之64位编译方案
  10. world Best 500 interview exam
  11. java触屏改_一个JAVA游戏改键改触屏的教程!
  12. 三只松鼠2020新财报:利润下滑都是疫情的锅?
  13. 关于LVGL下物理按键的使用
  14. 为什么要求高频pcb板低ε(Dk)
  15. Sentry离线部署
  16. 小白眼里的区块链和币圈 —— 持币待涨的故事
  17. SAP工序外协与库存外协区别及工序外协操作手册
  18. 华中首家华为旗舰店落户武汉万象城
  19. 全集]+华夏***联盟大型免费培训***技术系列教程
  20. 一二线城市知名IT互联网公司名单,有你的目标公司吗?

热门文章

  1. javascript~callback回调函数
  2. 丛高教授《空间数据管理和挖掘及在智慧城市的应用》演讲笔记
  3. 软件使用: word
  4. 听说你想去大厂看学妹,带你看看京东软件产品经理面经
  5. python多线程和多进程——python并行编程实验
  6. hadoop2.2.0安装,完全分布式安装
  7. java 正方形字符串_java编程:怎么画一个正方形?
  8. 面试总结-腾讯产品群面
  9. 体系化认识RPC--转
  10. 通过分析 JDK 源代码研究 Hash 存储机制--转载