├── .gitignore ├── README.md ├── apk ├── app-release-1.0.0.apk ├── app-release-1.0.1.apk └── patch_signed_7zip.apk ├── app ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro ├── src │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── xuexiang │ │ │ └── tinkertest │ │ │ └── ExampleInstrumentedTest.java │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── xuexiang │ │ │ │ └── tinkertest │ │ │ │ ├── App.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── util │ │ │ │ ├── ReopenAppUtils.java │ │ │ │ └── StartAppReceiver.java │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── test │ │ └── java │ │ └── com │ │ └── xuexiang │ │ └── tinkertest │ │ └── ExampleUnitTest.java └── tinkerpatch.gradle ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png └── demo.gif ├── keystores ├── README.md └── android.keystore ├── settings.gradle └── versions.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/ 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinkerTest 2 | 演示如何使用腾讯的热修复框架-Tinker 3 | 4 | ## Tinker热更新演示(请star支持) 5 | 6 | ![](https://github.com/xuexiangjys/TinkerTest/blob/master/img/demo.gif) 7 | 8 | ## 演示demo下载 9 | 10 | [![](https://img.shields.io/badge/演示apk-1M-blue.svg)](https://github.com/xuexiangjys/TinkerTest/blob/master/apk/app-release.apk) 11 | 12 | [![](https://img.shields.io/badge/补丁包-4K-blue.svg)](https://github.com/xuexiangjys/TinkerTest/blob/master/apk/patch_signed_7zip.apk) 13 | 14 | 15 | ## Tinker简介 16 | 17 | > Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。 18 | 19 | ## 相关链接 20 | 21 | * [Tinker Github](https://github.com/Tencent/tinker) 22 | 23 | * [TinkerPatch Github](https://github.com/TinkerPatch) 24 | 25 | * [Tinker Platform](http://www.tinkerpatch.com/) 26 | 27 | 28 | ## Tinker已知问题 29 | 30 | 由于原理与系统限制,Tinker有以下已知问题: 31 | 32 | * Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity); 33 | * 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码; 34 | * 在Android N上,补丁对应用启动时间有轻微的影响; 35 | * 不支持部分三星android-21机型,加载补丁时会主动抛出"TinkerRuntimeException:checkDexInstall failed"; 36 | * 对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。 37 | 38 | 官方说明请[点击查看](https://github.com/Tencent/tinker/wiki). 39 | 40 | ## Tinker接入 41 | 42 | ### 添加依赖 43 | 44 | 1. 在Project的根目录的build.gradle下添加`tinkerpatch`插件: 45 | 46 | ``` 47 | 48 | buildscript { 49 | ... 50 | dependencies { 51 | ... 52 | classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.2.13" 53 | } 54 | } 55 | ``` 56 | 57 | 2. 在module的build.gradle下增加`Tinker`的依赖。 58 | 59 | ``` 60 | dependencies { 61 | implementation 'com.android.support:multidex:1.0.3' 62 | implementation 'com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.2.13' 63 | } 64 | ``` 65 | 66 | 3. 配置代码混淆和打包配置。其中tinkerMultidexKeep.pro和proguard-rules.pro可选填。 67 | 68 | ``` 69 | android { 70 | compileSdkVersion 28 71 | 72 | defaultConfig { 73 | applicationId "com.xuexiang.tinkertest" 74 | minSdkVersion 14 75 | targetSdkVersion 28 76 | versionCode 1 77 | versionName "1.0" 78 | 79 | multiDexEnabled true 80 | } 81 | 82 | signingConfigs { 83 | release { 84 | //配置你的storekey 85 | storeFile file(app_release.storeFile) 86 | storePassword app_release.storePassword 87 | keyAlias app_release.keyAlias 88 | keyPassword app_release.keyPassword 89 | } 90 | } 91 | 92 | buildTypes { 93 | release { 94 | minifyEnabled true 95 | shrinkResources true 96 | signingConfig signingConfigs.release 97 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 98 | } 99 | debug { 100 | debuggable true 101 | minifyEnabled false 102 | } 103 | } 104 | 105 | //recommend 106 | dexOptions { 107 | jumboMode = true 108 | } 109 | 110 | sourceSets { 111 | main { 112 | jniLibs.srcDirs = ['libs'] 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | 4. 引用`tinkerpatch.gradle`执行脚本. 119 | 120 | ``` 121 | apply from: 'tinkerpatch.gradle' 122 | ``` 123 | 124 | 5. 配置`tinkerpatch.gradle`执行脚本.对于脚本的详细说明请参考[官方文档](http://www.tinkerpatch.com/Docs/SDK)。 125 | 126 | 这里我只想说几个比较关键的配置: 127 | 128 | * bakPath: 这里存放的是每次我们打包生成的apk目录,作为生成补丁包的参考包路径。(一般配置了就不用动了) 129 | 130 | * baseInfo: 这里设置的是,本次打补丁包参照的apk包所在文件夹路径,也就是旧APK的目录。(每次打补丁包的时候,都需要手动去更新) 131 | 132 | * variantName: 设置打补丁包的类型是release还是debug。 133 | 134 | * AppKey: 这是从[Tinker Platform](http://www.tinkerpatch.com/)上注册获得的应用的appkey。 135 | 136 | * AppVersion: 这也是在[Tinker Platform](http://www.tinkerpatch.com/)上,每次上传补丁包时都需要填写的应用版本,并且必须是唯一的。 137 | 138 | 【注意】:AppKey和AppVersion都是用于[Tinker Platform](http://www.tinkerpatch.com/)自定发布补丁包所需要的。如果你不使用Tinker Platform来管理你的热更新的话,可以随便设置。 139 | 140 | 以下是`tinkerpatch.gradle`的配置样例: 141 | 142 | ``` 143 | apply plugin: 'tinkerpatch-support' 144 | 145 | /** 146 | * TODO: 请按自己的需求修改为适应自己工程的参数 147 | */ 148 | def bakPath = file("${buildDir}/bakApk/") 149 | /** 每次在打补丁包的时候,需要更新这里的旧包的位置 **/ 150 | def baseInfo = "app-1.0.0-0810-17-28-31" 151 | def variantName = "release" 152 | 153 | def AppKey = "4c118de195c79b14" 154 | def AppVersion = "1.0.0" 155 | 156 | /** 157 | * 对于插件各参数的详细解析请参考 158 | * http://tinkerpatch.com/Docs/SDK 159 | */ 160 | tinkerpatchSupport { 161 | 162 | /** 可以在debug的时候关闭 tinkerPatch, isRelease() 可以判断BuildType是否为Release **/ 163 | tinkerEnable = isRelease() 164 | 165 | /** 是否使用一键接入功能 **/ 166 | 167 | reflectApplication = true 168 | /** 169 | * 是否开启加固模式,只能在APK将要进行加固时使用,否则会patch失败。 170 | * 如果只在某个渠道使用了加固,可使用多flavors配置 171 | **/ 172 | protectedApp = false 173 | /** 174 | * 实验功能 175 | * 补丁是否支持新增 Activity (新增Activity的exported属性必须为false) 176 | **/ 177 | supportComponent = true 178 | 179 | /** 在tinkerpatch.com得到的appKey,改成你的应用appKey **/ 180 | 181 | appKey = "${AppKey}" 182 | 183 | /** 注意: 若发布新的全量包, appVersion一定要更新 **/ 184 | appVersion = "${AppVersion}" 185 | 186 | autoBackupApkPath = "${bakPath}" 187 | 188 | def pathPrefix = "${bakPath}/${baseInfo}/${variantName}/" 189 | def name = "${project.name}-${variantName}" 190 | 191 | baseApkFile = "${pathPrefix}/${name}.apk" 192 | baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt" 193 | baseResourceRFile = "${pathPrefix}/${name}-R.txt" 194 | } 195 | 196 | /** 197 | * 用于用户在代码中判断tinkerPatch是否被使能 198 | */ 199 | android { 200 | defaultConfig { 201 | buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}" 202 | } 203 | } 204 | 205 | /** 206 | * 一般来说,我们无需对下面的参数做任何的修改 207 | * 对于各参数的详细介绍请参考: 208 | * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97 209 | */ 210 | tinkerPatch { 211 | ignoreWarning = true 212 | useSign = true 213 | dex { 214 | dexMode = "jar" 215 | pattern = ["classes*.dex"] 216 | loader = [] 217 | } 218 | lib { 219 | pattern = ["lib/*/*.so"] 220 | } 221 | 222 | res { 223 | pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] 224 | ignoreChange = [] 225 | largeModSize = 100 226 | } 227 | 228 | packageConfig { 229 | } 230 | sevenZip { 231 | zipArtifact = "com.tencent.mm:SevenZip:1.1.10" 232 | // path = "/usr/local/bin/7za" 233 | } 234 | buildConfig { 235 | keepDexApply = false 236 | } 237 | 238 | /** 239 | * 如果只想在Release中打开tinker,可以把tinkerEnable赋值为这个函数的return 240 | * @return 是否为release 241 | */ 242 | def isRelease() { 243 | Gradle gradle = getGradle() 244 | String tskReqStr = gradle.getStartParameter().getTaskRequests().toString() 245 | 246 | Pattern pattern 247 | if (tskReqStr.contains("assemble")) { 248 | println tskReqStr 249 | pattern = Pattern.compile("assemble(\\w*)(Release|Debug)") 250 | } else { 251 | pattern = Pattern.compile("generate(\\w*)(Release|Debug)") 252 | } 253 | Matcher matcher = pattern.matcher(tskReqStr) 254 | 255 | if (matcher.find()) { 256 | String task = matcher.group(0).toLowerCase() 257 | println("[BuildType] Current task: " + task) 258 | return task.contains("release") 259 | } else { 260 | println "[BuildType] NO MATCH FOUND" 261 | return true 262 | } 263 | } 264 | } 265 | 266 | ``` 267 | 268 | 6. 最后就是配置Application了。因为我在上面设置`reflectApplication = true`使用了一键接入功能,所以就不需要进行复杂的配置了,如下: 269 | 270 | ``` 271 | public class App extends Application { 272 | 273 | @Override 274 | public void onCreate() { 275 | super.onCreate(); 276 | initTinkerPatch(); 277 | } 278 | 279 | /** 280 | * 我们需要确保至少对主进程跟patch进程初始化 TinkerPatch 281 | */ 282 | private void initTinkerPatch() { 283 | if (BuildConfig.TINKER_ENABLE) { 284 | // 我们可以从这里获得Tinker加载过程的信息 285 | ApplicationLike tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike(); 286 | 287 | // 初始化TinkerPatch SDK, 更多配置可参照API章节中的,初始化SDK 288 | TinkerPatch.init(tinkerApplicationLike) 289 | .reflectPatchLibrary() 290 | //向后台获取是否有补丁包更新,默认的访问间隔为3个小时,若参数为true,即每次调用都会真正的访问后台配置 291 | //你也可以在用户登录或者APP启动等一些关键路径,使用fetchPatchUpdate(true)强制检查更新 292 | .fetchPatchUpdate(false) 293 | //设置访问后台补丁包更新配置的时间间隔,默认为3个小时 294 | .setFetchPatchIntervalByHours(3) 295 | //向后台获得动态配置,默认的访问间隔为3个小时 296 | //若参数为true,即每次调用都会真正的访问后台配置 297 | .fetchDynamicConfig(new ConfigRequestCallback() { 298 | @Override public void onSuccess(HashMap hashMap) { 299 | Log.e("xuexiang", "参数:" + JsonUtil.toJson(hashMap)); 300 | 301 | } 302 | @Override public void onFail(Exception e) { } 303 | }, true) 304 | //设置访问后台动态配置的时间间隔,默认为3个小时 305 | .setFetchDynamicConfigIntervalByHours(3) 306 | //设置收到后台回退要求时,锁屏清除补丁,默认是等主进程重启时自动清除 307 | .setPatchRollbackOnScreenOff(true) 308 | //设置补丁合成成功后,锁屏重启程序,默认是等应用自然重启 309 | .setPatchRestartOnSrceenOff(true) 310 | .setPatchResultCallback(new ResultCallBack() { 311 | @Override 312 | public void onPatchResult(PatchResult patchResult) { 313 | ToastUtils.toast("补丁修复:" + (patchResult.isSuccess ? "成功" : "失败")); 314 | } 315 | }); 316 | 317 | // 每隔3个小时(通过setFetchPatchIntervalByHours设置)去访问后台时候有更新,通过handler实现轮训的效果 318 | TinkerPatch.with().fetchPatchUpdateAndPollWithInterval(); 319 | } 320 | } 321 | } 322 | ``` 323 | 324 | 更多复杂的配置和高端自定义操作可参见[官方文档](http://www.tinkerpatch.com/Docs/api)。 325 | 326 | 以上就完成了Tinker的接入工作。 327 | 328 | ## 如何使用Tinker进行热修复 329 | 330 | 1. 先随便在程序中写一个bug,然后执行`./gradlew assembleRelease`进行打包。当然你也可以直接在AS右侧的`Gradle`中找到你的应用,并在`Tasks->build->assembleRelease`找到assembleRelease的任务,双击执行任务。 331 | 332 | ![](https://github.com/xuexiangjys/TinkerTest/blob/master/img/1.png) 333 | 334 | 执行完成后,你会在你模块的`build->bakApk`下看到你打的apk包。 335 | 336 | ![](https://github.com/xuexiangjys/TinkerTest/blob/master/img/2.png) 337 | 338 | 2. 你将刚才生成apk的那个文件夹的名称设置在之前说的`tinkerpatch.gradle`中的`baseInfo`。 339 | 340 | 3. 将bug修复后,执行`./gradlew tinkerPatchRelease`打补丁包。当然你也可以直接在AS右侧的`Gradle`中找到你的应用,并在`Tasks->tinker->tinkerPatchRelease`找到tinkerPatchRelease的任务,双击执行任务。 341 | 342 | ![](https://github.com/xuexiangjys/TinkerTest/blob/master/img/3.png) 343 | 344 | 执行完成后,你会在你模块的`build->outputs->apk->tinkerPatch->release`下看到你需要的补丁包`patch_signed_7zip.apk`。 345 | 346 | ![](https://github.com/xuexiangjys/TinkerTest/blob/master/img/4.png) 347 | 348 | 4. 最后调用`TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path);`, path传入你补丁包的所在的路径即可完成热更新。 349 | 350 | 需要注意的是,执行热更新后,需要重启程序才能生效! 351 | 352 | ## 如何使用Tinker Platform进行补丁管理 353 | 354 | ### 补丁发布 355 | 356 | 1.第一步你需要在[Tinker Platform](http://www.tinkerpatch.com/)上注册你的账号。 357 | ![](https://github.com/xuexiangjys/TinkerTest/blob/master/img/5.png) 358 | 359 | 2.第二步你需要新建一个APP,获取AppKey。 360 | ![](https://github.com/xuexiangjys/TinkerTest/blob/master/img/6.png) 361 | 362 | 3.第三步就需要在`tinkerpatch.gradle`中将AppKey和AppVersion都填写清楚。这里AppVersion一定要保重唯一性。 363 | ![](https://github.com/xuexiangjys/TinkerTest/blob/master/img/7.png) 364 | 365 | 4.第四步就是按照上面的步骤生成补丁,然后在Tinker Platform上填写补丁信息进行补丁发布。 366 | 367 | ### 补丁获取 368 | 369 | 1.向后台获取是否有补丁包更新,默认的访问间隔为3个小时,若参数immediately为 true,即每次调用都会真正的访问后台是否有更新。 370 | 371 | ``` 372 | TinkerPatch.with().fetchPatchUpdate(true); //设置为true便立即主动去拉取补丁信息,并进行热更新。 373 | ``` 374 | 375 | 2.我们可以通过以下方法,设置访问的时间间隔,单位为小时。若为 -1,即禁止以后都不再请求后台补丁更新。 376 | 377 | ``` 378 | TinkerPatch.with().setFetchPatchIntervalByHours(1);//设置一小时检查一次 379 | ``` 380 | 381 | ## 联系方式 382 | 383 | [![](https://img.shields.io/badge/点击一键加入QQ交流群-602082750-blue.svg)](http://shang.qq.com/wpa/qunwpa?idkey=9922861ef85c19f1575aecea0e8680f60d9386080a97ed310c971ae074998887) 384 | 385 | ![](https://github.com/xuexiangjys/XPage/blob/master/img/qq_group.jpg) 386 | 387 | 388 | -------------------------------------------------------------------------------- /apk/app-release-1.0.0.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/TinkerTest/c95683aecbc075832871fe6315a696519c10bf7c/apk/app-release-1.0.0.apk -------------------------------------------------------------------------------- /apk/app-release-1.0.1.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/TinkerTest/c95683aecbc075832871fe6315a696519c10bf7c/apk/app-release-1.0.1.apk -------------------------------------------------------------------------------- /apk/patch_signed_7zip.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/TinkerTest/c95683aecbc075832871fe6315a696519c10bf7c/apk/patch_signed_7zip.apk -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.xuexiang.xaop' 3 | 4 | android { 5 | compileSdkVersion build_versions.target_sdk 6 | buildToolsVersion build_versions.build_tools 7 | 8 | defaultConfig { 9 | applicationId "com.xuexiang.tinkertest" 10 | minSdkVersion 19 11 | targetSdkVersion build_versions.target_sdk 12 | versionCode 2 13 | versionName "1.0.1" 14 | 15 | multiDexEnabled true 16 | } 17 | 18 | signingConfigs { 19 | release { 20 | storeFile file(app_release.storeFile) 21 | storePassword app_release.storePassword 22 | keyAlias app_release.keyAlias 23 | keyPassword app_release.keyPassword 24 | } 25 | } 26 | 27 | buildTypes { 28 | release { 29 | minifyEnabled true 30 | shrinkResources true 31 | signingConfig signingConfigs.release 32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 33 | } 34 | debug { 35 | debuggable true 36 | minifyEnabled false 37 | } 38 | } 39 | 40 | //recommend 41 | dexOptions { 42 | jumboMode = true 43 | } 44 | 45 | sourceSets { 46 | main { 47 | jniLibs.srcDirs = ['libs'] 48 | } 49 | } 50 | 51 | compileOptions { 52 | targetCompatibility JavaVersion.VERSION_1_8 53 | sourceCompatibility JavaVersion.VERSION_1_8 54 | } 55 | 56 | 57 | } 58 | 59 | dependencies { 60 | implementation fileTree(dir: 'libs', include: ['*.jar']) 61 | implementation deps.support.app_compat 62 | 63 | implementation 'com.android.support:multidex:1.0.3' 64 | implementation 'com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.2.13' 65 | 66 | implementation 'com.github.xuexiangjys.XUtil:xutil-core:1.1.5' 67 | implementation 'com.github.xuexiangjys.XAOP:xaop-runtime:1.0.3' 68 | } 69 | 70 | apply from: 'tinkerpatch.gradle' -------------------------------------------------------------------------------- /app/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | android.enableD8.desugaring= true 16 | android.useDexArchive= true -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | #=========================================基础不变的混淆配置=========================================## 2 | #指定代码的压缩级别 3 | -optimizationpasses 5 4 | #包名不混合大小写 5 | -dontusemixedcaseclassnames 6 | #不去忽略非公共的库类 7 | -dontskipnonpubliclibraryclasses 8 | # 指定不去忽略非公共的库的类的成员 9 | -dontskipnonpubliclibraryclassmembers 10 | #优化 不优化输入的类文件 11 | -dontoptimize 12 | #预校验 13 | -dontpreverify 14 | #混淆时是否记录日志 15 | -verbose 16 | # 混淆时所采用的算法 17 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 18 | #保护注解 19 | -keepattributes *Annotation* 20 | #忽略警告 21 | -ignorewarning 22 | 23 | ##记录生成的日志数据,gradle build时在本项目根目录输出## 24 | #apk 包内所有 class 的内部结构 25 | -dump class_files.txt 26 | #未混淆的类和成员 27 | -printseeds seeds.txt 28 | #列出从 apk 中删除的代码 29 | -printusage unused.txt 30 | #混淆前后的映射 31 | -printmapping mapping.txt 32 | # 并保留源文件名为"Proguard"字符串,而非原始的类名 并保留行号 33 | -keepattributes SourceFile,LineNumberTable 34 | ########记录生成的日志数据,gradle build时 在本项目根目录输出-end##### 35 | 36 | #需要保留的东西 37 | # 保持哪些类不被混淆 38 | -keep public class * extends android.app.Fragment 39 | -keep public class * extends android.app.Activity 40 | -keep public class * extends android.app.Application 41 | -keep public class * extends android.app.Service 42 | -keep public class * extends android.content.BroadcastReceiver 43 | -keep public class * extends android.content.ContentProvider 44 | -keep public class * extends android.app.backup.BackupAgentHelper 45 | -keep public class * extends android.preference.Preference 46 | -keep public class * extends android.support.v4.** 47 | -keep public class com.android.vending.licensing.ILicensingService 48 | 49 | #如果有引用v4包可以添加下面这行 50 | -keep public class * extends android.support.v4.app.Fragment 51 | 52 | ##########JS接口类不混淆,否则执行不了 53 | -dontwarn com.android.JsInterface.** 54 | -keep class com.android.JsInterface.** {*; } 55 | 56 | #极光推送和百度lbs android sdk一起使用proguard 混淆的问题#http的类被混淆后,导致apk定位失败,保持apache 的http类不被混淆就好了 57 | -dontwarn org.apache.** 58 | -keep class org.apache.**{ *; } 59 | 60 | -keep public class * extends android.view.View { 61 | public (android.content.Context); 62 | public (android.content.Context, android.util.AttributeSet); 63 | public (android.content.Context, android.util.AttributeSet, int); 64 | public void set*(...); 65 | } 66 | 67 | #保持 native 方法不被混淆 68 | -keepclasseswithmembernames class * { 69 | native ; 70 | } 71 | 72 | #保持自定义控件类不被混淆 73 | -keepclasseswithmembers class * { 74 | public (android.content.Context, android.util.AttributeSet); 75 | } 76 | 77 | #保持自定义控件类不被混淆 78 | -keepclassmembers class * extends android.app.Activity { 79 | public void *(android.view.View); 80 | } 81 | 82 | #保持 Parcelable 不被混淆 83 | -keep class * implements android.os.Parcelable { 84 | public static final android.os.Parcelable$Creator *; 85 | } 86 | 87 | #保持 Serializable 不被混淆 88 | -keepnames class * implements java.io.Serializable 89 | 90 | #保持 Serializable 不被混淆并且enum 类也不被混淆 91 | -keepclassmembers class * implements java.io.Serializable { 92 | static final long serialVersionUID; 93 | private static final java.io.ObjectStreamField[] serialPersistentFields; 94 | !static !transient ; 95 | !private ; 96 | !private ; 97 | private void writeObject(java.io.ObjectOutputStream); 98 | private void readObject(java.io.ObjectInputStream); 99 | java.lang.Object writeReplace(); 100 | java.lang.Object readResolve(); 101 | } 102 | 103 | #保持枚举 enum 类不被混淆 如果混淆报错,建议直接使用上面的 -keepclassmembers class * implements java.io.Serializable即可 104 | -keepclassmembers enum * { 105 | public static **[] values(); 106 | public static ** valueOf(java.lang.String); 107 | } 108 | 109 | -keepclassmembers class * { 110 | public void *ButtonClicked(android.view.View); 111 | } 112 | 113 | #不混淆资源类 114 | -keep class **.R$* {*;} 115 | 116 | #===================================混淆保护自己项目的部分代码以及引用的第三方jar包library=============================####### 117 | #如果引用了v4或者v7包 118 | -dontwarn android.support.** 119 | 120 | # zxing 121 | -dontwarn com.google.zxing.** 122 | -keep class com.google.zxing.**{*;} 123 | 124 | #SignalR推送 125 | -keep class microsoft.aspnet.signalr.** { *; } 126 | 127 | # 极光推送混淆 128 | -dontoptimize 129 | -dontpreverify 130 | -dontwarn cn.jpush.** 131 | -keep class cn.jpush.** { *; } 132 | -dontwarn cn.jiguang.** 133 | -keep class cn.jiguang.** { *; } 134 | 135 | # 数据库框架OrmLite 136 | -keepattributes *DatabaseField* 137 | -keepattributes *DatabaseTable* 138 | -keepattributes *SerializedName* 139 | -keep class com.j256.** 140 | -keepclassmembers class com.j256.** { *; } 141 | -keep enum com.j256.** 142 | -keepclassmembers enum com.j256.** { *; } 143 | -keep interface com.j256.** 144 | -keepclassmembers interface com.j256.** { *; } 145 | 146 | #XHttp2 147 | -keep class com.xuexiang.xhttp2.model.** { *; } 148 | -keep class com.xuexiang.xhttp2.cache.model.** { *; } 149 | -keep class com.xuexiang.xhttp2.cache.stategy.**{*;} 150 | -keep class com.xuexiang.xhttp2.annotation.** { *; } 151 | 152 | #okhttp 153 | -dontwarn com.squareup.okhttp3.** 154 | -keep class com.squareup.okhttp3.** { *;} 155 | -dontwarn okio.** 156 | -dontwarn javax.annotation.Nullable 157 | -dontwarn javax.annotation.ParametersAreNonnullByDefault 158 | -dontwarn javax.annotation.** 159 | 160 | #如果用到Gson解析包的,直接添加下面这几行就能成功混淆,不然会报错 161 | -keepattributes Signature 162 | -keep class com.google.gson.stream.** { *; } 163 | -keepattributes EnclosingMethod 164 | -keep class org.xz_sale.entity.**{*;} 165 | -keep class com.google.gson.** {*;} 166 | -keep class com.google.**{*;} 167 | -keep class sun.misc.Unsafe { *; } 168 | -keep class com.google.gson.stream.** { *; } 169 | -keep class com.google.gson.examples.android.model.** { *; } 170 | 171 | # Retrofit 172 | -dontwarn retrofit2.** 173 | -keep class retrofit2.** { *; } 174 | -keepattributes Exceptions#XHt 175 | 176 | # RxJava RxAndroid 177 | -dontwarn sun.misc.** 178 | -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { 179 | long producerIndex; 180 | long consumerIndex; 181 | } 182 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { 183 | rx.internal.util.atomic.LinkedQueueNode producerNode; 184 | } 185 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { 186 | rx.internal.util.atomic.LinkedQueueNode consumerNode; 187 | } 188 | 189 | -dontwarn okio.** 190 | -dontwarn javax.annotation.Nullable 191 | -dontwarn javax.annotation.ParametersAreNonnullByDefault 192 | -dontwarn javax.annotation.** 193 | 194 | # fastjson 195 | -dontwarn com.alibaba.fastjson.** 196 | -keep class com.alibaba.fastjson.** { *; } 197 | -keepattributes Signature 198 | 199 | # xpage 200 | -keep class com.xuexiang.xpage.annotation.** { *; } 201 | 202 | # xaop 203 | -keep @com.xuexiang.xaop.annotation.* class * {*;} 204 | -keep class * { 205 | @com.xuexiang.xaop.annotation.* ; 206 | } 207 | -keepclassmembers class * { 208 | @com.xuexiang.xaop.annotation.* ; 209 | } 210 | 211 | # xrouter 212 | -keep public class com.xuexiang.xrouter.routes.**{*;} 213 | -keep class * implements com.xuexiang.xrouter.facade.template.ISyringe{*;} 214 | # 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口 215 | -keep interface * implements com.xuexiang.xrouter.facade.template.IProvider 216 | # 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现 217 | -keep class * implements com.xuexiang.xrouter.facade.template.IProvider 218 | 219 | # xupdate 220 | -keep class com.xuexiang.xupdate.entity.** { *; } 221 | 222 | # xvideo 223 | -keep class com.xuexiang.xvideo.jniinterface.** { *; } 224 | 225 | # xipc 226 | -keep @com.xuexiang.xipc.annotation.* class * {*;} 227 | -keep class * { 228 | @com.xuexiang.xipc.annotation.* ; 229 | } 230 | -keepclassmembers class * { 231 | @com.xuexiang.xipc.annotation.* ; 232 | } 233 | 234 | # help us to debug 235 | -renamesourcefileattribute SourceFile 236 | -keepattributes Exceptions 237 | -keepattributes SourceFile,LineNumberTable,keepattributes 238 | -keepattributes InnerClasses 239 | -keepattributes EnclosingMethod 240 | -keepattributes Signature 241 | -keepattributes *Annotation* 242 | -dontshrink 243 | 244 | # Config need by TinkerPatch 245 | -keep class com.tinkerpatch.sdk.TinkerPatch { *; } 246 | -keep class com.tinkerpatch.sdk.BuildConfig { *; } 247 | 248 | -keep class com.tinkerpatch.sdk.TinkerPatch$Builder { *; } 249 | -keep class com.tinkerpatch.sdk.server.RequestLoader { *; } 250 | -keep class com.tinkerpatch.sdk.util.ContentLengthInputStream { *; } 251 | -keep interface com.tinkerpatch.sdk.server.model.DataFetcher { *; } 252 | -keep interface com.tinkerpatch.sdk.server.model.DataFetcher$DataCallback { *; } 253 | -keep class com.tinkerpatch.sdk.server.model.TinkerClientUrl { *; } 254 | -keep class com.tinkerpatch.sdk.server.callback.** { *; } 255 | -keep class com.tinkerpatch.sdk.tinker.callback.** { *; } 256 | -keep public class * extends android.app.Application 257 | -keep class com.tinkerpatch.sdk.loader.TinkerPatchApplicationLike { *; } 258 | -keep class com.tencent.tinker.** { *; } 259 | 260 | # Config from tinker 261 | -dontwarn com.tencent.tinker.anno.AnnotationProcessor 262 | -keep @com.tencent.tinker.anno.DefaultLifeCycle public class * 263 | -keep public class * extends android.app.Application { 264 | *; 265 | } 266 | 267 | -keep public class com.tencent.tinker.loader.app.ApplicationLifeCycle { 268 | *; 269 | } 270 | -keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle { 271 | *; 272 | } 273 | 274 | -keep public class com.tencent.tinker.loader.TinkerLoader { 275 | *; 276 | } 277 | -keep public class * extends com.tencent.tinker.loader.TinkerLoader { 278 | *; 279 | } 280 | -keep public class com.tencent.tinker.loader.TinkerTestDexLoad { 281 | *; 282 | } 283 | -keep public class com.tencent.tinker.loader.TinkerTestAndroidNClassLoader { 284 | *; 285 | } 286 | 287 | #your dex.loader patterns here 288 | -keep class tinker.sample.android.app.SampleApplication 289 | -keep class com.tencent.tinker.loader.** 290 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/xuexiang/tinkertest/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xuexiang.tinkertest; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.xuexiang.tinkertest", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/tinkertest/App.java: -------------------------------------------------------------------------------- 1 | package com.xuexiang.tinkertest; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.tencent.tinker.entry.ApplicationLike; 7 | import com.tencent.tinker.lib.service.PatchResult; 8 | import com.tinkerpatch.sdk.TinkerPatch; 9 | import com.tinkerpatch.sdk.loader.TinkerPatchApplicationLike; 10 | import com.tinkerpatch.sdk.server.callback.ConfigRequestCallback; 11 | import com.tinkerpatch.sdk.tinker.callback.ResultCallBack; 12 | import com.xuexiang.xaop.XAOP; 13 | import com.xuexiang.xutil.XUtil; 14 | import com.xuexiang.xutil.net.JsonUtil; 15 | import com.xuexiang.xutil.tip.ToastUtils; 16 | 17 | import java.util.HashMap; 18 | 19 | /** 20 | * @author xuexiang 21 | * @since 2018/8/10 下午3:59 22 | */ 23 | public class App extends Application { 24 | 25 | @Override 26 | public void onCreate() { 27 | super.onCreate(); 28 | 29 | XUtil.init(this); 30 | XAOP.init(this); 31 | 32 | initTinkerPatch(); 33 | } 34 | 35 | /** 36 | * 我们需要确保至少对主进程跟patch进程初始化 TinkerPatch 37 | */ 38 | private void initTinkerPatch() { 39 | if (BuildConfig.TINKER_ENABLE) { 40 | // 我们可以从这里获得Tinker加载过程的信息 41 | ApplicationLike tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike(); 42 | 43 | // 初始化TinkerPatch SDK, 更多配置可参照API章节中的,初始化SDK 44 | TinkerPatch.init(tinkerApplicationLike) 45 | .reflectPatchLibrary() 46 | //向后台获取是否有补丁包更新,默认的访问间隔为3个小时,若参数为true,即每次调用都会真正的访问后台配置 47 | //你也可以在用户登录或者APP启动等一些关键路径,使用fetchPatchUpdate(true)强制检查更新 48 | .fetchPatchUpdate(false) 49 | //设置访问后台补丁包更新配置的时间间隔,默认为3个小时 50 | .setFetchPatchIntervalByHours(3) 51 | //向后台获得动态配置,默认的访问间隔为3个小时 52 | //若参数为true,即每次调用都会真正的访问后台配置 53 | .fetchDynamicConfig(new ConfigRequestCallback() { 54 | @Override public void onSuccess(HashMap hashMap) { 55 | Log.e("xuexiang", "参数:" + JsonUtil.toJson(hashMap)); 56 | 57 | } 58 | @Override public void onFail(Exception e) { } 59 | }, true) 60 | //设置访问后台动态配置的时间间隔,默认为3个小时 61 | .setFetchDynamicConfigIntervalByHours(3) 62 | //设置收到后台回退要求时,锁屏清除补丁,默认是等主进程重启时自动清除 63 | .setPatchRollbackOnScreenOff(true) 64 | //设置补丁合成成功后,锁屏重启程序,默认是等应用自然重启 65 | .setPatchRestartOnSrceenOff(true) 66 | .setPatchResultCallback(new ResultCallBack() { 67 | @Override 68 | public void onPatchResult(PatchResult patchResult) { 69 | ToastUtils.toast("补丁修复:" + (patchResult.isSuccess ? "成功" : "失败")); 70 | } 71 | }); 72 | 73 | // 每隔3个小时(通过setFetchPatchIntervalByHours设置)去访问后台时候有更新,通过handler实现轮训的效果 74 | TinkerPatch.with().fetchPatchUpdateAndPollWithInterval(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/tinkertest/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.xuexiang.tinkertest; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.View; 8 | import android.widget.TextView; 9 | 10 | import com.tencent.tinker.lib.tinker.TinkerInstaller; 11 | import com.tinkerpatch.sdk.TinkerPatch; 12 | import com.xuexiang.tinkertest.util.ReopenAppUtils; 13 | import com.xuexiang.xaop.annotation.Permission; 14 | import com.xuexiang.xutil.app.ActivityUtils; 15 | import com.xuexiang.xutil.app.AppUtils; 16 | import com.xuexiang.xutil.app.IntentUtils; 17 | import com.xuexiang.xutil.app.PathUtils; 18 | 19 | import static com.xuexiang.xaop.consts.PermissionConsts.STORAGE; 20 | 21 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 22 | 23 | private static final int REQUEST_CODE_GET_PATCH_PACKAGE = 20; 24 | private TextView mTvVersion; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_main); 30 | mTvVersion = findViewById(R.id.tv_version); 31 | mTvVersion.setText(String.format("当前版本:%s", AppUtils.getAppVersionName())); 32 | } 33 | 34 | @Override 35 | public void onClick(View v) { 36 | switch (v.getId()) { 37 | case R.id.btn_fix: 38 | choosePatchApk(); 39 | break; 40 | case R.id.btn_reopen: 41 | ReopenAppUtils.reopenApp(this); 42 | break; 43 | case R.id.btn_check_update: 44 | TinkerPatch.with().fetchPatchUpdate(true); 45 | break; 46 | default: 47 | break; 48 | } 49 | } 50 | 51 | @Permission(STORAGE) 52 | private void choosePatchApk() { 53 | ActivityUtils.startActivityForResult(this, IntentUtils.getDocumentPickerIntent(IntentUtils.DocumentType.ANY), REQUEST_CODE_GET_PATCH_PACKAGE); 54 | } 55 | 56 | @Override 57 | @SuppressLint("MissingPermission") 58 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 59 | super.onActivityResult(requestCode, resultCode, data); 60 | if (resultCode == RESULT_OK || requestCode == REQUEST_CODE_GET_PATCH_PACKAGE) { 61 | if (data != null) { 62 | String path = PathUtils.getFilePathByUri(data.getData()); 63 | TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path); 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/tinkertest/util/ReopenAppUtils.java: -------------------------------------------------------------------------------- 1 | package com.xuexiang.tinkertest.util; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.PendingIntent; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | 8 | /** 9 | * @author xuexiang 10 | * @since 2018/8/10 下午4:40 11 | */ 12 | public final class ReopenAppUtils { 13 | 14 | private ReopenAppUtils() { 15 | throw new UnsupportedOperationException("u can't instantiate me..."); 16 | } 17 | 18 | /** 19 | * 重启app 20 | * 21 | * @param context 22 | */ 23 | public static void reopenApp(Context context) { 24 | Intent intent = new Intent(context, StartAppReceiver.class); 25 | PendingIntent restartIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_ONE_SHOT); 26 | //退出程序 27 | AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 28 | mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, restartIntent); // 1秒钟后重启应用 29 | 30 | android.os.Process.killProcess(android.os.Process.myPid()); 31 | System.exit(0); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/xuexiang/tinkertest/util/StartAppReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | package com.xuexiang.tinkertest.util; 18 | 19 | import android.content.BroadcastReceiver; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | 23 | /** 24 | *
25 |  *     desc   : 启动程序广播接收器
26 |  *     author : xuexiang
27 |  *     time   : 2018/5/13 上午10:11
28 |  * 
29 | */ 30 | public class StartAppReceiver extends BroadcastReceiver { 31 | 32 | @Override 33 | public void onReceive(Context context, Intent intent) { 34 | context.startActivity(context.getPackageManager().getLaunchIntentForPackage(context.getPackageName())); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 26 | 27 |