├── .gitignore ├── .idea ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── keep_in_main_dex.txt ├── keystore │ ├── debug.keystore │ └── release.keystore ├── libs │ └── armeabi │ │ └── libstlport_shared.so ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── tinker │ │ └── sample │ │ └── android │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── tinker │ │ │ └── sample │ │ │ └── android │ │ │ ├── Log │ │ │ └── MyLogImp.java │ │ │ ├── app │ │ │ ├── BaseBuildInfo.java │ │ │ ├── Bean.java │ │ │ ├── BuildInfo.java │ │ │ ├── MainActivity.java │ │ │ └── SampleApplicationLike.java │ │ │ ├── crash │ │ │ └── SampleUncaughtExceptionHandler.java │ │ │ ├── reporter │ │ │ ├── SampleLoadReporter.java │ │ │ ├── SamplePatchListener.java │ │ │ ├── SamplePatchReporter.java │ │ │ └── SampleTinkerReport.java │ │ │ ├── service │ │ │ └── SampleResultService.java │ │ │ └── util │ │ │ ├── SampleApplicationContext.java │ │ │ ├── TinkerManager.java │ │ │ ├── UpgradePatchRetry.java │ │ │ └── Utils.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── tinker │ └── sample │ └── android │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── proguard-rules.pro ├── screenshots ├── assembleDebug.png ├── bug_apk.png ├── patch.png ├── tinker.gif └── tinkerPatchDebug.png ├── settings.gradle └── updateTinkerLib.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | /.idea/ 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning Tinker for Android 2 | 这是一个基于腾讯热修复框架[Tinker](https://github.com/Tencent/tinker)的一个Demo,如果你没用过还真会遇到很多问题。我这里就针对官方文档里没有提到的细节和可能遇到的问题,以及构建的详细过程,给大家作个总结。 3 | 4 | 5 | 6 | # 详解 7 | 8 | 1.项目导入Android Studio后,要加入版本控制,并提交一次,不然会报错。 9 | 10 | 2.如果你自己定义了Application,那需要把Application中的实现都移到DefaultApplicationLike的子类中,本例中就是SampleApplicationLike。 11 | 12 | 3.然后Application是由Tinker自动生成的,只需要指明类名。本类中的类名是`tinker.sample.android.app.SampleApplication`,然后别忘Manifest的Application加入name属性。 13 | 14 | ``` 15 | @DefaultLifeCycle( 16 | application = "tinker.sample.android.app.SampleApplication", //application name to generate 17 | flags = ShareConstants.TINKER_ENABLE_ALL) //tinkerFlags above 18 | public class SampleApplicationLike extends DefaultApplicationLike 19 | ``` 20 | 21 | 4.然后构件assembleDebug,会在bakApk文件下生成apk文件(记录下文件A)。然后运行到机子上,这里推荐使用真机作测试,这个就是有Bug的程序。 22 | 23 | 5.修复程序中Bug。 24 | 25 | 6.然后修改app/build.gradle,文件就是刚刚的文件A。 26 | 27 | 7.使用tinkerPatchDebug构建补丁,会生成patch—signed—7zip.apk补丁 28 | 29 | 8.将补丁patch—signed—7zip.apk放在机子的 /storage/sdcard0/ 目录下。 30 | 31 | ```adb push ./app/build/outputs/tinkerPatch/debug/patch_signed_7zip.apk /storage/sdcard0/``` 32 | 33 | 9.修复Bug,重启,然后再测试。 34 | 35 | ## About me 36 | 37 | An android developer in Beijing.Welcome to offer me an [Interview invitation](mailto:maat.xing@gmail.com). If you have any new idea about this project, feel free to [contact me](mailto:maat.xing@gmail.com). :smiley: 38 | 39 | 40 | 41 | 42 | 43 | 44 | License 45 | ======= 46 | 47 | Copyright 2016 Maat 48 | 49 | 50 | Licensed under the Apache License, Version 2.0 (the "License"); 51 | you may not use this file except in compliance with the License. 52 | You may obtain a copy of the License at 53 | 54 | http://www.apache.org/licenses/LICENSE-2.0 55 | 56 | Unless required by applicable law or agreed to in writing, software 57 | distributed under the License is distributed on an "AS IS" BASIS, 58 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 59 | See the License for the specific language governing permissions and 60 | limitations under the License. 61 | 62 | 63 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | 3 | /version.properties 4 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | 4 | 5 | dependencies { 6 | compile fileTree(dir: 'libs', include: ['*.jar']) 7 | testCompile 'junit:junit:4.12' 8 | compile "com.android.support:appcompat-v7:23.1.1" 9 | compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true } 10 | compile("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true } 11 | compile "com.android.support:multidex:1.0.1" 12 | 13 | //use for local maven test 14 | // compile("com.tencent.tinker:tinker-android-loader:${TINKER_VERSION}") { changing = true } 15 | // compile("com.tencent.tinker:aosp-dexutils:${TINKER_VERSION}") { changing = true } 16 | // compile("com.tencent.tinker:bsdiff-util:${TINKER_VERSION}") { changing = true } 17 | // compile("com.tencent.tinker:tinker-commons:${TINKER_VERSION}") { changing = true } 18 | 19 | //use to test multiDex 20 | // compile group: 'com.google.guava', name: 'guava', version: '19.0' 21 | // compile "org.scala-lang:scala-library:2.11.7" 22 | } 23 | 24 | def gitSha() { 25 | try { 26 | String gitRev = 'git rev-parse --short HEAD'.execute().text.trim() 27 | if (gitRev == null) { 28 | throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'") 29 | } 30 | return gitRev 31 | } catch (Exception e) { 32 | throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'") 33 | } 34 | } 35 | 36 | def javaVersion = JavaVersion.VERSION_1_7 37 | 38 | android { 39 | compileSdkVersion 23 40 | buildToolsVersion "23.0.2" 41 | 42 | compileOptions { 43 | sourceCompatibility javaVersion 44 | targetCompatibility javaVersion 45 | } 46 | //recommend 47 | dexOptions { 48 | jumboMode = true 49 | } 50 | 51 | signingConfigs { 52 | release { 53 | try { 54 | storeFile file("./keystore/release.keystore") 55 | storePassword "testres" 56 | keyAlias "testres" 57 | keyPassword "testres" 58 | } catch (ex) { 59 | throw new InvalidUserDataException(ex.toString()) 60 | } 61 | } 62 | 63 | debug { 64 | storeFile file("./keystore/debug.keystore") 65 | } 66 | } 67 | 68 | defaultConfig { 69 | applicationId "tinker.sample.android" 70 | minSdkVersion 10 71 | targetSdkVersion 22 72 | versionCode 1 73 | versionName "1.0.0" 74 | /** 75 | * you can use multiDex and install it in your ApplicationLifeCycle implement 76 | */ 77 | multiDexEnabled true 78 | /** 79 | * not like proguard, multiDexKeepProguard is not a list, so we can't just 80 | * add for you in our task. you can copy tinker keep rules at 81 | * build/intermediates/tinker_intermediates/tinker_multidexkeep.pro 82 | */ 83 | multiDexKeepProguard file("keep_in_main_dex.txt") 84 | /** 85 | * buildConfig can change during patch! 86 | * we can use the newly value when patch 87 | */ 88 | buildConfigField "String", "MESSAGE", "\"I am the base apk\"" 89 | // buildConfigField "String", "MESSAGE", "\"I am the patch apk\"" 90 | /** 91 | * client version would update with patch 92 | * so we can get the newly git version easily! 93 | */ 94 | buildConfigField "String", "CLIENTVERSION", "\"${gitSha()}\"" 95 | buildConfigField "String", "PLATFORM", "\"all\"" 96 | } 97 | // //use to test flavors support 98 | // productFlavors { 99 | // flavor1 { 100 | // applicationId 'tinker.sample.android.flavor1' 101 | // } 102 | // 103 | // flavor2 { 104 | // applicationId 'tinker.sample.android.flavor2' 105 | // } 106 | // } 107 | 108 | buildTypes { 109 | release { 110 | minifyEnabled true 111 | signingConfig signingConfigs.release 112 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 113 | } 114 | debug { 115 | debuggable true 116 | minifyEnabled false 117 | signingConfig signingConfigs.debug 118 | } 119 | } 120 | sourceSets { 121 | main { 122 | jniLibs.srcDirs = ['libs'] 123 | } 124 | } 125 | } 126 | 127 | def bakPath = file("${buildDir}/bakApk/") 128 | 129 | /** 130 | * you can use assembleRelease to build you base apk 131 | * use tinkerPatchRelease -POLD_APK= -PAPPLY_MAPPING= -PAPPLY_RESOURCE= to build patch 132 | * add apk from the build/bakApk 133 | */ 134 | ext { 135 | //for some reason, you may want to ignore tinkerBuild, such as instant run debug build? 136 | tinkerEnabled = true 137 | //you should bak the following files 138 | //old apk file to build patch apk 139 | tinkerOldApkPath = "${bakPath}/app-debug-0919-20-32-57.apk" 140 | //proguard mapping file to build patch apk 141 | tinkerApplyMappingPath = "${bakPath}/" 142 | //resource R.txt to build patch apk, must input if there is resource changed 143 | tinkerApplyResourcePath = "${bakPath}/" 144 | } 145 | 146 | 147 | def getOldApkPath() { 148 | return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath 149 | } 150 | 151 | def getApplyMappingPath() { 152 | return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath 153 | } 154 | 155 | def getApplyResourceMappingPath() { 156 | return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath 157 | } 158 | 159 | def getTinkerIdValue() { 160 | return hasProperty("TINKER_ID") ? TINKER_ID : gitSha() 161 | } 162 | 163 | def buildWithTinker() { 164 | return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled 165 | } 166 | 167 | if (buildWithTinker()) { 168 | apply plugin: 'com.tencent.tinker.patch' 169 | 170 | tinkerPatch { 171 | /** 172 | * necessary,default 'null' 173 | * the old apk path, use to diff with the new apk to build 174 | * add apk from the build/bakApk 175 | */ 176 | oldApk = "${bakPath}/app-debug-1011-12-17-36.apk" 177 | /** 178 | * optional,default 'false' 179 | * there are some cases we may get some warnings 180 | * if ignoreWarning is true, we would just assert the patch process 181 | * case 1: minSdkVersion is below 14, but you are using dexMode with raw. 182 | * it must be crash when load. 183 | * case 2: newly added Android Component in AndroidManifest.xml, 184 | * it must be crash when load. 185 | * case 3: loader classes in dex.loader{} are not keep in the main dex, 186 | * it must be let tinker not work. 187 | * case 4: loader classes in dex.loader{} changes, 188 | * loader classes is ues to load patch dex. it is useless to change them. 189 | * it won't crash, but these changes can't effect. you may ignore it 190 | * case 5: resources.arsc has changed, but we don't use applyResourceMapping to build 191 | */ 192 | ignoreWarning = false 193 | /** 194 | * optional,default 'true' 195 | * whether sign the patch file 196 | * if not, you must do yourself. otherwise it can't check success during the patch loading 197 | * we will use the sign config with your build type 198 | */ 199 | useSign = true 200 | 201 | /** 202 | * Warning, applyMapping will affect the normal android build! 203 | */ 204 | buildConfig { 205 | /** 206 | * optional,default 'null' 207 | * if we use tinkerPatch to build the patch apk, you'd better to apply the old 208 | * apk mapping file if minifyEnabled is enable! 209 | * Warning: 210 | * you must be careful that it will affect the normal assemble build! 211 | */ 212 | applyMapping = getApplyMappingPath() 213 | /** 214 | * optional,default 'null' 215 | * It is nice to keep the resource id from R.txt file to reduce java changes 216 | */ 217 | applyResourceMapping = getApplyResourceMappingPath() 218 | 219 | /** 220 | * necessary,default 'null' 221 | * because we don't want to check the base apk with md5 in the runtime(it is slow) 222 | * tinkerId is use to identify the unique base apk when the patch is tried to apply. 223 | * we can use git rev, svn rev or simply versionCode. 224 | * we will gen the tinkerId in your manifest automatic 225 | */ 226 | tinkerId = getTinkerIdValue() 227 | } 228 | 229 | dex { 230 | /** 231 | * optional,default 'jar' 232 | * only can be 'raw' or 'jar'. for raw, we would keep its original format 233 | * for jar, we would repack dexes with zip format. 234 | * if you want to support below 14, you must use jar 235 | * or you want to save rom or check quicker, you can use raw mode also 236 | */ 237 | dexMode = "jar" 238 | /** 239 | * necessary,default '[]' 240 | * what dexes in apk are expected to deal with tinkerPatch 241 | * it support * or ? pattern. 242 | */ 243 | pattern = ["classes*.dex", 244 | "assets/secondary-dex-?.jar"] 245 | /** 246 | * necessary,default '[]' 247 | * Warning, it is very very important, loader classes can't change with patch. 248 | * thus, they will be removed from patch dexes. 249 | * you must put the following class into main dex. 250 | * Simply, you should add your own application {@code tinker.sample.android.SampleApplication} 251 | * own tinkerLoader, and the classes you use in them 252 | * 253 | */ 254 | loader = ["com.tencent.tinker.loader.*", 255 | "tinker.sample.android.SampleApplication", 256 | //use sample, let BaseBuildInfo unchangeable with tinker 257 | "tinker.sample.android.app.BaseBuildInfo" 258 | ] 259 | } 260 | 261 | lib { 262 | /** 263 | * optional,default '[]' 264 | * what library in apk are expected to deal with tinkerPatch 265 | * it support * or ? pattern. 266 | * for library in assets, we would just recover them in the patch directory 267 | * you can get them in TinkerLoadResult with Tinker 268 | */ 269 | pattern = ["lib/armeabi/*.so"] 270 | } 271 | 272 | res { 273 | /** 274 | * optional,default '[]' 275 | * what resource in apk are expected to deal with tinkerPatch 276 | * it support * or ? pattern. 277 | * you must include all your resources in apk here, 278 | * otherwise, they won't repack in the new apk resources. 279 | */ 280 | pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] 281 | 282 | /** 283 | * optional,default '[]' 284 | * the resource file exclude patterns, ignore add, delete or modify resource change 285 | * it support * or ? pattern. 286 | * Warning, we can only use for files no relative with resources.arsc 287 | */ 288 | ignoreChange = ["assets/sample_meta.txt"] 289 | 290 | /** 291 | * default 100kb 292 | * for modify resource, if it is larger than 'largeModSize' 293 | * we would like to use bsdiff algorithm to reduce patch file size 294 | */ 295 | largeModSize = 100 296 | } 297 | 298 | packageConfig { 299 | /** 300 | * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE' 301 | * package meta file gen. path is assets/package_meta.txt in patch file 302 | * you can use securityCheck.getPackageProperties() in your ownPackageCheck method 303 | * or TinkerLoadResult.getPackageConfigByName 304 | * we will get the TINKER_ID from the old apk manifest for you automatic, 305 | * other config files (such as patchMessage below)is not necessary 306 | */ 307 | configField("patchMessage", "tinker is sample to use") 308 | /** 309 | * just a sample case, you can use such as sdkVersion, brand, channel... 310 | * you can parse it in the SamplePatchListener. 311 | * Then you can use patch conditional! 312 | */ 313 | configField("platform", "all") 314 | 315 | } 316 | //or you can add config filed outside, or get meta value from old apk 317 | //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test")) 318 | //project.tinkerPatch.packageConfig.configField("test2", "sample") 319 | 320 | /** 321 | * if you don't use zipArtifact or path, we just use 7za to try 322 | */ 323 | sevenZip { 324 | /** 325 | * optional,default '7za' 326 | * the 7zip artifact path, it will use the right 7za with your platform 327 | */ 328 | zipArtifact = "com.tencent.mm:SevenZip:1.1.10" 329 | /** 330 | * optional,default '7za' 331 | * you can specify the 7za path yourself, it will overwrite the zipArtifact value 332 | */ 333 | // path = "/usr/local/bin/7za" 334 | } 335 | } 336 | 337 | 338 | 339 | /** 340 | * bak apk and mapping 341 | */ 342 | android.applicationVariants.all { variant -> 343 | /** 344 | * task type, you want to bak 345 | */ 346 | def taskName = variant.name 347 | 348 | tasks.all { 349 | if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) { 350 | it.doLast { 351 | copy { 352 | def date = new Date().format("MMdd-HH-mm-ss") 353 | from "${buildDir}/outputs/apk/${project.getName()}-${taskName}.apk" 354 | into bakPath 355 | rename { String fileName -> 356 | fileName.replace("${project.getName()}-${taskName}.apk", "${project.getName()}-${taskName}-${date}.apk") 357 | } 358 | 359 | from "${buildDir}/outputs/mapping/${taskName}/mapping.txt" 360 | into bakPath 361 | rename { String fileName -> 362 | fileName.replace("mapping.txt", "${project.getName()}-${taskName}-${date}-mapping.txt") 363 | } 364 | 365 | from "${buildDir}/intermediates/symbols/${taskName}/R.txt" 366 | into bakPath 367 | rename { String fileName -> 368 | fileName.replace("R.txt", "${project.getName()}-${taskName}-${date}-R.txt") 369 | } 370 | } 371 | } 372 | } 373 | } 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /app/keep_in_main_dex.txt: -------------------------------------------------------------------------------- 1 | # you can copy the tinker keep rule at 2 | # build/intermediates/tinker_intermediates/tinker_multidexkeep.pro 3 | 4 | -keep class com.tencent.tinker.loader.** { 5 | *; 6 | } 7 | 8 | -keep class tinker.sample.android.app.SampleApplication { 9 | *; 10 | } 11 | 12 | -keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle { 13 | *; 14 | } 15 | 16 | -keep public class * extends com.tencent.tinker.loader.TinkerLoader { 17 | *; 18 | } 19 | 20 | -keep public class * extends com.tencent.tinker.loader.app.TinkerApplication { 21 | *; 22 | } 23 | 24 | # here, it is your own keep rules. 25 | # you must be careful that the class name you write won't be proguard 26 | # but the tinker class above is OK, we have already keep for you! 27 | -------------------------------------------------------------------------------- /app/keystore/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/app/keystore/debug.keystore -------------------------------------------------------------------------------- /app/keystore/release.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/app/keystore/release.keystore -------------------------------------------------------------------------------- /app/libs/armeabi/libstlport_shared.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/app/libs/armeabi/libstlport_shared.so -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/zhangshaowen/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -keepattributes SourceFile,LineNumberTable 19 | 20 | -------------------------------------------------------------------------------- /app/src/androidTest/java/tinker/sample/android/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android; 18 | 19 | import android.app.Application; 20 | import android.test.ApplicationTestCase; 21 | 22 | /** 23 | * Testing Fundamentals 24 | */ 25 | public class ApplicationTest extends ApplicationTestCase { 26 | public ApplicationTest() { 27 | super(Application.class); 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/Log/MyLogImp.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.Log; 18 | 19 | import android.util.Log; 20 | 21 | import com.tencent.tinker.lib.util.TinkerLog; 22 | 23 | /** 24 | * Created by zhangshaowen on 16/6/3. 25 | */ 26 | public class MyLogImp implements TinkerLog.TinkerLogImp { 27 | private static final String TAG = "Tinker.MyLogImp"; 28 | 29 | public static final int LEVEL_VERBOSE = 0; 30 | public static final int LEVEL_DEBUG = 1; 31 | public static final int LEVEL_INFO = 2; 32 | public static final int LEVEL_WARNING = 3; 33 | public static final int LEVEL_ERROR = 4; 34 | public static final int LEVEL_NONE = 5; 35 | private static int level = LEVEL_VERBOSE; 36 | 37 | public static int getLogLevel() { 38 | return level; 39 | } 40 | 41 | public static void setLevel(final int level) { 42 | MyLogImp.level = level; 43 | android.util.Log.w(TAG, "new log level: " + level); 44 | 45 | } 46 | 47 | @Override 48 | public void v(String s, String s1, Object... objects) { 49 | if (level <= LEVEL_VERBOSE) { 50 | final String log = objects == null ? s1 : String.format(s1, objects); 51 | android.util.Log.v(s, log); 52 | } 53 | } 54 | 55 | @Override 56 | public void i(String s, String s1, Object... objects) { 57 | if (level <= LEVEL_INFO) { 58 | final String log = objects == null ? s1 : String.format(s1, objects); 59 | android.util.Log.i(s, log); 60 | } 61 | } 62 | 63 | @Override 64 | public void w(String s, String s1, Object... objects) { 65 | if (level <= LEVEL_WARNING) { 66 | final String log = objects == null ? s1 : String.format(s1, objects); 67 | android.util.Log.w(s, log); 68 | } 69 | } 70 | 71 | @Override 72 | public void d(String s, String s1, Object... objects) { 73 | if (level <= LEVEL_DEBUG) { 74 | final String log = objects == null ? s1 : String.format(s1, objects); 75 | android.util.Log.d(s, log); 76 | } 77 | } 78 | 79 | @Override 80 | public void e(String s, String s1, Object... objects) { 81 | if (level <= LEVEL_ERROR) { 82 | final String log = objects == null ? s1 : String.format(s1, objects); 83 | android.util.Log.e(s, log); 84 | } 85 | } 86 | 87 | @Override 88 | public void printErrStackTrace(String s, Throwable throwable, String s1, Object... objects) { 89 | String log = objects == null ? s1 : String.format(s1, objects); 90 | if (log == null) { 91 | log = ""; 92 | } 93 | log = log + " " + Log.getStackTraceString(throwable); 94 | android.util.Log.e(s, log); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/app/BaseBuildInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.app; 18 | 19 | /** 20 | * Created by zhangshaowen on 16/6/30. 21 | * we add BaseBuildInfo to loader pattern, so it won't change with patch! 22 | */ 23 | public class BaseBuildInfo { 24 | public static String TEST_MESSAGE = "I won't change with tinker patch!"; 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/app/Bean.java: -------------------------------------------------------------------------------- 1 | package tinker.sample.android.app; 2 | 3 | /** 4 | * Created by xinghongfei on 16/10/10. 5 | */ 6 | 7 | public class Bean { 8 | private String mString="小主Bug已修复"; 9 | private String mBug="小主有一个Bug"; 10 | 11 | public String getMessage(){ 12 | return mBug; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/app/BuildInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.app; 18 | 19 | import tinker.sample.android.BuildConfig; 20 | 21 | /** 22 | * Created by zhangshaowen on 16/6/30. 23 | * we use BuildInfo instead of {@link BuildInfo} to make less change 24 | */ 25 | public class BuildInfo { 26 | /** 27 | * they are not final, so they won't change with the BuildConfig values! 28 | */ 29 | public static boolean DEBUG = BuildConfig.DEBUG; 30 | public static String VERSION_NAME = BuildConfig.VERSION_NAME; 31 | public static int VERSION_CODE = BuildConfig.VERSION_CODE; 32 | 33 | public static String MESSAGE = BuildConfig.MESSAGE; 34 | public static String CLIENTVERSION = BuildConfig.CLIENTVERSION; 35 | public static String PLATFORM = BuildConfig.PLATFORM; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/app/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.app; 18 | 19 | import android.app.AlertDialog; 20 | import android.content.Context; 21 | import android.graphics.Typeface; 22 | import android.os.Bundle; 23 | import android.os.Environment; 24 | import android.support.v7.app.AppCompatActivity; 25 | import android.util.Log; 26 | import android.util.TypedValue; 27 | import android.view.Gravity; 28 | import android.view.View; 29 | import android.view.ViewGroup; 30 | import android.widget.Button; 31 | import android.widget.TextView; 32 | import android.widget.Toast; 33 | 34 | import com.tencent.tinker.lib.tinker.Tinker; 35 | import com.tencent.tinker.lib.tinker.TinkerInstaller; 36 | import com.tencent.tinker.loader.shareutil.ShareConstants; 37 | import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; 38 | 39 | import tinker.sample.android.R; 40 | import tinker.sample.android.util.Utils; 41 | 42 | public class MainActivity extends AppCompatActivity { 43 | private static final String TAG = "Tinker.MainActivity"; 44 | private Bean mBean=new Bean(); 45 | 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_main); 50 | Log.e(TAG, "i am on onCreate classloader:" + MainActivity.class.getClassLoader().toString()); 51 | //test resource change 52 | Log.e(TAG, "i am on onCreate string:" + getResources().getString(R.string.test_resource)); 53 | Log.e(TAG, "i am on patch xing onCreate"); 54 | 55 | Button loadPatchButton = (Button) findViewById(R.id.loadPatch); 56 | 57 | loadPatchButton.setOnClickListener(new View.OnClickListener() { 58 | @Override 59 | public void onClick(View v) { 60 | TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk"); 61 | } 62 | }); 63 | 64 | 65 | Button killSelfButton = (Button) findViewById(R.id.killSelf); 66 | 67 | killSelfButton.setOnClickListener(new View.OnClickListener() { 68 | @Override 69 | public void onClick(View v) { 70 | android.os.Process.killProcess(android.os.Process.myPid()); 71 | } 72 | }); 73 | 74 | Button buildInfoButton = (Button) findViewById(R.id.showInfo); 75 | 76 | buildInfoButton.setOnClickListener(new View.OnClickListener() { 77 | @Override 78 | public void onClick(View v) { 79 | Toast.makeText(MainActivity.this,mBean.getMessage(),Toast.LENGTH_LONG).show(); 80 | // showInfo(MainActivity.this); 81 | Log.i("hehe","xing"); 82 | } 83 | }); 84 | } 85 | 86 | public boolean showInfo(Context context) { 87 | // add more Build Info 88 | final StringBuilder sb = new StringBuilder(); 89 | Tinker tinker = Tinker.with(getApplicationContext()); 90 | if (tinker.isTinkerLoaded()) { 91 | sb.append(String.format("[patch is loaded] \n")); 92 | sb.append(String.format("[buildConfig CLIENTVERSION] %s \n", BuildInfo.CLIENTVERSION)); 93 | sb.append(String.format("[buildConfig MESSSAGE] %s \n", BuildInfo.MESSAGE)); 94 | sb.append(String.format("[TINKER_ID] %s \n", tinker.getTinkerLoadResultIfPresent().getPackageConfigByName(ShareConstants.TINKER_ID))); 95 | sb.append(String.format("[REAL TINKER_ID] %s \n", tinker.getTinkerLoadResultIfPresent().getTinkerID())); 96 | sb.append(String.format("[packageConfig patchMessage] %s \n", tinker.getTinkerLoadResultIfPresent().getPackageConfigByName("patchMessage"))); 97 | sb.append(String.format("[TINKER_ID Rom Space] %d k \n", tinker.getTinkerRomSpace())); 98 | 99 | } else { 100 | sb.append(String.format("[patch is not loaded] \n")); 101 | sb.append(String.format("[buildConfig CLIENTVERSION] %s \n", BuildInfo.CLIENTVERSION)); 102 | sb.append(String.format("[buildConfig MESSSAGE] %s \n", BuildInfo.MESSAGE)); 103 | sb.append(String.format("[TINKER_ID] %s \n", ShareTinkerInternals.getManifestTinkerID(getApplicationContext()))); 104 | } 105 | sb.append(String.format("[BaseBuildInfo Message] %s \n", BaseBuildInfo.TEST_MESSAGE)); 106 | 107 | final TextView v = new TextView(context); 108 | v.setText(sb); 109 | v.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); 110 | v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10); 111 | v.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 112 | v.setTextColor(0xFF000000); 113 | v.setTypeface(Typeface.MONOSPACE); 114 | final int padding = 16; 115 | v.setPadding(padding, padding, padding, padding); 116 | 117 | final AlertDialog.Builder builder = new AlertDialog.Builder(context); 118 | builder.setCancelable(true); 119 | builder.setView(v); 120 | final AlertDialog alert = builder.create(); 121 | alert.show(); 122 | return true; 123 | } 124 | 125 | @Override 126 | protected void onResume() { 127 | Log.e(TAG, "i am on onResume"); 128 | // Log.e(TAG, "i am on patch onResume"); 129 | 130 | super.onResume(); 131 | Utils.setBackground(false); 132 | 133 | } 134 | 135 | @Override 136 | protected void onPause() { 137 | super.onPause(); 138 | Utils.setBackground(true); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/app/SampleApplicationLike.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.app; 18 | 19 | import android.annotation.TargetApi; 20 | import android.app.Application; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.res.AssetManager; 24 | import android.content.res.Resources; 25 | import android.os.Build; 26 | import android.support.multidex.MultiDex; 27 | 28 | import com.tencent.tinker.anno.DefaultLifeCycle; 29 | import com.tencent.tinker.lib.tinker.TinkerInstaller; 30 | import com.tencent.tinker.loader.app.ApplicationLifeCycle; 31 | import com.tencent.tinker.loader.app.DefaultApplicationLike; 32 | import com.tencent.tinker.loader.shareutil.ShareConstants; 33 | 34 | import tinker.sample.android.Log.MyLogImp; 35 | import tinker.sample.android.util.SampleApplicationContext; 36 | import tinker.sample.android.util.TinkerManager; 37 | 38 | /** 39 | * because you can not use any other class in your application, we need to 40 | * move your implement of Application to {@link ApplicationLifeCycle} 41 | * As Application, all its direct reference class should be in the main dex. 42 | * 43 | * We use tinker-android-anno to make sure all your classes can be patched. 44 | * 45 | * application: if it is start with '.', we will add SampleApplicationLifeCycle's package name 46 | * 47 | * flags: 48 | * TINKER_ENABLE_ALL: support dex, lib and resource 49 | * TINKER_DEX_MASK: just support dex 50 | * TINKER_NATIVE_LIBRARY_MASK: just support lib 51 | * TINKER_RESOURCE_MASK: just support resource 52 | * 53 | * loaderClass: define the tinker loader class, we can just use the default TinkerLoader 54 | * 55 | * loadVerifyFlag: whether check files' md5 on the load time, defualt it is false. 56 | * 57 | * Created by zhangshaowen on 16/3/17. 58 | */ 59 | @SuppressWarnings("unused") 60 | @DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication", 61 | flags = ShareConstants.TINKER_ENABLE_ALL, 62 | loadVerifyFlag = false) 63 | public class SampleApplicationLike extends DefaultApplicationLike { 64 | private static final String TAG = "Tinker.SampleApplicationLike"; 65 | 66 | public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, 67 | long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent, 68 | Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) { 69 | super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent, resources, classLoader, assetManager); 70 | } 71 | 72 | /** 73 | * install multiDex before install tinker 74 | * so we don't need to put the tinker lib classes in the main dex 75 | * 76 | * @param base 77 | */ 78 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 79 | @Override 80 | public void onBaseContextAttached(Context base) { 81 | super.onBaseContextAttached(base); 82 | //you must install multiDex whatever tinker is installed! 83 | MultiDex.install(base); 84 | 85 | SampleApplicationContext.application = getApplication(); 86 | SampleApplicationContext.context = getApplication(); 87 | TinkerManager.setTinkerApplicationLike(this); 88 | TinkerManager.initFastCrashProtect(); 89 | //should set before tinker is installed 90 | TinkerManager.setUpgradeRetryEnable(true); 91 | 92 | //optional set logIml, or you can use default debug log 93 | TinkerInstaller.setLogIml(new MyLogImp()); 94 | 95 | //installTinker after load multiDex 96 | //or you can put com.tencent.tinker.** to main dex 97 | TinkerManager.installTinker(this); 98 | } 99 | 100 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 101 | public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) { 102 | getApplication().registerActivityLifecycleCallbacks(callback); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/crash/SampleUncaughtExceptionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.crash; 18 | 19 | import android.content.Context; 20 | import android.content.SharedPreferences; 21 | import android.os.SystemClock; 22 | import android.widget.Toast; 23 | 24 | import com.tencent.tinker.lib.tinker.TinkerApplicationHelper; 25 | import com.tencent.tinker.lib.util.TinkerLog; 26 | import com.tencent.tinker.loader.app.ApplicationLike; 27 | import com.tencent.tinker.loader.shareutil.ShareConstants; 28 | import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; 29 | 30 | import tinker.sample.android.reporter.SampleTinkerReport; 31 | import tinker.sample.android.util.TinkerManager; 32 | import tinker.sample.android.util.Utils; 33 | 34 | /** 35 | * optional, use dynamic configuration is better way 36 | * for native crash, 37 | *

38 | * Created by zhangshaowen on 16/7/3. 39 | * tinker's crash is caught by {@code LoadReporter.onLoadException} 40 | * use {@code TinkerApplicationHelper} api, no need to install tinker! 41 | */ 42 | public class SampleUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { 43 | private static final String TAG = "Tinker.SampleUncaughtExHandler"; 44 | 45 | private final Thread.UncaughtExceptionHandler ueh; 46 | private static final long QUICK_CRASH_ELAPSE = 10 * 1000; 47 | public static final int MAX_CRASH_COUNT = 3; 48 | private static final String DALVIK_XPOSED_CRASH = "Class ref in pre-verified class resolved to unexpected implementation"; 49 | 50 | public SampleUncaughtExceptionHandler() { 51 | ueh = Thread.getDefaultUncaughtExceptionHandler(); 52 | } 53 | 54 | @Override 55 | public void uncaughtException(Thread thread, Throwable ex) { 56 | TinkerLog.e(TAG, "uncaughtException:" + ex.getMessage()); 57 | tinkerFastCrashProtect(); 58 | tinkerPreVerifiedCrashHandler(ex); 59 | ueh.uncaughtException(thread, ex); 60 | } 61 | 62 | /** 63 | * Such as Xposed, if it try to load some class before we load from patch files. 64 | * With dalvik, it will crash with "Class ref in pre-verified class resolved to unexpected implementation". 65 | * With art, it may crash at some times. But we can't know the actual crash type. 66 | * If it use Xposed, we can just clean patch or mention user to uninstall it. 67 | */ 68 | private void tinkerPreVerifiedCrashHandler(Throwable ex) { 69 | if (Utils.isXposedExists(ex)) { 70 | //method 1 71 | ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike(); 72 | if (applicationLike == null || applicationLike.getApplication() == null) { 73 | return; 74 | } 75 | 76 | if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) { 77 | return; 78 | } 79 | boolean isCausedByXposed = false; 80 | //for art, we can't know the actually crash type 81 | //art's xposed has not much people 82 | if (ShareTinkerInternals.isVmArt()) { 83 | isCausedByXposed = true; 84 | } else if (ex instanceof IllegalAccessError && ex.getMessage().contains(DALVIK_XPOSED_CRASH)) { 85 | //for dalvik, we know the actual crash type 86 | isCausedByXposed = true; 87 | } 88 | 89 | if (isCausedByXposed) { 90 | SampleTinkerReport.onXposedCrash(); 91 | TinkerLog.e(TAG, "have xposed: just clean tinker"); 92 | //kill all other process to ensure that all process's code is the same. 93 | ShareTinkerInternals.killAllOtherProcess(applicationLike.getApplication()); 94 | 95 | TinkerApplicationHelper.cleanPatch(applicationLike); 96 | ShareTinkerInternals.setTinkerDisableWithSharedPreferences(applicationLike.getApplication()); 97 | //method 2 98 | //or you can mention user to uninstall Xposed! 99 | Toast.makeText(applicationLike.getApplication(), "please uninstall Xposed, illegal modify the app", Toast.LENGTH_LONG).show(); 100 | } 101 | } 102 | } 103 | 104 | /** 105 | * if tinker is load, and it crash more than MAX_CRASH_COUNT, then we just clean patch. 106 | */ 107 | private boolean tinkerFastCrashProtect() { 108 | ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike(); 109 | 110 | if (applicationLike == null || applicationLike.getApplication() == null) { 111 | return false; 112 | } 113 | if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) { 114 | return false; 115 | } 116 | 117 | final long elapsedTime = SystemClock.elapsedRealtime() - applicationLike.getApplicationStartElapsedTime(); 118 | //this process may not install tinker, so we use TinkerApplicationHelper api 119 | if (elapsedTime < QUICK_CRASH_ELAPSE) { 120 | String currentVersion = TinkerApplicationHelper.getCurrentVersion(applicationLike); 121 | if (ShareTinkerInternals.isNullOrNil(currentVersion)) { 122 | return false; 123 | } 124 | 125 | SharedPreferences sp = applicationLike.getApplication().getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); 126 | int fastCrashCount = sp.getInt(currentVersion, 0); 127 | if (fastCrashCount >= MAX_CRASH_COUNT) { 128 | SampleTinkerReport.onFastCrashProtect(); 129 | TinkerApplicationHelper.cleanPatch(applicationLike); 130 | TinkerLog.e(TAG, "tinker has fast crash more than %d, we just clean patch!", fastCrashCount); 131 | return true; 132 | } else { 133 | sp.edit().putInt(currentVersion, ++fastCrashCount).commit(); 134 | TinkerLog.e(TAG, "tinker has fast crash %d times", fastCrashCount); 135 | } 136 | } 137 | 138 | return false; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/reporter/SampleLoadReporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.reporter; 18 | 19 | import android.content.Context; 20 | import android.os.Handler; 21 | import android.os.Looper; 22 | import android.os.MessageQueue; 23 | import android.widget.Toast; 24 | 25 | import com.tencent.tinker.lib.reporter.DefaultLoadReporter; 26 | import com.tencent.tinker.lib.tinker.Tinker; 27 | import com.tencent.tinker.lib.tinker.TinkerInstaller; 28 | import com.tencent.tinker.loader.shareutil.ShareConstants; 29 | 30 | import java.io.File; 31 | 32 | import tinker.sample.android.util.UpgradePatchRetry; 33 | import tinker.sample.android.util.Utils; 34 | 35 | /** 36 | * optional, you can just use DefaultLoadReporter 37 | * Created by zhangshaowen on 16/4/13. 38 | */ 39 | public class SampleLoadReporter extends DefaultLoadReporter { 40 | private Handler handler = new Handler(); 41 | 42 | public SampleLoadReporter(Context context) { 43 | super(context); 44 | } 45 | 46 | @Override 47 | public void onLoadPatchListenerReceiveFail(final File patchFile, int errorCode, final boolean isUpgrade) { 48 | super.onLoadPatchListenerReceiveFail(patchFile, errorCode, isUpgrade); 49 | switch (errorCode) { 50 | case ShareConstants.ERROR_PATCH_NOTEXIST: 51 | Toast.makeText(context, "patch file is not exist", Toast.LENGTH_LONG).show(); 52 | break; 53 | case ShareConstants.ERROR_PATCH_RUNNING: 54 | // try later 55 | // only retry for upgrade patch 56 | if (isUpgrade) { 57 | handler.postDelayed(new Runnable() { 58 | @Override 59 | public void run() { 60 | TinkerInstaller.onReceiveUpgradePatch(context, patchFile.getAbsolutePath()); 61 | } 62 | }, 60 * 1000); 63 | } 64 | break; 65 | case Utils.ERROR_PATCH_ROM_SPACE: 66 | Toast.makeText(context, "rom space is not enough", Toast.LENGTH_LONG).show(); 67 | break; 68 | } 69 | SampleTinkerReport.onTryApplyFail(errorCode); 70 | } 71 | 72 | @Override 73 | public void onLoadResult(File patchDirectory, int loadCode, long cost) { 74 | super.onLoadResult(patchDirectory, loadCode, cost); 75 | switch (loadCode) { 76 | case ShareConstants.ERROR_LOAD_OK: 77 | SampleTinkerReport.onLoaded(cost); 78 | break; 79 | } 80 | Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() { 81 | @Override public boolean queueIdle() { 82 | UpgradePatchRetry.getInstance(context).onPatchRetryLoad(); 83 | return false; 84 | } 85 | }); 86 | } 87 | @Override 88 | public void onLoadException(Throwable e, int errorCode) { 89 | super.onLoadException(e, errorCode); 90 | SampleTinkerReport.onLoadException(e, errorCode); 91 | } 92 | 93 | @Override 94 | public void onLoadFileMd5Mismatch(File file, int fileType) { 95 | super.onLoadFileMd5Mismatch(file, fileType); 96 | SampleTinkerReport.onLoadFileMisMatch(fileType); 97 | } 98 | 99 | @Override 100 | public void onLoadFileNotFound(File file, int fileType, boolean isDirectory) { 101 | super.onLoadFileNotFound(file, fileType, isDirectory); 102 | SampleTinkerReport.onLoadFileNotFound(fileType); 103 | } 104 | 105 | @Override 106 | public void onLoadPackageCheckFail(File patchFile, int errorCode) { 107 | super.onLoadPackageCheckFail(patchFile, errorCode); 108 | SampleTinkerReport.onLoadPackageCheckFail(errorCode); 109 | } 110 | 111 | @Override 112 | public void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) { 113 | super.onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile); 114 | SampleTinkerReport.onLoadInfoCorrupted(); 115 | } 116 | 117 | @Override 118 | public void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) { 119 | super.onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectoryFile, currentPatchName); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/reporter/SamplePatchListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.reporter; 18 | 19 | import android.app.ActivityManager; 20 | import android.content.Context; 21 | import android.content.SharedPreferences; 22 | 23 | import com.tencent.tinker.lib.listener.DefaultPatchListener; 24 | import com.tencent.tinker.lib.tinker.Tinker; 25 | import com.tencent.tinker.lib.tinker.TinkerLoadResult; 26 | import com.tencent.tinker.lib.util.TinkerLog; 27 | import com.tencent.tinker.loader.shareutil.ShareConstants; 28 | import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; 29 | import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; 30 | 31 | import java.io.File; 32 | import java.util.Properties; 33 | 34 | import tinker.sample.android.app.BuildInfo; 35 | import tinker.sample.android.crash.SampleUncaughtExceptionHandler; 36 | import tinker.sample.android.util.Utils; 37 | 38 | /** 39 | * Created by zhangshaowen on 16/4/30. 40 | * optional, you can just use DefaultPatchListener 41 | * we can check whatever you want whether we actually send a patch request 42 | * such as we can check rom space or apk channel 43 | */ 44 | public class SamplePatchListener extends DefaultPatchListener { 45 | private static final String TAG = "Tinker.SamplePatchListener"; 46 | 47 | protected static final long NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN = 60 * 1024 * 1024; 48 | protected static final long OLD_PATCH_RESTRICTION_SPACE_SIZE_MIN = 30 * 1024 * 1024; 49 | 50 | private final int maxMemory; 51 | 52 | public SamplePatchListener(Context context) { 53 | super(context); 54 | maxMemory = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); 55 | TinkerLog.i(TAG, "application maxMemory:" + maxMemory); 56 | } 57 | 58 | /** 59 | * because we use the defaultCheckPatchReceived method 60 | * the error code define by myself should after {@code ShareConstants.ERROR_RECOVER_INSERVICE 61 | * 62 | * @param path 63 | * @param newPatch 64 | * @return 65 | */ 66 | @Override 67 | public int patchCheck(String path, boolean isUpgrade) { 68 | File patchFile = new File(path); 69 | TinkerLog.i(TAG, "receive a patch file: %s, isUpgrade:%b, file size:%d", path, isUpgrade, SharePatchFileUtil.getFileOrDirectorySize(patchFile)); 70 | int returnCode = super.patchCheck(path, isUpgrade); 71 | 72 | if (returnCode == ShareConstants.ERROR_PATCH_OK) { 73 | if (isUpgrade) { 74 | returnCode = Utils.checkForPatchRecover(NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN, maxMemory); 75 | } else { 76 | returnCode = Utils.checkForPatchRecover(OLD_PATCH_RESTRICTION_SPACE_SIZE_MIN, maxMemory); 77 | } 78 | } 79 | 80 | if (returnCode == ShareConstants.ERROR_PATCH_OK) { 81 | String patchMd5 = SharePatchFileUtil.getMD5(patchFile); 82 | SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); 83 | //optional, only disable this patch file with md5 84 | int fastCrashCount = sp.getInt(patchMd5, 0); 85 | if (fastCrashCount >= SampleUncaughtExceptionHandler.MAX_CRASH_COUNT) { 86 | returnCode = Utils.ERROR_PATCH_CRASH_LIMIT; 87 | } else { 88 | //for upgrade patch, version must be not the same 89 | //for repair patch, we won't has the tinker load flag 90 | Tinker tinker = Tinker.with(context); 91 | 92 | if (tinker.isTinkerLoaded()) { 93 | TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent(); 94 | if (tinkerLoadResult != null) { 95 | String currentVersion = tinkerLoadResult.currentVersion; 96 | if (patchMd5.equals(currentVersion)) { 97 | returnCode = Utils.ERROR_PATCH_ALREADY_APPLY; 98 | } 99 | } 100 | } 101 | } 102 | } 103 | // Warning, it is just a sample case, you don't need to copy all of these 104 | // Interception some of the request 105 | if (returnCode == ShareConstants.ERROR_PATCH_OK) { 106 | Properties properties = ShareTinkerInternals.fastGetPatchPackageMeta(patchFile); 107 | if (properties == null) { 108 | returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED; 109 | } else { 110 | String platform = properties.getProperty(Utils.PLATFORM); 111 | TinkerLog.i(TAG, "get platform:" + platform); 112 | // check patch platform require 113 | if (platform == null || !platform.equals(BuildInfo.PLATFORM)) { 114 | returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED; 115 | } 116 | } 117 | } 118 | 119 | SampleTinkerReport.onTryApply(isUpgrade, returnCode == ShareConstants.ERROR_PATCH_OK); 120 | return returnCode; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/reporter/SamplePatchReporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.reporter; 18 | 19 | import android.content.Context; 20 | import android.content.Intent; 21 | 22 | import com.tencent.tinker.lib.reporter.DefaultPatchReporter; 23 | import com.tencent.tinker.loader.shareutil.SharePatchInfo; 24 | 25 | import java.io.File; 26 | 27 | import tinker.sample.android.util.UpgradePatchRetry; 28 | 29 | /** 30 | * optional, you can just use DefaultPatchReporter 31 | * Created by zhangshaowen on 16/4/8. 32 | */ 33 | public class SamplePatchReporter extends DefaultPatchReporter { 34 | public SamplePatchReporter(Context context) { 35 | super(context); 36 | } 37 | 38 | @Override 39 | public void onPatchServiceStart(Intent intent) { 40 | super.onPatchServiceStart(intent); 41 | SampleTinkerReport.onApplyPatchServiceStart(); 42 | UpgradePatchRetry.getInstance(context).onPatchServiceStart(intent); 43 | } 44 | 45 | @Override 46 | public void onPatchDexOptFail(File patchFile, File dexFile, String optDirectory, String dexName, Throwable t, boolean isUpgradePatch) { 47 | super.onPatchDexOptFail(patchFile, dexFile, optDirectory, dexName, t, isUpgradePatch); 48 | SampleTinkerReport.onApplyDexOptFail(t); 49 | } 50 | 51 | @Override 52 | public void onPatchException(File patchFile, Throwable e, boolean isUpgradePatch) { 53 | super.onPatchException(patchFile, e, isUpgradePatch); 54 | SampleTinkerReport.onApplyCrash(e); 55 | } 56 | 57 | @Override 58 | public void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion, boolean isUpgradePatch) { 59 | super.onPatchInfoCorrupted(patchFile, oldVersion, newVersion, isUpgradePatch); 60 | SampleTinkerReport.onApplyInfoCorrupted(); 61 | } 62 | 63 | @Override 64 | public void onPatchPackageCheckFail(File patchFile, boolean isUpgradePatch, int errorCode) { 65 | super.onPatchPackageCheckFail(patchFile, isUpgradePatch, errorCode); 66 | SampleTinkerReport.onApplyPackageCheckFail(errorCode); 67 | } 68 | 69 | @Override 70 | public void onPatchResult(File patchFile, boolean success, long cost, boolean isUpgradePatch) { 71 | super.onPatchResult(patchFile, success, cost, isUpgradePatch); 72 | SampleTinkerReport.onApplied(isUpgradePatch, cost, success); 73 | UpgradePatchRetry.getInstance(context).onPatchServiceResult(isUpgradePatch); 74 | } 75 | 76 | @Override 77 | public void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType, boolean isUpgradePatch) { 78 | super.onPatchTypeExtractFail(patchFile, extractTo, filename, fileType, isUpgradePatch); 79 | SampleTinkerReport.onApplyExtractFail(fileType); 80 | } 81 | 82 | @Override 83 | public void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion, boolean isUpgradePatch) { 84 | super.onPatchVersionCheckFail(patchFile, oldPatchInfo, patchFileVersion, isUpgradePatch); 85 | SampleTinkerReport.onApplyVersionCheckFail(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/reporter/SampleTinkerReport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.reporter; 18 | 19 | import com.tencent.tinker.lib.util.TinkerLog; 20 | import com.tencent.tinker.loader.shareutil.ShareConstants; 21 | import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; 22 | 23 | import tinker.sample.android.util.Utils; 24 | 25 | /** 26 | * a simple tinker data reporter 27 | * Created by zhangshaowen on 16/9/17. 28 | */ 29 | public class SampleTinkerReport { 30 | private static final String TAG = "Tinker.SampleTinkerReport"; 31 | 32 | // KEY - PV 33 | public static final int KEY_REQUEST = 0; 34 | public static final int KEY_DOWNLOAD = 1; 35 | public static final int KEY_TRY_APPLY = 2; 36 | public static final int KEY_TRY_APPLY_SUCCESS = 3; 37 | public static final int KEY_APPLIED_START = 4; 38 | public static final int KEY_APPLIED = 5; 39 | public static final int KEY_LOADED = 6; 40 | public static final int KEY_CRASH_FAST_PROTECT = 7; 41 | public static final int KEY_CRASH_CAUSE_XPOSED_DALVIK = 8; 42 | public static final int KEY_CRASH_CAUSE_XPOSED_ART = 9; 43 | public static final int KEY_APPLY_WITH_RETRY = 10; 44 | 45 | //Key -- try apply detail 46 | public static final int KEY_TRY_APPLY_REPAIR = 70; 47 | public static final int KEY_TRY_APPLY_UPGRADE = 71; 48 | public static final int KEY_TRY_APPLY_DISABLE = 72; 49 | public static final int KEY_TRY_APPLY_RUNNING = 73; 50 | public static final int KEY_TRY_APPLY_INSERVICE = 74; 51 | public static final int KEY_TRY_APPLY_NOT_EXIST = 75; 52 | public static final int KEY_TRY_APPLY_GOOGLEPLAY = 76; 53 | public static final int KEY_TRY_APPLY_ROM_SPACE = 77; 54 | public static final int KEY_TRY_APPLY_ALREADY_APPLY = 78; 55 | public static final int KEY_TRY_APPLY_MEMORY_LIMIT = 79; 56 | public static final int KEY_TRY_APPLY_CRASH_LIMIT = 80; 57 | public static final int KEY_TRY_APPLY_CONDITION_NOT_SATISFIED = 81; 58 | 59 | //Key -- apply detail 60 | public static final int KEY_APPLIED_REPAIR = 100; 61 | public static final int KEY_APPLIED_UPGRADE = 101; 62 | public static final int KEY_APPLIED_REPAIR_FAIL = 102; 63 | public static final int KEY_APPLIED_UPGRADE_FAIL = 103; 64 | 65 | public static final int KEY_APPLIED_EXCEPTION = 120; 66 | public static final int KEY_APPLIED_DEXOPT = 121; 67 | public static final int KEY_APPLIED_INFO_CORRUPTED = 122; 68 | //package check 69 | public static final int KEY_APPLIED_PACKAGE_CHECK_SIGNATURE = 150; 70 | public static final int KEY_APPLIED_PACKAGE_CHECK_DEX_META = 151; 71 | public static final int KEY_APPLIED_PACKAGE_CHECK_LIB_META = 152; 72 | public static final int KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = 153; 73 | public static final int KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 154; 74 | public static final int KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND = 155; 75 | public static final int KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = 156; 76 | public static final int KEY_APPLIED_PACKAGE_CHECK_RES_META = 157; 77 | //version check 78 | public static final int KEY_APPLIED_VERSION_CHECK = 180; 79 | //extract error 80 | public static final int KEY_APPLIED_PATCH_FILE_EXTRACT = 181; 81 | public static final int KEY_APPLIED_DEX_EXTRACT = 182; 82 | /** 83 | * for art small dex 84 | */ 85 | public static final int KEY_APPLIED_DEX_ART_EXTRACT = 183; 86 | public static final int KEY_APPLIED_LIB_EXTRACT = 184; 87 | public static final int KEY_APPLIED_RESOURCE_EXTRACT = 185; 88 | //cost time 89 | public static final int KEY_APPLIED_SUCC_COST_5S_LESS = 200; 90 | public static final int KEY_APPLIED_SUCC_COST_10S_LESS = 201; 91 | public static final int KEY_APPLIED_SUCC_COST_30S_LESS = 202; 92 | public static final int KEY_APPLIED_SUCC_COST_60S_LESS = 203; 93 | public static final int KEY_APPLIED_SUCC_COST_OTHER = 204; 94 | 95 | public static final int KEY_APPLIED_FAIL_COST_5S_LESS = 205; 96 | public static final int KEY_APPLIED_FAIL_COST_10S_LESS = 206; 97 | public static final int KEY_APPLIED_FAIL_COST_30S_LESS = 207; 98 | public static final int KEY_APPLIED_FAIL_COST_60S_LESS = 208; 99 | public static final int KEY_APPLIED_FAIL_COST_OTHER = 209; 100 | 101 | 102 | // KEY -- load detail 103 | public static final int KEY_LOADED_UNKNOWN_EXCEPTION = 250; 104 | public static final int KEY_LOADED_UNCAUGHT_EXCEPTION = 251; 105 | public static final int KEY_LOADED_EXCEPTION_DEX = 252; 106 | public static final int KEY_LOADED_EXCEPTION_DEX_CHECK = 253; 107 | public static final int KEY_LOADED_EXCEPTION_RESOURCE = 254; 108 | 109 | public static final int KEY_LOADED_MISMATCH_DEX = 300; 110 | public static final int KEY_LOADED_MISMATCH_LIB = 301; 111 | public static final int KEY_LOADED_MISMATCH_RESOURCE = 302; 112 | public static final int KEY_LOADED_MISSING_DEX = 303; 113 | public static final int KEY_LOADED_MISSING_LIB = 304; 114 | public static final int KEY_LOADED_MISSING_PATCH_FILE = 305; 115 | public static final int KEY_LOADED_MISSING_PATCH_INFO = 306; 116 | public static final int KEY_LOADED_MISSING_DEX_OPT = 307; 117 | public static final int KEY_LOADED_MISSING_RES = 308; 118 | public static final int KEY_LOADED_INFO_CORRUPTED = 309; 119 | 120 | //load package check 121 | public static final int KEY_LOADED_PACKAGE_CHECK_SIGNATURE = 350; 122 | public static final int KEY_LOADED_PACKAGE_CHECK_DEX_META = 351; 123 | public static final int KEY_LOADED_PACKAGE_CHECK_LIB_META = 352; 124 | public static final int KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = 353; 125 | public static final int KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 354; 126 | public static final int KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = 355; 127 | public static final int KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND = 356; 128 | public static final int KEY_LOADED_PACKAGE_CHECK_RES_META = 357; 129 | 130 | public static final int KEY_LOADED_SUCC_COST_500_LESS = 400; 131 | public static final int KEY_LOADED_SUCC_COST_1000_LESS = 401; 132 | public static final int KEY_LOADED_SUCC_COST_3000_LESS = 402; 133 | public static final int KEY_LOADED_SUCC_COST_5000_LESS = 403; 134 | public static final int KEY_LOADED_SUCC_COST_OTHER = 404; 135 | 136 | interface Reporter { 137 | void onReport(int key); 138 | 139 | void onReport(String message); 140 | } 141 | 142 | private static Reporter reporter = null; 143 | 144 | public void setReporter(Reporter reporter) { 145 | this.reporter = reporter; 146 | } 147 | 148 | public static void onTryApply(boolean upgrade, boolean success) { 149 | if (reporter == null) { 150 | return; 151 | } 152 | reporter.onReport(KEY_TRY_APPLY); 153 | if (upgrade) { 154 | reporter.onReport(KEY_TRY_APPLY_UPGRADE); 155 | } else { 156 | reporter.onReport(KEY_TRY_APPLY_REPAIR); 157 | } 158 | if (success) { 159 | reporter.onReport(KEY_TRY_APPLY_SUCCESS); 160 | } 161 | } 162 | 163 | public static void onTryApplyFail(int errorCode) { 164 | if (reporter == null) { 165 | return; 166 | } 167 | switch (errorCode) { 168 | case ShareConstants.ERROR_PATCH_NOTEXIST: 169 | reporter.onReport(KEY_TRY_APPLY_NOT_EXIST); 170 | break; 171 | case ShareConstants.ERROR_PATCH_DISABLE: 172 | reporter.onReport(KEY_TRY_APPLY_DISABLE); 173 | break; 174 | case ShareConstants.ERROR_PATCH_INSERVICE: 175 | reporter.onReport(KEY_TRY_APPLY_INSERVICE); 176 | break; 177 | case ShareConstants.ERROR_PATCH_RUNNING: 178 | reporter.onReport(KEY_TRY_APPLY_RUNNING); 179 | break; 180 | case Utils.ERROR_PATCH_ROM_SPACE: 181 | reporter.onReport(KEY_TRY_APPLY_ROM_SPACE); 182 | break; 183 | case Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL: 184 | reporter.onReport(KEY_TRY_APPLY_GOOGLEPLAY); 185 | break; 186 | case Utils.ERROR_PATCH_ALREADY_APPLY: 187 | reporter.onReport(KEY_TRY_APPLY_ALREADY_APPLY); 188 | break; 189 | case Utils.ERROR_PATCH_CRASH_LIMIT: 190 | reporter.onReport(KEY_TRY_APPLY_CRASH_LIMIT); 191 | break; 192 | case Utils.ERROR_PATCH_MEMORY_LIMIT: 193 | reporter.onReport(KEY_TRY_APPLY_MEMORY_LIMIT); 194 | break; 195 | case Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED: 196 | reporter.onReport(KEY_TRY_APPLY_CONDITION_NOT_SATISFIED); 197 | break; 198 | } 199 | } 200 | 201 | public static void onLoadPackageCheckFail(int errorCode) { 202 | if (reporter == null) { 203 | return; 204 | } 205 | switch (errorCode) { 206 | case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL: 207 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_SIGNATURE); 208 | break; 209 | case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED: 210 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_DEX_META); 211 | break; 212 | case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED: 213 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_LIB_META); 214 | break; 215 | case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND: 216 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND); 217 | break; 218 | case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND: 219 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND); 220 | break; 221 | case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL: 222 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL); 223 | 224 | break; 225 | case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND: 226 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND); 227 | break; 228 | case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED: 229 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_RES_META); 230 | break; 231 | } 232 | } 233 | 234 | public static void onLoaded(long cost) { 235 | if (reporter == null) { 236 | return; 237 | } 238 | reporter.onReport(KEY_LOADED); 239 | 240 | if (cost < 0L) { 241 | TinkerLog.e(TAG, "hp_report report load cost failed, invalid cost"); 242 | return; 243 | } 244 | 245 | if (cost <= 500) { 246 | reporter.onReport(KEY_LOADED_SUCC_COST_500_LESS); 247 | } else if (cost <= 1000) { 248 | reporter.onReport(KEY_LOADED_SUCC_COST_1000_LESS); 249 | } else if (cost <= 3000) { 250 | reporter.onReport(KEY_LOADED_SUCC_COST_3000_LESS); 251 | } else if (cost <= 5000) { 252 | reporter.onReport(KEY_LOADED_SUCC_COST_5000_LESS); 253 | } else { 254 | reporter.onReport(KEY_LOADED_SUCC_COST_OTHER); 255 | } 256 | } 257 | 258 | public static void onLoadInfoCorrupted() { 259 | if (reporter == null) { 260 | return; 261 | } 262 | reporter.onReport(KEY_LOADED_INFO_CORRUPTED); 263 | } 264 | 265 | public static void onLoadFileNotFound(int fileType) { 266 | if (reporter == null) { 267 | return; 268 | } 269 | switch (fileType) { 270 | case ShareConstants.TYPE_DEX_OPT: 271 | reporter.onReport(KEY_LOADED_MISSING_DEX_OPT); 272 | break; 273 | case ShareConstants.TYPE_DEX: 274 | reporter.onReport(KEY_LOADED_MISSING_DEX); 275 | break; 276 | case ShareConstants.TYPE_LIBRARY: 277 | reporter.onReport(KEY_LOADED_MISSING_LIB); 278 | break; 279 | case ShareConstants.TYPE_PATCH_FILE: 280 | reporter.onReport(KEY_LOADED_MISSING_PATCH_FILE); 281 | break; 282 | case ShareConstants.TYPE_PATCH_INFO: 283 | reporter.onReport(KEY_LOADED_MISSING_PATCH_INFO); 284 | break; 285 | case ShareConstants.TYPE_RESOURCE: 286 | reporter.onReport(KEY_LOADED_MISSING_RES); 287 | break; 288 | } 289 | } 290 | 291 | public static void onLoadFileMisMatch(int fileType) { 292 | if (reporter == null) { 293 | return; 294 | } 295 | switch (fileType) { 296 | case ShareConstants.TYPE_DEX: 297 | reporter.onReport(KEY_LOADED_MISMATCH_DEX); 298 | break; 299 | case ShareConstants.TYPE_LIBRARY: 300 | reporter.onReport(KEY_LOADED_MISMATCH_LIB); 301 | break; 302 | case ShareConstants.TYPE_RESOURCE: 303 | reporter.onReport(KEY_LOADED_MISMATCH_RESOURCE); 304 | break; 305 | } 306 | } 307 | 308 | public static void onLoadException(Throwable throwable, int errorCode) { 309 | if (reporter == null) { 310 | return; 311 | } 312 | boolean isDexCheckFail = false; 313 | switch (errorCode) { 314 | case ShareConstants.ERROR_LOAD_EXCEPTION_DEX: 315 | if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_INSTALL_FAIL)) { 316 | reporter.onReport(KEY_LOADED_EXCEPTION_DEX_CHECK); 317 | isDexCheckFail = true; 318 | TinkerLog.e(TAG, "tinker dex check fail:" + throwable.getMessage()); 319 | } else { 320 | reporter.onReport(KEY_LOADED_EXCEPTION_DEX); 321 | TinkerLog.e(TAG, "tinker dex reflect fail:" + throwable.getMessage()); 322 | } 323 | break; 324 | case ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE: 325 | reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE); 326 | break; 327 | case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT: 328 | reporter.onReport(KEY_LOADED_UNCAUGHT_EXCEPTION); 329 | break; 330 | case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN: 331 | reporter.onReport(KEY_LOADED_UNKNOWN_EXCEPTION); 332 | break; 333 | } 334 | //reporter exception, for dex check fail, we don't need to report stacktrace 335 | if (!isDexCheckFail) { 336 | reporter.onReport("Tinker Exception:load tinker occur exception " + Utils.getExceptionCauseString(throwable)); 337 | } 338 | } 339 | 340 | public static void onApplyPatchServiceStart() { 341 | if (reporter == null) { 342 | return; 343 | } 344 | reporter.onReport(KEY_APPLIED_START); 345 | } 346 | 347 | public static void onApplyDexOptFail(Throwable throwable) { 348 | if (reporter == null) { 349 | return; 350 | } 351 | reporter.onReport(KEY_APPLIED_DEXOPT); 352 | reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable)); 353 | } 354 | 355 | public static void onApplyInfoCorrupted() { 356 | if (reporter == null) { 357 | return; 358 | } 359 | reporter.onReport(KEY_APPLIED_INFO_CORRUPTED); 360 | } 361 | 362 | public static void onApplyVersionCheckFail() { 363 | if (reporter == null) { 364 | return; 365 | } 366 | reporter.onReport(KEY_APPLIED_VERSION_CHECK); 367 | } 368 | 369 | public static void onApplyExtractFail(int fileType) { 370 | if (reporter == null) { 371 | return; 372 | } 373 | switch (fileType) { 374 | case ShareConstants.TYPE_DEX: 375 | reporter.onReport(KEY_APPLIED_DEX_EXTRACT); 376 | break; 377 | case ShareConstants.TYPE_DEX_FOR_ART: 378 | reporter.onReport(KEY_APPLIED_DEX_ART_EXTRACT); 379 | break; 380 | case ShareConstants.TYPE_LIBRARY: 381 | reporter.onReport(KEY_APPLIED_LIB_EXTRACT); 382 | break; 383 | case ShareConstants.TYPE_PATCH_FILE: 384 | reporter.onReport(KEY_APPLIED_PATCH_FILE_EXTRACT); 385 | break; 386 | case ShareConstants.TYPE_RESOURCE: 387 | reporter.onReport(KEY_APPLIED_RESOURCE_EXTRACT); 388 | break; 389 | } 390 | } 391 | 392 | public static void onApplied(boolean isUpgrade, long cost, boolean success) { 393 | if (reporter == null) { 394 | return; 395 | } 396 | if (success) { 397 | reporter.onReport(KEY_APPLIED); 398 | } 399 | 400 | if (isUpgrade) { 401 | if (success) { 402 | reporter.onReport(KEY_APPLIED_UPGRADE); 403 | } else { 404 | reporter.onReport(KEY_APPLIED_UPGRADE_FAIL); 405 | } 406 | 407 | } else { 408 | if (success) { 409 | reporter.onReport(KEY_APPLIED_REPAIR); 410 | } else { 411 | reporter.onReport(KEY_APPLIED_REPAIR_FAIL); 412 | } 413 | } 414 | 415 | TinkerLog.i(TAG, "hp_report report apply cost = %d", cost); 416 | 417 | if (cost < 0L) { 418 | TinkerLog.e(TAG, "hp_report report apply cost failed, invalid cost"); 419 | return; 420 | } 421 | 422 | if (cost <= 5000) { 423 | if (success) { 424 | reporter.onReport(KEY_APPLIED_SUCC_COST_5S_LESS); 425 | } else { 426 | reporter.onReport(KEY_APPLIED_FAIL_COST_5S_LESS); 427 | } 428 | } else if (cost <= 10 * 1000) { 429 | if (success) { 430 | reporter.onReport(KEY_APPLIED_SUCC_COST_10S_LESS); 431 | } else { 432 | reporter.onReport(KEY_APPLIED_FAIL_COST_10S_LESS); 433 | } 434 | } else if (cost <= 30 * 1000) { 435 | if (success) { 436 | reporter.onReport(KEY_APPLIED_SUCC_COST_30S_LESS); 437 | } else { 438 | reporter.onReport(KEY_APPLIED_FAIL_COST_30S_LESS); 439 | } 440 | } else if (cost <= 60 * 1000) { 441 | if (success) { 442 | reporter.onReport(KEY_APPLIED_SUCC_COST_60S_LESS); 443 | } else { 444 | reporter.onReport(KEY_APPLIED_FAIL_COST_60S_LESS); 445 | } 446 | } else { 447 | if (success) { 448 | reporter.onReport(KEY_APPLIED_SUCC_COST_OTHER); 449 | } else { 450 | reporter.onReport(KEY_APPLIED_FAIL_COST_OTHER); 451 | } 452 | } 453 | } 454 | 455 | public static void onApplyPackageCheckFail(int errorCode) { 456 | if (reporter == null) { 457 | return; 458 | } 459 | TinkerLog.i(TAG, "hp_report package check failed, error = %d", errorCode); 460 | 461 | switch (errorCode) { 462 | case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL: 463 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_SIGNATURE); 464 | break; 465 | case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED: 466 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_DEX_META); 467 | break; 468 | case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED: 469 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_LIB_META); 470 | break; 471 | case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND: 472 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND); 473 | break; 474 | case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND: 475 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND); 476 | break; 477 | case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL: 478 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL); 479 | break; 480 | case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND: 481 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND); 482 | break; 483 | case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED: 484 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_RES_META); 485 | break; 486 | } 487 | } 488 | 489 | public static void onApplyCrash(Throwable throwable) { 490 | if (reporter == null) { 491 | return; 492 | } 493 | reporter.onReport(KEY_APPLIED_EXCEPTION); 494 | reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable)); 495 | } 496 | 497 | public static void onFastCrashProtect() { 498 | if (reporter == null) { 499 | return; 500 | } 501 | reporter.onReport(KEY_CRASH_FAST_PROTECT); 502 | } 503 | 504 | public static void onXposedCrash() { 505 | if (reporter == null) { 506 | return; 507 | } 508 | if (ShareTinkerInternals.isVmArt()) { 509 | reporter.onReport(KEY_CRASH_CAUSE_XPOSED_ART); 510 | } else { 511 | reporter.onReport(KEY_CRASH_CAUSE_XPOSED_DALVIK); 512 | } 513 | } 514 | 515 | public static void onReportRetryPatch() { 516 | if (reporter == null) { 517 | return; 518 | } 519 | reporter.onReport(KEY_APPLY_WITH_RETRY); 520 | } 521 | 522 | } 523 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/service/SampleResultService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.service; 18 | 19 | import android.content.BroadcastReceiver; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.content.IntentFilter; 23 | import android.os.Handler; 24 | import android.os.Looper; 25 | import android.widget.Toast; 26 | 27 | import com.tencent.tinker.lib.service.DefaultTinkerResultService; 28 | import com.tencent.tinker.lib.service.PatchResult; 29 | import com.tencent.tinker.lib.tinker.Tinker; 30 | import com.tencent.tinker.lib.util.TinkerLog; 31 | import com.tencent.tinker.lib.util.TinkerServiceInternals; 32 | import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; 33 | 34 | import java.io.File; 35 | import java.util.zip.ZipFile; 36 | 37 | import tinker.sample.android.util.Utils; 38 | 39 | /** 40 | * optional, you can just use DefaultTinkerResultService 41 | * we can restart process when we are at background or screen off 42 | * Created by zhangshaowen on 16/4/13. 43 | */ 44 | public class SampleResultService extends DefaultTinkerResultService { 45 | private static final String TAG = "Tinker.SampleResultService"; 46 | 47 | 48 | @Override 49 | public void onPatchResult(final PatchResult result) { 50 | if (result == null) { 51 | TinkerLog.e(TAG, "SampleResultService received null result!!!!"); 52 | return; 53 | } 54 | TinkerLog.i(TAG, "SampleResultService receive result: %s", result.toString()); 55 | 56 | //first, we want to kill the recover process 57 | TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext()); 58 | 59 | Handler handler = new Handler(Looper.getMainLooper()); 60 | handler.post(new Runnable() { 61 | @Override 62 | public void run() { 63 | if (result.isSuccess) { 64 | Toast.makeText(getApplicationContext(), "patch success, please restart process", Toast.LENGTH_LONG).show(); 65 | } else { 66 | Toast.makeText(getApplicationContext(), "patch fail, please check reason", Toast.LENGTH_LONG).show(); 67 | } 68 | } 69 | }); 70 | // is success and newPatch, it is nice to delete the raw file, and restart at once 71 | // for old patch, you can't delete the patch file 72 | if (result.isSuccess && result.isUpgradePatch) { 73 | File rawFile = new File(result.rawPatchFilePath); 74 | if (rawFile.exists()) { 75 | TinkerLog.i(TAG, "save delete raw patch file"); 76 | SharePatchFileUtil.safeDeleteFile(rawFile); 77 | } 78 | //not like TinkerResultService, I want to restart just when I am at background! 79 | //if you have not install tinker this moment, you can use TinkerApplicationHelper api 80 | if (checkIfNeedKill(result)) { 81 | if (Utils.isBackground()) { 82 | TinkerLog.i(TAG, "it is in background, just restart process"); 83 | restartProcess(); 84 | } else { 85 | //we can wait process at background, such as onAppBackground 86 | //or we can restart when the screen off 87 | TinkerLog.i(TAG, "tinker wait screen to restart process"); 88 | new ScreenState(getApplicationContext(), new ScreenState.IOnScreenOff() { 89 | @Override 90 | public void onScreenOff() { 91 | restartProcess(); 92 | } 93 | }); 94 | } 95 | } else { 96 | TinkerLog.i(TAG, "I have already install the newly patch version!"); 97 | } 98 | } 99 | 100 | //repair current patch fail, just clean! 101 | if (!result.isSuccess && !result.isUpgradePatch) { 102 | //if you have not install tinker this moment, you can use TinkerApplicationHelper api 103 | Tinker.with(getApplicationContext()).cleanPatch(); 104 | } 105 | } 106 | 107 | /** 108 | * you can restart your process through service or broadcast 109 | */ 110 | private void restartProcess() { 111 | TinkerLog.i(TAG, "app is background now, i can kill quietly"); 112 | //you can send service or broadcast intent to restart your process 113 | android.os.Process.killProcess(android.os.Process.myPid()); 114 | } 115 | 116 | static class ScreenState { 117 | interface IOnScreenOff { 118 | void onScreenOff(); 119 | } 120 | 121 | ScreenState(Context context, final IOnScreenOff onScreenOffInterface) { 122 | IntentFilter filter = new IntentFilter(); 123 | filter.addAction(Intent.ACTION_SCREEN_OFF); 124 | context.registerReceiver(new BroadcastReceiver() { 125 | 126 | @Override 127 | public void onReceive(Context context, Intent in) { 128 | String action = in == null ? "" : in.getAction(); 129 | TinkerLog.i(TAG, "ScreenReceiver action [%s] ", action); 130 | if (Intent.ACTION_SCREEN_OFF.equals(action)) { 131 | 132 | context.unregisterReceiver(this); 133 | 134 | if (onScreenOffInterface != null) { 135 | onScreenOffInterface.onScreenOff(); 136 | } 137 | } 138 | } 139 | }, filter); 140 | } 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/util/SampleApplicationContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.util; 18 | 19 | import android.app.Application; 20 | import android.content.Context; 21 | 22 | /** 23 | * Created by zhangshaowen on 16/8/9. 24 | */ 25 | public class SampleApplicationContext { 26 | public static Application application = null; 27 | public static Context context = null; 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/util/TinkerManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.util; 18 | 19 | import com.tencent.tinker.lib.listener.PatchListener; 20 | import com.tencent.tinker.lib.patch.AbstractPatch; 21 | import com.tencent.tinker.lib.patch.RepairPatch; 22 | import com.tencent.tinker.lib.patch.UpgradePatch; 23 | import com.tencent.tinker.lib.reporter.LoadReporter; 24 | import com.tencent.tinker.lib.reporter.PatchReporter; 25 | import com.tencent.tinker.lib.tinker.TinkerInstaller; 26 | import com.tencent.tinker.lib.util.TinkerLog; 27 | import com.tencent.tinker.loader.app.ApplicationLike; 28 | 29 | import tinker.sample.android.crash.SampleUncaughtExceptionHandler; 30 | import tinker.sample.android.reporter.SampleLoadReporter; 31 | import tinker.sample.android.reporter.SamplePatchListener; 32 | import tinker.sample.android.reporter.SamplePatchReporter; 33 | import tinker.sample.android.service.SampleResultService; 34 | 35 | /** 36 | * Created by zhangshaowen on 16/7/3. 37 | */ 38 | public class TinkerManager { 39 | private static final String TAG = "Tinker.TinkerManager"; 40 | 41 | private static ApplicationLike applicationLike; 42 | private static SampleUncaughtExceptionHandler uncaughtExceptionHandler; 43 | private static boolean isInstalled = false; 44 | 45 | public static void setTinkerApplicationLike(ApplicationLike appLike) { 46 | applicationLike = appLike; 47 | } 48 | 49 | public static ApplicationLike getTinkerApplicationLike() { 50 | return applicationLike; 51 | } 52 | 53 | public static void initFastCrashProtect() { 54 | if (uncaughtExceptionHandler == null) { 55 | uncaughtExceptionHandler = new SampleUncaughtExceptionHandler(); 56 | Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); 57 | } 58 | } 59 | 60 | public static void setUpgradeRetryEnable(boolean enable) { 61 | UpgradePatchRetry.getInstance(applicationLike.getApplication()).setRetryEnable(enable); 62 | } 63 | 64 | 65 | /** 66 | * all use default class, simply Tinker install method 67 | */ 68 | public static void sampleInstallTinker(ApplicationLike appLike) { 69 | if (isInstalled) { 70 | TinkerLog.w(TAG, "install tinker, but has installed, ignore"); 71 | return; 72 | } 73 | TinkerInstaller.install(appLike); 74 | isInstalled = true; 75 | 76 | } 77 | 78 | /** 79 | * you can specify all class you want. 80 | * sometimes, you can only install tinker in some process you want! 81 | * 82 | * @param appLike 83 | */ 84 | public static void installTinker(ApplicationLike appLike) { 85 | if (isInstalled) { 86 | TinkerLog.w(TAG, "install tinker, but has installed, ignore"); 87 | return; 88 | } 89 | //or you can just use DefaultLoadReporter 90 | LoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication()); 91 | //or you can just use DefaultPatchReporter 92 | PatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication()); 93 | //or you can just use DefaultPatchListener 94 | PatchListener patchListener = new SamplePatchListener(appLike.getApplication()); 95 | //you can set your own upgrade patch if you need 96 | AbstractPatch upgradePatchProcessor = new UpgradePatch(); 97 | //you can set your own repair patch if you need 98 | AbstractPatch repairPatchProcessor = new RepairPatch(); 99 | 100 | TinkerInstaller.install(appLike, 101 | loadReporter, patchReporter, patchListener, 102 | SampleResultService.class, upgradePatchProcessor, repairPatchProcessor); 103 | 104 | isInstalled = true; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/util/UpgradePatchRetry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.util; 18 | 19 | import android.content.Context; 20 | import android.content.Intent; 21 | 22 | import com.tencent.tinker.lib.service.TinkerPatchService; 23 | import com.tencent.tinker.lib.tinker.Tinker; 24 | import com.tencent.tinker.lib.tinker.TinkerInstaller; 25 | import com.tencent.tinker.lib.util.TinkerLog; 26 | import com.tencent.tinker.lib.util.TinkerServiceInternals; 27 | import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; 28 | 29 | import java.io.File; 30 | import java.io.FileInputStream; 31 | import java.io.FileOutputStream; 32 | import java.io.IOException; 33 | import java.util.Properties; 34 | 35 | import tinker.sample.android.reporter.SampleTinkerReport; 36 | 37 | /** 38 | * optional 39 | * tinker :patch process may killed by some reason, we can retry it to increase upgrade success rate 40 | * if patch file is at sdcard, copy it to dataDir first. because some software may delete it. 41 | * 42 | * Created by zhangshaowen on 16/7/3. 43 | */ 44 | public class UpgradePatchRetry { 45 | private static final String TAG = "Tinker.UpgradePatchRetry"; 46 | 47 | private static final String RETRY_INFO_NAME = "patch.retry"; 48 | private static final String TEMP_PATCH_NAME = "temp.apk"; 49 | 50 | private static final String RETRY_FILE_MD5_PROPERTY = "md5"; 51 | private static final String RETRY_COUNT_PROPERTY = "times"; 52 | private static final int RETRY_MAX_COUNT = 2; 53 | 54 | private boolean isRetryEnable = false; 55 | private File retryInfoFile = null; 56 | private File tempPatchFile = null; 57 | 58 | private Context context = null; 59 | private static UpgradePatchRetry sInstance; 60 | 61 | /** 62 | * you must set after tinker has installed 63 | * 64 | * @param context 65 | */ 66 | public UpgradePatchRetry(Context context) { 67 | this.context = context; 68 | retryInfoFile = new File(SharePatchFileUtil.getPatchDirectory(context), RETRY_INFO_NAME); 69 | tempPatchFile = new File(SharePatchFileUtil.getPatchDirectory(context), TEMP_PATCH_NAME); 70 | } 71 | 72 | public static UpgradePatchRetry getInstance(Context context) { 73 | if (sInstance == null) { 74 | sInstance = new UpgradePatchRetry(context); 75 | } 76 | return sInstance; 77 | } 78 | 79 | public void onPatchRetryLoad() { 80 | if (!isRetryEnable) { 81 | TinkerLog.w(TAG, "onPatchRetryLoad retry disabled, just return"); 82 | return; 83 | } 84 | Tinker tinker = Tinker.with(context); 85 | //only retry on main process 86 | if (!tinker.isMainProcess()) { 87 | TinkerLog.w(TAG, "onPatchRetryLoad retry is not main process, just return"); 88 | return; 89 | } 90 | 91 | if (!retryInfoFile.exists()) { 92 | TinkerLog.w(TAG, "onPatchRetryLoad retry info not exist, just return"); 93 | return; 94 | } 95 | 96 | if (TinkerServiceInternals.isTinkerPatchServiceRunning(context)) { 97 | TinkerLog.w(TAG, "onPatchRetryLoad tinker service is running, just return"); 98 | return; 99 | } 100 | //must use temp file 101 | String path = tempPatchFile.getAbsolutePath(); 102 | if (path == null || !new File(path).exists()) { 103 | TinkerLog.w(TAG, "onPatchRetryLoad patch file: %s is not exist, just return", path); 104 | return; 105 | } 106 | TinkerLog.w(TAG, "onPatchRetryLoad patch file: %s is exist, retry to patch", path); 107 | TinkerInstaller.onReceiveUpgradePatch(context, path); 108 | SampleTinkerReport.onReportRetryPatch(); 109 | } 110 | 111 | private void copyToTempFile(File patchFile) { 112 | if (patchFile.getAbsolutePath().equals(tempPatchFile.getAbsolutePath())) { 113 | return; 114 | } 115 | TinkerLog.w(TAG, "try copy file: %s to %s", patchFile.getAbsolutePath(), tempPatchFile.getAbsolutePath()); 116 | 117 | try { 118 | SharePatchFileUtil.copyFileUsingStream(patchFile, tempPatchFile); 119 | } catch (IOException e) { 120 | } 121 | } 122 | 123 | public void onPatchServiceStart(Intent intent) { 124 | if (!isRetryEnable) { 125 | TinkerLog.w(TAG, "onPatchServiceStart retry disabled, just return"); 126 | return; 127 | } 128 | 129 | if (intent == null) { 130 | TinkerLog.e(TAG, "onPatchServiceStart intent is null, just return"); 131 | return; 132 | } 133 | 134 | boolean isUpgrade = TinkerPatchService.getPatchUpgradeExtra(intent); 135 | 136 | if (!isUpgrade) { 137 | TinkerLog.w(TAG, "onPatchServiceStart is not upgrade patch, just return"); 138 | return; 139 | } 140 | 141 | String path = TinkerPatchService.getPatchPathExtra(intent); 142 | 143 | if (path == null) { 144 | TinkerLog.w(TAG, "onPatchServiceStart patch path is null, just return"); 145 | return; 146 | } 147 | 148 | RetryInfo retryInfo; 149 | File patchFile = new File(path); 150 | 151 | String patchMd5 = SharePatchFileUtil.getMD5(patchFile); 152 | 153 | if (retryInfoFile.exists()) { 154 | retryInfo = RetryInfo.readRetryProperty(retryInfoFile); 155 | if (retryInfo.md5 == null || retryInfo.times == null || !patchMd5.equals(retryInfo.md5)) { 156 | copyToTempFile(patchFile); 157 | retryInfo.md5 = patchMd5; 158 | retryInfo.times = "1"; 159 | } else { 160 | int nowTimes = Integer.parseInt(retryInfo.times); 161 | if (nowTimes >= RETRY_MAX_COUNT) { 162 | SharePatchFileUtil.safeDeleteFile(retryInfoFile); 163 | SharePatchFileUtil.safeDeleteFile(tempPatchFile); 164 | TinkerLog.w(TAG, "onPatchServiceStart retry more than max count, delete retry info file!"); 165 | return; 166 | } else { 167 | retryInfo.times = String.valueOf(nowTimes + 1); 168 | } 169 | } 170 | 171 | } else { 172 | copyToTempFile(patchFile); 173 | retryInfo = new RetryInfo(patchMd5, "1"); 174 | } 175 | 176 | RetryInfo.writeRetryProperty(retryInfoFile, retryInfo); 177 | 178 | } 179 | 180 | /** 181 | * if we receive any result, we can delete the temp retry info file 182 | * 183 | * @param isUpgradePatch 184 | */ 185 | public void onPatchServiceResult(boolean isUpgradePatch) { 186 | if (!isRetryEnable) { 187 | TinkerLog.w(TAG, "onPatchServiceResult retry disabled, just return"); 188 | return; 189 | } 190 | 191 | if (!isUpgradePatch) { 192 | TinkerLog.w(TAG, "onPatchServiceResult is not upgrade patch, just return"); 193 | return; 194 | } 195 | 196 | //delete info file 197 | if (retryInfoFile.exists()) { 198 | SharePatchFileUtil.safeDeleteFile(retryInfoFile); 199 | } 200 | //delete temp patch file 201 | if (tempPatchFile.exists()) { 202 | SharePatchFileUtil.safeDeleteFile(tempPatchFile); 203 | } 204 | } 205 | 206 | public void setRetryEnable(boolean enable) { 207 | isRetryEnable = enable; 208 | } 209 | 210 | static class RetryInfo { 211 | String md5; 212 | String times; 213 | 214 | RetryInfo(String md5, String times) { 215 | this.md5 = md5; 216 | this.times = times; 217 | } 218 | 219 | static RetryInfo readRetryProperty(File infoFile) { 220 | String md5 = null; 221 | String times = null; 222 | 223 | Properties properties = new Properties(); 224 | FileInputStream inputStream = null; 225 | try { 226 | inputStream = new FileInputStream(infoFile); 227 | properties.load(inputStream); 228 | md5 = properties.getProperty(RETRY_FILE_MD5_PROPERTY); 229 | times = properties.getProperty(RETRY_COUNT_PROPERTY); 230 | } catch (IOException e) { 231 | e.printStackTrace(); 232 | } finally { 233 | SharePatchFileUtil.closeQuietly(inputStream); 234 | } 235 | 236 | return new RetryInfo(md5, times); 237 | } 238 | 239 | static void writeRetryProperty(File infoFile, RetryInfo info) { 240 | if (info == null) { 241 | return; 242 | } 243 | 244 | File parentFile = infoFile.getParentFile(); 245 | if (!parentFile.exists()) { 246 | parentFile.mkdirs(); 247 | } 248 | 249 | Properties newProperties = new Properties(); 250 | newProperties.put(RETRY_FILE_MD5_PROPERTY, info.md5); 251 | newProperties.put(RETRY_COUNT_PROPERTY, info.times); 252 | FileOutputStream outputStream = null; 253 | try { 254 | outputStream = new FileOutputStream(infoFile, false); 255 | newProperties.store(outputStream, null); 256 | } catch (Exception e) { 257 | // e.printStackTrace(); 258 | TinkerLog.printErrStackTrace(TAG, e, "retry write property fail"); 259 | } finally { 260 | SharePatchFileUtil.closeQuietly(outputStream); 261 | } 262 | 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /app/src/main/java/tinker/sample/android/util/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making Tinker available. 3 | * 4 | * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tinker.sample.android.util; 18 | 19 | import android.os.Environment; 20 | import android.os.StatFs; 21 | 22 | import com.tencent.tinker.loader.shareutil.ShareConstants; 23 | 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.io.PrintStream; 28 | 29 | /** 30 | * Created by zhangshaowen on 16/4/7. 31 | */ 32 | public class Utils { 33 | 34 | /** 35 | * the error code define by myself 36 | * should after {@code ShareConstants.ERROR_PATCH_INSERVICE 37 | */ 38 | public static final int ERROR_PATCH_GOOGLEPLAY_CHANNEL = -5; 39 | public static final int ERROR_PATCH_ROM_SPACE = -6; 40 | public static final int ERROR_PATCH_MEMORY_LIMIT = -7; 41 | public static final int ERROR_PATCH_ALREADY_APPLY = -8; 42 | public static final int ERROR_PATCH_CRASH_LIMIT = -9; 43 | public static final int ERROR_PATCH_CONDITION_NOT_SATISFIED = -10; 44 | 45 | public static final String PLATFORM = "platform"; 46 | 47 | public static final int MIN_MEMORY_HEAP_SIZE = 45; 48 | 49 | private static boolean background = false; 50 | 51 | public static boolean isGooglePlay() { 52 | return false; 53 | } 54 | 55 | public static boolean isBackground() { 56 | return background; 57 | } 58 | 59 | public static void setBackground(boolean back) { 60 | background = back; 61 | } 62 | 63 | public static int checkForPatchRecover(long roomSize, int maxMemory) { 64 | if (Utils.isGooglePlay()) { 65 | return Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL; 66 | } 67 | if (maxMemory < MIN_MEMORY_HEAP_SIZE) { 68 | return Utils.ERROR_PATCH_MEMORY_LIMIT; 69 | } 70 | //or you can mention user to clean their rom space! 71 | if (!checkRomSpaceEnough(roomSize)) { 72 | return Utils.ERROR_PATCH_ROM_SPACE; 73 | } 74 | 75 | return ShareConstants.ERROR_PATCH_OK; 76 | } 77 | 78 | public static boolean isXposedExists(Throwable thr) { 79 | StackTraceElement[] stackTraces = thr.getStackTrace(); 80 | for (StackTraceElement stackTrace : stackTraces) { 81 | final String clazzName = stackTrace.getClassName(); 82 | if (clazzName != null && clazzName.contains("de.robv.android.xposed.XposedBridge")) { 83 | return true; 84 | } 85 | } 86 | return false; 87 | } 88 | 89 | @Deprecated 90 | public static boolean checkRomSpaceEnough(long limitSize) { 91 | long allSize; 92 | long availableSize = 0; 93 | try { 94 | File data = Environment.getDataDirectory(); 95 | StatFs sf = new StatFs(data.getPath()); 96 | availableSize = (long) sf.getAvailableBlocks() * (long) sf.getBlockSize(); 97 | allSize = (long) sf.getBlockCount() * (long) sf.getBlockSize(); 98 | } catch (Exception e) { 99 | allSize = 0; 100 | } 101 | 102 | if (allSize != 0 && availableSize > limitSize) { 103 | return true; 104 | } 105 | return false; 106 | } 107 | 108 | public static String getExceptionCauseString(final Throwable ex) { 109 | final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 110 | final PrintStream ps = new PrintStream(bos); 111 | 112 | try { 113 | // print directly 114 | Throwable t = ex; 115 | while (t.getCause() != null) { 116 | t = t.getCause(); 117 | } 118 | t.printStackTrace(ps); 119 | return toVisualString(bos.toString()); 120 | } finally { 121 | try { 122 | bos.close(); 123 | } catch (IOException e) { 124 | e.printStackTrace(); 125 | } 126 | } 127 | } 128 | 129 | private static String toVisualString(String src) { 130 | boolean cutFlg = false; 131 | 132 | if (null == src) { 133 | return null; 134 | } 135 | 136 | char[] chr = src.toCharArray(); 137 | if (null == chr) { 138 | return null; 139 | } 140 | 141 | int i = 0; 142 | for (; i < chr.length; i++) { 143 | if (chr[i] > 127) { 144 | chr[i] = 0; 145 | cutFlg = true; 146 | break; 147 | } 148 | } 149 | 150 | if (cutFlg) { 151 | return new String(chr, 0, i); 152 | } else { 153 | return src; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 |