前言
公司最近项目上线后总是遇见各种问题或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);
}
@Override
public void onCreate() {
super.onCreate();
//在这里把之前Application中的初始化搬到这里
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public 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);
}
@Override
public void onLoadPatchListenerReceiveFail(final File patchFile, int errorCode) {
super.onLoadPatchListenerReceiveFail(patchFile, errorCode);
SampleTinkerReport.onTryApplyFail(errorCode);
}
@Override
public void onLoadResult(File patchDirectory, int loadCode, long cost) {
super.onLoadResult(patchDirectory, loadCode, cost);
switch (loadCode) {
case ShareConstants.ERROR_LOAD_OK:
SampleTinkerReport.onLoaded(cost);
break;
}
Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
if (UpgradePatchRetry.getInstance(context).onPatchRetryLoad()) {
SampleTinkerReport.onReportRetryPatch();
}
return false;
}
});
}
@Override
public void onLoadException(Throwable e, int errorCode) {
super.onLoadException(e, errorCode);
SampleTinkerReport.onLoadException(e, errorCode);
}
@Override
public void onLoadFileMd5Mismatch(File file, int fileType) {
super.onLoadFileMd5Mismatch(file, fileType);
SampleTinkerReport.onLoadFileMisMatch(fileType);
}
/**
* try to recover patch oat file
*
* @param file
* @param fileType
* @param isDirectory
*/
@Override
public void onLoadFileNotFound(File file, int fileType, boolean isDirectory) {
super.onLoadFileNotFound(file, fileType, isDirectory);
SampleTinkerReport.onLoadFileNotFound(fileType);
}
@Override
public void onLoadPackageCheckFail(File patchFile, int errorCode) {
super.onLoadPackageCheckFail(patchFile, errorCode);
SampleTinkerReport.onLoadPackageCheckFail(errorCode);
}
@Override
public void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) {
super.onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile);
SampleTinkerReport.onLoadInfoCorrupted();
}
@Override
public void onLoadInterpret(int type, Throwable e) {
super.onLoadInterpret(type, e);
SampleTinkerReport.onLoadInterpretReport(type, e);
}
@Override
public void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) {
super.onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectoryFile, currentPatchName);
}
}
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
*/
@Override
public 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 md5
int 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 request
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
Properties properties = ShareTinkerInternals.fastGetPatchPackageMeta(patchFile);
if (properties == null) {
returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
} else {
String platform = properties.getProperty(Utils.PLATFORM);
TinkerLog.i(TAG, "get platform:" + platform);
// check patch platform require
if (platform == null || !platform.equals(BuildInfo.PLATFORM)) {
returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
}
}
}
SampleTinkerReport.onTryApply(returnCode == ShareConstants.ERROR_PATCH_OK);
return returnCode;
}
}
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);
}
@Override
public void onPatchServiceStart(Intent intent) {
super.onPatchServiceStart(intent);
SampleTinkerReport.onApplyPatchServiceStart();
}
@Override
public void onPatchDexOptFail(File patchFile, List<File> dexFiles, Throwable t) {
super.onPatchDexOptFail(patchFile, dexFiles, t);
SampleTinkerReport.onApplyDexOptFail(t);
}
@Override
public void onPatchException(File patchFile, Throwable e) {
super.onPatchException(patchFile, e);
SampleTinkerReport.onApplyCrash(e);
}
@Override
public void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion) {
super.onPatchInfoCorrupted(patchFile, oldVersion, newVersion);
SampleTinkerReport.onApplyInfoCorrupted();
}
@Override
public void onPatchPackageCheckFail(File patchFile, int errorCode) {
super.onPatchPackageCheckFail(patchFile, errorCode);
SampleTinkerReport.onApplyPackageCheckFail(errorCode);
}
@Override
public void onPatchResult(File patchFile, boolean success, long cost) {
super.onPatchResult(patchFile, success, cost);
SampleTinkerReport.onApplied(cost, success);
}
@Override
public void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType) {
super.onPatchTypeExtractFail(patchFile, extractTo, filename, fileType);
SampleTinkerReport.onApplyExtractFail(fileType);
}
@Override
public void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion) {
super.onPatchVersionCheckFail(patchFile, oldPatchInfo, patchFileVersion);
SampleTinkerReport.onApplyVersionCheckFail();
}
}
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;
@Override
public void onPatchResult(final PatchResult result) {
if (result == null) {
TinkerLog.e(TAG, "SampleResultService received null result!!!!");
return;
}
TinkerLog.i(TAG, "SampleResultService receive result: %s", result.toString());
//first, we want to kill the recover process
TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
//在这里自定义处理加载成功和失败
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 file
if (result.isSuccess) {
deleteRawPatchFile(new File(result.rawPatchFilePath));
//not like TinkerResultService, I want to restart just when I am at background!
//if you have not install tinker this moment, you can use TinkerApplicationHelper api
if (checkIfNeedKill(result)) {
if (Utils.isBackground()) {
TinkerLog.i(TAG, "it is in background, just restart process");
restartProcess();
} else {
//we can wait process at background, such as onAppBackground
//or we can restart when the screen off
TinkerLog.i(TAG, "tinker wait screen to restart process");
new Utils.ScreenState(getApplicationContext(), new Utils.ScreenState.IOnScreenOff() {
@Override
public void onScreenOff() {
restartProcess();
}
});
}
} else {
TinkerLog.i(TAG, "I have already install the newly patch version!");
}
}
}
/**
* you can restart your process through service or broadcast
*/
private void restartProcess() {
TinkerLog.i(TAG, "app is background now, i can kill quietly");
//you can send service or broadcast intent to restart your process
android.os.Process.killProcess(android.os.Process.myPid());
}
}
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 - PV
public static final int KEY_REQUEST = 0;
public static final int KEY_DOWNLOAD = 1;
public static final int KEY_TRY_APPLY = 2;
public static final int KEY_TRY_APPLY_SUCCESS = 3;
public static final int KEY_APPLIED_START = 4;
public static final int KEY_APPLIED = 5;
public static final int KEY_LOADED = 6;
public static final int KEY_CRASH_FAST_PROTECT = 7;
public static final int KEY_CRASH_CAUSE_XPOSED_DALVIK = 8;
public static final int KEY_CRASH_CAUSE_XPOSED_ART = 9;
public static final int KEY_APPLY_WITH_RETRY = 10;
//Key -- try apply detail
public static final int KEY_TRY_APPLY_UPGRADE = 70;
public static final int KEY_TRY_APPLY_DISABLE = 71;
public static final int KEY_TRY_APPLY_RUNNING = 72;
public static final int KEY_TRY_APPLY_INSERVICE = 73;
public static final int KEY_TRY_APPLY_NOT_EXIST = 74;
public static final int KEY_TRY_APPLY_GOOGLEPLAY = 75;
public static final int KEY_TRY_APPLY_ROM_SPACE = 76;
public static final int KEY_TRY_APPLY_ALREADY_APPLY = 77;
public static final int KEY_TRY_APPLY_MEMORY_LIMIT = 78;
public static final int KEY_TRY_APPLY_CRASH_LIMIT = 79;
public static final int KEY_TRY_APPLY_CONDITION_NOT_SATISFIED = 80;
public static final int KEY_TRY_APPLY_JIT = 81;
//Key -- apply detail
public static final int KEY_APPLIED_UPGRADE = 100;
public static final int KEY_APPLIED_UPGRADE_FAIL = 101;
public static final int KEY_APPLIED_EXCEPTION = 120;
public static final int KEY_APPLIED_DEXOPT_OTHER = 121;
public static final int KEY_APPLIED_DEXOPT_EXIST = 122;
public static final int KEY_APPLIED_DEXOPT_FORMAT = 123;
public static final int KEY_APPLIED_INFO_CORRUPTED = 124;
//package check
public static final int KEY_APPLIED_PACKAGE_CHECK_SIGNATURE = 150;
public static final int KEY_APPLIED_PACKAGE_CHECK_DEX_META = 151;
public static final int KEY_APPLIED_PACKAGE_CHECK_LIB_META = 152;
public static final int KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = 153;
public static final int KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 154;
public static final int KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND = 155;
public static final int KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = 156;
public static final int KEY_APPLIED_PACKAGE_CHECK_RES_META = 157;
public static final int KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT = 158;
//version check
public static final int KEY_APPLIED_VERSION_CHECK = 180;
//extract error
public static final int KEY_APPLIED_PATCH_FILE_EXTRACT = 181;
public static final int KEY_APPLIED_DEX_EXTRACT = 182;
public static final int KEY_APPLIED_LIB_EXTRACT = 183;
public static final int KEY_APPLIED_RESOURCE_EXTRACT = 184;
//cost time
public static final int KEY_APPLIED_SUCC_COST_5S_LESS = 200;
public static final int KEY_APPLIED_SUCC_COST_10S_LESS = 201;
public static final int KEY_APPLIED_SUCC_COST_30S_LESS = 202;
public static final int KEY_APPLIED_SUCC_COST_60S_LESS = 203;
public static final int KEY_APPLIED_SUCC_COST_OTHER = 204;
public static final int KEY_APPLIED_FAIL_COST_5S_LESS = 205;
public static final int KEY_APPLIED_FAIL_COST_10S_LESS = 206;
public static final int KEY_APPLIED_FAIL_COST_30S_LESS = 207;
public static final int KEY_APPLIED_FAIL_COST_60S_LESS = 208;
public static final int KEY_APPLIED_FAIL_COST_OTHER = 209;
// KEY -- load detail
public static final int KEY_LOADED_UNKNOWN_EXCEPTION = 250;
public static final int KEY_LOADED_UNCAUGHT_EXCEPTION = 251;
public static final int KEY_LOADED_EXCEPTION_DEX = 252;
public static final int KEY_LOADED_EXCEPTION_DEX_CHECK = 253;
public static final int KEY_LOADED_EXCEPTION_RESOURCE = 254;
public static final int KEY_LOADED_EXCEPTION_RESOURCE_CHECK = 255;
public static final int KEY_LOADED_MISMATCH_DEX = 300;
public static final int KEY_LOADED_MISMATCH_LIB = 301;
public static final int KEY_LOADED_MISMATCH_RESOURCE = 302;
public static final int KEY_LOADED_MISSING_DEX = 303;
public static final int KEY_LOADED_MISSING_LIB = 304;
public static final int KEY_LOADED_MISSING_PATCH_FILE = 305;
public static final int KEY_LOADED_MISSING_PATCH_INFO = 306;
public static final int KEY_LOADED_MISSING_DEX_OPT = 307;
public static final int KEY_LOADED_MISSING_RES = 308;
public static final int KEY_LOADED_INFO_CORRUPTED = 309;
//load package check
public static final int KEY_LOADED_PACKAGE_CHECK_SIGNATURE = 350;
public static final int KEY_LOADED_PACKAGE_CHECK_DEX_META = 351;
public static final int KEY_LOADED_PACKAGE_CHECK_LIB_META = 352;
public static final int KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = 353;
public static final int KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 354;
public static final int KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = 355;
public static final int KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND = 356;
public static final int KEY_LOADED_PACKAGE_CHECK_RES_META = 357;
public static final int KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT = 358;
public static final int KEY_LOADED_SUCC_COST_500_LESS = 400;
public static final int KEY_LOADED_SUCC_COST_1000_LESS = 401;
public static final int KEY_LOADED_SUCC_COST_3000_LESS = 402;
public static final int KEY_LOADED_SUCC_COST_5000_LESS = 403;
public static final int KEY_LOADED_SUCC_COST_OTHER = 404;
public static final int KEY_LOADED_INTERPRET_GET_INSTRUCTION_SET_ERROR = 450;
public static final int KEY_LOADED_INTERPRET_INTERPRET_COMMAND_ERROR = 451;
public static final int KEY_LOADED_INTERPRET_TYPE_INTERPRET_OK = 452;
interface Reporter {
void onReport(int key);
void onReport(String message);
}
private static Reporter reporter = null;
public void setReporter(Reporter reporter) {
this.reporter = reporter;
}
public static void onTryApply(boolean success) {
if (reporter == null) {
return;
}
reporter.onReport(KEY_TRY_APPLY);
reporter.onReport(KEY_TRY_APPLY_UPGRADE);
if (success) {
reporter.onReport(KEY_TRY_APPLY_SUCCESS);
}
}
public static void onTryApplyFail(int errorCode) {
if (reporter == null) {
return;
}
switch (errorCode) {
case ShareConstants.ERROR_PATCH_NOTEXIST:
reporter.onReport(KEY_TRY_APPLY_NOT_EXIST);
break;
case ShareConstants.ERROR_PATCH_DISABLE:
reporter.onReport(KEY_TRY_APPLY_DISABLE);
break;
case ShareConstants.ERROR_PATCH_INSERVICE:
reporter.onReport(KEY_TRY_APPLY_INSERVICE);
break;
case ShareConstants.ERROR_PATCH_RUNNING:
reporter.onReport(KEY_TRY_APPLY_RUNNING);
break;
case ShareConstants.ERROR_PATCH_JIT:
reporter.onReport(KEY_TRY_APPLY_JIT);
break;
case Utils.ERROR_PATCH_ROM_SPACE:
reporter.onReport(KEY_TRY_APPLY_ROM_SPACE);
break;
case Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL:
reporter.onReport(KEY_TRY_APPLY_GOOGLEPLAY);
break;
case 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 stacktrace
if (!isCheckFail) {
reporter.onReport("Tinker Exception:load tinker occur exception " + Utils.getExceptionCauseString(throwable));
}
}
public static void onApplyPatchServiceStart() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_APPLIED_START);
}
public static void onApplyDexOptFail(Throwable throwable) {
if (reporter == null) {
return;
}
if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_OAT_EXIST_FAIL)) {
reporter.onReport(KEY_APPLIED_DEXOPT_EXIST);
} else if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_OAT_FORMAT_FAIL)) {
reporter.onReport(KEY_APPLIED_DEXOPT_FORMAT);
} else {
reporter.onReport(KEY_APPLIED_DEXOPT_OTHER);
reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable));
}
}
public static void onApplyInfoCorrupted() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_APPLIED_INFO_CORRUPTED);
}
public static void onApplyVersionCheckFail() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_APPLIED_VERSION_CHECK);
}
public static void onApplyExtractFail(int fileType) {
if (reporter == null) {
return;
}
switch (fileType) {
case ShareConstants.TYPE_DEX:
reporter.onReport(KEY_APPLIED_DEX_EXTRACT);
break;
case ShareConstants.TYPE_LIBRARY:
reporter.onReport(KEY_APPLIED_LIB_EXTRACT);
break;
case ShareConstants.TYPE_PATCH_FILE:
reporter.onReport(KEY_APPLIED_PATCH_FILE_EXTRACT);
break;
case ShareConstants.TYPE_RESOURCE:
reporter.onReport(KEY_APPLIED_RESOURCE_EXTRACT);
break;
}
}
public static void onApplied(long cost, boolean success) {
if (reporter == null) {
return;
}
if (success) {
reporter.onReport(KEY_APPLIED);
}
if (success) {
reporter.onReport(KEY_APPLIED_UPGRADE);
} else {
reporter.onReport(KEY_APPLIED_UPGRADE_FAIL);
}
TinkerLog.i(TAG, "hp_report report apply cost = %d", cost);
if (cost < 0L) {
TinkerLog.e(TAG, "hp_report report apply cost failed, invalid cost");
return;
}
if (cost <= 5000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_5S_LESS);
} else {
reporter.onReport(KEY_APPLIED_FAIL_COST_5S_LESS);
}
} else if (cost <= 10 * 1000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_10S_LESS);
} else {
reporter.onReport(KEY_APPLIED_FAIL_COST_10S_LESS);
}
} else if (cost <= 30 * 1000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_30S_LESS);
} else {
reporter.onReport(KEY_APPLIED_FAIL_COST_30S_LESS);
}
} else if (cost <= 60 * 1000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_60S_LESS);
} else {
reporter.onReport(KEY_APPLIED_FAIL_COST_60S_LESS);
}
} else {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_OTHER);
} else {
reporter.onReport(KEY_APPLIED_FAIL_COST_OTHER);
}
}
}
public static void onApplyPackageCheckFail(int errorCode) {
if (reporter == null) {
return;
}
TinkerLog.i(TAG, "hp_report package check failed, error = %d", errorCode);
switch (errorCode) {
case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_SIGNATURE);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_DEX_META);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_LIB_META);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_RES_META);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);
break;
}
}
public static void onApplyCrash(Throwable throwable) {
if (reporter == null) {
return;
}
reporter.onReport(KEY_APPLIED_EXCEPTION);
reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable));
}
public static void onFastCrashProtect() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_CRASH_FAST_PROTECT);
}
public static void onXposedCrash() {
if (reporter == null) {
return;
}
if (ShareTinkerInternals.isVmArt()) {
reporter.onReport(KEY_CRASH_CAUSE_XPOSED_ART);
} else {
reporter.onReport(KEY_CRASH_CAUSE_XPOSED_DALVIK);
}
}
public static void onReportRetryPatch() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_APPLY_WITH_RETRY);
}
}
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();
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
TinkerLog.e(TAG, "uncaughtException:" + ex.getMessage());
tinkerFastCrashProtect();
tinkerPreVerifiedCrashHandler(ex);
ueh.uncaughtException(thread, ex);
}
/**
* Such as Xposed, if it try to load some class before we load from patch files.
* With dalvik, it will crash with "Class ref in pre-verified class resolved to unexpected implementation".
* With art, it may crash at some times. But we can't know the actual crash type.
* If it use Xposed, we can just clean patch or mention user to uninstall it.
*/
private void tinkerPreVerifiedCrashHandler(Throwable ex) {
ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike();
if (applicationLike == null || applicationLike.getApplication() == null) {
TinkerLog.w(TAG, "applicationlike is null");
return;
}
if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) {
TinkerLog.w(TAG, "tinker is not loaded");
return;
}
Throwable throwable = ex;
boolean isXposed = false;
while (throwable != null) {
if (!isXposed) {
isXposed = Utils.isXposedExists(throwable);
}
// xposed?
if (isXposed) {
boolean isCausedByXposed = false;
//for art, we can't know the actually crash type
//just ignore art
if (throwable instanceof IllegalAccessError && throwable.getMessage().contains(DALVIK_XPOSED_CRASH)) {
//for dalvik, we know the actual crash type
isCausedByXposed = true;
}
if (isCausedByXposed) {
SampleTinkerReport.onXposedCrash();
TinkerLog.e(TAG, "have xposed: just clean tinker");
//kill all other process to ensure that all process's code is the same.
ShareTinkerInternals.killAllOtherProcess(applicationLike.getApplication());
TinkerApplicationHelper.cleanPatch(applicationLike);
ShareTinkerInternals.setTinkerDisableWithSharedPreferences(applicationLike.getApplication());
return;
}
}
throwable = throwable.getCause();
}
}
/**
* if tinker is load, and it crash more than MAX_CRASH_COUNT, then we just clean patch.
*/
private boolean tinkerFastCrashProtect() {
ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike();
if (applicationLike == null || applicationLike.getApplication() == null) {
return false;
}
if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) {
return false;
}
final long elapsedTime = SystemClock.elapsedRealtime() - applicationLike.getApplicationStartElapsedTime();
//this process may not install tinker, so we use TinkerApplicationHelper api
if (elapsedTime < QUICK_CRASH_ELAPSE) {
String currentVersion = TinkerApplicationHelper.getCurrentVersion(applicationLike);
if (ShareTinkerInternals.isNullOrNil(currentVersion)) {
return false;
}
SharedPreferences sp = applicationLike.getApplication().getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS);
int fastCrashCount = sp.getInt(currentVersion, 0) + 1;
if (fastCrashCount >= MAX_CRASH_COUNT) {
SampleTinkerReport.onFastCrashProtect();
TinkerApplicationHelper.cleanPatch(applicationLike);
TinkerLog.e(TAG, "tinker has fast crash more than %d, we just clean patch!", fastCrashCount);
return true;
} else {
sp.edit().putInt(currentVersion, fastCrashCount).commit();
TinkerLog.e(TAG, "tinker has fast crash %d times", fastCrashCount);
}
}
return false;
}
}
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 DefaultLoadReporter
LoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication());
//or you can just use DefaultPatchReporter
PatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication());
//or you can just use DefaultPatchListener
PatchListener patchListener = new SamplePatchListener(appLike.getApplication());
//you can set your own upgrade patch if you need
AbstractPatch upgradePatchProcessor = new UpgradePatch();
TinkerInstaller.install(appLike,
loadReporter, patchReporter, patchListener,
SampleResultService.class, upgradePatchProcessor);
isInstalled = true;
}
}
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;
}
@Deprecated
public static boolean checkRomSpaceEnough(long limitSize) {
long allSize;
long availableSize = 0;
try {
File data = Environment.getDataDirectory();
StatFs sf = new StatFs(data.getPath());
availableSize = (long) sf.getAvailableBlocks() * (long) sf.getBlockSize();
allSize = (long) sf.getBlockCount() * (long) sf.getBlockSize();
} catch (Exception e) {
allSize = 0;
}
if (allSize != 0 && availableSize > limitSize) {
return true;
}
return false;
}
public static String getExceptionCauseString(final Throwable ex) {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final PrintStream ps = new PrintStream(bos);
try {
// print directly
Throwable t = ex;
while (t.getCause() != null) {
t = t.getCause();
}
t.printStackTrace(ps);
return toVisualString(bos.toString());
} finally {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static String toVisualString(String src) {
boolean cutFlg = false;
if (null == src) {
return null;
}
char[] chr = src.toCharArray();
if (null == chr) {
return null;
}
int i = 0;
for (; i < chr.length; i++) {
if (chr[i] > 127) {
chr[i] = 0;
cutFlg = true;
break;
}
}
if (cutFlg) {
return new String(chr, 0, i);
} else {
return src;
}
}
public static class ScreenState {
public interface IOnScreenOff {
void onScreenOff();
}
public ScreenState(final Context context, final IOnScreenOff onScreenOffInterface) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent in) {
String action = in == null ? "" : in.getAction();
TinkerLog.i(TAG, "ScreenReceiver action [%s] ", action);
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
if (onScreenOffInterface != null) {
onScreenOffInterface.onScreenOff();
}
}
context.unregisterReceiver(this);
}
}, filter);
}
}
}
5.配置清单文件
Application配置上刚才写的那个BaseApplication
然后配置service
<service
android: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.aaa
return 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() > 0
def date = new Date().format("MMdd-HH-mm-ss")
/**
* bak apk and mapping
*/
android.applicationVariants.all { variant ->
/**
* task type, you want to bak
*/
def taskName = variant.name
tasks.all {
if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
it.doLast {
copy {
def fileNamePrefix = "${project.name}-${variant.baseName}"
def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"
def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
if (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 destPath
rename { String fileName ->
fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
}
from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
into destPath
rename { String fileName ->
fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
}
from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
from "${buildDir}/intermediates/symbol_list/${variant.dirName}/R.txt"
from "${buildDir}/intermediates/runtime_symbol_list/${variant.dirName}/R.txt"
into destPath
rename { String fileName ->
fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
}
}
}
}
}
}
project.afterEvaluate {
//sample use for build all flavor for one time
if (hasFlavors) {
task(tinkerPatchAllFlavorRelease) {
group = 'tinker'
def originOldPath = getTinkerBuildFlavorDirectory()
for (String flavor : flavors) {
def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
dependsOn tinkerTask
def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
preAssembleTask.doFirst {
// String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
// project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
// project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
// project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"
project.tinkerPatch.oldApk = getOldApkPath()
System.out.println("lllttt oldApk=" + project.tinkerPatch.oldApk)
project.tinkerPatch.buildConfig.applyMapping = ""
project.tinkerPatch.buildConfig.applyResourceMapping = originOldPath
System.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 tinkerTask
def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
preAssembleTask.doFirst {
// String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
// project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
// project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
// project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
project.tinkerPatch.oldApk = getOldApkPath()
System.out.println("lllttt oldApk=" + project.tinkerPatch.oldApk)
project.tinkerPatch.buildConfig.applyMapping = ""
project.tinkerPatch.buildConfig.applyResourceMapping = originOldPath
System.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
对Kotlin或KMP感兴趣的同学可以进Q群 101786950
如果这篇文章对您有帮助的话
可以扫码请我喝瓶饮料或咖啡(如果对什么比较感兴趣可以在备注里写出来)