├── .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 |
26 |
27 |
28 |
36 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | tinker-sample-android
3 | I am in the base apk
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/test/java/tinker/sample/android/ExampleUnitTest.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 org.junit.Test;
20 |
21 | import static org.junit.Assert.*;
22 |
23 | /**
24 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
25 | */
26 | public class ExampleUnitTest {
27 | @Test
28 | public void addition_isCorrect() throws Exception {
29 | assertEquals(4, 2 + 2);
30 | }
31 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | buildscript {
3 | repositories {
4 | mavenLocal()
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.0'
9 | classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | mavenLocal()
16 | jcenter()
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | TINKER_VERSION=1.6.2
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Oct 21 11:34:03 PDT 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/screenshots/assembleDebug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/screenshots/assembleDebug.png
--------------------------------------------------------------------------------
/screenshots/bug_apk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/screenshots/bug_apk.png
--------------------------------------------------------------------------------
/screenshots/patch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/screenshots/patch.png
--------------------------------------------------------------------------------
/screenshots/tinker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/screenshots/tinker.gif
--------------------------------------------------------------------------------
/screenshots/tinkerPatchDebug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinghongfei/Hello-Tinker/673283755f0a747ef1975f8940b8edf1fdebe331/screenshots/tinkerPatchDebug.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/updateTinkerLib.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | rm -rf ~/.gradle/caches/modules-2/metadata-2.16/descriptors/com.tencent.tinker
3 |
4 | #rm -rf ~/.m2/repository/com/tencent/tinker
5 | #adb push ./app/build/outputs/tinkerPatch/debug/patch_signed_7zip.apk /storage/sdcard0/
--------------------------------------------------------------------------------