├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
└── runConfigurations.xml
├── .travis.yml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── keep_in_main_dex.txt
├── keystore
│ ├── debug.keystore
│ └── release.keystore
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── tinker
│ │ └── sample
│ │ └── android
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── tinker
│ │ │ └── sample
│ │ │ └── android
│ │ │ ├── Log
│ │ │ └── MyLogImp.java
│ │ │ ├── app
│ │ │ ├── MainActivity.java
│ │ │ └── SampleApplicationLike.java
│ │ │ ├── crash
│ │ │ └── SampleUncaughtExceptionHandler.java
│ │ │ ├── patchserver
│ │ │ ├── SamplePatchRequestCallback.java
│ │ │ └── TinkerServerManager.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
├── settings.gradle
├── tinker
└── bakApk
│ ├── base-app-debug-v1.0.1-2016-1125-R.txt
│ ├── base-app-debug-v1.0.1-2016-1125.apk
│ ├── base-app-release-v1.0.1-2016-1125-R.txt
│ ├── base-app-release-v1.0.1-2016-1125-mapping.txt
│ └── base-app-release-v1.0.1-2016-1125.apk
└── updateTinkerLib.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | .idea/
3 | *.iml
4 | /local.properties
5 | .DS_Store
6 | /build
7 | /captures
8 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | Android Lint
46 |
47 |
48 | Java
49 |
50 |
51 | Java language level migration aidsJava
52 |
53 |
54 | Performance issuesJava
55 |
56 |
57 | Portability issuesJava
58 |
59 |
60 | Probable bugsJava
61 |
62 |
63 |
64 |
65 | Android
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | 1.8
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk: openjdk7
3 | sudo: false
4 | android:
5 | components:
6 | - tools
7 | - platform-tools
8 | - build-tools-23.0.2
9 | - android-23
10 | - extra-google-m2repository
11 | - extra-android-m2repository
12 | git:
13 | submodules: false
14 | before_install:
15 | - chmod +x gradlew
16 | script:
17 | - "./gradlew assembleDebug"
18 | deploy:
19 | provider: releases
20 | api_key:
21 | secure: b3myAZkOrcdhnXlvd+mwoWDCQfk3KUJHSbEv1pGacUO3pUHrJhD/pUIpMJXb0VwbtIxkIELV55SGDRfUPApN+9yk+rkRw/KU+luUtq8HaDq+bkNLCa4pOilD4XpHGpYtDnNkKrAaxQ8cPO4KFmriqAhzUs8c7IFYdYLYT1k8s0pzpHoiFdnFIqUQ7k7tAtBn2W9cyLAQdwPwVF1TBDw62mMNdeEYgjUq5uD+3rF7gZi5lAEXiwmP2GgJxOLsb7sQwy9HaoUfFPQujg3VM2VYnzkZKmwDZp9w7bcxZgadEWVz3nWieSxaXqfkK9WGyraU6RWRrKgOzZnK6L8ai7qhahZ3hSxVVbikbcQTX9OBmssT0Of/j+sChDQnAH/MaAdCRHF8PQLJoEaagmBlpDzcnwyOPNAWlJ2EF7IfO6EEYVkyx6X3lJAZGGwO/Ip3OWDJ3Z0FVXnwG/hqAXMVkFLwv0t9zrFMjGMNIQ4PUDp/EFPtXt0Xr871xbbbH1HvHuz95BDCwH0PXVoXxCgVWuvfSUZHNUS8SNywcR/lqX5YpliBLdaVElTX1O+CzM4HBQBASo2RU28VwBxO+8JDzMxCG0W+sVHr0Ah2IDC7RRrj0yXBVWuSkNOmo3TiT8jjKKaBPfvQ+cniYPSz9iTUmq63ONmsh8QWIa0Lbf+wgRNLF34=
22 | file: ''
23 | on:
24 | repo: jp1017/tinker-sample-android
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 微信热补丁 `tinker` 及 `tinker server` 官方示例
2 |
3 | [](https://travis-ci.org/jp1017/tinker-sample-android)
4 |
5 |
6 |
7 | # 版本说明
8 |
9 | ## v1.0.6 增加tinker server
10 |
11 | ## v1.0.3 add CI
12 |
13 | ## v1.0.2 补丁测试
14 |
15 | ## v1.0.1 基础包
16 |
17 | + 该版本作为基础包, 分为debug和release
18 |
19 | # tinker 接入指南
20 |
21 | ## 安装tinker gradle插件
22 |
23 | 1 在项目的build.gradle中, 添加tinker-patch-gradle-plugin的依赖
24 |
25 | ```
26 | buildscript {
27 | dependencies {
28 | classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.5')
29 | }
30 | }
31 | ```
32 |
33 | 2 然后在app的gradle文件app/build.gradle,我们需要添加tinker的库依赖以及apply tinker的gradle插件.
34 |
35 | //apply tinker插件
36 | >apply plugin: 'com.tencent.tinker.patch'
37 |
38 | ```
39 | dependencies {
40 | //可选,用于生成application类
41 | provided('com.tencent.tinker:tinker-android-anno:1.7.5')
42 | //tinker的核心库
43 | compile('com.tencent.tinker:tinker-android-lib:1.7.5')
44 | }
45 | ```
46 |
47 | ## 配置tinker task
48 |
49 | 配置基础包, tinkerid, dexMode等,详见gradle配置: [tinker task 配置](https://github.com/jp1017/tinker-sample-android/blob/master/app/build.gradle)
50 |
51 | 我做了如下修改:
52 |
53 | 1 修改tinkerid为版本号, 跳过了需要commit一次的坑:smile:
54 | ```
55 | def getTinkerIdValue() {
56 | //版本作为id
57 | return android.defaultConfig.versionName
58 | }
59 | ```
60 | 2 移动备份文件到/tinker/bakApk/下, 防止clean掉基础包文件
61 |
62 | 3 重命名备份文件, 比如`base-app-debug-v1.0.1-2016-1125.apk`, 当然自动生成的是`app-debug-v1.0.1-2016-1125.apk`, 需要手动添加前缀作为基础包, 后面多次编译不会把基础包覆盖掉, 也不会像官方demo里那样以秒命名产生很多文件...
63 |
64 | 4 修改tinker message 为 `I am the patch apk-v版本号`
65 |
66 | 5 修改patchVersion为版本号, 这个在tinker server需要
67 |
68 | ```
69 | -configField("patchVersion", "1.0.7")
70 | +configField("patchVersion", android.defaultConfig.versionName)
71 | ```
72 |
73 |
74 | **注意** 里面有些修改的地方, 包名修改为你的包名等, 我用todo做了标记
75 |
76 | ## 生成 Application
77 |
78 | 如果你有Application类, 那么需要自定义一个DefaultApplicationLike, 让tinker帮你生成Application
79 |
80 | 正如项目里的`public class SampleApplicationLike extends DefaultApplicationLike {`
81 |
82 | 并对类添加注解, 比如添加如下注解:
83 |
84 | ```
85 | @DefaultLifeCycle(
86 | application = "tinker.sample.android.app.SampleApplication", //application name to generate
87 | flags = ShareConstants.TINKER_ENABLE_ALL)
88 | ```
89 |
90 | 编译后, 会生成一个SampleApplication, 用这个作为你的Application, 写入清单文件
91 |
92 | 好了, tinker到这里就配置好了, 下面开始打补丁
93 |
94 | ## 打补丁包
95 |
96 | 1 命令行
97 |
98 | 打debug补丁: `./gradlew tinkerPatchDebug`
99 |
100 | 打release补丁: `./gradlew tinkerReleaseDebug`
101 |
102 | 这里需要注意, 命令在linux和mac下最好是`./gradlew`, 意思是当前项目的gradlew, 如果写成`gradlew`可以会去下载gradle等, 因为那是全局的, 比如AS2.2.2带的版本是2.14.1
103 | 而我现在的是最新版本3.2.1, 可输入`./gradlew -v` 和 `gradlew -v` 查看
104 | 而windows就可以是`gradlew`
105 |
106 | **注意** debug和release配置的基包不同, 和他们一一对应, 另外, release还需要配置mapping文件.
107 |
108 | 2 双击对应task
109 |
110 | 就是去gradle projects里找到对应task, 双击执行就可以, 如下图:
111 |
112 | 
113 |
114 | 比如, 打debug补丁, 双击`tinkerPatchDebug`就可以了
115 |
116 | 下一次打补丁时就可以从快捷栏选择,然后点击右侧运行, 如下图:
117 |
118 | 
119 |
120 | ## 安装及卸载补丁
121 |
122 | ### 加载补丁
123 | 第二个参数是补丁包存放路径, 名称任意, 可以不以 `.bak` 结尾
124 |
125 | >TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), patchPath);
126 |
127 | 还可以自定义加载成功等交互, 请参考 `SampleResultService`, 别忘记添加进清单
128 |
129 | ### 清除补丁
130 |
131 | 当补丁出现异常或者某些情况,我们可能希望清空全部补丁,调用方法为:
132 |
133 | >Tinker.with(context).cleanPatch();
134 |
135 | 当然我们也可以选择卸载某个版本的补丁文件:
136 |
137 | >Tinker.with(context).cleanPatchByVersion();
138 |
139 | 在升级版本时我们也无须手动去清除补丁,框架已经为我们做了这件事情。需要注意的是,在补丁已经加载的前提下清除补丁,可能会引起crash。这个时候更好重启一下所有的进程。
140 |
141 | ### 查看补丁是否加载
142 |
143 | >boolean isPatched = tinker.isTinkerLoaded();
144 |
145 | # tinker server 接入及使用
146 |
147 | tinker server 提供tinker补丁包下发及监控等, 使用也是很简单
148 |
149 | ## gradle 配置环境
150 |
151 | 1 gradle远程仓库依赖jcenter
152 |
153 | ```
154 | repositories {
155 | jcenter()
156 | }
157 | ```
158 |
159 | 2 再添加sdk库的dependencies依赖:
160 | ```
161 | dependencies {
162 | compile("com.tencent.tinker:tinker-server-android:0.3.0")
163 | }
164 | ```
165 |
166 | 3 在 TinkerPatch 平台中得到的 AppKey 以及 AppVersion,将他们写入 buildConfig 中:
167 |
168 | 比如:
169 |
170 | ```
171 | buildConfigField "String", "APP_KEY", "\"f938475486f91936\""
172 | buildConfigField "String", "APP_VERSION", "\"3.0.0\""
173 | ```
174 | 平台链接: [tinkerpatch.com](http://tinkerpatch.com/)
175 |
176 | 新增app后可以得到AppKey, 至于AppVersion, 就是补丁的版本, 我这里都是版本号, 可以参考这个issue: [关于AppVersion问题](https://github.com/simpleton/tinker_server_client/issues/2)
177 |
178 | 4 清单配置网络及sd卡读写权限
179 |
180 | ```
181 |
182 |
183 |
184 | ```
185 |
186 |
187 | ## 代码初始化
188 |
189 | >TinkerServerManager.installTinkerServer(getApplication(), Tinker.with(getApplication()), 3);
190 |
191 | 后面的3表示每隔3小时请求一次服务器, 检查是否有更新包
192 |
193 | ## 请求更新补丁
194 |
195 | 1 主动请求更新
196 |
197 | > TinkerServerManager.checkTinkerUpdate(true);
198 |
199 | 2 获取新增参数
200 |
201 | >TinkerServerManager.getDynamicConfig(new ConfigRequestCallback() {...
202 |
203 | 下面来一个该demo的tinker server 截图:
204 |
205 | 
206 |
207 | # 参考
208 |
209 | 更多使用及问题请参考官方文档:
210 |
211 | [Tinker -- 微信Android热补丁方案](https://github.com/Tencent/tinker/wiki)
212 |
213 | [Tinker 接入指南](https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97)
214 |
215 | [Tinker API概览](https://github.com/Tencent/tinker/wiki/Tinker-API%E6%A6%82%E8%A7%88)
216 |
217 | [Tinker 自定义扩展](https://github.com/Tencent/tinker/wiki/Tinker-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%89%A9%E5%B1%95)
218 |
219 | [Tinker 常见问题](https://github.com/Tencent/tinker/wiki/Tinker-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
220 |
221 | [平台使用文档](http://tinkerpatch.com/Docs/api)
222 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
3 | /version.properties
4 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 |
4 | dependencies {
5 | compile fileTree(dir: 'libs', include: ['*.jar'])
6 | testCompile 'junit:junit:4.12'
7 | compile "com.android.support:appcompat-v7:23.4.0"
8 | compile "com.tencent.tinker:tinker-server-android:0.3.0"
9 | compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
10 | provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
11 |
12 | compile "com.android.support:multidex:1.0.1"
13 |
14 | //use to test multiDex
15 | // compile group: 'com.google.guava', name: 'guava', version: '19.0'
16 | // compile "org.scala-lang:scala-library:2.11.7"
17 |
18 | //use for local maven test
19 | // compile("com.tencent.tinker:tinker-android-loader:${TINKER_VERSION}") { changing = true }
20 | // compile("com.tencent.tinker:aosp-dexutils:${TINKER_VERSION}") { changing = true }
21 | // compile("com.tencent.tinker:bsdiff-util:${TINKER_VERSION}") { changing = true }
22 | // compile("com.tencent.tinker:tinker-commons:${TINKER_VERSION}") { changing = true }
23 |
24 | }
25 |
26 | def gitSha() {
27 | try {
28 | // String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim()
29 | String gitRev = android.defaultConfig.versionName
30 | if (gitRev == null) {
31 | throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
32 | }
33 | return gitRev
34 | } catch (Exception e) {
35 | throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
36 | }
37 | }
38 |
39 | def javaVersion = JavaVersion.VERSION_1_7
40 |
41 | android {
42 | compileSdkVersion 23
43 | buildToolsVersion "23.0.3"
44 |
45 | compileOptions {
46 | sourceCompatibility javaVersion
47 | targetCompatibility javaVersion
48 | }
49 | //tinker recommend
50 | dexOptions {
51 | jumboMode = true
52 | }
53 |
54 | //关闭aapt对png优化
55 | aaptOptions{
56 | cruncherEnabled false
57 | }
58 |
59 | signingConfigs {
60 | release {
61 | try {
62 | storeFile file("./keystore/release.keystore")
63 | storePassword "testres"
64 | keyAlias "testres"
65 | keyPassword "testres"
66 | } catch (ex) {
67 | throw new InvalidUserDataException(ex.toString())
68 | }
69 | }
70 |
71 | debug {
72 | storeFile file("./keystore/debug.keystore")
73 | }
74 | }
75 |
76 | defaultConfig {
77 | applicationId "tinker.sample.android"
78 | minSdkVersion 10
79 | targetSdkVersion 22
80 | versionCode 1
81 | versionName "1.0.7"
82 | /**
83 | * you can use multiDex and install it in your ApplicationLifeCycle implement
84 | */
85 | multiDexEnabled true
86 | /**
87 | * not like proguard, multiDexKeepProguard is not a list, so we can't just
88 | * add for you in our task. you can copy tinker keep rules at
89 | * build/intermediates/tinker_intermediates/tinker_multidexkeep.pro
90 | */
91 | multiDexKeepProguard file("keep_in_main_dex.txt")
92 | /**
93 | * buildConfig can change during patch!
94 | * we can use the newly value when patch
95 | */
96 | buildConfigField "String", "MESSAGE", "\"I am the patch apk-v" + "${versionName}\""
97 | // buildConfigField "String", "MESSAGE", "\"I am the patch apk\""
98 | /**
99 | * client version would update with patch
100 | * so we can get the newly git version easily!
101 | */
102 | buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""
103 | buildConfigField "String", "PLATFORM", "\"all\""
104 |
105 | //tinker server
106 | buildConfigField "String", "APP_KEY", "\"392922ac60061245\""
107 | buildConfigField "String", "APP_VERSION", "\"${versionName}\""
108 | }
109 |
110 | // aaptOptions{
111 | // cruncherEnabled false
112 | // }
113 |
114 | // //use to test flavors support
115 | // productFlavors {
116 | // flavor1 {
117 | // applicationId 'tinker.sample.android.flavor1'
118 | // }
119 | //
120 | // flavor2 {
121 | // applicationId 'tinker.sample.android.flavor2'
122 | // }
123 | // }
124 |
125 | buildTypes {
126 | release {
127 | minifyEnabled true
128 | signingConfig signingConfigs.release
129 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
130 | }
131 | debug {
132 | debuggable true
133 | minifyEnabled false
134 | signingConfig signingConfigs.debug
135 | }
136 | }
137 | sourceSets {
138 | main {
139 | jniLibs.srcDirs = ['libs']
140 | }
141 | }
142 | }
143 |
144 | //开始配置tinker task
145 | def bakPath = file("${rootDir}/tinker/bakApk")
146 |
147 | /**
148 | * you can use assembleRelease to build you base apk
149 | * use tinkerPatchRelease -POLD_APK= -PAPPLY_MAPPING= -PAPPLY_RESOURCE= to build patch
150 | * add apk from the build/bakApk
151 | */
152 | ext {
153 | //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
154 | tinkerEnabled = true
155 |
156 | //for normal build
157 | //todo old apk file to build patch apk
158 | tinkerOldApkPath = "${bakPath}/base-app-release-v1.0.1-2016-1125.apk"
159 | //proguard mapping file to build patch apk
160 | tinkerApplyMappingPath = "${bakPath}/base-app-release-v1.0.1-2016-1125-mapping.txt"
161 | //resource R.txt to build patch apk, must input if there is resource changed
162 | tinkerApplyResourcePath = "${bakPath}/base-app-release-v1.0.1-2016-1125-R.txt"
163 |
164 | //only use for build all flavor, if not, just ignore this field
165 | tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
166 | }
167 |
168 |
169 | def getOldApkPath() {
170 | return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
171 | }
172 |
173 | def getApplyMappingPath() {
174 | return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
175 | }
176 |
177 | def getApplyResourceMappingPath() {
178 | return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
179 | }
180 |
181 | def getTinkerIdValue() {
182 | //版本作为id
183 | return android.defaultConfig.versionName
184 | // return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
185 | }
186 |
187 | def buildWithTinker() {
188 | return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
189 | }
190 |
191 | def getTinkerBuildFlavorDirectory() {
192 | return ext.tinkerBuildFlavorDirectory
193 | }
194 |
195 | if (buildWithTinker()) {
196 | apply plugin: 'com.tencent.tinker.patch'
197 |
198 | tinkerPatch {
199 | /**
200 | * necessary,default 'null'
201 | * the old apk path, use to diff with the new apk to build
202 | * add apk from the build/bakApk
203 | */
204 | oldApk = getOldApkPath()
205 | /**
206 | * optional,default 'false'
207 | * there are some cases we may get some warnings
208 | * if ignoreWarning is true, we would just assert the patch process
209 | * case 1: minSdkVersion is below 14, but you are using dexMode with raw.
210 | * it must be crash when load.
211 | * case 2: newly added Android Component in AndroidManifest.xml,
212 | * it must be crash when load.
213 | * case 3: loader classes in dex.loader{} are not keep in the main dex,
214 | * it must be let tinker not work.
215 | * case 4: loader classes in dex.loader{} changes,
216 | * loader classes is ues to load patch dex. it is useless to change them.
217 | * it won't crash, but these changes can't effect. you may ignore it
218 | * case 5: resources.arsc has changed, but we don't use applyResourceMapping to build
219 | */
220 | ignoreWarning = false
221 |
222 | /**
223 | * optional,default 'true'
224 | * whether sign the patch file
225 | * if not, you must do yourself. otherwise it can't check success during the patch loading
226 | * we will use the sign config with your build type
227 | */
228 | useSign = true
229 |
230 | /**
231 | * Warning, applyMapping will affect the normal android build!
232 | */
233 | buildConfig {
234 | /**
235 | * optional,default 'null'
236 | * if we use tinkerPatch to build the patch apk, you'd better to apply the old
237 | * apk mapping file if minifyEnabled is enable!
238 | * Warning:
239 | * you must be careful that it will affect the normal assemble build!
240 | */
241 | applyMapping = getApplyMappingPath()
242 | /**
243 | * optional,default 'null'
244 | * It is nice to keep the resource id from R.txt file to reduce java changes
245 | */
246 | applyResourceMapping = getApplyResourceMappingPath()
247 |
248 | /**
249 | * necessary,default 'null'
250 | * because we don't want to check the base apk with md5 in the runtime(it is slow)
251 | * tinkerId is use to identify the unique base apk when the patch is tried to apply.
252 | * we can use git rev, svn rev or simply versionCode.
253 | * we will gen the tinkerId in your manifest automatic
254 | */
255 | tinkerId = getTinkerIdValue()
256 | }
257 |
258 | dex {
259 | /**
260 | * optional,default 'jar'
261 | * only can be 'raw' or 'jar'. for raw, we would keep its original format
262 | * for jar, we would repack dexes with zip format.
263 | * if you want to support below 14, you must use jar
264 | * or you want to save rom or check quicker, you can use raw mode also
265 | */
266 | dexMode = "jar"
267 | /**
268 | * optional,default 'false'
269 | * if usePreGeneratedPatchDex is true, tinker framework will generate auxiliary class
270 | * and insert auxiliary instruction when compiling base package using
271 | * assemble{Debug/Release} task to prevent class pre-verified issue in dvm.
272 | * Besides, a real dex file contains necessary class will be generated and packed into
273 | * patch package instead of any patch info files.
274 | *
275 | * Use this mode if you have to use any dex encryption solutions.
276 | *
277 | * Notice: If you change this value, please trigger clean task
278 | * and regenerate base package.
279 | */
280 | usePreGeneratedPatchDex = false
281 | /**
282 | * necessary,default '[]'
283 | * what dexes in apk are expected to deal with tinkerPatch
284 | * it support * or ? pattern.
285 | */
286 | pattern = ["classes*.dex",
287 | "assets/secondary-dex-?.jar"]
288 | /**
289 | * necessary,default '[]'
290 | * Warning, it is very very important, loader classes can't change with patch.
291 | * thus, they will be removed from patch dexes.
292 | * you must put the following class into main dex.
293 | * Simply, you should add your own application {@code tinker.sample.android.SampleApplication}
294 | * own tinkerLoader, and the classes you use in them
295 | *
296 | */
297 | loader = ["com.tencent.tinker.loader.*",
298 | //todo, you must change it with your application
299 | "tinker.sample.android.app.SampleApplication",
300 | //use sample, let BaseBuildInfo unchangeable with tinker
301 | "tinker.sample.android.app.BaseBuildInfo"
302 | ]
303 | }
304 |
305 | lib {
306 | /**
307 | * optional,default '[]'
308 | * what library in apk are expected to deal with tinkerPatch
309 | * it support * or ? pattern.
310 | * for library in assets, we would just recover them in the patch directory
311 | * you can get them in TinkerLoadResult with Tinker
312 | */
313 | pattern = ["lib/armeabi/*.so"]
314 | }
315 |
316 | res {
317 | /**
318 | * optional,default '[]'
319 | * what resource in apk are expected to deal with tinkerPatch
320 | * it support * or ? pattern.
321 | * you must include all your resources in apk here,
322 | * otherwise, they won't repack in the new apk resources.
323 | */
324 | pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
325 |
326 | /**
327 | * optional,default '[]'
328 | * the resource file exclude patterns, ignore add, delete or modify resource change
329 | * it support * or ? pattern.
330 | * Warning, we can only use for files no relative with resources.arsc
331 | */
332 | ignoreChange = ["assets/sample_meta.txt"]
333 |
334 | /**
335 | * default 100kb
336 | * for modify resource, if it is larger than 'largeModSize'
337 | * we would like to use bsdiff algorithm to reduce patch file size
338 | */
339 | largeModSize = 100
340 | }
341 |
342 | packageConfig {
343 | /**
344 | * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE'
345 | * package meta file gen. path is assets/package_meta.txt in patch file
346 | * you can use securityCheck.getPackageProperties() in your ownPackageCheck method
347 | * or TinkerLoadResult.getPackageConfigByName
348 | * we will get the TINKER_ID from the old apk manifest for you automatic,
349 | * other config files (such as patchMessage below)is not necessary
350 | */
351 | //todo 每次升级时,填写升级信息
352 | configField("patchMessage", "tinker is sample to use")
353 | /**
354 | * just a sample case, you can use such as sdkVersion, brand, channel...
355 | * you can parse it in the SamplePatchListener.
356 | * Then you can use patch conditional!
357 | */
358 | configField("platform", "all")
359 | /**
360 | * tinker server needed. patch version via packageConfig
361 | */
362 | // configField("patchVersion", "1.0.7")
363 | configField("patchVersion", android.defaultConfig.versionName)
364 | }
365 | //or you can add config filed outside, or get meta value from old apk
366 | //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test"))
367 | //project.tinkerPatch.packageConfig.configField("test2", "sample")
368 |
369 | /**
370 | * if you don't use zipArtifact or path, we just use 7za to try
371 | */
372 | sevenZip {
373 | /**
374 | * optional,default '7za'
375 | * the 7zip artifact path, it will use the right 7za with your platform
376 | */
377 | zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
378 | /**
379 | * optional,default '7za'
380 | * todo you can specify the 7za path yourself, it will overwrite the zipArtifact value
381 | */
382 | path = "/usr/bin/7za"
383 | }
384 | }
385 |
386 | List flavors = new ArrayList<>();
387 | project.android.productFlavors.each {flavor ->
388 | flavors.add(flavor.name)
389 | }
390 | boolean hasFlavors = flavors.size() > 0
391 | /**
392 | * bak apk and mapping
393 | */
394 | android.applicationVariants.all { variant ->
395 | /**
396 | * task type, you want to bak
397 | */
398 | def taskName = variant.name
399 | def date = new Date().format("yyyy-MMdd")
400 |
401 | tasks.all {
402 | if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
403 |
404 | it.doLast {
405 | copy {
406 | def fileNamePrefix = "${project.name}-${variant.baseName}"
407 | def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-v${variant.versionName}-${date}"
408 |
409 | def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
410 | from variant.outputs.outputFile
411 | into destPath
412 | rename { String fileName ->
413 | fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
414 | }
415 |
416 | from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
417 | into destPath
418 | rename { String fileName ->
419 | fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
420 | }
421 |
422 | from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
423 | into destPath
424 | rename { String fileName ->
425 | fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
426 | }
427 | }
428 | }
429 | }
430 | }
431 | }
432 | project.afterEvaluate {
433 | //sample use for build all flavor for one time
434 | if (hasFlavors) {
435 | task(tinkerPatchAllFlavorRelease) {
436 | group = 'tinker'
437 | def originOldPath = getTinkerBuildFlavorDirectory()
438 | for (String flavor : flavors) {
439 | def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
440 | dependsOn tinkerTask
441 | def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
442 | preAssembleTask.doFirst {
443 | String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
444 | project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
445 | project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
446 | project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"
447 |
448 | }
449 |
450 | }
451 | }
452 |
453 | task(tinkerPatchAllFlavorDebug) {
454 | group = 'tinker'
455 | def originOldPath = getTinkerBuildFlavorDirectory()
456 | for (String flavor : flavors) {
457 | def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")
458 | dependsOn tinkerTask
459 | def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
460 | preAssembleTask.doFirst {
461 | String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
462 | project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
463 | project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
464 | project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
465 | }
466 |
467 | }
468 | }
469 | }
470 | }
471 | }
472 |
--------------------------------------------------------------------------------
/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/jp1017/tinker-sample-android/c0704ca448d896075e3c045dcb7e1cc2701ec23e/app/keystore/debug.keystore
--------------------------------------------------------------------------------
/app/keystore/release.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp1017/tinker-sample-android/c0704ca448d896075e3c045dcb7e1cc2701ec23e/app/keystore/release.keystore
--------------------------------------------------------------------------------
/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/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.lib.util.TinkerLog;
37 | import com.tencent.tinker.loader.shareutil.ShareConstants;
38 | import com.tencent.tinker.loader.shareutil.ShareTinkerInternals;
39 | import com.tencent.tinker.server.client.ConfigRequestCallback;
40 |
41 | import java.util.regex.Matcher;
42 | import java.util.regex.Pattern;
43 |
44 | import tinker.sample.android.BuildConfig;
45 | import tinker.sample.android.R;
46 | import tinker.sample.android.patchserver.TinkerServerManager;
47 | import tinker.sample.android.util.Utils;
48 |
49 | public class MainActivity extends AppCompatActivity {
50 | private static final String TAG = "Tinker.MainActivity";
51 |
52 | @Override
53 | protected void onCreate(Bundle savedInstanceState) {
54 | super.onCreate(savedInstanceState);
55 | setContentView(R.layout.activity_main);
56 | Log.e(TAG, "i am on onCreate classloader:" + MainActivity.class.getClassLoader().toString());
57 | //test resource change
58 | Log.e(TAG, "i am on onCreate string:" + getResources().getString(R.string.test_resource));
59 | // Log.e(TAG, "i am on patch onCreate");
60 |
61 | Button loadPatchButton = (Button) findViewById(R.id.loadPatch);
62 |
63 | //加载补丁包
64 | loadPatchButton.setOnClickListener(new View.OnClickListener() {
65 | @Override
66 | public void onClick(View v) {
67 | String patchPath = Environment.getExternalStorageDirectory().getAbsolutePath()
68 | + "/patch_signed_7zip.apk";
69 | Log.w("patch path", patchPath);
70 | TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), patchPath);
71 | }
72 | });
73 |
74 | Button loadLibraryButton = (Button) findViewById(R.id.loadLibrary);
75 |
76 | //加载so等
77 | loadLibraryButton.setOnClickListener(new View.OnClickListener() {
78 | @Override
79 | public void onClick(View v) {
80 | //for lib/armeabi, just use TinkerInstaller.loadLibrary
81 | TinkerInstaller.loadArmLibrary(getApplicationContext(), "stlport_shared");
82 | // TinkerInstaller.loadLibraryFromTinker(getApplicationContext(), "assets/x86", "stlport_shared");
83 | }
84 | });
85 |
86 | Button requestPatchButton = (Button) findViewById(R.id.requestPatch);
87 |
88 | //请求更新补丁
89 | requestPatchButton.setOnClickListener(new View.OnClickListener() {
90 | @Override
91 | public void onClick(View v) {
92 | TinkerServerManager.checkTinkerUpdate(true);
93 | }
94 | });
95 |
96 | Button requestConfigButton = (Button) findViewById(R.id.requestConfig);
97 |
98 | //请求新增参数
99 | requestConfigButton.setOnClickListener(new View.OnClickListener() {
100 | @Override
101 | public void onClick(View v) {
102 | TinkerServerManager.getDynamicConfig(new ConfigRequestCallback() {
103 | @Override
104 | public void onSuccess(String s) {
105 | final String config = unicodeToString(s);
106 | TinkerLog.w(TAG, "request config success, config:" + config);
107 | runOnUiThread(new Runnable() {
108 | @Override
109 | public void run() {
110 | Toast.makeText(MainActivity.this, "获取到新增参数: " + config, Toast.LENGTH_SHORT).show();
111 | }
112 | });
113 | }
114 |
115 | @Override
116 | public void onFail(Exception e) {
117 | TinkerLog.w(TAG, "request config failed, exception:" + e);
118 |
119 | runOnUiThread(new Runnable() {
120 | @Override
121 | public void run() {
122 | Toast.makeText(MainActivity.this, "获取新增参数异常", Toast.LENGTH_SHORT).show();
123 | }
124 | });
125 | }
126 | }, true);
127 | }
128 | });
129 |
130 | Button cleanPatchButton = (Button) findViewById(R.id.cleanPatch);
131 |
132 | //卸载补丁
133 | cleanPatchButton.setOnClickListener(new View.OnClickListener() {
134 | @Override
135 | public void onClick(View v) {
136 | Tinker.with(getApplicationContext()).cleanPatch();
137 | }
138 | });
139 |
140 | Button killSelfButton = (Button) findViewById(R.id.killSelf);
141 |
142 | //杀死app
143 | killSelfButton.setOnClickListener(new View.OnClickListener() {
144 | @Override
145 | public void onClick(View v) {
146 | android.os.Process.killProcess(android.os.Process.myPid());
147 | }
148 | });
149 |
150 | Button buildInfoButton = (Button) findViewById(R.id.showInfo);
151 |
152 | //显示信息
153 | buildInfoButton.setOnClickListener(new View.OnClickListener() {
154 | @Override
155 | public void onClick(View v) {
156 | showInfo(MainActivity.this);
157 | }
158 | });
159 | }
160 |
161 |
162 | /**
163 | * unicode to 中文
164 | * @param str
165 | * @return
166 | */
167 | public static String unicodeToString(String str) {
168 |
169 | Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
170 | Matcher matcher = pattern.matcher(str);
171 | char ch;
172 | while (matcher.find()) {
173 | ch = (char) Integer.parseInt(matcher.group(2), 16);
174 | str = str.replace(matcher.group(1), ch + "");
175 | }
176 | return str;
177 | }
178 |
179 |
180 | public boolean showInfo(Context context) {
181 | // add more Build Info
182 | final StringBuilder sb = new StringBuilder();
183 | Tinker tinker = Tinker.with(getApplicationContext());
184 | //判断补丁是否加载
185 | if (tinker.isTinkerLoaded()) {
186 | sb.append(String.format("[patch is loaded] \n"));
187 | sb.append(String.format("[buildConfig TINKER_ID] %s \n", BuildConfig.TINKER_ID));
188 | // sb.append(String.format("[buildConfig BASE_TINKER_ID] %s \n", BuildConfig.BASE_TINKER_ID));
189 |
190 | sb.append(String.format("[buildConfig MESSSAGE] %s \n", BuildConfig.MESSAGE));
191 | sb.append(String.format("[TINKER_ID] %s \n", tinker.getTinkerLoadResultIfPresent().getPackageConfigByName(ShareConstants.TINKER_ID)));
192 | sb.append(String.format("[packageConfig patchMessage] %s \n", tinker.getTinkerLoadResultIfPresent().getPackageConfigByName("patchMessage")));
193 | sb.append(String.format("[TINKER_ID Rom Space] %d k \n", tinker.getTinkerRomSpace()));
194 |
195 | } else {
196 | sb.append(String.format("[patch is not loaded] \n"));
197 | sb.append(String.format("[buildConfig TINKER_ID] %s \n", BuildConfig.TINKER_ID));
198 |
199 | // sb.append(String.format("[buildConfig MESSSAGE] %s \n", BuildConfig.MESSAGE));
200 | sb.append(String.format("[TINKER_ID] %s \n", ShareTinkerInternals.getManifestTinkerID(getApplicationContext())));
201 | }
202 | // sb.append(String.format("[BaseBuildInfo Message] %s \n", BuildConfig.TEST_MESSAGE));
203 |
204 | final TextView v = new TextView(context);
205 | v.setText(sb);
206 | v.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
207 | v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10);
208 | v.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
209 | v.setTextColor(0xFF000000);
210 | v.setTypeface(Typeface.MONOSPACE);
211 | final int padding = 16;
212 | v.setPadding(padding, padding, padding, padding);
213 |
214 | final AlertDialog.Builder builder = new AlertDialog.Builder(context);
215 | builder.setCancelable(true);
216 | builder.setView(v);
217 | final AlertDialog alert = builder.create();
218 | alert.show();
219 | return true;
220 | }
221 |
222 | @Override
223 | protected void onResume() {
224 | Log.e(TAG, "i am on onResume");
225 | // Log.e(TAG, "i am on patch onResume");
226 |
227 | super.onResume();
228 | Utils.setBackground(false);
229 |
230 | }
231 |
232 | @Override
233 | protected void onPause() {
234 | super.onPause();
235 | Utils.setBackground(true);
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/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.Tinker;
30 | import com.tencent.tinker.lib.tinker.TinkerInstaller;
31 | import com.tencent.tinker.loader.app.ApplicationLifeCycle;
32 | import com.tencent.tinker.loader.app.DefaultApplicationLike;
33 | import com.tencent.tinker.loader.shareutil.ShareConstants;
34 |
35 | import tinker.sample.android.Log.MyLogImp;
36 | import tinker.sample.android.patchserver.TinkerServerManager;
37 | import tinker.sample.android.util.SampleApplicationContext;
38 | import tinker.sample.android.util.TinkerManager;
39 |
40 | /**
41 | * because you can not use any other class in your application, we need to
42 | * move your implement of Application to {@link ApplicationLifeCycle}
43 | * As Application, all its direct reference class should be in the main dex.
44 | *
45 | * We use tinker-android-anno to make sure all your classes can be patched.
46 | *
47 | * application: if it is start with '.', we will add SampleApplicationLifeCycle's package name
48 | *
49 | * flags:
50 | * TINKER_ENABLE_ALL: support dex, lib and resource
51 | * TINKER_DEX_MASK: just support dex
52 | * TINKER_NATIVE_LIBRARY_MASK: just support lib
53 | * TINKER_RESOURCE_MASK: just support resource
54 | *
55 | * loaderClass: define the tinker loader class, we can just use the default TinkerLoader
56 | *
57 | * loadVerifyFlag: whether check files' md5 on the load time, defualt it is false.
58 | *
59 | * Created by zhangshaowen on 16/3/17.
60 | */
61 | @SuppressWarnings("unused")
62 | @DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication",
63 | flags = ShareConstants.TINKER_ENABLE_ALL,
64 | loadVerifyFlag = false)
65 | public class SampleApplicationLike extends DefaultApplicationLike {
66 | private static final String TAG = "Tinker.SampleApplicationLike";
67 |
68 | public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
69 | long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent,
70 | Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) {
71 | super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent, resources, classLoader, assetManager);
72 | }
73 |
74 | /**
75 | * install multiDex before install tinker
76 | * so we don't need to put the tinker lib classes in the main dex
77 | *
78 | * @param base
79 | */
80 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
81 | @Override
82 | public void onBaseContextAttached(Context base) {
83 | super.onBaseContextAttached(base);
84 | //you must install multiDex whatever tinker is installed!
85 | MultiDex.install(base);
86 |
87 | SampleApplicationContext.application = getApplication();
88 | SampleApplicationContext.context = getApplication();
89 | TinkerManager.setTinkerApplicationLike(this);
90 | TinkerManager.initFastCrashProtect();
91 | //should set before tinker is installed
92 | TinkerManager.setUpgradeRetryEnable(true);
93 |
94 | //optional set logIml, or you can use default debug log
95 | TinkerInstaller.setLogIml(new MyLogImp());
96 |
97 | //installTinker after load multiDex
98 | //or you can put com.tencent.tinker.** to main dex
99 | TinkerManager.installTinker(this);
100 |
101 |
102 | //初始化TinkerPatch 服务器 SDK
103 | TinkerServerManager.installTinkerServer(getApplication(), Tinker.with(getApplication()), 3);
104 | //每隔访问三小时服务器是否有更新
105 | TinkerServerManager.checkTinkerUpdate(false);
106 | }
107 |
108 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
109 | public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
110 | getApplication().registerActivityLifecycleCallbacks(callback);
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/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/patchserver/SamplePatchRequestCallback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2013-2016 Shengjie Sim Sun
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package tinker.sample.android.patchserver;
26 |
27 | import android.content.Context;
28 | import android.content.SharedPreferences;
29 |
30 | import com.tencent.tinker.lib.tinker.Tinker;
31 | import com.tencent.tinker.lib.tinker.TinkerInstaller;
32 | import com.tencent.tinker.lib.tinker.TinkerLoadResult;
33 | import com.tencent.tinker.lib.util.TinkerLog;
34 | import com.tencent.tinker.loader.shareutil.SharePatchFileUtil;
35 | import com.tencent.tinker.server.TinkerServerClient;
36 | import com.tencent.tinker.server.client.DefaultPatchRequestCallback;
37 | import com.tencent.tinker.server.utils.ServerUtils;
38 |
39 | import java.io.File;
40 |
41 | import tinker.sample.android.util.Utils;
42 |
43 |
44 | public class SamplePatchRequestCallback extends DefaultPatchRequestCallback {
45 | private static final String TAG = "Tinker.SampleRequestCallback";
46 |
47 | public static final String TINKER_RETRY_PATCH = "tinker_retry_patch";
48 | public static final int TINKER_MAX_RETRY_COUNT = 3;
49 |
50 | @Override
51 | public boolean beforePatchRequest() {
52 | boolean result = super.beforePatchRequest();
53 | if (result) {
54 | TinkerServerClient client = TinkerServerClient.get();
55 | Tinker tinker = client.getTinker();
56 | Context context = client.getContext();
57 |
58 | if (!tinker.isMainProcess()) {
59 | TinkerLog.e(TAG, "beforePatchRequest, only request on the main process");
60 | return false;
61 | }
62 | if (Utils.isGooglePlay()) {
63 | TinkerLog.e(TAG, "beforePatchRequest, google play channel, return false");
64 | return false;
65 | }
66 | // main process must be the newly version
67 | // check whether it is pending work
68 | String currentPatchMd5 = client.getCurrentPatchMd5();
69 | TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent();
70 |
71 | if (tinkerLoadResult.currentVersion == null || !currentPatchMd5.equals(tinkerLoadResult.currentVersion)) {
72 | Integer version = client.getCurrentPatchVersion();
73 | if (version > 0) {
74 | File patchFile = ServerUtils.getServerFile(context, client.getAppVersion(), String.valueOf(version));
75 | if (patchFile.exists() && patchFile.isFile()) {
76 |
77 | SharedPreferences sp = context.getSharedPreferences(
78 | TinkerServerClient.SHARE_SERVER_PREFERENCE_CONFIG, Context.MODE_PRIVATE
79 | );
80 | int current = sp.getInt(TINKER_RETRY_PATCH, 0);
81 | if (current >= TINKER_MAX_RETRY_COUNT) {
82 | SharePatchFileUtil.safeDeleteFile(patchFile);
83 | sp.edit().putInt(TINKER_RETRY_PATCH, 0).commit();
84 | TinkerLog.w(TAG, "beforePatchRequest, retry patch install have more than %d count, " +
85 | "version: %d, patch:%s", current, version, patchFile.getPath());
86 | } else {
87 | TinkerLog.w(TAG, "beforePatchRequest, have pending patch to install, " +
88 | "version: %d, patch:%s", version, patchFile.getPath());
89 |
90 | sp.edit().putInt(TINKER_RETRY_PATCH, ++current).commit();
91 | TinkerInstaller.onReceiveUpgradePatch(context, patchFile.getAbsolutePath());
92 | return false;
93 |
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | return result;
101 | }
102 |
103 | @Override
104 | public void onPatchRollback() {
105 | TinkerLog.w(TAG, "onPatchRollback");
106 | TinkerServerClient client = TinkerServerClient.get();
107 |
108 | if (Utils.isBackground()) {
109 | TinkerLog.i(TAG, "onPatchRollback, it is in background, just clean patch and kill all process");
110 | rollbackPatchDirectly();
111 | } else {
112 | //we can wait process at background, such as onAppBackground
113 | //or we can restart when the screen off
114 | TinkerLog.i(TAG, "tinker wait screen to clean patch and kill all process");
115 | new Utils.ScreenState(client.getContext(), new Utils.IOnScreenOff() {
116 | @Override
117 | public void onScreenOff() {
118 | rollbackPatchDirectly();
119 | }
120 | });
121 | }
122 | }
123 |
124 | @Override
125 | public void onPatchDownloadFail(Exception e, Integer newVersion, Integer currentVersion) {
126 | super.onPatchDownloadFail(e, newVersion, currentVersion);
127 | }
128 |
129 | @Override
130 | public void onPatchSyncFail(Exception e) {
131 | super.onPatchSyncFail(e);
132 | }
133 |
134 | @Override
135 | public boolean onPatchUpgrade(File file, Integer newVersion, Integer currentVersion) {
136 | boolean result = super.onPatchUpgrade(file, newVersion, currentVersion);
137 | if (result) {
138 | TinkerServerClient client = TinkerServerClient.get();
139 | Context context = client.getContext();
140 | SharedPreferences sp = context.getSharedPreferences(
141 | TinkerServerClient.SHARE_SERVER_PREFERENCE_CONFIG, Context.MODE_PRIVATE
142 | );
143 | sp.edit().putInt(TINKER_RETRY_PATCH, 0).commit();
144 | }
145 | return result;
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/app/src/main/java/tinker/sample/android/patchserver/TinkerServerManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2013-2016 Shengjie Sim Sun
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package tinker.sample.android.patchserver;
26 |
27 | import android.content.Context;
28 | import android.os.Looper;
29 | import android.os.MessageQueue;
30 |
31 | import com.tencent.tinker.lib.service.PatchResult;
32 | import com.tencent.tinker.lib.tinker.Tinker;
33 | import com.tencent.tinker.lib.util.TinkerLog;
34 | import com.tencent.tinker.loader.shareutil.ShareConstants;
35 | import com.tencent.tinker.loader.shareutil.SharePatchFileUtil;
36 | import com.tencent.tinker.server.TinkerServerClient;
37 | import com.tencent.tinker.server.client.ConfigRequestCallback;
38 | import com.tencent.tinker.server.client.DefaultPatchRequestCallback;
39 | import com.tencent.tinker.server.utils.Debugger;
40 | import com.tencent.tinker.server.utils.ServerUtils;
41 |
42 | import org.json.JSONException;
43 | import org.json.JSONObject;
44 |
45 | import java.io.File;
46 | import java.util.HashMap;
47 | import java.util.Iterator;
48 |
49 | import tinker.sample.android.BuildConfig;
50 | import tinker.sample.android.util.Utils;
51 |
52 | /**
53 | * Created by zhangshaowen on 16/11/3.
54 | */
55 |
56 | public class TinkerServerManager {
57 | private static final String TAG = "Tinker.ServerManager";
58 |
59 | public static final String CONDITION_CHANNEL = "channel";
60 |
61 | public static TinkerServerClient sTinkerServerClient;
62 |
63 | /**
64 | * 初始化 TinkerServer 实例
65 | * @param context
66 | * @param tinker tinker 实例
67 | * @param hours 访问服务器的时间间隔, 单位为小时, 应为 >= 0
68 | */
69 | public static void installTinkerServer(Context context, Tinker tinker, int hours) {
70 | boolean debug = Debugger.getInstance(context).isDebug();
71 | TinkerLog.w(TAG, "installTinkerServer, debug value:" + debug);
72 | sTinkerServerClient = TinkerServerClient.init(context, tinker, BuildConfig.APP_KEY, BuildConfig.APP_VERSION,
73 | debug, new SamplePatchRequestCallback());
74 | // add channel condition
75 | sTinkerServerClient.updateTinkerCondition(CONDITION_CHANNEL, Utils.getChannel());
76 | sTinkerServerClient.setCheckIntervalByHours(hours);
77 | }
78 |
79 | /**
80 | * 检查服务器是否有补丁更新
81 | * @param immediately 是否立刻检查,忽略时间间隔限制
82 | */
83 | public static void checkTinkerUpdate(final boolean immediately) {
84 | if (sTinkerServerClient == null) {
85 | TinkerLog.e(TAG, "checkTinkerUpdate, sTinkerServerClient == null");
86 | return;
87 | }
88 | Tinker tinker = sTinkerServerClient.getTinker();
89 | //only check at the main process
90 | if (tinker.isMainProcess()) {
91 | Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
92 | @Override public boolean queueIdle() {
93 | sTinkerServerClient.checkTinkerUpdate(immediately);
94 | return false;
95 | }
96 | });
97 | }
98 | }
99 |
100 | /**
101 | * 向服务器请求在线参数信息
102 | * @param configRequestCallback
103 | * @param immediately 是否立刻请求,忽略时间间隔限制
104 | */
105 | public static void getDynamicConfig(final ConfigRequestCallback configRequestCallback, final boolean immediately) {
106 | if (sTinkerServerClient == null) {
107 | TinkerLog.e(TAG, "checkTinkerUpdate, sTinkerServerClient == null");
108 | return;
109 | }
110 | Tinker tinker = sTinkerServerClient.getTinker();
111 | //only check at the main process
112 | if (tinker.isMainProcess()) {
113 | Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
114 | @Override public boolean queueIdle() {
115 | sTinkerServerClient.getDynamicConfig(configRequestCallback, immediately);
116 | return false;
117 | }
118 | });
119 | }
120 | }
121 |
122 | /**
123 | * 设置在线参数的时间间隔
124 | * @param hours 大于等于0的整数
125 | */
126 | public static void setGetConfigIntervalByHours(int hours) {
127 | if (sTinkerServerClient == null) {
128 | TinkerLog.e(TAG, "setGetConfigIntervalByHours, sTinkerServerClient == null");
129 | return;
130 | }
131 | sTinkerServerClient.setGetConfigIntervalByHours(hours);
132 | }
133 |
134 | /**
135 | * 将在线参数返回的 json 转化为 Hashmap
136 | * @param jsonString
137 | * @return
138 | * @throws JSONException
139 | */
140 | public static HashMap jsonToMap(String jsonString) throws JSONException {
141 | HashMap map = new HashMap<>();
142 | JSONObject jObject = new JSONObject(jsonString);
143 | Iterator keys = jObject.keys();
144 |
145 | while( keys.hasNext() ){
146 | String key = keys.next();
147 | String value = jObject.getString(key);
148 | map.put(key, value);
149 | }
150 | return map;
151 | }
152 |
153 |
154 | /**
155 | * 设置条件下发的属性
156 | * @param key
157 | * @param value
158 | */
159 | public void updateTinkerCondition(String key, String value) {
160 | if (sTinkerServerClient == null) {
161 | TinkerLog.e(TAG, "updateTinkerCondition, sTinkerServerClient == null");
162 | return;
163 | }
164 | sTinkerServerClient.updateTinkerCondition(key, value);
165 |
166 | }
167 |
168 | /**
169 | * 上报补丁合成情况
170 | * @param patchResult
171 | */
172 | public static void reportTinkerPatchFail(PatchResult patchResult) {
173 | if (sTinkerServerClient == null) {
174 | TinkerLog.e(TAG, "reportTinkerPatchFail, sTinkerServerClient == null");
175 | return;
176 | }
177 | if (patchResult == null) {
178 | TinkerLog.e(TAG, "reportTinkerPatchFail, patchResult == null");
179 | return;
180 | }
181 |
182 | if (patchResult.isSuccess) {
183 | TinkerLog.i(TAG, "reportTinkerPatchFail, patch success, just return");
184 | return;
185 | }
186 | String patchMd5 = (patchResult.patchVersion != null)
187 | ? patchResult.patchVersion : SharePatchFileUtil.getMD5(new File(patchResult.rawPatchFilePath));
188 |
189 | if (!patchMd5.equals(sTinkerServerClient.getCurrentPatchMd5())) {
190 | TinkerLog.e(TAG, "reportTinkerPatchFail, md5 not equal, " +
191 | "patchMd5:%s, currentPatchMd5:%s", patchMd5, sTinkerServerClient.getCurrentPatchMd5());
192 | return;
193 | }
194 | sTinkerServerClient.reportPatchFail(sTinkerServerClient.getCurrentPatchVersion(), DefaultPatchRequestCallback.ERROR_PATCH_FAIL);
195 | }
196 |
197 | /**
198 | * 上报补丁合成情况
199 | * @param patchMd5
200 | */
201 | public static void reportTinkerPatchListenerFail(int returnCode, String patchMd5) {
202 | if (sTinkerServerClient == null) {
203 | TinkerLog.e(TAG, "reportTinkerPatchListenerFail, sTinkerServerClient == null");
204 | return;
205 | }
206 | if (returnCode == ShareConstants.ERROR_PATCH_OK) {
207 | return;
208 | }
209 | if (patchMd5 == null) {
210 | TinkerLog.e(TAG, "reportTinkerPatchListenerFail, patchMd5 == null");
211 | return;
212 | }
213 | if (!patchMd5.equals(sTinkerServerClient.getCurrentPatchMd5())) {
214 | TinkerLog.e(TAG, "reportTinkerPatchListenerFail, md5 not equal, " +
215 | "patchMd5:%s, currentPatchMd5:%s", patchMd5, sTinkerServerClient.getCurrentPatchMd5());
216 | return;
217 | }
218 | sTinkerServerClient.reportPatchFail(sTinkerServerClient.getCurrentPatchVersion(), DefaultPatchRequestCallback.ERROR_LISTENER_CHECK_FAIL);
219 | }
220 |
221 |
222 | /**
223 | * 上报补丁加载情况
224 | */
225 | public static void reportTinkerLoadFail() {
226 | if (sTinkerServerClient == null) {
227 | TinkerLog.e(TAG, "reportTinkerPatchFail, sTinkerServerClient == null");
228 | return;
229 | }
230 | sTinkerServerClient.reportPatchFail(sTinkerServerClient.getCurrentPatchVersion(), DefaultPatchRequestCallback.ERROR_LOAD_FAIL);
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/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.BuildConfig;
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(BuildConfig.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 | public static final int KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT = 158;
78 |
79 | //version check
80 | public static final int KEY_APPLIED_VERSION_CHECK = 180;
81 | //extract error
82 | public static final int KEY_APPLIED_PATCH_FILE_EXTRACT = 181;
83 | public static final int KEY_APPLIED_DEX_EXTRACT = 182;
84 | /**
85 | * for art small dex
86 | */
87 | public static final int KEY_APPLIED_DEX_ART_EXTRACT = 183;
88 | public static final int KEY_APPLIED_LIB_EXTRACT = 184;
89 | public static final int KEY_APPLIED_RESOURCE_EXTRACT = 185;
90 | //cost time
91 | public static final int KEY_APPLIED_SUCC_COST_5S_LESS = 200;
92 | public static final int KEY_APPLIED_SUCC_COST_10S_LESS = 201;
93 | public static final int KEY_APPLIED_SUCC_COST_30S_LESS = 202;
94 | public static final int KEY_APPLIED_SUCC_COST_60S_LESS = 203;
95 | public static final int KEY_APPLIED_SUCC_COST_OTHER = 204;
96 |
97 | public static final int KEY_APPLIED_FAIL_COST_5S_LESS = 205;
98 | public static final int KEY_APPLIED_FAIL_COST_10S_LESS = 206;
99 | public static final int KEY_APPLIED_FAIL_COST_30S_LESS = 207;
100 | public static final int KEY_APPLIED_FAIL_COST_60S_LESS = 208;
101 | public static final int KEY_APPLIED_FAIL_COST_OTHER = 209;
102 |
103 |
104 | // KEY -- load detail
105 | public static final int KEY_LOADED_UNKNOWN_EXCEPTION = 250;
106 | public static final int KEY_LOADED_UNCAUGHT_EXCEPTION = 251;
107 | public static final int KEY_LOADED_EXCEPTION_DEX = 252;
108 | public static final int KEY_LOADED_EXCEPTION_DEX_CHECK = 253;
109 | public static final int KEY_LOADED_EXCEPTION_RESOURCE = 254;
110 | public static final int KEY_LOADED_EXCEPTION_RESOURCE_CEHCK = 255;
111 |
112 |
113 | public static final int KEY_LOADED_MISMATCH_DEX = 300;
114 | public static final int KEY_LOADED_MISMATCH_LIB = 301;
115 | public static final int KEY_LOADED_MISMATCH_RESOURCE = 302;
116 | public static final int KEY_LOADED_MISSING_DEX = 303;
117 | public static final int KEY_LOADED_MISSING_LIB = 304;
118 | public static final int KEY_LOADED_MISSING_PATCH_FILE = 305;
119 | public static final int KEY_LOADED_MISSING_PATCH_INFO = 306;
120 | public static final int KEY_LOADED_MISSING_DEX_OPT = 307;
121 | public static final int KEY_LOADED_MISSING_RES = 308;
122 | public static final int KEY_LOADED_INFO_CORRUPTED = 309;
123 |
124 | //load package check
125 | public static final int KEY_LOADED_PACKAGE_CHECK_SIGNATURE = 350;
126 | public static final int KEY_LOADED_PACKAGE_CHECK_DEX_META = 351;
127 | public static final int KEY_LOADED_PACKAGE_CHECK_LIB_META = 352;
128 | public static final int KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = 353;
129 | public static final int KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 354;
130 | public static final int KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = 355;
131 | public static final int KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND = 356;
132 | public static final int KEY_LOADED_PACKAGE_CHECK_RES_META = 357;
133 | public static final int KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT = 358;
134 |
135 |
136 | public static final int KEY_LOADED_SUCC_COST_500_LESS = 400;
137 | public static final int KEY_LOADED_SUCC_COST_1000_LESS = 401;
138 | public static final int KEY_LOADED_SUCC_COST_3000_LESS = 402;
139 | public static final int KEY_LOADED_SUCC_COST_5000_LESS = 403;
140 | public static final int KEY_LOADED_SUCC_COST_OTHER = 404;
141 |
142 | interface Reporter {
143 | void onReport(int key);
144 |
145 | void onReport(String message);
146 | }
147 |
148 | private static Reporter reporter = null;
149 |
150 | public void setReporter(Reporter reporter) {
151 | this.reporter = reporter;
152 | }
153 |
154 | public static void onTryApply(boolean upgrade, boolean success) {
155 | if (reporter == null) {
156 | return;
157 | }
158 | reporter.onReport(KEY_TRY_APPLY);
159 | if (upgrade) {
160 | reporter.onReport(KEY_TRY_APPLY_UPGRADE);
161 | } else {
162 | reporter.onReport(KEY_TRY_APPLY_REPAIR);
163 | }
164 | if (success) {
165 | reporter.onReport(KEY_TRY_APPLY_SUCCESS);
166 | }
167 | }
168 |
169 | public static void onTryApplyFail(int errorCode) {
170 | if (reporter == null) {
171 | return;
172 | }
173 | switch (errorCode) {
174 | case ShareConstants.ERROR_PATCH_NOTEXIST:
175 | reporter.onReport(KEY_TRY_APPLY_NOT_EXIST);
176 | break;
177 | case ShareConstants.ERROR_PATCH_DISABLE:
178 | reporter.onReport(KEY_TRY_APPLY_DISABLE);
179 | break;
180 | case ShareConstants.ERROR_PATCH_INSERVICE:
181 | reporter.onReport(KEY_TRY_APPLY_INSERVICE);
182 | break;
183 | case ShareConstants.ERROR_PATCH_RUNNING:
184 | reporter.onReport(KEY_TRY_APPLY_RUNNING);
185 | break;
186 | case Utils.ERROR_PATCH_ROM_SPACE:
187 | reporter.onReport(KEY_TRY_APPLY_ROM_SPACE);
188 | break;
189 | case Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL:
190 | reporter.onReport(KEY_TRY_APPLY_GOOGLEPLAY);
191 | break;
192 | case Utils.ERROR_PATCH_ALREADY_APPLY:
193 | reporter.onReport(KEY_TRY_APPLY_ALREADY_APPLY);
194 | break;
195 | case Utils.ERROR_PATCH_CRASH_LIMIT:
196 | reporter.onReport(KEY_TRY_APPLY_CRASH_LIMIT);
197 | break;
198 | case Utils.ERROR_PATCH_MEMORY_LIMIT:
199 | reporter.onReport(KEY_TRY_APPLY_MEMORY_LIMIT);
200 | break;
201 | case Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED:
202 | reporter.onReport(KEY_TRY_APPLY_CONDITION_NOT_SATISFIED);
203 | break;
204 | }
205 | }
206 |
207 | public static void onLoadPackageCheckFail(int errorCode) {
208 | if (reporter == null) {
209 | return;
210 | }
211 | switch (errorCode) {
212 | case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL:
213 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_SIGNATURE);
214 | break;
215 | case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:
216 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_DEX_META);
217 | break;
218 | case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:
219 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_LIB_META);
220 | break;
221 | case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:
222 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);
223 | break;
224 | case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:
225 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);
226 | break;
227 | case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:
228 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);
229 |
230 | break;
231 | case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:
232 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND);
233 | break;
234 | case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:
235 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_RES_META);
236 | break;
237 | case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:
238 | reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);
239 | break;
240 | }
241 | }
242 |
243 | public static void onLoaded(long cost) {
244 | if (reporter == null) {
245 | return;
246 | }
247 | reporter.onReport(KEY_LOADED);
248 |
249 | if (cost < 0L) {
250 | TinkerLog.e(TAG, "hp_report report load cost failed, invalid cost");
251 | return;
252 | }
253 |
254 | if (cost <= 500) {
255 | reporter.onReport(KEY_LOADED_SUCC_COST_500_LESS);
256 | } else if (cost <= 1000) {
257 | reporter.onReport(KEY_LOADED_SUCC_COST_1000_LESS);
258 | } else if (cost <= 3000) {
259 | reporter.onReport(KEY_LOADED_SUCC_COST_3000_LESS);
260 | } else if (cost <= 5000) {
261 | reporter.onReport(KEY_LOADED_SUCC_COST_5000_LESS);
262 | } else {
263 | reporter.onReport(KEY_LOADED_SUCC_COST_OTHER);
264 | }
265 | }
266 |
267 | public static void onLoadInfoCorrupted() {
268 | if (reporter == null) {
269 | return;
270 | }
271 | reporter.onReport(KEY_LOADED_INFO_CORRUPTED);
272 | }
273 |
274 | public static void onLoadFileNotFound(int fileType) {
275 | if (reporter == null) {
276 | return;
277 | }
278 | switch (fileType) {
279 | case ShareConstants.TYPE_DEX_OPT:
280 | reporter.onReport(KEY_LOADED_MISSING_DEX_OPT);
281 | break;
282 | case ShareConstants.TYPE_DEX:
283 | reporter.onReport(KEY_LOADED_MISSING_DEX);
284 | break;
285 | case ShareConstants.TYPE_LIBRARY:
286 | reporter.onReport(KEY_LOADED_MISSING_LIB);
287 | break;
288 | case ShareConstants.TYPE_PATCH_FILE:
289 | reporter.onReport(KEY_LOADED_MISSING_PATCH_FILE);
290 | break;
291 | case ShareConstants.TYPE_PATCH_INFO:
292 | reporter.onReport(KEY_LOADED_MISSING_PATCH_INFO);
293 | break;
294 | case ShareConstants.TYPE_RESOURCE:
295 | reporter.onReport(KEY_LOADED_MISSING_RES);
296 | break;
297 | }
298 | }
299 |
300 | public static void onLoadFileMisMatch(int fileType) {
301 | if (reporter == null) {
302 | return;
303 | }
304 | switch (fileType) {
305 | case ShareConstants.TYPE_DEX:
306 | reporter.onReport(KEY_LOADED_MISMATCH_DEX);
307 | break;
308 | case ShareConstants.TYPE_LIBRARY:
309 | reporter.onReport(KEY_LOADED_MISMATCH_LIB);
310 | break;
311 | case ShareConstants.TYPE_RESOURCE:
312 | reporter.onReport(KEY_LOADED_MISMATCH_RESOURCE);
313 | break;
314 | }
315 | }
316 |
317 | public static void onLoadException(Throwable throwable, int errorCode) {
318 | if (reporter == null) {
319 | return;
320 | }
321 | boolean isCheckFail = false;
322 | switch (errorCode) {
323 | case ShareConstants.ERROR_LOAD_EXCEPTION_DEX:
324 | if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_INSTALL_FAIL)) {
325 | reporter.onReport(KEY_LOADED_EXCEPTION_DEX_CHECK);
326 | isCheckFail = true;
327 | TinkerLog.e(TAG, "tinker dex check fail:" + throwable.getMessage());
328 | } else {
329 | reporter.onReport(KEY_LOADED_EXCEPTION_DEX);
330 | TinkerLog.e(TAG, "tinker dex reflect fail:" + throwable.getMessage());
331 | }
332 | break;
333 | case ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE:
334 | if (throwable.getMessage().contains(ShareConstants.CHECK_RES_INSTALL_FAIL)) {
335 | reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE_CEHCK);
336 | isCheckFail = true;
337 | TinkerLog.e(TAG, "tinker res check fail:" + throwable.getMessage());
338 | } else {
339 | reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE);
340 | TinkerLog.e(TAG, "tinker res reflect fail:" + throwable.getMessage());
341 | }
342 | break;
343 | case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT:
344 | reporter.onReport(KEY_LOADED_UNCAUGHT_EXCEPTION);
345 | break;
346 | case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN:
347 | reporter.onReport(KEY_LOADED_UNKNOWN_EXCEPTION);
348 | break;
349 | }
350 | //reporter exception, for dex check fail, we don't need to report stacktrace
351 | if (!isCheckFail) {
352 | reporter.onReport("Tinker Exception:load tinker occur exception " + Utils.getExceptionCauseString(throwable));
353 | }
354 | }
355 |
356 | public static void onApplyPatchServiceStart() {
357 | if (reporter == null) {
358 | return;
359 | }
360 | reporter.onReport(KEY_APPLIED_START);
361 | }
362 |
363 | public static void onApplyDexOptFail(Throwable throwable) {
364 | if (reporter == null) {
365 | return;
366 | }
367 | reporter.onReport(KEY_APPLIED_DEXOPT);
368 | reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable));
369 | }
370 |
371 | public static void onApplyInfoCorrupted() {
372 | if (reporter == null) {
373 | return;
374 | }
375 | reporter.onReport(KEY_APPLIED_INFO_CORRUPTED);
376 | }
377 |
378 | public static void onApplyVersionCheckFail() {
379 | if (reporter == null) {
380 | return;
381 | }
382 | reporter.onReport(KEY_APPLIED_VERSION_CHECK);
383 | }
384 |
385 | public static void onApplyExtractFail(int fileType) {
386 | if (reporter == null) {
387 | return;
388 | }
389 | switch (fileType) {
390 | case ShareConstants.TYPE_DEX:
391 | reporter.onReport(KEY_APPLIED_DEX_EXTRACT);
392 | break;
393 | case ShareConstants.TYPE_DEX_FOR_ART:
394 | reporter.onReport(KEY_APPLIED_DEX_ART_EXTRACT);
395 | break;
396 | case ShareConstants.TYPE_LIBRARY:
397 | reporter.onReport(KEY_APPLIED_LIB_EXTRACT);
398 | break;
399 | case ShareConstants.TYPE_PATCH_FILE:
400 | reporter.onReport(KEY_APPLIED_PATCH_FILE_EXTRACT);
401 | break;
402 | case ShareConstants.TYPE_RESOURCE:
403 | reporter.onReport(KEY_APPLIED_RESOURCE_EXTRACT);
404 | break;
405 | }
406 | }
407 |
408 | public static void onApplied(boolean isUpgrade, long cost, boolean success) {
409 | if (reporter == null) {
410 | return;
411 | }
412 | if (success) {
413 | reporter.onReport(KEY_APPLIED);
414 | }
415 |
416 | if (isUpgrade) {
417 | if (success) {
418 | reporter.onReport(KEY_APPLIED_UPGRADE);
419 | } else {
420 | reporter.onReport(KEY_APPLIED_UPGRADE_FAIL);
421 | }
422 |
423 | } else {
424 | if (success) {
425 | reporter.onReport(KEY_APPLIED_REPAIR);
426 | } else {
427 | reporter.onReport(KEY_APPLIED_REPAIR_FAIL);
428 | }
429 | }
430 |
431 | TinkerLog.i(TAG, "hp_report report apply cost = %d", cost);
432 |
433 | if (cost < 0L) {
434 | TinkerLog.e(TAG, "hp_report report apply cost failed, invalid cost");
435 | return;
436 | }
437 |
438 | if (cost <= 5000) {
439 | if (success) {
440 | reporter.onReport(KEY_APPLIED_SUCC_COST_5S_LESS);
441 | } else {
442 | reporter.onReport(KEY_APPLIED_FAIL_COST_5S_LESS);
443 | }
444 | } else if (cost <= 10 * 1000) {
445 | if (success) {
446 | reporter.onReport(KEY_APPLIED_SUCC_COST_10S_LESS);
447 | } else {
448 | reporter.onReport(KEY_APPLIED_FAIL_COST_10S_LESS);
449 | }
450 | } else if (cost <= 30 * 1000) {
451 | if (success) {
452 | reporter.onReport(KEY_APPLIED_SUCC_COST_30S_LESS);
453 | } else {
454 | reporter.onReport(KEY_APPLIED_FAIL_COST_30S_LESS);
455 | }
456 | } else if (cost <= 60 * 1000) {
457 | if (success) {
458 | reporter.onReport(KEY_APPLIED_SUCC_COST_60S_LESS);
459 | } else {
460 | reporter.onReport(KEY_APPLIED_FAIL_COST_60S_LESS);
461 | }
462 | } else {
463 | if (success) {
464 | reporter.onReport(KEY_APPLIED_SUCC_COST_OTHER);
465 | } else {
466 | reporter.onReport(KEY_APPLIED_FAIL_COST_OTHER);
467 | }
468 | }
469 | }
470 |
471 | public static void onApplyPackageCheckFail(int errorCode) {
472 | if (reporter == null) {
473 | return;
474 | }
475 | TinkerLog.i(TAG, "hp_report package check failed, error = %d", errorCode);
476 |
477 | switch (errorCode) {
478 | case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL:
479 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_SIGNATURE);
480 | break;
481 | case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:
482 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_DEX_META);
483 | break;
484 | case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:
485 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_LIB_META);
486 | break;
487 | case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:
488 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);
489 | break;
490 | case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:
491 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);
492 | break;
493 | case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:
494 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);
495 | break;
496 | case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:
497 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND);
498 | break;
499 | case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:
500 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_RES_META);
501 | break;
502 | case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:
503 | reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);
504 | break;
505 | }
506 | }
507 |
508 | public static void onApplyCrash(Throwable throwable) {
509 | if (reporter == null) {
510 | return;
511 | }
512 | reporter.onReport(KEY_APPLIED_EXCEPTION);
513 | reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable));
514 | }
515 |
516 | public static void onFastCrashProtect() {
517 | if (reporter == null) {
518 | return;
519 | }
520 | reporter.onReport(KEY_CRASH_FAST_PROTECT);
521 | }
522 |
523 | public static void onXposedCrash() {
524 | if (reporter == null) {
525 | return;
526 | }
527 | if (ShareTinkerInternals.isVmArt()) {
528 | reporter.onReport(KEY_CRASH_CAUSE_XPOSED_ART);
529 | } else {
530 | reporter.onReport(KEY_CRASH_CAUSE_XPOSED_DALVIK);
531 | }
532 | }
533 |
534 | public static void onReportRetryPatch() {
535 | if (reporter == null) {
536 | return;
537 | }
538 | reporter.onReport(KEY_APPLY_WITH_RETRY);
539 | }
540 |
541 | }
542 |
--------------------------------------------------------------------------------
/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 = 3;
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 | if (patchMd5 == null) {
153 | TinkerLog.w(TAG, "onPatchServiceStart patch md5 is null, just return");
154 | return;
155 | }
156 |
157 | if (retryInfoFile.exists()) {
158 | retryInfo = RetryInfo.readRetryProperty(retryInfoFile);
159 | if (retryInfo.md5 == null || retryInfo.times == null || !patchMd5.equals(retryInfo.md5)) {
160 | copyToTempFile(patchFile);
161 | retryInfo.md5 = patchMd5;
162 | retryInfo.times = "1";
163 | } else {
164 | int nowTimes = Integer.parseInt(retryInfo.times);
165 | if (nowTimes >= RETRY_MAX_COUNT) {
166 | SharePatchFileUtil.safeDeleteFile(retryInfoFile);
167 | SharePatchFileUtil.safeDeleteFile(tempPatchFile);
168 | TinkerLog.w(TAG, "onPatchServiceStart retry more than max count, delete retry info file!");
169 | return;
170 | } else {
171 | retryInfo.times = String.valueOf(nowTimes + 1);
172 | }
173 | }
174 |
175 | } else {
176 | copyToTempFile(patchFile);
177 | retryInfo = new RetryInfo(patchMd5, "1");
178 | }
179 |
180 | RetryInfo.writeRetryProperty(retryInfoFile, retryInfo);
181 |
182 | }
183 |
184 | /**
185 | * if we receive any result, we can delete the temp retry info file
186 | *
187 | * @param isUpgradePatch
188 | */
189 | public void onPatchServiceResult(boolean isUpgradePatch) {
190 | if (!isRetryEnable) {
191 | TinkerLog.w(TAG, "onPatchServiceResult retry disabled, just return");
192 | return;
193 | }
194 |
195 | if (!isUpgradePatch) {
196 | TinkerLog.w(TAG, "onPatchServiceResult is not upgrade patch, just return");
197 | return;
198 | }
199 |
200 | //delete info file
201 | if (retryInfoFile.exists()) {
202 | SharePatchFileUtil.safeDeleteFile(retryInfoFile);
203 | }
204 | //delete temp patch file
205 | if (tempPatchFile.exists()) {
206 | SharePatchFileUtil.safeDeleteFile(tempPatchFile);
207 | }
208 | }
209 |
210 | public void setRetryEnable(boolean enable) {
211 | isRetryEnable = enable;
212 | }
213 |
214 | static class RetryInfo {
215 | String md5;
216 | String times;
217 |
218 | RetryInfo(String md5, String times) {
219 | this.md5 = md5;
220 | this.times = times;
221 | }
222 |
223 | static RetryInfo readRetryProperty(File infoFile) {
224 | String md5 = null;
225 | String times = null;
226 |
227 | Properties properties = new Properties();
228 | FileInputStream inputStream = null;
229 | try {
230 | inputStream = new FileInputStream(infoFile);
231 | properties.load(inputStream);
232 | md5 = properties.getProperty(RETRY_FILE_MD5_PROPERTY);
233 | times = properties.getProperty(RETRY_COUNT_PROPERTY);
234 | } catch (IOException e) {
235 | e.printStackTrace();
236 | } finally {
237 | SharePatchFileUtil.closeQuietly(inputStream);
238 | }
239 |
240 | return new RetryInfo(md5, times);
241 | }
242 |
243 | static void writeRetryProperty(File infoFile, RetryInfo info) {
244 | if (info == null) {
245 | return;
246 | }
247 |
248 | File parentFile = infoFile.getParentFile();
249 | if (!parentFile.exists()) {
250 | parentFile.mkdirs();
251 | }
252 |
253 | Properties newProperties = new Properties();
254 | newProperties.put(RETRY_FILE_MD5_PROPERTY, info.md5);
255 | newProperties.put(RETRY_COUNT_PROPERTY, info.times);
256 | FileOutputStream outputStream = null;
257 | try {
258 | outputStream = new FileOutputStream(infoFile, false);
259 | newProperties.store(outputStream, null);
260 | } catch (Exception e) {
261 | // e.printStackTrace();
262 | TinkerLog.printErrStackTrace(TAG, e, "retry write property fail");
263 | } finally {
264 | SharePatchFileUtil.closeQuietly(outputStream);
265 | }
266 |
267 | }
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/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.content.BroadcastReceiver;
20 | import android.content.Context;
21 | import android.content.Intent;
22 | import android.content.IntentFilter;
23 | import android.os.Environment;
24 | import android.os.StatFs;
25 |
26 | import com.tencent.tinker.lib.util.TinkerLog;
27 | import com.tencent.tinker.loader.shareutil.ShareConstants;
28 |
29 | import java.io.ByteArrayOutputStream;
30 | import java.io.File;
31 | import java.io.IOException;
32 | import java.io.PrintStream;
33 |
34 | import static com.tencent.tinker.server.client.TinkerClientAPI.TAG;
35 |
36 | /**
37 | * Created by zhangshaowen on 16/4/7.
38 | */
39 | public class Utils {
40 |
41 | /**
42 | * the error code define by myself
43 | * should after {@code ShareConstants.ERROR_PATCH_INSERVICE
44 | */
45 | public static final int ERROR_PATCH_GOOGLEPLAY_CHANNEL = -5;
46 | public static final int ERROR_PATCH_ROM_SPACE = -6;
47 | public static final int ERROR_PATCH_MEMORY_LIMIT = -7;
48 | public static final int ERROR_PATCH_ALREADY_APPLY = -8;
49 | public static final int ERROR_PATCH_CRASH_LIMIT = -9;
50 | public static final int ERROR_PATCH_CONDITION_NOT_SATISFIED = -10;
51 |
52 | public static final String PLATFORM = "platform";
53 |
54 | public static final int MIN_MEMORY_HEAP_SIZE = 45;
55 |
56 | private static boolean background = false;
57 |
58 | public static boolean isGooglePlay() {
59 | return false;
60 | }
61 |
62 | public static String getChannel() {
63 | return "default";
64 | }
65 |
66 | public static boolean isBackground() {
67 | return background;
68 | }
69 |
70 | public static void setBackground(boolean back) {
71 | background = back;
72 | }
73 |
74 | public static int checkForPatchRecover(long roomSize, int maxMemory) {
75 | if (Utils.isGooglePlay()) {
76 | return Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL;
77 | }
78 | if (maxMemory < MIN_MEMORY_HEAP_SIZE) {
79 | return Utils.ERROR_PATCH_MEMORY_LIMIT;
80 | }
81 | //or you can mention user to clean their rom space!
82 | if (!checkRomSpaceEnough(roomSize)) {
83 | return Utils.ERROR_PATCH_ROM_SPACE;
84 | }
85 |
86 | return ShareConstants.ERROR_PATCH_OK;
87 | }
88 |
89 | public static boolean isXposedExists(Throwable thr) {
90 | StackTraceElement[] stackTraces = thr.getStackTrace();
91 | for (StackTraceElement stackTrace : stackTraces) {
92 | final String clazzName = stackTrace.getClassName();
93 | if (clazzName != null && clazzName.contains("de.robv.android.xposed.XposedBridge")) {
94 | return true;
95 | }
96 | }
97 | return false;
98 | }
99 |
100 | @Deprecated
101 | public static boolean checkRomSpaceEnough(long limitSize) {
102 | long allSize;
103 | long availableSize = 0;
104 | try {
105 | File data = Environment.getDataDirectory();
106 | StatFs sf = new StatFs(data.getPath());
107 | availableSize = (long) sf.getAvailableBlocks() * (long) sf.getBlockSize();
108 | allSize = (long) sf.getBlockCount() * (long) sf.getBlockSize();
109 | } catch (Exception e) {
110 | allSize = 0;
111 | }
112 |
113 | if (allSize != 0 && availableSize > limitSize) {
114 | return true;
115 | }
116 | return false;
117 | }
118 |
119 | public static String getExceptionCauseString(final Throwable ex) {
120 | final ByteArrayOutputStream bos = new ByteArrayOutputStream();
121 | final PrintStream ps = new PrintStream(bos);
122 |
123 | try {
124 | // print directly
125 | Throwable t = ex;
126 | while (t.getCause() != null) {
127 | t = t.getCause();
128 | }
129 | t.printStackTrace(ps);
130 | return toVisualString(bos.toString());
131 | } finally {
132 | try {
133 | bos.close();
134 | } catch (IOException e) {
135 | e.printStackTrace();
136 | }
137 | }
138 | }
139 |
140 | private static String toVisualString(String src) {
141 | boolean cutFlg = false;
142 |
143 | if (null == src) {
144 | return null;
145 | }
146 |
147 | char[] chr = src.toCharArray();
148 | if (null == chr) {
149 | return null;
150 | }
151 |
152 | int i = 0;
153 | for (; i < chr.length; i++) {
154 | if (chr[i] > 127) {
155 | chr[i] = 0;
156 | cutFlg = true;
157 | break;
158 | }
159 | }
160 |
161 | if (cutFlg) {
162 | return new String(chr, 0, i);
163 | } else {
164 | return src;
165 | }
166 | }
167 |
168 | public interface IOnScreenOff {
169 | void onScreenOff();
170 | }
171 |
172 | public static class ScreenState {
173 | public ScreenState(Context context, final IOnScreenOff onScreenOffInterface) {
174 | IntentFilter filter = new IntentFilter();
175 | filter.addAction(Intent.ACTION_SCREEN_OFF);
176 | context.registerReceiver(new BroadcastReceiver() {
177 |
178 | @Override
179 | public void onReceive(Context context, Intent in) {
180 | String action = in == null ? "" : in.getAction();
181 | TinkerLog.i(TAG, "ScreenReceiver action [%s] ", action);
182 | if (Intent.ACTION_SCREEN_OFF.equals(action)) {
183 |
184 | context.unregisterReceiver(this);
185 |
186 | if (onScreenOffInterface != null) {
187 | onScreenOffInterface.onScreenOff();
188 | }
189 | }
190 | }
191 | }, filter);
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
18 |
26 |
27 |
35 |
36 |
37 |
45 |
46 |
54 |
55 |
63 |
71 |
79 |
80 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp1017/tinker-sample-android/c0704ca448d896075e3c045dcb7e1cc2701ec23e/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp1017/tinker-sample-android/c0704ca448d896075e3c045dcb7e1cc2701ec23e/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp1017/tinker-sample-android/c0704ca448d896075e3c045dcb7e1cc2701ec23e/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp1017/tinker-sample-android/c0704ca448d896075e3c045dcb7e1cc2701ec23e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp1017/tinker-sample-android/c0704ca448d896075e3c045dcb7e1cc2701ec23e/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.2'
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.7.5
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp1017/tinker-sample-android/c0704ca448d896075e3c045dcb7e1cc2701ec23e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Nov 25 14:04:31 CST 2016
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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/tinker/bakApk/base-app-debug-v1.0.1-2016-1125.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp1017/tinker-sample-android/c0704ca448d896075e3c045dcb7e1cc2701ec23e/tinker/bakApk/base-app-debug-v1.0.1-2016-1125.apk
--------------------------------------------------------------------------------
/tinker/bakApk/base-app-release-v1.0.1-2016-1125.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jp1017/tinker-sample-android/c0704ca448d896075e3c045dcb7e1cc2701ec23e/tinker/bakApk/base-app-release-v1.0.1-2016-1125.apk
--------------------------------------------------------------------------------
/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/
--------------------------------------------------------------------------------