├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── robustsign2 ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── anlaiye │ │ │ │ └── swt │ │ │ │ └── gradletest │ │ │ │ ├── MainActivity.java │ │ │ │ └── SimpleTinkerInApplicationLike.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 │ │ └── com │ │ └── anlaiye │ │ └── swt │ │ └── gradletest │ │ └── ExampleUnitTest.java └── tinker_multidexkeep.pro ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweet/TinkerGradleDemo/09fda4b35c7b4b21541a05abc232f7ec5e5c908b/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 29 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | 58 | 59 | 1.8 60 | 61 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinkerGradleDemo 2 | 考虑到现在csdn改版下载代码都需要分了,所以我重新上传了一份到了github 3 | 4 | 原博客地址为 5 | https://blog.csdn.net/qq_15527709/article/details/61921447 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | testImplementation 'junit:junit:4.12' 6 | implementation "androidx.appcompat:appcompat:1.1.0" 7 | api("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true } 8 | 9 | // Maven local cannot handle transist dependencies. 10 | implementation("com.tencent.tinker:tinker-android-loader:${TINKER_VERSION}") { changing = true } 11 | 12 | annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true } 13 | compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true } 14 | compileOnly("com.tencent.tinker:tinker-android-anno-support:${TINKER_VERSION}") { changing = true } 15 | 16 | implementation "androidx.multidex:multidex:2.0.1" 17 | 18 | } 19 | 20 | def gitSha() { 21 | try { 22 | String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim() 23 | if (gitRev == null) { 24 | throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'") 25 | } 26 | return gitRev 27 | } catch (Exception e) { 28 | throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'") 29 | } 30 | } 31 | 32 | def javaVersion = JavaVersion.VERSION_1_7 33 | 34 | android { 35 | compileSdkVersion 28 36 | buildToolsVersion '28.0.3' 37 | 38 | compileOptions { 39 | sourceCompatibility javaVersion 40 | targetCompatibility javaVersion 41 | } 42 | //recommend 43 | dexOptions { 44 | jumboMode = true 45 | } 46 | 47 | signingConfigs { 48 | debug { 49 | keyAlias "key0" 50 | keyPassword "123456" 51 | storeFile file('robustsign2') 52 | storePassword "123456" 53 | } 54 | release { 55 | keyAlias "key0" 56 | keyPassword "123456" 57 | storeFile file('robustsign2') 58 | storePassword "123456" 59 | } 60 | } 61 | 62 | defaultConfig { 63 | applicationId "tinker.sample.android" 64 | minSdkVersion 14 65 | targetSdkVersion 26 66 | versionCode 1 67 | versionName "1.0.0" 68 | /** 69 | * you can use multiDex and install it in your ApplicationLifeCycle implement 70 | */ 71 | multiDexEnabled true 72 | multiDexKeepProguard file("tinker_multidexkeep.pro") 73 | /** 74 | * buildConfig can change during patch! 75 | * we can use the newly value when patch 76 | */ 77 | buildConfigField "String", "MESSAGE", "\"I am the base apk\"" 78 | // buildConfigField "String", "MESSAGE", "\"I am the patch apk\"" 79 | /** 80 | * client version would update with patch 81 | * so we can get the newly git version easily! 82 | */ 83 | buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\"" 84 | buildConfigField "String", "PLATFORM", "\"all\"" 85 | } 86 | 87 | // aaptOptions{ 88 | // additionalParameters "--emit-ids", "${project.file('public.txt')}" 89 | // cruncherEnabled false 90 | // } 91 | 92 | // //use to test flavors support 93 | // productFlavors { 94 | // flavor1 { 95 | // applicationId 'tinker.sample.android.flavor1' 96 | // } 97 | // 98 | // flavor2 { 99 | // applicationId 'tinker.sample.android.flavor2' 100 | // } 101 | // } 102 | 103 | buildTypes { 104 | release { 105 | minifyEnabled true 106 | signingConfig signingConfigs.release 107 | proguardFiles getDefaultProguardFile('proguard-android.txt'), project.file('proguard-rules.pro') 108 | } 109 | debug { 110 | debuggable true 111 | minifyEnabled false 112 | signingConfig signingConfigs.debug 113 | } 114 | } 115 | sourceSets { 116 | main { 117 | jniLibs.srcDirs = ['libs'] 118 | } 119 | } 120 | 121 | packagingOptions { 122 | exclude "/META-INF/**" 123 | } 124 | } 125 | 126 | def bakPath = file("${buildDir}/bakApk/") 127 | 128 | /** 129 | * you can use assembleRelease to build you base apk 130 | * use tinkerPatchRelease -POLD_APK= -PAPPLY_MAPPING= -PAPPLY_RESOURCE= to build patch 131 | * add apk from the build/bakApk 132 | */ 133 | ext { 134 | //for some reason, you may want to ignore tinkerBuild, such as instant run debug build? 135 | tinkerEnabled = true 136 | 137 | //for normal build 138 | //old apk file to build patch apk 139 | 140 | tinkerOldApkPath = "${bakPath}/app-release-0429-16-28-46.apk" 141 | //proguard mapping file to build patch apk 142 | tinkerApplyMappingPath = "${bakPath}/app-release-0429-16-28-46-mapping.txt" 143 | //resource R.txt to build patch apk, must input if there is resource changed 144 | tinkerApplyResourcePath = "${bakPath}/app-release-0429-16-28-46-R.txt" 145 | 146 | //only use for build all flavor, if not, just ignore this field 147 | tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47" 148 | } 149 | 150 | 151 | def getOldApkPath() { 152 | return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath 153 | } 154 | 155 | def getApplyMappingPath() { 156 | return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath 157 | } 158 | 159 | def getApplyResourceMappingPath() { 160 | return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath 161 | } 162 | 163 | def getTinkerIdValue() { 164 | return "1.0" 165 | } 166 | 167 | def buildWithTinker() { 168 | return hasProperty("TINKER_ENABLE") ? Boolean.parseBoolean(TINKER_ENABLE) : ext.tinkerEnabled 169 | } 170 | 171 | def getTinkerBuildFlavorDirectory() { 172 | return ext.tinkerBuildFlavorDirectory 173 | } 174 | 175 | if (buildWithTinker()) { 176 | apply plugin: 'com.tencent.tinker.patch' 177 | 178 | tinkerPatch { 179 | /** 180 | * necessary,default 'null' 181 | * the old apk path, use to diff with the new apk to build 182 | * add apk from the build/bakApk 183 | */ 184 | oldApk = getOldApkPath() 185 | /** 186 | * optional,default 'false' 187 | * there are some cases we may get some warnings 188 | * if ignoreWarning is true, we would just assert the patch process 189 | * case 1: minSdkVersion is below 14, but you are using dexMode with raw. 190 | * it must be crash when load. 191 | * case 2: newly added Android Component in AndroidManifest.xml, 192 | * it must be crash when load. 193 | * case 3: loader classes in dex.loader{} are not keep in the main dex, 194 | * it must be let tinker not work. 195 | * case 4: loader classes in dex.loader{} changes, 196 | * loader classes is ues to load patch dex. it is useless to change them. 197 | * it won't crash, but these changes can't effect. you may ignore it 198 | * case 5: resources.arsc has changed, but we don't use applyResourceMapping to build 199 | */ 200 | ignoreWarning = false 201 | 202 | /** 203 | * optional,default 'true' 204 | * whether sign the patch file 205 | * if not, you must do yourself. otherwise it can't check success during the patch loading 206 | * we will use the sign config with your build type 207 | */ 208 | useSign = true 209 | 210 | /** 211 | * optional,default 'true' 212 | * whether use tinker to build 213 | */ 214 | tinkerEnable = buildWithTinker() 215 | 216 | /** 217 | * Warning, applyMapping will affect the normal android build! 218 | */ 219 | buildConfig { 220 | /** 221 | * optional,default 'null' 222 | * if we use tinkerPatch to build the patch apk, you'd better to apply the old 223 | * apk mapping file if minifyEnabled is enable! 224 | * Warning: 225 | * you must be careful that it will affect the normal assemble build! 226 | */ 227 | applyMapping = getApplyMappingPath() 228 | /** 229 | * optional,default 'null' 230 | * It is nice to keep the resource id from R.txt file to reduce java changes 231 | */ 232 | applyResourceMapping = getApplyResourceMappingPath() 233 | 234 | /** 235 | * necessary,default 'null' 236 | * because we don't want to check the base apk with md5 in the runtime(it is slow) 237 | * tinkerId is use to identify the unique base apk when the patch is tried to apply. 238 | * we can use git rev, svn rev or simply versionCode. 239 | * we will gen the tinkerId in your manifest automatic 240 | */ 241 | tinkerId = getTinkerIdValue() 242 | 243 | /** 244 | * if keepDexApply is true, class in which dex refer to the old apk. 245 | * open this can reduce the dex diff file size. 246 | */ 247 | keepDexApply = false 248 | 249 | /** 250 | * optional, default 'false' 251 | * Whether tinker should treat the base apk as the one being protected by app 252 | * protection tools. 253 | * If this attribute is true, the generated patch package will contain a 254 | * dex including all changed classes instead of any dexdiff patch-info files. 255 | */ 256 | isProtectedApp = false 257 | 258 | /** 259 | * optional, default 'false' 260 | * Whether tinker should support component hotplug (add new component dynamically). 261 | * If this attribute is true, the component added in new apk will be available after 262 | * patch is successfully loaded. Otherwise an error would be announced when generating patch 263 | * on compile-time. 264 | * 265 | * Notice that currently this feature is incubating and only support NON-EXPORTED Activity 266 | */ 267 | supportHotplugComponent = false 268 | } 269 | 270 | dex { 271 | /** 272 | * optional,default 'jar' 273 | * only can be 'raw' or 'jar'. for raw, we would keep its original format 274 | * for jar, we would repack dexes with zip format. 275 | * if you want to support below 14, you must use jar 276 | * or you want to save rom or check quicker, you can use raw mode also 277 | */ 278 | dexMode = "jar" 279 | 280 | /** 281 | * necessary,default '[]' 282 | * what dexes in apk are expected to deal with tinkerPatch 283 | * it support * or ? pattern. 284 | */ 285 | pattern = ["classes*.dex", 286 | "assets/secondary-dex-?.jar"] 287 | /** 288 | * necessary,default '[]' 289 | * Warning, it is very very important, loader classes can't change with patch. 290 | * thus, they will be removed from patch dexes. 291 | * you must put the following class into main dex. 292 | * Simply, you should add your own application {@code tinker.sample.android.SampleApplication} 293 | * own tinkerLoader, and the classes you use in them 294 | * 295 | */ 296 | loader = [ 297 | //use sample, let BaseBuildInfo unchangeable with tinker 298 | "tinker.sample.android.app.BaseBuildInfo" 299 | ] 300 | } 301 | 302 | lib { 303 | /** 304 | * optional,default '[]' 305 | * what library in apk are expected to deal with tinkerPatch 306 | * it support * or ? pattern. 307 | * for library in assets, we would just recover them in the patch directory 308 | * you can get them in TinkerLoadResult with Tinker 309 | */ 310 | pattern = ["lib/*/*.so"] 311 | } 312 | 313 | res { 314 | /** 315 | * optional,default '[]' 316 | * what resource in apk are expected to deal with tinkerPatch 317 | * it support * or ? pattern. 318 | * you must include all your resources in apk here, 319 | * otherwise, they won't repack in the new apk resources. 320 | */ 321 | pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] 322 | 323 | /** 324 | * optional,default '[]' 325 | * the resource file exclude patterns, ignore add, delete or modify resource change 326 | * it support * or ? pattern. 327 | * Warning, we can only use for files no relative with resources.arsc 328 | */ 329 | ignoreChange = ["assets/sample_meta.txt"] 330 | 331 | /** 332 | * default 100kb 333 | * for modify resource, if it is larger than 'largeModSize' 334 | * we would like to use bsdiff algorithm to reduce patch file size 335 | */ 336 | largeModSize = 100 337 | } 338 | 339 | packageConfig { 340 | /** 341 | * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE' 342 | * package meta file gen. path is assets/package_meta.txt in patch file 343 | * you can use securityCheck.getPackageProperties() in your ownPackageCheck method 344 | * or TinkerLoadResult.getPackageConfigByName 345 | * we will get the TINKER_ID from the old apk manifest for you automatic, 346 | * other config files (such as patchMessage below)is not necessary 347 | */ 348 | configField("patchMessage", "tinker is sample to use") 349 | /** 350 | * just a sample case, you can use such as sdkVersion, brand, channel... 351 | * you can parse it in the SamplePatchListener. 352 | * Then you can use patch conditional! 353 | */ 354 | configField("platform", "all") 355 | /** 356 | * patch version via packageConfig 357 | */ 358 | configField("patchVersion", "1.0") 359 | } 360 | //or you can add config filed outside, or get meta value from old apk 361 | //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test")) 362 | //project.tinkerPatch.packageConfig.configField("test2", "sample") 363 | 364 | /** 365 | * if you don't use zipArtifact or path, we just use 7za to try 366 | */ 367 | sevenZip { 368 | /** 369 | * optional,default '7za' 370 | * the 7zip artifact path, it will use the right 7za with your platform 371 | */ 372 | zipArtifact = "com.tencent.mm:SevenZip:1.1.10" 373 | /** 374 | * optional,default '7za' 375 | * you can specify the 7za path yourself, it will overwrite the zipArtifact value 376 | */ 377 | // path = "/usr/local/bin/7za" 378 | } 379 | } 380 | 381 | List flavors = new ArrayList<>(); 382 | project.android.productFlavors.each { flavor -> 383 | flavors.add(flavor.name) 384 | } 385 | boolean hasFlavors = flavors.size() > 0 386 | def date = new Date().format("MMdd-HH-mm-ss") 387 | 388 | /** 389 | * bak apk and mapping 390 | */ 391 | android.applicationVariants.all { variant -> 392 | /** 393 | * task type, you want to bak 394 | */ 395 | def taskName = variant.name 396 | 397 | tasks.all { 398 | if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) { 399 | 400 | it.doLast { 401 | copy { 402 | def fileNamePrefix = "${project.name}-${variant.baseName}" 403 | def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}" 404 | 405 | def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath 406 | 407 | if (variant.metaClass.hasProperty(variant, 'packageApplicationProvider')) { 408 | def packageAndroidArtifact = variant.packageApplicationProvider.get() 409 | if (packageAndroidArtifact != null) { 410 | try { 411 | from new File(packageAndroidArtifact.outputDirectory.getAsFile().get(), variant.outputs.first().apkData.outputFileName) 412 | } catch (Exception e) { 413 | from new File(packageAndroidArtifact.outputDirectory, variant.outputs.first().apkData.outputFileName) 414 | } 415 | } else { 416 | from variant.outputs.first().mainOutputFile.outputFile 417 | } 418 | } else { 419 | from variant.outputs.first().outputFile 420 | } 421 | 422 | into destPath 423 | rename { String fileName -> 424 | fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk") 425 | } 426 | 427 | def dirName = variant.dirName 428 | if (hasFlavors) { 429 | dirName = taskName 430 | } 431 | from "${buildDir}/outputs/mapping/${dirName}/mapping.txt" 432 | into destPath 433 | rename { String fileName -> 434 | fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt") 435 | } 436 | 437 | from "${buildDir}/intermediates/symbols/${dirName}/R.txt" 438 | from "${buildDir}/intermediates/symbol_list/${dirName}/R.txt" 439 | from "${buildDir}/intermediates/runtime_symbol_list/${dirName}/R.txt" 440 | into destPath 441 | rename { String fileName -> 442 | fileName.replace("R.txt", "${newFileNamePrefix}-R.txt") 443 | } 444 | } 445 | } 446 | } 447 | } 448 | } 449 | project.afterEvaluate { 450 | //sample use for build all flavor for one time 451 | if (hasFlavors) { 452 | task(tinkerPatchAllFlavorRelease) { 453 | group = 'tinker' 454 | def originOldPath = getTinkerBuildFlavorDirectory() 455 | for (String flavor : flavors) { 456 | def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release") 457 | dependsOn tinkerTask 458 | def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest") 459 | preAssembleTask.doFirst { 460 | String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15) 461 | project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk" 462 | project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt" 463 | project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt" 464 | 465 | } 466 | 467 | } 468 | } 469 | 470 | task(tinkerPatchAllFlavorDebug) { 471 | group = 'tinker' 472 | def originOldPath = getTinkerBuildFlavorDirectory() 473 | for (String flavor : flavors) { 474 | def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug") 475 | dependsOn tinkerTask 476 | def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest") 477 | preAssembleTask.doFirst { 478 | String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13) 479 | project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk" 480 | project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt" 481 | project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt" 482 | } 483 | 484 | } 485 | } 486 | } 487 | } 488 | } 489 | 490 | 491 | task sortPublicTxt() { 492 | doLast { 493 | File originalFile = project.file("public.txt") 494 | File sortedFile = project.file("public_sort.txt") 495 | List sortedLines = new ArrayList<>() 496 | originalFile.eachLine { 497 | sortedLines.add(it) 498 | } 499 | Collections.sort(sortedLines) 500 | sortedFile.delete() 501 | sortedLines.each { 502 | sortedFile.append("${it}\n") 503 | } 504 | } 505 | } -------------------------------------------------------------------------------- /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 C:\Users\pc\AppData\Local\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 | -------------------------------------------------------------------------------- /app/robustsign2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigSweet/TinkerGradleDemo/09fda4b35c7b4b21541a05abc232f7ec5e5c908b/app/robustsign2 -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/anlaiye/swt/gradletest/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.anlaiye.swt.gradletest; 2 | 3 | import android.Manifest; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.os.Environment; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.widget.Toast; 11 | 12 | import androidx.appcompat.app.AppCompatActivity; 13 | import androidx.core.app.ActivityCompat; 14 | import androidx.core.content.ContextCompat; 15 | 16 | import com.tencent.tinker.lib.tinker.TinkerInstaller; 17 | 18 | import java.io.File; 19 | 20 | public class MainActivity extends AppCompatActivity { 21 | 22 | public static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 1; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_main); 28 | if (!hasRequiredPermissions()) { 29 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 0); 30 | } 31 | } 32 | 33 | private boolean hasRequiredPermissions() { 34 | if (Build.VERSION.SDK_INT >= 16) { 35 | final int res = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE); 36 | return res == PackageManager.PERMISSION_GRANTED; 37 | } else { 38 | // When SDK_INT is below 16, READ_EXTERNAL_STORAGE will also be granted if WRITE_EXTERNAL_STORAGE is granted. 39 | final int res = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE); 40 | return res == PackageManager.PERMISSION_GRANTED; 41 | } 42 | } 43 | 44 | //加载补丁 45 | public void loadPath(View view) { 46 | requestPermi(); 47 | } 48 | 49 | public void loadApk() { 50 | String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/app-release-patch_signed_7zip.apk"; 51 | File file = new File(path); 52 | if (file.exists()) { 53 | Toast.makeText(this, "补丁已经存在", Toast.LENGTH_SHORT).show(); 54 | TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path); 55 | Log.d("swy", path); 56 | } else { 57 | Toast.makeText(this, "补丁已经不存在", Toast.LENGTH_SHORT).show(); 58 | Log.d("swy", path); 59 | } 60 | } 61 | 62 | private void requestPermi() { 63 | if (ContextCompat.checkSelfPermission(MainActivity.this, 64 | Manifest.permission.READ_EXTERNAL_STORAGE) 65 | != PackageManager.PERMISSION_GRANTED) { 66 | // Should we show an explanation? 67 | if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, 68 | Manifest.permission.READ_EXTERNAL_STORAGE)) { 69 | // Show an expanation to the user *asynchronously* -- don't block 70 | // this thread waiting for the user's response! After the user 71 | // sees the explanation, try again to request the permission. 72 | 73 | } else { 74 | // No explanation needed, we can request the permission. 75 | ActivityCompat.requestPermissions(MainActivity.this, 76 | new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 77 | MY_PERMISSIONS_REQUEST_READ_CONTACTS); 78 | Log.d("swt", "requestPermi: "); 79 | // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an 80 | // app-defined int constant. The callback method gets the 81 | // result of the request. 82 | } 83 | } else { 84 | //有权限直接加载apk 85 | loadApk(); 86 | } 87 | } 88 | 89 | @Override 90 | public void onRequestPermissionsResult(int requestCode, 91 | String permissions[], int[] grantResults) { 92 | switch (requestCode) { 93 | case MY_PERMISSIONS_REQUEST_READ_CONTACTS: { 94 | // If request is cancelled, the result arrays are empty. 95 | if (grantResults.length > 0 96 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 97 | // permission was granted, yay! Do the 98 | // contacts-related task you need to do. 99 | //权限申请成功加载apk 100 | loadApk(); 101 | Log.d("swt", "permissionsuss: "); 102 | } else { 103 | 104 | // permission denied, boo! Disable the 105 | // functionality that depends on this permission. 106 | } 107 | return; 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/anlaiye/swt/gradletest/SimpleTinkerInApplicationLike.java: -------------------------------------------------------------------------------- 1 | package com.anlaiye.swt.gradletest; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.Build; 8 | 9 | import androidx.multidex.MultiDex; 10 | 11 | import com.tencent.tinker.anno.DefaultLifeCycle; 12 | import com.tencent.tinker.entry.ApplicationLike; 13 | import com.tencent.tinker.lib.tinker.TinkerInstaller; 14 | import com.tencent.tinker.loader.shareutil.ShareConstants; 15 | 16 | @DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication", 17 | flags = ShareConstants.TINKER_ENABLE_ALL, 18 | loadVerifyFlag = false) 19 | public class SimpleTinkerInApplicationLike extends ApplicationLike { 20 | 21 | public SimpleTinkerInApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { 22 | super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); 23 | } 24 | 25 | @Override 26 | public void onBaseContextAttached(Context base) { 27 | super.onBaseContextAttached(base); 28 | MultiDex.install(base); 29 | TinkerInstaller.install(this); 30 | 31 | } 32 | 33 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 34 | public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) { 35 | getApplication().registerActivityLifecycleCallbacks(callback); 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 19 | 20 | 21 |