日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

接入Tinker热修复和踩坑

發(fā)布時(shí)間:2025/3/21 编程问答 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 接入Tinker热修复和踩坑 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

公司最近項(xiàng)目上線后總是遇見各種問題或bug,而我最近就一直在背黑鍋,幸虧最近不用上Google Play了,趕緊加上熱更新來脫離苦海吧,誰知道接入Tinker的過程中也踩了將近一周的坑,哎...一言難盡

為什么選擇Tinker和對(duì)比了也懶得說了(其實(shí)是因?yàn)楦舯诎⒗锸召M(fèi))

而且需要注意,中間很多版本不要使用最新的,要使用我寫的,不然有問題了找都找不到(我在這中間查,試,測,中間用了不知道多長時(shí)間)

正文

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

官方地址

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

1.項(xiàng)目的gradle->buildscript.dependencies中加入

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

2.module的gradle->dependencies中加入

implementation('com.tencent.tinker:tinker-android-lib:1.9.9')//版本號(hào)不能改

3.改造Application,Tinker有兩種改造Application的方法:

第一種手動(dòng)改造,兼容性最高

第二種使用注解自動(dòng)改造

本來嫌費(fèi)事用的第二種,然后接入后各種問題(但也不知這的問題),然后我又改成第一種了

首先寫一個(gè)類

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,讓其"真"·什么都不做,改成我這個(gè)樣子

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 補(bǔ)丁加載成功,請(qǐng)重啟應(yīng)用" : "patch fail, please check reason 補(bǔ)丁加載失敗!!!!!!!!!!!";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配置上剛才寫的那個(gè)BaseApplication

然后配置service

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

6.版本號(hào)配置

gradle版本改為3.5.3

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

gradle.zip版本修改(在gradle-wrapper.properties文件內(nèi))

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

app.gradle中修改版本號(hào)

compileSdkVersion 29 //推薦 buildToolsVersion '29.0.3' //推薦 minSdkVersion 19//必須小于等于19,因?yàn)榇笥?9之后就改了生成dex的方式,這個(gè)東西坑了我好久 minifyEnabled false//公司項(xiàng)目里沒有用混淆,用的是加固,這里如果打開會(huì)有很多配置不相同,沒有試過

