├── .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 | 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 26 | 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 | 87 | 88 | 89 | 90 | 91 | 1.8 92 | 93 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 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 | 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 | [![Build Status](https://travis-ci.org/jp1017/tinker-sample-android.svg?branch=master)](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 | ![gradle](http://7xlah4.com1.z0.glb.clouddn.com/20161125%20125123tinker%20gradle.png) 113 | 114 | 比如, 打debug补丁, 双击`tinkerPatchDebug`就可以了 115 | 116 | 下一次打补丁时就可以从快捷栏选择,然后点击右侧运行, 如下图: 117 | 118 | ![patch](http://7xlah4.com1.z0.glb.clouddn.com/20161125130855tinker1.png) 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 | ![tinker_server](http://7xlah4.com1.z0.glb.clouddn.com/20161125183501tinker_server.png) 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 |