黑暗冲突 看片base.apk下载为什么要更新才能玩?

Hello! 大家好.
前段时间比较忙,今天终于闲下来了.于是研究研究比较高大上的东西. 热更新!
网上一搜热更新,好家伙一点一大堆,各种框架让人应接不暇.最后综合来看选择了 微信的Tinker 热修复框架.
至于缺点就不多说了 ,网上一大堆,这里我贴一个官方的对比图
废话不多,直接进入正题.
一: &新建一个空的项目. TrustHotFix
里面就是两个Button 和一个TextView.
贴一下MainActivity代码 &很干净&
ok 一个空的项目好了 .接下来进行相关配置
二: Tinker配置
1:在项目build.gradle 下面添加一下代码&
classpath &com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}&
2:在目录gradle.properties 中 &添加 下面代码 &&
TINKER_VERSION=1.7.11
注意 &TIMKER_VERSIOM 最好用最新版的,我用的时候是直接按git上的Demo上的版本设置的.
3:在app &build.gradle中添加相应的依赖&
compile(&com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}&) { changing = true }
provided(&com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}&) { changing = true }
compile &com.android.support:multidex:1.0.1&
4:接着配置其他参数
还是在app &build.gradle中添加相应
def gitSha() {
String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim()
if (gitRev == null) {
throw new GradleException(&can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'&)
return gitRev
} catch (Exception e) {
throw new GradleException(&can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'&)
def javaVersion = JavaVersion.VERSION_1_7
在 android {} 添加下面代码
compileOptions {
sourceCompatibility javaVersion
targetCompatibility javaVersion
//recommend
dexOptions {
jumboMode = true
在 &signingConfigs{}添加签名配置&
storeFile file(&./keystore/release.keystore&)
storePassword &testres&
keyAlias &testres&
keyPassword &testres&
} catch (ex) {
throw new InvalidUserDataException(ex.toString())
storeFile file(&./keystore/debug.keystore&)
上面这个签名文件是 官方Demo里面的
在 defaultConfig{}添加下面代码
//热更新-----------
applicationId &tinker.sample.android&
minSdkVersion 10
targetSdkVersion 22
versionCode 1
versionName &1.0.0&
* you can use multiDex and install it in your ApplicationLifeCycle implement
multiDexEnabled true
* buildConfig can change during patch!
* we can use the newly value when patch
buildConfigField &String&, &MESSAGE&, &\&I am the base apk\&&
buildConfigField &String&, &MESSAGE&, &\&I am the patch apk\&&
* client version would update with patch
* so we can get the newly git version easily!
buildConfigField &String&, &TINKER_ID&, &\&${getTinkerIdValue()}\&&
buildConfigField &String&, &PLATFORM&,
//---------------------------------------------
然后添加代码
//热更新------
def bakPath = file(&${buildDir}/bakApk/&)
* you can use assembleRelease to build you base apk
* use tinkerPatchRelease -POLD_APK=
-PAPPLY_MAPPING=
-PAPPLY_RESOURCE= to build patch
* add apk from the build/bakApk
//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
tinkerEnabled = true
//for normal build
//old apk file to build patch apk
tinkerOldApkPath = &${bakPath}/app-release--53.apk&
//proguard mapping file to build patch apk
tinkerApplyMappingPath = &${bakPath}/app-release--53-mapping.txt&
//resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = &${bakPath}/app-release--53-R.txt&
//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = &${bakPath}/app--47&
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 getTinkerIdValue() {
return hasProperty(&TINKER_ID&) ? TINKER_ID : gitSha()
def buildWithTinker() {
return hasProperty(&TINKER_ENABLE&) ? TINKER_ENABLE : ext.tinkerEnabled
def getTinkerBuildFlavorDirectory() {
return ext.tinkerBuildFlavorDirectory
if (buildWithTinker()) {
apply plugin: 'com.tencent.tinker.patch'
tinkerPatch {
* necessary,default 'null'
* the old apk path, use to diff with the new apk to build
* add apk from the build/bakApk
oldApk = getOldApkPath()
* optional,default 'false'
* there are some cases we may get some warnings
* if ignoreWarning is true, we would just assert the patch process
* case 1: minSdkVersion is below 14, but you are using dexMode with raw.
it must be crash when load.
* case 2: newly added Android Component in AndroidManifest.xml,
it must be crash when load.
* case 3: loader classes in dex.loader{} are not keep in the main dex,
it must be let tinker not work.
* case 4: loader classes in dex.loader{} changes,
loader classes is ues to load patch dex. it is useless to change them.
it won't crash, but these changes can't effect. you may ignore it
* case 5: resources.arsc has changed, but we don't use applyResourceMapping to build
ignoreWarning = false
* optional,default 'true'
* whether sign the patch file
* if not, you must do yourself. otherwise it can't check success during the patch loading
* we will use the sign config with your build type
useSign = true
* optional,default 'true'
* whether use tinker to build
tinkerEnable = buildWithTinker()
* Warning, applyMapping will affect the normal android build!
buildConfig {
* optional,default 'null'
* if we use tinkerPatch to build the patch apk, you'd better to apply the old
* apk mapping file if minifyEnabled is enable!
* Warning:
* you must be careful that it will affect the normal assemble build!
applyMapping = getApplyMappingPath()
* optional,default 'null'
* It is nice to keep the resource id from R.txt file to reduce java changes
applyResourceMapping = getApplyResourceMappingPath()
* necessary,default 'null'
* because we don't want to check the base apk with md5 in the runtime(it is slow)
* tinkerId is use to identify the unique base apk when the patch is tried to apply.
* we can use git rev, svn rev or simply versionCode.
* we will gen the tinkerId in your manifest automatic
tinkerId = getTinkerIdValue()
* if keepDexApply is true, class in which dex refer to the old apk.
* open this can reduce the dex diff file size.
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 = false
* optional,default 'jar'
* only can be 'raw' or 'jar'. for raw, we would keep its original format
* for jar, we would repack dexes with zip format.
* if you want to support below 14, you must use jar
* or you want to save rom or check quicker, you can use raw mode also
dexMode = &jar&
* necessary,default '[]'
* what dexes in apk are expected to deal with tinkerPatch
* it support * or ? pattern.
pattern = [&classes*.dex&,
&assets/secondary-dex-?.jar&]
* necessary,default '[]'
* Warning, it is very very important, loader classes can't change with patch.
* thus, they will be removed from patch dexes.
* you must put the following class into main dex.
* Simply, you should add your own application {@code tinker.sample.android.SampleApplication}
* own tinkerLoader, and the classes you use in them
loader = [
//use sample, let BaseBuildInfo unchangeable with tinker
&tinker.sample.android.app.BaseBuildInfo&
* optional,default '[]'
* what library in apk are expected to deal with tinkerPatch
* it support * or ? pattern.
* for library in assets, we would just recover them in the patch directory
* you can get them in TinkerLoadResult with Tinker
pattern = [&lib/*/*.so&]
* optional,default '[]'
* what resource in apk are expected to deal with tinkerPatch
* it support * or ? pattern.
* you must include all your resources in apk here,
* otherwise, they won't repack in the new apk resources.
pattern = [&res/*&, &assets/*&, &resources.arsc&, &AndroidManifest.xml&]
* optional,default '[]'
* the resource file exclude patterns, ignore add, delete or modify resource change
* it support * or ? pattern.
* Warning, we can only use for files no relative with resources.arsc
ignoreChange = [&assets/sample_meta.txt&]
* default 100kb
* for modify resource, if it is larger than 'largeModSize'
* we would like to use bsdiff algorithm to reduce patch file size
largeModSize = 100
packageConfig {
* optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE'
* package meta file gen. path is assets/package_meta.txt in patch file
* you can use securityCheck.getPackageProperties() in your ownPackageCheck method
* or TinkerLoadResult.getPackageConfigByName
* we will get the TINKER_ID from the old apk manifest for you automatic,
* other config files (such as patchMessage below)is not necessary
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&)
//or you can add config filed outside, or get meta value from old apk
//project.tinkerPatch.packageConfig.configField(&test1&, project.tinkerPatch.packageConfig.getMetaDataFromOldApk(&Test&))
//project.tinkerPatch.packageConfig.configField(&test2&, &sample&)
* if you don't use zipArtifact or path, we just use 7za to try
sevenZip {
* optional,default '7za'
* the 7zip artifact path, it will use the right 7za with your platform
zipArtifact = &com.tencent.mm:SevenZip:1.1.10&
* optional,default '7za'
* you can specify the 7za path yourself, it will overwrite the zipArtifact value
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 {
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&)
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&
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&
以上就是tinker所需要的配置,如果不知道这些配置对应的位置可以下载官方Demo查看 或者在文末连接下载Demo查看
Clean 我们的项目,这个时候会发现一个错误
不好意思的是这个错,我忘了截图了.抱歉啊各位
错误的指向是你的build.gradle文件里面的 &
看到这里懂英文的大腿们,应该知道是什么原因了.
首先tinker是需要一个TINKER_ID的 &这TINKER_ID可以是手写的(我没试过手写),也可以通过和git关联起来自动更新的.这个对以后的升级是很重要的.TINKER_ID必须是唯一的否则热更新的时候会加载之前的旧版的补丁
首先创建一个class文件 名字随便起,这里我用的是和官方Demo一样的名字 SampleApplicationLike
extends DefaultApplicationLike
在上面添加下面代码
@SuppressWarnings(&unused&)
@DefaultLifeCycle(application = &com.trust.trusthotfix.SampleApplication&,
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
注意:application 里面的包名不要随便乱起,前面的是你&SampleApplicationLike这个class文件的包名 &后面的这个SampleApplication 是要在AndroidManifest.xml 文件里面使用的名字
这里没什么好说的都是直接复制的官方Demo 直接上整个类的代码 &
SampleApplicationLike
@SuppressWarnings(&unused&)
@DefaultLifeCycle(application = &com.trust.trusthotfix.SampleApplication&,
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class SampleApplicationLike extends DefaultApplicationLike {
private static final String TAG = &Tinker.SampleApplicationLike&;
public SampleApplicationLike(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
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
//you must install multiDex whatever tinker is installed!
MultiDex.install(base);
SampleApplicationContext.application = getApplication();
SampleApplicationContext.context = getApplication();
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());
//installTinker after load multiDex
//or you can put com.tencent.tinker.** to main dex
TinkerManager.installTinker(this);
Tinker tinker = Tinker.with(getApplication());
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
getApplication().registerActivityLifecycleCallbacks(callback);
下面还需要几个相关类 &&
BaseBuildInfo 类
public class BaseBuildInfo {
public static String TEST_MESSAGE = &I won't change with tinker patch!&;
public static String BASE_TINKER_ID = BuildConfig.TINKER_ID;
BuildInfo类
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;
SampleApplicationContext类
public class SampleApplicationContext {
public static Application application = null;
public static Context context = null;
}.TinkerManager类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 = appL
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&);
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&);
//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;
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
public static final int ERROR_PATCH_ROM_SPACE
public static final int ERROR_PATCH_MEMORY_LIMIT
public static final int ERROR_PATCH_CRASH_LIMIT
public static final int ERROR_PATCH_CONDITION_NOT_SATISFIED = -10;
public static final int ERROR_PATCH_ALREADY_APPLY
public static final int ERROR_PATCH_RETRY_COUNT_LIMIT
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 =
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;
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);
// print directly
Throwable t =
while (t.getCause() != null) {
t = t.getCause();
t.printStackTrace(ps);
return toVisualString(bos.toString());
} finally {
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;
if (cutFlg) {
return new String(chr, 0, i);
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() {
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);
SampleResultService类
public class SampleResultService extends DefaultTinkerResultService {
private static final String TAG = &Tinker.SampleResultService&;
public void onPatchResult(final PatchResult result) {
if (result == null) {
TinkerLog.e(TAG, &SampleResultService received null result!!!!&);
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() {
public void run() {
if (result.isSuccess) {
Toast.makeText(getApplicationContext(), &patch success, please restart process&, Toast.LENGTH_LONG).show();
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();
//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() {
public void onScreenOff() {
restartProcess();
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());
SampleLoadReporter类
public class SampleLoadReporter extends DefaultLoadReporter {
private final static String TAG = &Tinker.SampleLoadReporter&;
public SampleLoadReporter(Context context) {
super(context);
public void onLoadPatchListenerReceiveFail(final File patchFile, int errorCode) {
super.onLoadPatchListenerReceiveFail(patchFile, errorCode);
SampleTinkerReport.onTryApplyFail(errorCode);
public void onLoadResult(File patchDirectory, int loadCode, long cost) {
super.onLoadResult(patchDirectory, loadCode, cost);
switch (loadCode) {
case ShareConstants.ERROR_LOAD_OK:
SampleTinkerReport.onLoaded(cost);
Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
public boolean queueIdle() {
if (UpgradePatchRetry.getInstance(context).onPatchRetryLoad()) {
SampleTinkerReport.onReportRetryPatch();
return false;
public void onLoadException(Throwable e, int errorCode) {
super.onLoadException(e, errorCode);
SampleTinkerReport.onLoadException(e, errorCode);
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
public void onLoadFileNotFound(File file, int fileType, boolean isDirectory) {
super.onLoadFileNotFound(file, fileType, isDirectory);
SampleTinkerReport.onLoadFileNotFound(fileType);
public void onLoadPackageCheckFail(File patchFile, int errorCode) {
super.onLoadPackageCheckFail(patchFile, errorCode);
SampleTinkerReport.onLoadPackageCheckFail(errorCode);
public void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) {
super.onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile);
SampleTinkerReport.onLoadInfoCorrupted();
public void onLoadInterpret(int type, Throwable e) {
super.onLoadInterpret(type, e);
SampleTinkerReport.onLoadInterpretReport(type, e);
public void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) {
super.onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectoryFile, currentPatchName);
SamplePatchListener类
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
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;
//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;
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;
SamplePatchReporter类
public class SamplePatchReporter extends DefaultPatchReporter {
private final static String TAG = &Tinker.SamplePatchReporter&;
public SamplePatchReporter(Context context) {
super(context);
public void onPatchServiceStart(Intent intent) {
super.onPatchServiceStart(intent);
SampleTinkerReport.onApplyPatchServiceStart();
public void onPatchDexOptFail(File patchFile, List&File& dexFiles, Throwable t) {
super.onPatchDexOptFail(patchFile, dexFiles, t);
SampleTinkerReport.onApplyDexOptFail(t);
public void onPatchException(File patchFile, Throwable e) {
super.onPatchException(patchFile, e);
SampleTinkerReport.onApplyCrash(e);
public void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion) {
super.onPatchInfoCorrupted(patchFile, oldVersion, newVersion);
SampleTinkerReport.onApplyInfoCorrupted();
public void onPatchPackageCheckFail(File patchFile, int errorCode) {
super.onPatchPackageCheckFail(patchFile, errorCode);
SampleTinkerReport.onApplyPackageCheckFail(errorCode);
public void onPatchResult(File patchFile, boolean success, long cost) {
super.onPatchResult(patchFile, success, cost);
SampleTinkerReport.onApplied(cost, success);
public void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType) {
super.onPatchTypeExtractFail(patchFile, extractTo, filename, fileType);
SampleTinkerReport.onApplyExtractFail(fileType);
public void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion) {
super.onPatchVersionCheckFail(patchFile, oldPatchInfo, patchFileVersion);
SampleTinkerReport.onApplyVersionCheckFail();
SampleTinkerReport类
public class SampleTinkerReport {
private static final String TAG = &Tinker.SampleTinkerReport&;
// KEY - PV
public static final int KEY_REQUEST
public static final int KEY_DOWNLOAD
public static final int KEY_TRY_APPLY
public static final int KEY_TRY_APPLY_SUCCESS
public static final int KEY_APPLIED_START
public static final int KEY_APPLIED
public static final int KEY_LOADED
public static final int KEY_CRASH_FAST_PROTECT
public static final int KEY_CRASH_CAUSE_XPOSED_DALVIK = 8;
public static final int KEY_CRASH_CAUSE_XPOSED_ART
public static final int KEY_APPLY_WITH_RETRY
//Key -- try apply detail
public static final int KEY_TRY_APPLY_UPGRADE
public static final int KEY_TRY_APPLY_DISABLE
public static final int KEY_TRY_APPLY_RUNNING
public static final int KEY_TRY_APPLY_INSERVICE
public static final int KEY_TRY_APPLY_NOT_EXIST
public static final int KEY_TRY_APPLY_GOOGLEPLAY
public static final int KEY_TRY_APPLY_ROM_SPACE
public static final int KEY_TRY_APPLY_ALREADY_APPLY
public static final int KEY_TRY_APPLY_MEMORY_LIMIT
public static final int KEY_TRY_APPLY_CRASH_LIMIT
public static final int KEY_TRY_APPLY_CONDITION_NOT_SATISFIED = 80;
public static final int KEY_TRY_APPLY_JIT
//Key -- apply detail
public static final int KEY_APPLIED_UPGRADE
public static final int KEY_APPLIED_UPGRADE_FAIL = 101;
public static final int KEY_APPLIED_EXCEPTION
public static final int KEY_APPLIED_DEXOPT_OTHER
public static final int KEY_APPLIED_DEXOPT_EXIST
public static final int KEY_APPLIED_DEXOPT_FORMAT
public static final int KEY_APPLIED_INFO_CORRUPTED
//package check
public static final int KEY_APPLIED_PACKAGE_CHECK_SIGNATURE
public static final int KEY_APPLIED_PACKAGE_CHECK_DEX_META
public static final int KEY_APPLIED_PACKAGE_CHECK_LIB_META
public static final int KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND
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
public static final int KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL
public static final int KEY_APPLIED_PACKAGE_CHECK_RES_META
public static final int KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT
//version check
public static final int KEY_APPLIED_VERSION_CHECK
//extract error
public static final int KEY_APPLIED_PATCH_FILE_EXTRACT = 181;
public static final int KEY_APPLIED_DEX_EXTRACT
public static final int KEY_APPLIED_LIB_EXTRACT
public static final int KEY_APPLIED_RESOURCE_EXTRACT
//cost time
public static final int KEY_APPLIED_SUCC_COST_5S_LESS
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
public static final int KEY_APPLIED_FAIL_COST_5S_LESS
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
// KEY -- load detail
public static final int KEY_LOADED_UNKNOWN_EXCEPTION
public static final int KEY_LOADED_UNCAUGHT_EXCEPTION
public static final int KEY_LOADED_EXCEPTION_DEX
public static final int KEY_LOADED_EXCEPTION_DEX_CHECK
public static final int KEY_LOADED_EXCEPTION_RESOURCE
public static final int KEY_LOADED_EXCEPTION_RESOURCE_CHECK = 255;
public static final int KEY_LOADED_MISMATCH_DEX
public static final int KEY_LOADED_MISMATCH_LIB
public static final int KEY_LOADED_MISMATCH_RESOURCE
public static final int KEY_LOADED_MISSING_DEX
public static final int KEY_LOADED_MISSING_LIB
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
public static final int KEY_LOADED_MISSING_RES
public static final int KEY_LOADED_INFO_CORRUPTED
//load package check
public static final int KEY_LOADED_PACKAGE_CHECK_SIGNATURE
public static final int KEY_LOADED_PACKAGE_CHECK_DEX_META
public static final int KEY_LOADED_PACKAGE_CHECK_LIB_META
public static final int KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND
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
public static final int KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND
public static final int KEY_LOADED_PACKAGE_CHECK_RES_META
public static final int KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT
public static final int KEY_LOADED_SUCC_COST_500_LESS
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
public static final int KEY_LOADED_INTERPRET_GET_INSTRUCTION_SET_ERROR = 450;
public static final int KEY_LOADED_INTERPRET_INTERPRET_COMMAND_ERROR
public static final int KEY_LOADED_INTERPRET_TYPE_INTERPRET_OK
interface Reporter {
void onReport(int key);
void onReport(String message);
private static Reporter reporter = null;
public void setReporter(Reporter reporter) {
this.reporter =
public static void onTryApply(boolean success) {
if (reporter == null) {
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) {
switch (errorCode) {
case ShareConstants.ERROR_PATCH_NOTEXIST:
reporter.onReport(KEY_TRY_APPLY_NOT_EXIST);
case ShareConstants.ERROR_PATCH_DISABLE:
reporter.onReport(KEY_TRY_APPLY_DISABLE);
case ShareConstants.ERROR_PATCH_INSERVICE:
reporter.onReport(KEY_TRY_APPLY_INSERVICE);
case ShareConstants.ERROR_PATCH_RUNNING:
reporter.onReport(KEY_TRY_APPLY_RUNNING);
case ShareConstants.ERROR_PATCH_JIT:
reporter.onReport(KEY_TRY_APPLY_JIT);
case Utils.ERROR_PATCH_ROM_SPACE:
reporter.onReport(KEY_TRY_APPLY_ROM_SPACE);
case Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL:
reporter.onReport(KEY_TRY_APPLY_GOOGLEPLAY);
case Utils.ERROR_PATCH_ALREADY_APPLY:
reporter.onReport(KEY_TRY_APPLY_ALREADY_APPLY);
case Utils.ERROR_PATCH_CRASH_LIMIT:
reporter.onReport(KEY_TRY_APPLY_CRASH_LIMIT);
case Utils.ERROR_PATCH_MEMORY_LIMIT:
reporter.onReport(KEY_TRY_APPLY_MEMORY_LIMIT);
case Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED:
reporter.onReport(KEY_TRY_APPLY_CONDITION_NOT_SATISFIED);
public static void onLoadPackageCheckFail(int errorCode) {
if (reporter == null) {
switch (errorCode) {
case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_SIGNATURE);
case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_DEX_META);
case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_LIB_META);
case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);
case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);
case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);
case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND);
case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_RES_META);
case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:
reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);
public static void onLoaded(long cost) {
if (reporter == null) {
reporter.onReport(KEY_LOADED);
if (cost & 0L) {
TinkerLog.e(TAG, &hp_report report load cost failed, invalid cost&);
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);
reporter.onReport(KEY_LOADED_SUCC_COST_OTHER);
public static void onLoadInfoCorrupted() {
if (reporter == null) {
reporter.onReport(KEY_LOADED_INFO_CORRUPTED);
public static void onLoadFileNotFound(int fileType) {
if (reporter == null) {
switch (fileType) {
case ShareConstants.TYPE_DEX_OPT:
reporter.onReport(KEY_LOADED_MISSING_DEX_OPT);
case ShareConstants.TYPE_DEX:
reporter.onReport(KEY_LOADED_MISSING_DEX);
case ShareConstants.TYPE_LIBRARY:
reporter.onReport(KEY_LOADED_MISSING_LIB);
case ShareConstants.TYPE_PATCH_FILE:
reporter.onReport(KEY_LOADED_MISSING_PATCH_FILE);
case ShareConstants.TYPE_PATCH_INFO:
reporter.onReport(KEY_LOADED_MISSING_PATCH_INFO);
case ShareConstants.TYPE_RESOURCE:
reporter.onReport(KEY_LOADED_MISSING_RES);
public static void onLoadInterpretReport(int type, Throwable e) {
if (reporter == null) {
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));
case ShareConstants.TYPE_INTERPRET_COMMAND_ERROR:
reporter.onReport(KEY_LOADED_INTERPRET_INTERPRET_COMMAND_ERROR);
reporter.onReport(&Tinker Exception:interpret occur exception & + Utils.getExceptionCauseString(e));
case ShareConstants.TYPE_INTERPRET_OK:
reporter.onReport(KEY_LOADED_INTERPRET_TYPE_INTERPRET_OK);
public static void onLoadFileMisMatch(int fileType) {
if (reporter == null) {
switch (fileType) {
case ShareConstants.TYPE_DEX:
reporter.onReport(KEY_LOADED_MISMATCH_DEX);
case ShareConstants.TYPE_LIBRARY:
reporter.onReport(KEY_LOADED_MISMATCH_LIB);
case ShareConstants.TYPE_RESOURCE:
reporter.onReport(KEY_LOADED_MISMATCH_RESOURCE);
public static void onLoadException(Throwable throwable, int errorCode) {
if (reporter == null) {
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());
reporter.onReport(KEY_LOADED_EXCEPTION_DEX);
TinkerLog.e(TAG, &tinker dex reflect fail:& + throwable.getMessage());
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());
reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE);
TinkerLog.e(TAG, &tinker res reflect fail:& + throwable.getMessage());
case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT:
reporter.onReport(KEY_LOADED_UNCAUGHT_EXCEPTION);
case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN:
reporter.onReport(KEY_LOADED_UNKNOWN_EXCEPTION);
//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) {
reporter.onReport(KEY_APPLIED_START);
public static void onApplyDexOptFail(Throwable throwable) {
if (reporter == null) {
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);
reporter.onReport(KEY_APPLIED_DEXOPT_OTHER);
reporter.onReport(&Tinker Exception:apply tinker occur exception & + Utils.getExceptionCauseString(throwable));
public static void onApplyInfoCorrupted() {
if (reporter == null) {
reporter.onReport(KEY_APPLIED_INFO_CORRUPTED);
public static void onApplyVersionCheckFail() {
if (reporter == null) {
reporter.onReport(KEY_APPLIED_VERSION_CHECK);
public static void onApplyExtractFail(int fileType) {
if (reporter == null) {
switch (fileType) {
case ShareConstants.TYPE_DEX:
reporter.onReport(KEY_APPLIED_DEX_EXTRACT);
case ShareConstants.TYPE_LIBRARY:
reporter.onReport(KEY_APPLIED_LIB_EXTRACT);
case ShareConstants.TYPE_PATCH_FILE:
reporter.onReport(KEY_APPLIED_PATCH_FILE_EXTRACT);
case ShareConstants.TYPE_RESOURCE:
reporter.onReport(KEY_APPLIED_RESOURCE_EXTRACT);
public static void onApplied(long cost, boolean success) {
if (reporter == null) {
if (success) {
reporter.onReport(KEY_APPLIED);
if (success) {
reporter.onReport(KEY_APPLIED_UPGRADE);
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&);
if (cost &= 5000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_5S_LESS);
reporter.onReport(KEY_APPLIED_FAIL_COST_5S_LESS);
} else if (cost &= 10 * 1000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_10S_LESS);
reporter.onReport(KEY_APPLIED_FAIL_COST_10S_LESS);
} else if (cost &= 30 * 1000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_30S_LESS);
reporter.onReport(KEY_APPLIED_FAIL_COST_30S_LESS);
} else if (cost &= 60 * 1000) {
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_60S_LESS);
reporter.onReport(KEY_APPLIED_FAIL_COST_60S_LESS);
if (success) {
reporter.onReport(KEY_APPLIED_SUCC_COST_OTHER);
reporter.onReport(KEY_APPLIED_FAIL_COST_OTHER);
public static void onApplyPackageCheckFail(int errorCode) {
if (reporter == null) {
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);
case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_DEX_META);
case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_LIB_META);
case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);
case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);
case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);
case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND);
case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_RES_META);
case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:
reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);
public static void onApplyCrash(Throwable throwable) {
if (reporter == null) {
reporter.onReport(KEY_APPLIED_EXCEPTION);
reporter.onReport(&Tinker Exception:apply tinker occur exception & + Utils.getExceptionCauseString(throwable));
public static void onFastCrashProtect() {
if (reporter == null) {
reporter.onReport(KEY_CRASH_FAST_PROTECT);
public static void onXposedCrash() {
if (reporter == null) {
if (ShareTinkerInternals.isVmArt()) {
reporter.onReport(KEY_CRASH_CAUSE_XPOSED_ART);
reporter.onReport(KEY_CRASH_CAUSE_XPOSED_DALVIK);
public static void onReportRetryPatch() {
if (reporter == null) {
reporter.onReport(KEY_APPLY_WITH_RETRY);
MyLogImp类
public class MyLogImp implements TinkerLog.TinkerLogImp {
private static final String TAG = &Tinker.MyLogImp&;
public static final int LEVEL_VERBOSE = 0;
public static final int LEVEL_DEBUG
public static final int LEVEL_INFO
public static final int LEVEL_WARNING = 3;
public static final int LEVEL_ERROR
public static final int LEVEL_NONE
private static int level = LEVEL_VERBOSE;
public static int getLogLevel() {
return level;
public static void setLevel(final int level) {
MyLogImp.level =
android.util.Log.w(TAG, &new log level: & + level);
public void v(String s, String s1, Object... objects) {
if (level &= LEVEL_VERBOSE) {
final String log = objects == null ? s1 : String.format(s1, objects);
android.util.Log.v(s, log);
public void i(String s, String s1, Object... objects) {
if (level &= LEVEL_INFO) {
final String log = objects == null ? s1 : String.format(s1, objects);
android.util.Log.i(s, log);
public void w(String s, String s1, Object... objects) {
if (level &= LEVEL_WARNING) {
final String log = objects == null ? s1 : String.format(s1, objects);
android.util.Log.w(s, log);
public void d(String s, String s1, Object... objects) {
if (level &= LEVEL_DEBUG) {
final String log = objects == null ? s1 : String.format(s1, objects);
android.util.Log.d(s, log);
public void e(String s, String s1, Object... objects) {
if (level &= LEVEL_ERROR) {
final String log = objects == null ? s1 : String.format(s1, objects);
android.util.Log.e(s, log);
public void printErrStackTrace(String s, Throwable throwable, String s1, Object... objects) {
String log = objects == null ? s1 : String.format(s1, objects);
if (log == null) {
log = log + &
& + Log.getStackTraceString(throwable);
android.util.Log.e(s, log);
SampleUncaughtExceptionHandler类
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
MAX_CRASH_COUNT
private static final String DALVIK_XPOSED_CRASH = &Class ref in pre-verified class resolved to unexpected implementation&;
public SampleUncaughtExceptionHandler() {
ueh = Thread.getDefaultUncaughtExceptionHandler();
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&);
if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) {
TinkerLog.w(TAG, &tinker is not loaded&);
Throwable throwable =
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());
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;
sp.edit().putInt(currentVersion, fastCrashCount).commit();
TinkerLog.e(TAG, &tinker has fast crash %d times&, fastCrashCount);
return false;
到这里需要的相关类已经完了,复制的好累.
接着是我们调用tinker的代码了&
在MainActivity中两个按钮点击里面添加调用代码&
加载需要更改的补丁
public void loadPath(View v){
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),
Environment.getExternalStorageDirectory().getAbsolutePath()
+ &/patch_signed_7zip.apk&);
删除补丁包已经杀死进程
public void kill (View v){
ShareTinkerInternals.killAllOtherProcess(getApplicationContext());
android.os.Process.killProcess(android.os.Process.myPid());
在as上点击Gradle&
在build文件里面点击 assembleRelease & 用assembleReleaseDebug也是可以的&
点击以后tinker就会进行编译,等待编译成功后在app目录build里面找到bakApk文件打开里面会有apk和R.txt文件
这个apk就可以直接使用经过签名的.把apk文件安装到手机上.正常运行这个时候点击加载Path按钮是不会有任何提示的.点击杀死自己 就会把自己杀了但是重新进来的时候还是一样的布局点击Path还是没反应.
在APP build.gradle 里面找到下面代码&
//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
tinkerEnabled = true
//for normal build
//old apk file to build patch apk
tinkerOldApkPath = &${bakPath}/app-release--53.apk&
//proguard mapping file to build patch apk
tinkerApplyMappingPath = &${bakPath}/app-release--53-mapping.txt&
//resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = &${bakPath}/app-release--53-R.txt&
//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = &${bakPath}/app--47&
tinkerOldApkPath 是你apk的路径&
tinkerApplyMappingPath &如果你没混淆的话这个可以仿照我写的那样 ,你混淆以后就会在bakApk 会生成mapping文件
tinkerApplyResourcePath 这是生成R.txt文件路径
配置好了以后
对MainActivity里面的代码以及布局做一些修改 &
显示布局文件 &
&?xml version=&1.0& encoding=&utf-8&?&
&LinearLayout
xmlns:android=&/apk/res/android&
xmlns:tools=&/tools&
xmlns:app=&/apk/res-auto&
android:layout_width=&match_parent&
android:layout_height=&match_parent&
android:orientation=&vertical&
tools:context=&com.trust.trusthotfix.MainActivity&&
&ImageView
android:src=&@mipmap/ic_launcher&
android:layout_width=&wrap_content&
android:layout_height=&wrap_content& /&
android:text=&BUG 已经修复!!!!!1&
android:layout_width=&wrap_content&
android:layout_height=&wrap_content& /&
android:id=&@+id/ed&
android:layout_width=&match_parent&
android:layout_height=&48dp& /&
android:onClick=&click&
android:text=&确定&
android:layout_width=&wrap_content&
android:layout_height=&wrap_content& /&
android:visibility=&gone&
android:onClick=&loadPath&
android:text=&加载path&
android:layout_width=&wrap_content&
android:layout_height=&wrap_content& /&
android:visibility=&gone&
android:onClick=&kill&
android:text=&杀死自己&
android:layout_width=&wrap_content&
android:layout_height=&wrap_content& /&
&/LinearLayout&
添加一个系统图片 &添加一个 输入框 & 把之前的两个按钮隐藏 & &添加一个确定按钮
逻辑代码 &
public class MainActivity extends AppCompatActivity {
EditText ed;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(&lhh&, &onCreate: BUG is
nothing&);
ed = (EditText) findViewById(R.id.ed);
public void loadPath(View v){
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),
Environment.getExternalStorageDirectory().getAbsolutePath()
+ &/patch_signed_7zip.apk&);
public void kill (View v){
ShareTinkerInternals.killAllOtherProcess(getApplicationContext());
android.os.Process.killProcess(android.os.Process.myPid());
public void click(View v){
String msg = ed.getText().toString().trim();
if(msg != null && !msg.equals(&&)){
Toast.makeText(this,&你输入的是:&+msg,Toast.LENGTH_LONG).show();
Toast.makeText(this,&不能为空!&+msg,Toast.LENGTH_LONG).show();
只是简单的用toast 显示输入的文字 &
ok 继续&Gradle &---- &tinker----&tinkerPathRelease 双击&
等待编译成功后,在build文件 ------&outputs------&tinkerPath-----&patch_signed_7zip.apk
patch_signed_7zip.apk这个文件就是我们需要的更新补丁了.把这个补丁直接放入手机里面的根目录中,打开手机上的app.
点击 加载Path 成的话 会有一个toast 提示 success &,然后点击杀死自己,重新进入APP就会发现布局已经改变了
ok到这里 tinker 热更新 替换class 文件已经写完了.接下来时 更新lib文件介绍.
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:8139次
排名:千里之外
(1)(1)(2)(2)(1)(1)(1)(2)

我要回帖

更多关于 base.apk 的文章

 

随机推荐