7.app.gradle中增加如下代碼(標(biāo)記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 每次發(fā)布熱更新先配置本文件,在使用tinker->tinkerPatchAliRelease命令,然后找到對(duì)應(yīng)文件修改文件名為path.aaareturn android.defaultConfig.versionName+"_1" //todo edit this,這里經(jīng)我測試每次打基礎(chǔ)包和增量包都要修改,所以我設(shè)置的是版本名稱和追加熱更版本 }ext {//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?tinkerEnabled = true//基準(zhǔn)apk路徑tinkerOldApkPath = "C:\\Users\\a\\Desktop\\oldApks\\ali-release_v0.8.6.apk"//todo edit this//未開啟混淆,則不需要填寫tinkerApplyMappingPath = "C:\\Users\\a\\Desktop\\oldApks"//基準(zhǔn)apk中的R文件路徑tinkerApplyResourcePath = "C:\\Users\\a\\Desktop\\oldApks\\app-ali-release-R.txt"//todo edit this//如果你修復(fù)了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 {/*** 默認(rèn)為null* 將舊的apk和新的apk建立關(guān)聯(lián)* 從build / bakApk添加apk*/oldApk = getOldApkPath()/*** 可選,默認(rèn)'false'* 有些情況下我們可能會(huì)收到一些警告* 如果ignoreWarning為true,我們只是斷言補(bǔ)丁過程* case 1:minSdkVersion低于14,但是你使用dexMode與raw。* case 2:在AndroidManifest.xml中新添加Android組件,* case 3:裝載器類在dex.loader {}不保留在主要的dex,* 它必須讓tinker不工作。* case 4:在dex.loader {}中的loader類改變,* 加載器類是加載補(bǔ)丁dex。改變它們是沒有用的。* 它不會(huì)崩潰,但這些更改不會(huì)影響。你可以忽略它* case 5:resources.arsc已經(jīng)改變,但是我們不使用applyResourceMapping來構(gòu)建*/ignoreWarning = true/*** 可選,默認(rèn)為“true”* 是否簽名補(bǔ)丁文件* 如果沒有,你必須自己做。否則在補(bǔ)丁加載過程中無法檢查成功* 我們將使用sign配置與您的構(gòu)建類型*/useSign = true/*** 可選,默認(rèn)為“true”* 是否使用tinker構(gòu)建*/tinkerEnable = buildWithTinker()/*** 警告,applyMapping會(huì)影響正常的android build!*/buildConfig {/*** 可選,默認(rèn)為'null'* 如果我們使用tinkerPatch構(gòu)建補(bǔ)丁apk,你最好應(yīng)用舊的* apk映射文件如果minifyEnabled是啟用!* 警告:你必須小心,它會(huì)影響正常的組裝構(gòu)建!*/applyMapping = getApplyMappingPath()/*** 可選,默認(rèn)為'null'* 最好保留R.txt文件中的資源id,以減少java更改*/applyResourceMapping = getApplyResourceMappingPath()/*** 必需,默認(rèn)'null'* 因?yàn)槲覀儾幌霗z查基地apk與md5在運(yùn)行時(shí)(它是慢)* tinkerId用于在試圖應(yīng)用補(bǔ)丁時(shí)標(biāo)識(shí)唯一的基本apk。* 我們可以使用git rev,svn rev或者簡單的versionCode。* 我們將在您的清單中自動(dòng)生成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.** 是否使用加固模式,僅僅將變更的類合成補(bǔ)丁。注意,這種模式僅僅可以用于加固應(yīng)用中。*/isProtectedApp = true //todo 注意這里,如果是debug測試或者app不進(jìn)行加固,請(qǐng)關(guān)閉/*** 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 {/*** 可選,默認(rèn)'jar'* 只能是'raw'或'jar'。對(duì)于原始,我們將保持其原始格式* 對(duì)于jar,我們將使用zip格式重新包裝dexes。* 如果你想支持下面14,你必須使用jar* 或者你想保存rom或檢查更快,你也可以使用原始模式*/dexMode = "jar"/*** 必需,默認(rèn)'[]'* apk中的dexes應(yīng)該處理tinkerPatch* 它支持*或?模式。*/pattern = ["classes*.dex","assets/secondary-dex-?.jar"]/*** 必需,默認(rèn)'[]'* 警告,這是非常非常重要的,加載類不能隨補(bǔ)丁改變。* 因此,它們將從補(bǔ)丁程序中刪除。* 你必須把下面的類放到主要的dex。* 簡單地說,你應(yīng)該添加自己的應(yīng)用程序{@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 {/*** 可選,默認(rèn)'[]'* apk中的圖書館應(yīng)該處理tinkerPatch* 它支持*或?模式。* 對(duì)于資源庫,我們只是在補(bǔ)丁目錄中恢復(fù)它們* 你可以得到他們?cè)赥inkerLoadResult與Tinker*/pattern = ["lib/*/*.so"]}res {/*** 可選,默認(rèn)'[]'* apk中的什么資源應(yīng)該處理tinkerPatch* 它支持*或?模式。* 你必須包括你在這里的所有資源,* 否則,他們不會(huì)重新包裝在新的apk資源。*/pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]/*** 可選,默認(rèn)'[]'* 資源文件排除模式,忽略添加,刪除或修改資源更改* *它支持*或?模式。* *警告,我們只能使用文件沒有relative與resources.arsc*/ignoreChange = ["assets/sample_meta.txt"]/*** 默認(rèn)100kb* *對(duì)于修改資源,如果它大于'largeModSize'* *我們想使用bsdiff算法來減少補(bǔ)丁文件的大小*/largeModSize = 100}packageConfig {/*** 可選,默認(rèn)'TINKER_ID,TINKER_ID_VALUE','NEW_TINKER_ID,NEW_TINKER_ID_VALUE'* 包元文件gen。路徑是修補(bǔ)程序文件中的assets / package_meta.txt* 你可以在您自己的PackageCheck方法中使用securityCheck.getPackageProperties()* 或TinkerLoadResult.getPackageConfigByName* 我們將從舊的apk清單為您自動(dòng)獲取TINKER_ID,* 其他配置文件(如下面的patchMessage)不是必需的*/configField("patchMessage", "tinker is sample to use")/*** 只是一個(gè)例子,你可以使用如sdkVersion,品牌,渠道...* 你可以在SamplePatchListener中解析它。* 然后你可以使用補(bǔ)丁條件!*/configField("platform", "all")/*** 補(bǔ)丁版本通過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 {/*** 可選,默認(rèn)'7za'* 7zip工件路徑,它將使用正確的7za與您的平臺(tái)*/zipArtifact = "com.tencent.mm:SevenZip:1.1.10"/*** 可選,默認(rèn)'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.經(jīng)過上面的配置后就可以用了,這一節(jié)我們說說怎么使用

雙擊該位置生成基準(zhǔn)包,如果沒有多渠道就點(diǎn)擊assemble,這時(shí)會(huì)生成debug和release包和對(duì)應(yīng)的R.txt文件

具體生成位置是:build-bakApk

然后修改app.gradle中的tinkerOldApkPath,tinkerApplyResourcePath,tinkerBuildFlavorDirectory三項(xiàng)配置和getTINKER_ID()方法中的后綴自增,并修改bug或者修改ui或邏輯

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

生成的位置是:build-outputs-apk-tinkerPatch-patch_signed_7zip.apk(這個(gè)就是生成的差量包)

我們可以放到存儲(chǔ)目錄,也可以放到cache目錄

然后調(diào)用下面的api來安裝增量包,并且會(huì)在上面配置的SampleResultService中有回調(diào),成功后重啟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

然后就可以難過的玩耍了(其實(shí)聽說bugly接入方式更簡單,問題更少,不過這是后話了)

結(jié)語

第一次寫文章不想寫結(jié)語

完成這次接入并測試完成,我大概用了三個(gè)這樣的瀏覽器tab(同時(shí)打開,因?yàn)槎加杏?沒用的都關(guān)了)

如果有問題或者有問題(沒有語句bug)請(qǐng)?zhí)峤辉u(píng)論

end

總結(jié)

以上是生活随笔為你收集整理的接入Tinker热修复和踩坑的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。