1.配置Project下的build.gradle
buildscript {
repositories {
...
mavenLocal()
}
dependencies {
...
classpath "com.tencent.tinker:tinker-patch-gradle-plugin:1.7.11"
}
}
allprojects {
repositories {
...
mavenLocal()
}
}
2.配置Module下的build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.tencent.tinker.patch'
android {
...
defaultConfig {
...
multiDexEnabled true
buildConfigField "String", "MESSAGE", "\"I am the base apk\""
buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""
buildConfigField "String", "PLATFORM", "\"all\""
}
}
dependencies {
...
compile "com.android.support:multidex:1.0.1"
provided('com.tencent.tinker:tinker-android-anno:1.7.11')
compile('com.tencent.tinker:tinker-android-lib:1.7.11')
}
//定义基准apk构建的位置
def bakPath = file("${buildDir}/bakApk/")
//额外属性,实际上这些也是可以不用写的,腾讯真是良心,可以支持Gradle脚本自定义,这些值实际上都可以在gradle.properites中自定义
ext {
//是否支持tinker构建
tinkerEnabled = true
//如果需要构建patch包,这里需要天上同一个其基准包地址
tinkerOldApkPath = "${bakPath}/app-debug-05-27.apk"
//如果需要构建patch包,这里需要天上同一个其基准包的混淆文件
tinkerApplyMappingPath = "${bakPath}/app-debug-05-27-mapping.txt"
//如果差分包有修改资源文件,则必须需要输入以前基准包的R文件,主要是为了固定id来用的。
tinkerApplyResourcePath = "${bakPath}/app-debug-05-27-R.txt"
}
if (ext.tinkerEnabled) {
//引用patch插件
apply plugin: 'com.tencent.tinker.patch'
//tinkerPath任务,补丁关键任务,源码也有,后面我会有详细撰述,这里只知道怎么用即可
//直接使用基准apk包与新编译出来的apk包做差异,得到最终的补丁包
tinkerPatch {
oldApk = getTinkerOldApkPath()
/**
* 是否忽略警告
* 值为false将中断编译。因为这些情况可能会导致编译出来的patch包带来风险:
* 1. minSdkVersion小于14,但是dexMode的值为"raw"; dexmode下面会有介绍
* 2. 新编译的安装包出现新增的四大组件(Activity, BroadcastReceiver...);
* 3. 定义在dex.loader用于加载补丁的类不在main dex中;
* 4. 定义在dex.loader用于加载补丁的类出现修改;
* 5. resources.arsc改变,但没有使用applyResourceMapping编译。
*/
ignoreWarning = false
//是否签名,保证基准包和补丁包的签名一致,代码里有判断逻辑
useSign = true
/**
* 编译相关配置项
*/
buildConfig {
//在编译新的apk时候,通过保持旧apk的proguard混淆方式,从而减少补丁包的大小。这个只是推荐设置,不设置applyMapping也不会影响任何的assemble编译
applyMapping = getTinkerApplyMappingPath()
//在编译新的apk时候,通过旧apk的R.txt文件保持ResId的分配,这样不仅可以减少补丁包的大小,同时也避免由于ResId改变导致remote view异常。
applyResourceMapping = getTinkerApplyResourcePath()
//tinkerID
tinkerId = getTinkerIdValue()
//如果有多个dex,编译补丁时可能会由于类的移动导致变更增多。若打开keepDexApply模式,补丁包将根据基准包的类分布来编译。
keepDexApply = false
}
dex {
//只能是'raw'或者'jar'。
//对于'raw'模式,将会保持输入dex的格式。
//对于'jar'模式,将会把输入dex重新压缩封装到jar。如果你的minSdkVersion小于14,你必须选择‘jar’模式,而且它更省存储空间,但是验证md5时比'raw'模式耗时。默认我们并不会去校验md5,一般情况下选择jar模式即可。
dexMode = "jar"
//需要处理dex路径,支持*、?通配符,必须使用'/'分割。路径是相对安装包的,例如assets/...
pattern = ["classes*.dex", "assets/secondary-dex-?.jar"]
/**
* 这一项非常重要,它定义了哪些类在加载补丁包的时候会用到。这些类是通过Tinker无法修改的类,也是一定要放在main dex的类。
这里需要定义的类有:
1. 你自己定义的Application类;
2. Tinker库中用于加载补丁包的部分类,即com.tencent.tinker.loader.*;
3. 如果你自定义了TinkerLoader,需要将它以及它引用的所有类也加入loader中;
4. 其他一些你不希望被更改的类,例如Sample中的BaseBuildInfo类。这里需要注意的是,这些类的直接引用类也需要加入到loader中。或者你需要将这个类变成非preverify。
5. 使用1.7.6版本之后版本,参数1、2会自动填写。
*/
loader = [
//use sample, let BaseBuildInfo unchangeable with tinker
// "tinker.sample.android.app.BaseBuildInfo"
]
}
lib {
//需要处理lib路径,支持*、?通配符,必须使用'/'分割。与dex.pattern一致, 路径是相对安装包的,例如assets/...
pattern = ["lib/armeabi/*.so"]
}
res {
//需要处理res路径,支持*、?通配符,必须使用'/'分割。与dex.pattern一致, 路径是相对安装包的,例如assets/...,务必注意的是,只有满足pattern的资源才会放到合成后的资源包。
pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
//支持*、?通配符,必须使用'/'分割。若满足ignoreChange的pattern,在编译时会忽略该文件的新增、删除与修改。 最极端的情况,ignoreChange与上面的pattern一致,即会完全忽略所有资源的修改。
ignoreChange = ["assets/sample_meta.txt"]
//对于修改的资源,如果大于largeModSize,我们将使用bsdiff算法。这可以降低补丁包的大小,但是会增加合成时的复杂度。默认大小为100kb
largeModSize = 100
}
packageConfig {
//configField("key", "value"), 默认我们自动从基准安装包与新安装包的Manifest中读取tinkerId,并自动写入configField。在这里,你可以定义其他的信息,在运行时可以通过TinkerLoadResult.getPackageConfigByName得到相应的数值。但是建议直接通过修改代码来实现,例如BuildConfig。
configField("patchMessage", "tinker is sample to use")
/**
* just a sample case, you can use such as sdkVersion, brand, channel...
* you can parse it in the SamplePatchListener.
* Then you can use patch conditional!
*/
configField("platform", "all")
//patch version via packageConfig
configField("patchVersion", "1.0")
}
//7zip路径配置项,执行前提是useSign为true
sevenZip {
//例如"com.tencent.mm:SevenZip:1.1.10",将自动根据机器属性获得对应的7za运行文件,推荐使用
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
}
}
List<String> flavors = new ArrayList<>();
project.android.productFlavors.each { flavor ->
flavors.add(flavor.name)
}
boolean hasFlavors = flavors.size() > 0
//bak apk and mapping, 渠道包相关配置。
android.applicationVariants.all { variant ->
//task type, you want to bak
def taskName = variant.name
//def date = new Date().format("MMdd-HH-mm-ss")
def date = new Date().format("mm-ss")
tasks.all {
if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
it.doLast {
copy {
def fileNamePrefix = "${project.name}-${variant.baseName}"
def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"
def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
from variant.outputs.outputFile
into destPath
rename { String fileName ->
fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
}
from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
into destPath
rename { String fileName ->
fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
}
from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
into destPath
rename { String fileName ->
fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
}
}
}
}
}
}
}
def getTinkerApplyResourcePath() {
return ext.tinkerApplyResourcePath
}
def getTinkerApplyMappingPath() {
return ext.tinkerApplyMappingPath
}
def getTinkerOldApkPath() {
return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
}
def getTinkerIdValue() {
return hasProperty("TINKER_ID") ? TINKER_ID : 1
}
3.配置Application
@SuppressWarnings("unused")
@DefaultLifeCycle(application = ".BaseApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class SampleApplication extends DefaultApplicationLike {
private static final String TAG = "Tinker.SampleApplicationLike";
public SampleApplication(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
/**
* install multiDex before install tinker
* so we don't need to put the tinker lib classes in the main dex
*
* @param base
*/
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
//you must install multiDex whatever tinker is installed!
MultiDex.install(base);
TinkerManager.setTinkerApplicationLike(this);
TinkerManager.initFastCrashProtect();
//should set before tinker is installed
TinkerManager.setUpgradeRetryEnable(true);
//optional set logIml, or you can use default debug log
//TinkerInstaller.setLogIml(new MyLogImp());
TinkerInstaller.setLogIml(TinkerLog.getImpl());
//installTinker after load multiDex
//or you can put com.tencent.tinker.** to main dex
TinkerManager.installTinker(this);
Tinker tinker = Tinker.with(getApplication());
}
}
其中注解参数BaseApplication才是真正的Application,需要在AndroidManifest.xml文件中进行配置
4.相关文件如下
1.TinkerManager.java
public class TinkerManager {
private static final String TAG = "Tinker.TinkerManager";
private static ApplicationLike applicationLike;
private static SampleUncaughtExceptionHandler uncaughtExceptionHandler;
private static boolean isInstalled = false;
public static void setTinkerApplicationLike(ApplicationLike appLike) {
applicationLike = appLike;
}
public static ApplicationLike getTinkerApplicationLike() {
return applicationLike;
}
public static void initFastCrashProtect() {
if (uncaughtExceptionHandler == null) {
uncaughtExceptionHandler = new SampleUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
}
}
public static void setUpgradeRetryEnable(boolean enable) {
UpgradePatchRetry.getInstance(applicationLike.getApplication()).setRetryEnable(enable);
}
/**
* all use default class, simply Tinker install method
*/
public static void sampleInstallTinker(ApplicationLike appLike) {
if (isInstalled) {
TinkerLog.w(TAG, "install tinker, but has installed, ignore");
return;
}
TinkerInstaller.install(appLike);
isInstalled = true;
}
/**
* you can specify all class you want.
* sometimes, you can only install tinker in some process you want!
*
* @param appLike
*/
public static void installTinker(ApplicationLike appLike) {
if (isInstalled) {
TinkerLog.w(TAG, "install tinker, but has installed, ignore");
return;
}
//or you can just use DefaultLoadReporter
LoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication());
//or you can just use DefaultPatchReporter
PatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication());
//or you can just use DefaultPatchListener
PatchListener patchListener = new SamplePatchListener(appLike.getApplication());
//you can set your own upgrade patch if you need
AbstractPatch upgradePatchProcessor = new UpgradePatch();
TinkerInstaller.install(appLike,
loadReporter, patchReporter, patchListener,
SampleResultService.class, upgradePatchProcessor);
isInstalled = true;
}
}
2.Utils.java
public class Utils {
private static final String TAG = "Tinker.Utils";
/**
* the error code define by myself
* should after {@code ShareConstants.ERROR_PATCH_INSERVICE
*/
public static final int ERROR_PATCH_GOOGLEPLAY_CHANNEL = -6;
public static final int ERROR_PATCH_ROM_SPACE = -7;
public static final int ERROR_PATCH_MEMORY_LIMIT = -8;
public static final int ERROR_PATCH_CRASH_LIMIT = -9;
public static final int ERROR_PATCH_CONDITION_NOT_SATISFIED = -10;
public static final int ERROR_PATCH_ALREADY_APPLY = -11;
public static final int ERROR_PATCH_RETRY_COUNT_LIMIT = -12;
public static final String PLATFORM = "platform";
public static final int MIN_MEMORY_HEAP_SIZE = 45;
private static boolean background = false;
public static boolean isGooglePlay() {
return false;
}
public static boolean isBackground() {
return background;
}
public static void setBackground(boolean back) {
background = back;
}
public static int checkForPatchRecover(long roomSize, int maxMemory) {
if (Utils.isGooglePlay()) {
return Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL;
}
if (maxMemory < MIN_MEMORY_HEAP_SIZE) {
return Utils.ERROR_PATCH_MEMORY_LIMIT;
}
//or you can mention user to clean their rom space!
if (!checkRomSpaceEnough(roomSize)) {
return Utils.ERROR_PATCH_ROM_SPACE;
}
return ShareConstants.ERROR_PATCH_OK;
}
public static boolean isXposedExists(Throwable thr) {
StackTraceElement[] stackTraces = thr.getStackTrace();
for (StackTraceElement stackTrace : stackTraces) {
final String clazzName = stackTrace.getClassName();
if (clazzName != null && clazzName.contains("de.robv.android.xposed.XposedBridge")) {
return true;
}
}
return false;
}
@Deprecated
public static boolean checkRomSpaceEnough(long limitSize) {
long allSize;
long availableSize = 0;
try {
File data = Environment.getDataDirectory();
StatFs sf = new StatFs(data.getPath());
availableSize = (long) sf.getAvailableBlocks() * (long) sf.getBlockSize();
allSize = (long) sf.getBlockCount() * (long) sf.getBlockSize();
} catch (Exception e) {
allSize = 0;
}
if (allSize != 0 && availableSize > limitSize) {
return true;
}
return false;
}
public static String getExceptionCauseString(final Throwable ex) {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final PrintStream ps = new PrintStream(bos);
try {
// print directly
Throwable t = ex;
while (t.getCause() != null) {
t = t.getCause();
}
t.printStackTrace(ps);
return toVisualString(bos.toString());
} finally {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static String toVisualString(String src) {
boolean cutFlg = false;
if (null == src) {
return null;
}
char[] chr = src.toCharArray();
if (null == chr) {
return null;
}
int i = 0;
for (; i < chr.length; i++) {
if (chr[i] > 127) {
chr[i] = 0;
cutFlg = true;
break;
}
}
if (cutFlg) {
return new String(chr, 0, i);
} else {
return src;
}
}
public static class ScreenState {
public interface IOnScreenOff {
void onScreenOff();
}
public ScreenState(final Context context, final IOnScreenOff onScreenOffInterface) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent in) {
String action = in == null ? "" : in.getAction();
TinkerLog.i(TAG, "ScreenReceiver action [%s] ", action);
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
if (onScreenOffInterface != null) {
onScreenOffInterface.onScreenOff();
}
}
context.unregisterReceiver(this);
}
}, filter);
}
}
}
3.SampleLoadReporter.java
public class SampleLoadReporter extends DefaultLoadReporter {
public SampleLoadReporter(Context context) {
super(context);
}
@Override
public void onLoadPatchListenerReceiveFail(final File patchFile, int errorCode) {
super.onLoadPatchListenerReceiveFail(patchFile, errorCode);
SampleTinkerReport.onTryApplyFail(errorCode);
}
@Override
public void onLoadResult(File patchDirectory, int loadCode, long cost) {
super.onLoadResult(patchDirectory, loadCode, cost);
switch (loadCode) {
case ShareConstants.ERROR_LOAD_OK:
SampleTinkerReport.onLoaded(cost);
break;
}
Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
if (UpgradePatchRetry.getInstance(context).onPatchRetryLoad()) {
SampleTinkerReport.onReportRetryPatch();
}
return false;
}
});
}
@Override
public void onLoadException(Throwable e, int errorCode) {
super.onLoadException(e, errorCode);
SampleTinkerReport.onLoadException(e, errorCode);
}
@Override
public void onLoadFileMd5Mismatch(File file, int fileType) {
super.onLoadFileMd5Mismatch(file, fileType);
SampleTinkerReport.onLoadFileMisMatch(fileType);
}
/**
* try to recover patch oat file
*
* @param file
* @param fileType
* @param isDirectory
*/
@Override
public void onLoadFileNotFound(File file, int fileType, boolean isDirectory) {
super.onLoadFileNotFound(file, fileType, isDirectory);
SampleTinkerReport.onLoadFileNotFound(fileType);
}
@Override
public void onLoadPackageCheckFail(File patchFile, int errorCode) {
super.onLoadPackageCheckFail(patchFile, errorCode);
SampleTinkerReport.onLoadPackageCheckFail(errorCode);
}
@Override
public void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) {
super.onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile);
SampleTinkerReport.onLoadInfoCorrupted();
}
@Override
public void onLoadInterpret(int type, Throwable e) {
super.onLoadInterpret(type, e);
SampleTinkerReport.onLoadInterpretReport(type, e);
}
@Override
public void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) {
super.onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectoryFile, currentPatchName);
}
}
4.SamplePatchListener.java
public class SamplePatchListener extends DefaultPatchListener {
private static final String TAG = "Tinker.SamplePatchListener";
protected static final long NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN = 60 * 1024 * 1024;
private final int maxMemory;
public SamplePatchListener(Context context) {
super(context);
maxMemory = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
TinkerLog.i(TAG, "application maxMemory:" + maxMemory);
}
/**
* because we use the defaultCheckPatchReceived method
* the error code define by myself should after {@code ShareConstants.ERROR_RECOVER_INSERVICE
*
* @param path
* @param newPatch
* @return
*/
@Override
public int patchCheck(String path) {
File patchFile = new File(path);
TinkerLog.i(TAG, "receive a patch file: %s, file size:%d", path, SharePatchFileUtil.getFileOrDirectorySize(patchFile));
int returnCode = super.patchCheck(path);
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
returnCode = Utils.checkForPatchRecover(NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN, maxMemory);
}
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS);
//optional, only disable this patch file with md5
int fastCrashCount = sp.getInt(patchMd5, 0);
if (fastCrashCount >= SampleUncaughtExceptionHandler.MAX_CRASH_COUNT) {
returnCode = Utils.ERROR_PATCH_CRASH_LIMIT;
} else {
//for upgrade patch, version must be not the same
//for repair patch, we won't has the tinker load flag
Tinker tinker = Tinker.with(context);
if (tinker.isTinkerLoaded()) {
TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent();
if (tinkerLoadResult != null && !tinkerLoadResult.useInterpretMode) {
String currentVersion = tinkerLoadResult.currentVersion;
if (patchMd5.equals(currentVersion)) {
returnCode = Utils.ERROR_PATCH_ALREADY_APPLY;
}
}
}
}
//check whether retry so many times
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
returnCode = UpgradePatchRetry.getInstance(context).onPatchListenerCheck(patchMd5)
? ShareConstants.ERROR_PATCH_OK : Utils.ERROR_PATCH_RETRY_COUNT_LIMIT;
}
}
// Warning, it is just a sample case, you don't need to copy all of these
// Interception some of the request
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
Properties properties = ShareTinkerInternals.fastGetPatchPackageMeta(patchFile);
if (properties == null) {
returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
} else {
String platform = properties.getProperty(Utils.PLATFORM);
TinkerLog.i(TAG, "get platform:" + platform);
// check patch platform require
if (platform == null || !platform.equals(BuildConfig.PLATFORM)) {
returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
}
}
}
SampleTinkerReport.onTryApply(returnCode == ShareConstants.ERROR_PATCH_OK);
return returnCode;
}
}
5.SamplePatchReporter.java
public class SamplePatchReporter extends DefaultPatchReporter {
public SamplePatchReporter(Context context) {
super(context);
}
@Override
public void onPatchServiceStart(Intent intent) {
super.onPatchServiceStart(intent);
SampleTinkerReport.onApplyPatchServiceStart();
}
@Override
public void onPatchDexOptFail(File patchFile, List<File> dexFiles, Throwable t) {
super.onPatchDexOptFail(patchFile, dexFiles, t);
SampleTinkerReport.onApplyDexOptFail(t);
}
@Override
public void onPatchException(File patchFile, Throwable e) {
super.onPatchException(patchFile, e);
SampleTinkerReport.onApplyCrash(e);
}
@Override
public void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion) {
super.onPatchInfoCorrupted(patchFile, oldVersion, newVersion);
SampleTinkerReport.onApplyInfoCorrupted();
}
@Override
public void onPatchPackageCheckFail(File patchFile, int errorCode) {
super.onPatchPackageCheckFail(patchFile, errorCode);
SampleTinkerReport.onApplyPackageCheckFail(errorCode);
}
@Override
public void onPatchResult(File patchFile, boolean success, long cost) {
super.onPatchResult(patchFile, success, cost);
SampleTinkerReport.onApplied(cost, success);
}
@Override
public void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType) {
super.onPatchTypeExtractFail(patchFile, extractTo, filename, fileType);
SampleTinkerReport.onApplyExtractFail(fileType);
}
@Override
public void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion) {
super.onPatchVersionCheckFail(patchFile, oldPatchInfo, patchFileVersion);
SampleTinkerReport.onApplyVersionCheckFail();
}
}
6.SampleResultService.java
public class SampleResultService extends DefaultTinkerResultService {
private static final String TAG = "Tinker.SampleResultService";
@Override
public void onPatchResult(final PatchResult result) {
if (result == null) {
TinkerLog.e(TAG, "SampleResultService received null result!!!!");
return;
}
TinkerLog.i(TAG, "SampleResultService receive result: %s", result.toString());
//first, we want to kill the recover process
TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
if (result.isSuccess) {
Toast.makeText(getApplicationContext(), "patch success, please restart process", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), "patch fail, please check reason", Toast.LENGTH_LONG).show();
}
}
});
// is success and newPatch, it is nice to delete the raw file, and restart at once
// for old patch, you can't delete the patch file
if (result.isSuccess) {
deleteRawPatchFile(new File(result.rawPatchFilePath));
//not like TinkerResultService, I want to restart just when I am at background!
//if you have not install tinker this moment, you can use TinkerApplicationHelper api
if (checkIfNeedKill(result)) {
if (Utils.isBackground()) {
TinkerLog.i(TAG, "it is in background, just restart process");
restartProcess();
} else {
//we can wait process at background, such as onAppBackground
//or we can restart when the screen off
TinkerLog.i(TAG, "tinker wait screen to restart process");
new Utils.ScreenState(getApplicationContext(), new Utils.ScreenState.IOnScreenOff() {
@Override
public void onScreenOff() {
restartProcess();
}
});
}
} else {
TinkerLog.i(TAG, "I have already install the newly patch version!");
}
}
}
/**
* you can restart your process through service or broadcast
*/
private void restartProcess() {
TinkerLog.i(TAG, "app is background now, i can kill quietly");
//you can send service or broadcast intent to restart your process
android.os.Process.killProcess(android.os.Process.myPid());
}
}
7.SampleTinkerReport.java
public class SampleTinkerReport {
private static final String TAG = "Tinker.SampleTinkerReport";
// KEY - PV
public static final int KEY_REQUEST = 0;
public static final int KEY_DOWNLOAD = 1;
public static final int KEY_TRY_APPLY = 2;
public static final int KEY_TRY_APPLY_SUCCESS = 3;
public static final int KEY_APPLIED_START = 4;
public static final int KEY_APPLIED = 5;
public static final int KEY_LOADED = 6;
public static final int KEY_CRASH_FAST_PROTECT = 7;
public static final int KEY_CRASH_CAUSE_XPOSED_DALVIK = 8;
public static final int KEY_CRASH_CAUSE_XPOSED_ART = 9;
public static final int KEY_APPLY_WITH_RETRY = 10;
//Key -- try apply detail
public static final int KEY_TRY_APPLY_UPGRADE = 70;
public static final int KEY_TRY_APPLY_DISABLE = 71;
public static final int KEY_TRY_APPLY_RUNNING = 72;
public static final int KEY_TRY_APPLY_INSERVICE = 73;
public static final int KEY_TRY_APPLY_NOT_EXIST = 74;
public static final int KEY_TRY_APPLY_GOOGLEPLAY = 75;
public static final int KEY_TRY_APPLY_ROM_SPACE = 76;
public static final int KEY_TRY_APPLY_ALREADY_APPLY = 77;
public static final int KEY_TRY_APPLY_MEMORY_LIMIT = 78;
public static final int KEY_TRY_APPLY_CRASH_LIMIT = 79;
public static final int KEY_TRY_APPLY_CONDITION_NOT_SATISFIED = 80;
public static final int KEY_TRY_APPLY_JIT = 81;
//Key -- apply detail
public static final int KEY_APPLIED_UPGRADE = 100;
public static final int KEY_APPLIED_UPGRADE_FAIL = 101;
public static final int KEY_APPLIED_EXCEPTION = 120;
public static final int KEY_APPLIED_DEXOPT_OTHER = 121;
public static final int KEY_APPLIED_DEXOPT_EXIST = 122;
public static final int KEY_APPLIED_DEXOPT_FORMAT = 123;
public static final int KEY_APPLIED_INFO_CORRUPTED = 124;
//package check
public static final int KEY_APPLIED_PACKAGE_CHECK_SIGNATURE = 150;
public static final int KEY_APPLIED_PACKAGE_CHECK_DEX_META = 151;
public static final int KEY_APPLIED_PACKAGE_CHECK_LIB_META = 152;
public static final int KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = 153;
public static final int KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 154;
public static final int KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND = 155;
public static final int KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = 156;
public static final int KEY_APPLIED_PACKAGE_CHECK_RES_META = 157;
public static final int KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT = 158;
//version check
public static final int KEY_APPLIED_VERSION_CHECK = 180;
//extract error
public static final int KEY_APPLIED_PATCH_FILE_EXTRACT = 181;
public static final int KEY_APPLIED_DEX_EXTRACT = 182;
public static final int KEY_APPLIED_LIB_EXTRACT = 183;
public static final int KEY_APPLIED_RESOURCE_EXTRACT = 184;
//cost time
public static final int KEY_APPLIED_SUCC_COST_5S_LESS = 200;
public static final int KEY_APPLIED_SUCC_COST_10S_LESS = 201;
public static final int KEY_APPLIED_SUCC_COST_30S_LESS = 202;
public static final int KEY_APPLIED_SUCC_COST_60S_LESS = 203;
public static final int KEY_APPLIED_SUCC_COST_OTHER = 204;
public static final int KEY_APPLIED_FAIL_COST_5S_LESS = 205;
public static final int KEY_APPLIED_FAIL_COST_10S_LESS = 206;
public static final int KEY_APPLIED_FAIL_COST_30S_LESS = 207;
public static final int KEY_APPLIED_FAIL_COST_60S_LESS = 208;
public static final int KEY_APPLIED_FAIL_COST_OTHER = 209;
// KEY -- load detail
public static final int KEY_LOADED_UNKNOWN_EXCEPTION = 250;
public static final int KEY_LOADED_UNCAUGHT_EXCEPTION = 251;
public static final int KEY_LOADED_EXCEPTION_DEX = 252;
public static final int KEY_LOADED_EXCEPTION_DEX_CHECK = 253;
public static final int KEY_LOADED_EXCEPTION_RESOURCE = 254;
public static final int KEY_LOADED_EXCEPTION_RESOURCE_CHECK = 255;
public static final int KEY_LOADED_MISMATCH_DEX = 300;
public static final int KEY_LOADED_MISMATCH_LIB = 301;
public static final int KEY_LOADED_MISMATCH_RESOURCE = 302;
public static final int KEY_LOADED_MISSING_DEX = 303;
public static final int KEY_LOADED_MISSING_LIB = 304;
public static final int KEY_LOADED_MISSING_PATCH_FILE = 305;
public static final int KEY_LOADED_MISSING_PATCH_INFO = 306;
public static final int KEY_LOADED_MISSING_DEX_OPT = 307;
public static final int KEY_LOADED_MISSING_RES = 308;
public static final int KEY_LOADED_INFO_CORRUPTED = 309;
//load package check
public static final int KEY_LOADED_PACKAGE_CHECK_SIGNATURE = 350;
public static final int KEY_LOADED_PACKAGE_CHECK_DEX_META = 351;
public static final int KEY_LOADED_PACKAGE_CHECK_LIB_META = 352;
public static final int KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = 353;
public static final int KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 354;
public static final int KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = 355;
public static final int KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND = 356;
public static final int KEY_LOADED_PACKAGE_CHECK_RES_META = 357;
public static final int KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT = 358;
public static final int KEY_LOADED_SUCC_COST_500_LESS = 400;
public static final int KEY_LOADED_SUCC_COST_1000_LESS = 401;
public static final int KEY_LOADED_SUCC_COST_3000_LESS = 402;
public static final int KEY_LOADED_SUCC_COST_5000_LESS = 403;
public static final int KEY_LOADED_SUCC_COST_OTHER = 404;
public static final int KEY_LOADED_INTERPRET_GET_INSTRUCTION_SET_ERROR = 450;
public static final int KEY_LOADED_INTERPRET_INTERPRET_COMMAND_ERROR = 451;
public static final int KEY_LOADED_INTERPRET_TYPE_INTERPRET_OK = 452;
interface Reporter {
void onReport(int key);
void onReport(String message);
}
private static Reporter reporter = null;
public void setReporter(Reporter reporter) {
this.reporter = reporter;
}
public static void onTryApply(boolean success) {
if (reporter == null) {
return;
}
reporter.onReport(KEY_TRY_APPLY);
reporter.onReport(KEY_TRY_APPLY_UPGRADE);
if (success) {
reporter.onReport(KEY_TRY_APPLY_SUCCESS);
}
}
public static void onTryApplyFail(int errorCode) {
if (reporter == null) {
return;
}
switch (errorCode) {
case ShareConstants.ERROR_PATCH_NOTEXIST:
reporter.onReport(KEY_TRY_APPLY_NOT_EXIST);
break;
case ShareConstants.ERROR_PATCH_DISABLE:
reporter.onReport(KEY_TRY_APPLY_DISABLE);
break;
case ShareConstants.ERROR_PATCH_INSERVICE:
reporter.onReport(KEY_TRY_APPLY_INSERVICE);
break;
case ShareConstants.ERROR_PATCH_RUNNING:
reporter.onReport(KEY_TRY_APPLY_RUNNING);
break;
case ShareConstants.ERROR_PATCH_JIT:
reporter.onReport(KEY_TRY_APPLY_JIT);
break;
case Utils.ERROR_PATCH_ROM_SPACE:
reporter.onReport(KEY_TRY_APPLY_ROM_SPACE);
break;
case Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL:
reporter.onReport(KEY_TRY_APPLY_GOOGLEPLAY);
break;
case Utils.ERROR_PATCH_ALREADY_APPLY:
reporter.onReport(KEY_TRY_APPLY_ALREADY_APPLY);
break;
case Utils.ERROR_PATCH_CRASH_LIMIT:
reporter.onReport(KEY_TRY_APPLY_CRASH_LIMIT);
break;
case Utils.ERROR_PATCH_MEMORY_LIMIT:
reporter.onReport(KEY_TRY_APPLY_MEMORY_LIMIT);
break;
case Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED:
reporter.onReport(KEY_TRY_APPLY_CONDITION_NOT_SATISFIED);
break;
}
}
public static void onLoadPackageCheckFail(int errorCode) {
if (reporter == null) {
return;
}
switch (errorCode) {
case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_SIGNATURE);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_DEX_META);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_LIB_META);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_RES_META);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);
break;
}
}
public static void onLoaded(long cost) {
if (reporter == null) {
return;
}
reporter.onReport(KEY_LOADED);
if (cost < 0L) {
TinkerLog.e(TAG, "hp_report report load cost failed, invalid cost");
return;
}
if (cost <= 500) {
reporter.onReport(KEY_LOADED_SUCC_COST_500_LESS);
} else if (cost <= 1000) {
reporter.onReport(KEY_LOADED_SUCC_COST_1000_LESS);
} else if (cost <= 3000) {
reporter.onReport(KEY_LOADED_SUCC_COST_3000_LESS);
} else if (cost <= 5000) {
reporter.onReport(KEY_LOADED_SUCC_COST_5000_LESS);
} else {
reporter.onReport(KEY_LOADED_SUCC_COST_OTHER);
}
}
public static void onLoadInfoCorrupted() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_LOADED_INFO_CORRUPTED);
}
public static void onLoadFileNotFound(int fileType) {
if (reporter == null) {
return;
}
switch (fileType) {
case ShareConstants.TYPE_DEX_OPT:
reporter.onReport(KEY_LOADED_MISSING_DEX_OPT);
break;
case ShareConstants.TYPE_DEX:
reporter.onReport(KEY_LOADED_MISSING_DEX);
break;
case ShareConstants.TYPE_LIBRARY:
reporter.onReport(KEY_LOADED_MISSING_LIB);
break;
case ShareConstants.TYPE_PATCH_FILE:
reporter.onReport(KEY_LOADED_MISSING_PATCH_FILE);
break;
case ShareConstants.TYPE_PATCH_INFO:
reporter.onReport(KEY_LOADED_MISSING_PATCH_INFO);
break;
case ShareConstants.TYPE_RESOURCE:
reporter.onReport(KEY_LOADED_MISSING_RES);
break;
}
}
public static void onLoadInterpretReport(int type, Throwable e) {
if (reporter == null) {
return;
}
switch (type) {
case ShareConstants.TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR:
reporter.onReport(KEY_LOADED_INTERPRET_GET_INSTRUCTION_SET_ERROR);
reporter.onReport("Tinker Exception:interpret occur exception " + Utils.getExceptionCauseString(e));
break;
case ShareConstants.TYPE_INTERPRET_COMMAND_ERROR:
reporter.onReport(KEY_LOADED_INTERPRET_INTERPRET_COMMAND_ERROR);
reporter.onReport("Tinker Exception:interpret occur exception " + Utils.getExceptionCauseString(e));
break;
case ShareConstants.TYPE_INTERPRET_OK:
reporter.onReport(KEY_LOADED_INTERPRET_TYPE_INTERPRET_OK);
break;
}
}
public static void onLoadFileMisMatch(int fileType) {
if (reporter == null) {
return;
}
switch (fileType) {
case ShareConstants.TYPE_DEX:
reporter.onReport(KEY_LOADED_MISMATCH_DEX);
break;
case ShareConstants.TYPE_LIBRARY:
reporter.onReport(KEY_LOADED_MISMATCH_LIB);
break;
case ShareConstants.TYPE_RESOURCE:
reporter.onReport(KEY_LOADED_MISMATCH_RESOURCE);
break;
}
}
public static void onLoadException(Throwable throwable, int errorCode) {
if (reporter == null) {
return;
}
boolean isCheckFail = false;
switch (errorCode) {
case ShareConstants.ERROR_LOAD_EXCEPTION_DEX:
if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_INSTALL_FAIL)) {
reporter.onReport(KEY_LOADED_EXCEPTION_DEX_CHECK);
isCheckFail = true;
TinkerLog.e(TAG, "tinker dex check fail:" + throwable.getMessage());
} else {
reporter.onReport(KEY_LOADED_EXCEPTION_DEX);
TinkerLog.e(TAG, "tinker dex reflect fail:" + throwable.getMessage());
}
break;
case ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE:
if (throwable.getMessage().contains(ShareConstants.CHECK_RES_INSTALL_FAIL)) {
reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE_CHECK);
isCheckFail = true;
TinkerLog.e(TAG, "tinker res check fail:" + throwable.getMessage());
} else {
reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE);
TinkerLog.e(TAG, "tinker res reflect fail:" + throwable.getMessage());
}
break;
case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT:
reporter.onReport(KEY_LOADED_UNCAUGHT_EXCEPTION);
break;
case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN:
reporter.onReport(KEY_LOADED_UNKNOWN_EXCEPTION);
break;
}
//reporter exception, for dex check fail, we don't need to report stacktrace
if (!isCheckFail) {
reporter.onReport("Tinker Exception:load tinker occur exception " + Utils.getExceptionCauseString(throwable));
}
}
public static void onApplyPatchServiceStart() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_APPLIED_START);
}
public static void onApplyDexOptFail(Throwable throwable) {
if (reporter == null) {
return;
}
if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_OAT_EXIST_FAIL)) {
reporter.onReport(KEY_APPLIED_DEXOPT_EXIST);
} else if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_OAT_FORMAT_FAIL)) {
reporter.onReport(KEY_APPLIED_DEXOPT_FORMAT);
} else {
reporter.onReport(KEY_APPLIED_DEXOPT_OTHER);
reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable));
}
}
public static void onApplyInfoCorrupted() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_APPLIED_INFO_CORRUPTED);
}
public static void onApplyVersionCheckFail() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_APPLIED_VERSION_CHECK);
}
public static void onApplyExtractFail(int fileType) {
if (reporter == null) {
return;
}
switch (fileType) {
case ShareConstants.TYPE_DEX:
reporter.onReport(KEY_APPLIED_DEX_EXTRACT);
break;
case ShareConstants.TYPE_LIBRARY:
reporter.onReport(KEY_APPLIED_LIB_EXTRACT);
break;
case ShareConstants.TYPE_PATCH_FILE:
reporter.onReport(KEY_APPLIED_PATCH_FILE_EXTRACT);
break;
case ShareConstants.TYPE_RESOURCE:
reporter.onReport(KEY_APPLIED_RESOURCE_EXTRACT);
break;
}
}
public static void onApplied(long cost, boolean success) {
if (reporter == null) {
return;
}
if (success) {
reporter.onReport(KEY_APPLIED);
}
if (success) {
reporter.onReport(KEY_APPLIED_UPGRADE);
} else {
reporter.onReport(KEY_APPLIED_UPGRADE_FAIL);
}
TinkerLog.i(TAG, "hp_report report apply cost = %d", cost);
if (cost < 0L) {
TinkerLog.e(TAG, "hp_report report apply cost failed, invalid cost");
return;
}
if (cost <= 5000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_5S_LESS);
} else {
reporter.onReport(KEY_APPLIED_FAIL_COST_5S_LESS);
}
} else if (cost <= 10 * 1000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_10S_LESS);
} else {
reporter.onReport(KEY_APPLIED_FAIL_COST_10S_LESS);
}
} else if (cost <= 30 * 1000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_30S_LESS);
} else {
reporter.onReport(KEY_APPLIED_FAIL_COST_30S_LESS);
}
} else if (cost <= 60 * 1000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_60S_LESS);
} else {
reporter.onReport(KEY_APPLIED_FAIL_COST_60S_LESS);
}
} else {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_OTHER);
} else {
reporter.onReport(KEY_APPLIED_FAIL_COST_OTHER);
}
}
}
public static void onApplyPackageCheckFail(int errorCode) {
if (reporter == null) {
return;
}
TinkerLog.i(TAG, "hp_report package check failed, error = %d", errorCode);
switch (errorCode) {
case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_SIGNATURE);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_DEX_META);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_LIB_META);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_RES_META);
break;
case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);
break;
}
}
public static void onApplyCrash(Throwable throwable) {
if (reporter == null) {
return;
}
reporter.onReport(KEY_APPLIED_EXCEPTION);
reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable));
}
public static void onFastCrashProtect() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_CRASH_FAST_PROTECT);
}
public static void onXposedCrash() {
if (reporter == null) {
return;
}
if (ShareTinkerInternals.isVmArt()) {
reporter.onReport(KEY_CRASH_CAUSE_XPOSED_ART);
} else {
reporter.onReport(KEY_CRASH_CAUSE_XPOSED_DALVIK);
}
}
public static void onReportRetryPatch() {
if (reporter == null) {
return;
}
reporter.onReport(KEY_APPLY_WITH_RETRY);
}
}
8.SampleUncaughtExceptionHandler.java
public class SampleUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private static final String TAG = "Tinker.SampleUncaughtExHandler";
private final Thread.UncaughtExceptionHandler ueh;
private static final long QUICK_CRASH_ELAPSE = 10 * 1000;
public static final int MAX_CRASH_COUNT = 3;
private static final String DALVIK_XPOSED_CRASH = "Class ref in pre-verified class resolved to unexpected implementation";
public SampleUncaughtExceptionHandler() {
ueh = Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
TinkerLog.e(TAG, "uncaughtException:" + ex.getMessage());
tinkerFastCrashProtect();
tinkerPreVerifiedCrashHandler(ex);
ueh.uncaughtException(thread, ex);
}
/**
* Such as Xposed, if it try to load some class before we load from patch files.
* With dalvik, it will crash with "Class ref in pre-verified class resolved to unexpected implementation".
* With art, it may crash at some times. But we can't know the actual crash type.
* If it use Xposed, we can just clean patch or mention user to uninstall it.
*/
private void tinkerPreVerifiedCrashHandler(Throwable ex) {
ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike();
if (applicationLike == null || applicationLike.getApplication() == null) {
TinkerLog.w(TAG, "applicationlike is null");
return;
}
if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) {
TinkerLog.w(TAG, "tinker is not loaded");
return;
}
Throwable throwable = ex;
boolean isXposed = false;
while (throwable != null) {
if (!isXposed) {
isXposed = Utils.isXposedExists(throwable);
}
// xposed?
if (isXposed) {
boolean isCausedByXposed = false;
//for art, we can't know the actually crash type
//just ignore art
if (throwable instanceof IllegalAccessError && throwable.getMessage().contains(DALVIK_XPOSED_CRASH)) {
//for dalvik, we know the actual crash type
isCausedByXposed = true;
}
if (isCausedByXposed) {
SampleTinkerReport.onXposedCrash();
TinkerLog.e(TAG, "have xposed: just clean tinker");
//kill all other process to ensure that all process's code is the same.
ShareTinkerInternals.killAllOtherProcess(applicationLike.getApplication());
TinkerApplicationHelper.cleanPatch(applicationLike);
ShareTinkerInternals.setTinkerDisableWithSharedPreferences(applicationLike.getApplication());
return;
}
}
throwable = throwable.getCause();
}
}
/**
* if tinker is load, and it crash more than MAX_CRASH_COUNT, then we just clean patch.
*/
private boolean tinkerFastCrashProtect() {
ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike();
if (applicationLike == null || applicationLike.getApplication() == null) {
return false;
}
if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) {
return false;
}
final long elapsedTime = SystemClock.elapsedRealtime() - applicationLike.getApplicationStartElapsedTime();
//this process may not install tinker, so we use TinkerApplicationHelper api
if (elapsedTime < QUICK_CRASH_ELAPSE) {
String currentVersion = TinkerApplicationHelper.getCurrentVersion(applicationLike);
if (ShareTinkerInternals.isNullOrNil(currentVersion)) {
return false;
}
SharedPreferences sp = applicationLike.getApplication().getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS);
int fastCrashCount = sp.getInt(currentVersion, 0) + 1;
if (fastCrashCount >= MAX_CRASH_COUNT) {
SampleTinkerReport.onFastCrashProtect();
TinkerApplicationHelper.cleanPatch(applicationLike);
TinkerLog.e(TAG, "tinker has fast crash more than %d, we just clean patch!", fastCrashCount);
return true;
} else {
sp.edit().putInt(currentVersion, fastCrashCount).commit();
TinkerLog.e(TAG, "tinker has fast crash %d times", fastCrashCount);
}
}
return false;
}
}
5.生成补丁文件
1.生成安装包:
打开终端Terminal执行gradlew assembleDebug命令后,会在build/bakApk目录下生成一个安装包和文本文件。
在build.gradle文件中将后面的编号给为一致,如图所示
2.生成补丁包:
打开终端Terminal执行gradlew tinkerPatchDebug命令后,在build/outputs/tinkerPatch/debug目录下会生成补丁文件,有签名和不签名的,
我们将补丁包patch_signed_7zip.apk模拟从服务器下载到手机本地,放在SDcard目录下
6.加载补丁
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
运行上面代码即可加载补丁。
在打补丁之前,我们点击按钮弹个Toast,在补丁包中更改Toast内容。打完补丁后,再点击按钮即可看见Toast内容已改变为Patch