├── README.md ├── app ├── build.gradle ├── package.gradle ├── proguard-rules.pro ├── src │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── wustor │ │ │ └── gradlemodule │ │ │ └── ExampleInstrumentedTest.java │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── wustor │ │ │ │ └── gradlemodule │ │ │ │ └── MainActivity.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 │ │ └── wustor │ │ └── gradlemodule │ │ └── ExampleUnitTest.java └── tinker.gradle ├── build.gradle ├── config.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── keystore.properties ├── settings.gradle ├── upToFir.py └── upload.gradle /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## exclude配置 3 | 4 | 其实我的博客里面忽略了一种情况,就是dependencies中有需要exclude的情况,多谢[Vanish](https://github.com/Vanish136)提出了这个问题,如果单纯的去遍历一个map是不可以的,比如说下面Glide的配置情况 5 | 6 | 7 | 8 | compile("com.github.bumptech.glide:glide:4.3.1") { 9 | exclude(group: 'com.android.support', module: 'support-v4') 10 | exclude(group: 'com.android.support', module: 'appcompat-v7') 11 | exclude(group: 'com.squareup.okhttp3', module: 'okhttp3') 12 | } 13 | 14 | 对于这种情况的话,需要额外定义一个关于exclude的map,因为可能多个依赖需要exclude,下面就用Glide举个栗子: 15 | 16 | ### config.gradle 17 | 18 | map的key是compile的依赖,然后value是一个数组,因为有可能需要剔除多个重复依赖,所以用数组来表示 19 | 20 | excludes = ["com.github.bumptech.glide:glide:4.3.1": 21 | [ 22 | 'com.android.support' : 'support-v4', 23 | 'com.android.support' : 'appcompat-v7', 24 | 'com.squareup.okhttp3': 'okhttp3']] 25 | 26 | ### build.gradle 27 | 28 | ```java 29 | excludes.each { entry -> 30 | compile(entry.key) { 31 | entry.value.each { childEntry -> 32 | exclude(group: childEntry.key, module: childEntry.value) 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | 然后在build.gradle中拿到这个map,然后进行遍历就好,代码已上传,有需要的可以下载进行查看。 39 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply from: "package.gradle" 3 | 4 | def cfg = rootProject.ext.android 5 | def librarys = rootProject.ext.dependencies 6 | def excludes = rootProject.ext.excludes 7 | def tinker = rootProject.ext.tinker 8 | def url = rootProject.ext.url 9 | def startUpload = rootProject.ext.startUpload 10 | def keystoreProperties = new Properties() 11 | def keystorePropertiesFile = rootProject.file("keystore.properties") 12 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 13 | android { 14 | signingConfigs { 15 | config { 16 | keyAlias keystoreProperties['keyAlias'] 17 | keyPassword keystoreProperties['keyPassword'] 18 | storeFile file(keystoreProperties['storeFile']) 19 | storePassword keystoreProperties['storePassword'] 20 | } 21 | } 22 | 23 | compileSdkVersion cfg.compileSdkVersion 24 | buildToolsVersion cfg.buildToolsVersion 25 | dexOptions { 26 | jumboMode = true 27 | } 28 | 29 | defaultConfig { 30 | ndk { abiFilters "armeabi", "armeabi-v7a", "x86", "mips" } 31 | applicationId cfg.applicationId 32 | minSdkVersion cfg.minSdkVersion 33 | targetSdkVersion cfg.targetSdkVersion 34 | versionCode cfg.versionCode//更新次数 35 | versionName cfg.versionName//版本号 36 | // manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"] 37 | //Tinker配置信息 38 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 39 | multiDexEnabled true 40 | buildConfigField "String", "MESSAGE", "\"${tinker.message}\"" 41 | buildConfigField "String", "TINKER_ID", "\"${tinker.id}\"" 42 | buildConfigField "String", "PLATFORM", "\"${tinker.plateform}\"" 43 | //Tinker相关配置end====================================== 44 | } 45 | lintOptions { 46 | checkReleaseBuilds false 47 | abortOnError false 48 | } 49 | configurations.all { 50 | resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9' 51 | } 52 | buildTypes { 53 | release { 54 | minifyEnabled true 55 | shrinkResources true 56 | buildConfigField "String", "AlphaUrl", "\"${url["release"]}\"" 57 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 58 | signingConfig signingConfigs.config 59 | } 60 | debug { 61 | minifyEnabled true 62 | shrinkResources true 63 | debuggable true 64 | buildConfigField "String", "AlphaUrl", "\"${url["release"]}\"" 65 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 66 | signingConfig signingConfigs.config 67 | } 68 | } 69 | } 70 | apply from: "tinker.gradle" 71 | dependencies { 72 | compile fileTree(include: ['*.jar'], dir: 'libs') 73 | librarys.each { k, v -> compile v } 74 | excludes.each { entry -> 75 | compile(entry.key) { 76 | entry.value.each { childEntry -> 77 | exclude(group: childEntry.key, module: childEntry.value) 78 | } 79 | } 80 | 81 | } 82 | } 83 | 84 | task toFir << { 85 | startUpload() 86 | } 87 | -------------------------------------------------------------------------------- /app/package.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'packer' 2 | packer { 3 | archiveNameFormat = '${buildType}-v${versionName}-${channel}' 4 | archiveOutput = new File(project.rootProject.buildDir, "apks") 5 | channelList = ['xiaomi','meizu'] 6 | // channelFile = new File(project.rootDir, "markets.txt") 7 | // channelMap = [ 8 | // "Cat" : project.rootProject.file("channels/cat.txt"), 9 | // "Dog" : project.rootProject.file("channels/dog.txt"), 10 | // "Fish": project.rootProject.file("channels/channels.txt") 11 | // ] 12 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/wustor/gradlemodule/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.wustor.gradlemodule; 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() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.wustor.gradlemodule", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/wustor/gradlemodule/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wustor.gradlemodule; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | public class MainActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_main); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | GradleModule 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/wustor/gradlemodule/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.wustor.gradlemodule; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/tinker.gradle: -------------------------------------------------------------------------------- 1 | 2 | def bakPath = file("${buildDir}/bakApk/") 3 | ext { 4 | //是否打开tinker的功能 5 | tinkerEnabled = true 6 | //old apk地址 7 | tinkerOldApkPath = "${bakPath}/app-debug.apk" 8 | //old apk 混淆文件地址 9 | tinkerApplyMappingPath = "${bakPath}/app-debug-mapping.txt" 10 | //old apk R 文件地址 11 | tinkerApplyResourcePath = "${bakPath}/app-debug-R.txt" 12 | 13 | //only use for build all flavor, if not, just ignore this field 14 | tinkerBuildFlavorDirectory = "${buildDir}" 15 | } 16 | 17 | /** 18 | * you can use assembleRelease to build you base apk 19 | * use tinkerPatchRelease -POLD_APK= -PAPPLY_MAPPING= -PAPPLY_RESOURCE= to build patch 20 | * add apk from the build/bakApk 21 | */ 22 | 23 | 24 | def getOldApkPath() { 25 | return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath 26 | } 27 | 28 | def getApplyMappingPath() { 29 | return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath 30 | } 31 | 32 | def getApplyResourceMappingPath() { 33 | return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath 34 | } 35 | 36 | def getTinkerIdValue() { 37 | return rootProject.ext.tinker.id 38 | 39 | } 40 | 41 | def buildWithTinker() { 42 | return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled 43 | } 44 | 45 | def getTinkerBuildFlavorDirectory() { 46 | return ext.tinkerBuildFlavorDirectory 47 | } 48 | 49 | if (buildWithTinker()) { 50 | apply plugin: 'com.tencent.tinker.patch' 51 | 52 | tinkerPatch { 53 | /** 54 | * necessary,default 'null' 55 | * the old apk path, use to diff with the new apk to build 56 | * add apk from the build/bakApk 57 | */ 58 | oldApk = getOldApkPath() 59 | /** 60 | * optional,default 'false' 61 | * there are some cases we may get some warnings 62 | * if ignoreWarning is true, we would just assert the patch process 63 | * case 1: minSdkVersion is below 14, but you are using dexMode with raw. 64 | * it must be crash when load. 65 | * case 2: newly added Android Component in AndroidManifest.xml, 66 | * it must be crash when load. 67 | * case 3: loader classes in dex.loader{} are not keep in the main dex, 68 | * it must be let tinker not work. 69 | * case 4: loader classes in dex.loader{} changes, 70 | * loader classes is ues to load patch dex. it is useless to change them. 71 | * it won't crash, but these changes can't effect. you may ignore it 72 | * case 5: resources.arsc has changed, but we don't use applyResourceMapping to build 73 | */ 74 | ignoreWarning = false 75 | 76 | /** 77 | * optional,default 'true' 78 | * whether sign the patch file 79 | * if not, you must do yourself. otherwise it can't check success during the patch loading 80 | * we will use the sign config with your build type 81 | */ 82 | useSign = true 83 | 84 | /** 85 | * optional,default 'true' 86 | * whether use tinker to build 87 | */ 88 | tinkerEnable = buildWithTinker() 89 | 90 | /** 91 | * Warning, applyMapping will affect the normal android build! 92 | */ 93 | buildConfig { 94 | /** 95 | * optional,default 'null' 96 | * if we use tinkerPatch to build the patch apk, you'd better to apply the old 97 | * apk mapping file if minifyEnabled is enable! 98 | * Warning: 99 | * you must be careful that it will affect the normal assemble build! 100 | */ 101 | applyMapping = getApplyMappingPath() 102 | /** 103 | * optional,default 'null' 104 | * It is nice to keep the resource id from R.txt file to reduce java changes 105 | */ 106 | applyResourceMapping = getApplyResourceMappingPath() 107 | 108 | /** 109 | * necessary,default 'null' 110 | * because we don't want to check the base apk with md5 in the runtime(it is slow) 111 | * tinkerId is use to identify the unique base apk when the patch is tried to apply. 112 | * we can use git rev, svn rev or simply versionCode. 113 | * we will gen the tinkerId in your manifest automatic 114 | */ 115 | tinkerId = getTinkerIdValue() 116 | 117 | /** 118 | * if keepDexApply is true, class in which dex refer to the old apk. 119 | * open this can reduce the dex diff file size. 120 | */ 121 | keepDexApply = false 122 | 123 | //是否开启加固 124 | isProtectedApp = false 125 | } 126 | 127 | dex { 128 | /** 129 | * optional,default 'jar' 130 | * only can be 'raw' or 'jar'. for raw, we would keep its original format 131 | * for jar, we would repack dexes with zip format. 132 | * if you want to support below 14, you must use jar 133 | * or you want to save rom or check quicker, you can use raw mode also 134 | */ 135 | dexMode = "jar" 136 | 137 | /** 138 | * necessary,default '[]' 139 | * what dexes in apk are expected to deal with tinkerPatch 140 | * it support * or ? pattern. 141 | */ 142 | pattern = ["classes*.dex", 143 | "assets/secondary-dex-?.jar"] 144 | /** 145 | * necessary,default '[]' 146 | * Warning, it is very very important, loader classes can't change with patch. 147 | * thus, they will be removed from patch dexes. 148 | * you must put the following class into main dex. 149 | * Simply, you should add your own application {@code tinker.sample.android.SampleApplication} 150 | * own tinkerLoader, and the classes you use in them 151 | * 152 | */ 153 | loader = [ 154 | //use sample, let BaseBuildInfo unchangeable with tinker 155 | "tinker.sample.android.app.BaseBuildInfo" 156 | ] 157 | } 158 | 159 | lib { 160 | /** 161 | * optional,default '[]' 162 | * what library in apk are expected to deal with tinkerPatch 163 | * it support * or ? pattern. 164 | * for library in assets, we would just recover them in the patch directory 165 | * you can get them in TinkerLoadResult with Tinker 166 | */ 167 | pattern = ["lib/*/*.so"] 168 | } 169 | 170 | res { 171 | /** 172 | * optional,default '[]' 173 | * what resource in apk are expected to deal with tinkerPatch 174 | * it support * or ? pattern. 175 | * you must include all your resources in apk here, 176 | * otherwise, they won't repack in the new apk resources. 177 | */ 178 | pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] 179 | 180 | /** 181 | * optional,default '[]' 182 | * the resource file exclude patterns, ignore add, delete or modify resource change 183 | * it support * or ? pattern. 184 | * Warning, we can only use for files no relative with resources.arsc 185 | */ 186 | ignoreChange = ["assets/sample_meta.txt"] 187 | 188 | /** 189 | * default 100kb 190 | * for modify resource, if it is larger than 'largeModSize' 191 | * we would like to use bsdiff algorithm to reduce patch file size 192 | */ 193 | largeModSize = 100 194 | } 195 | 196 | packageConfig { 197 | /** 198 | * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE' 199 | * package meta file gen. path is assets/package_meta.txt in patch file 200 | * you can use securityCheck.getPackageProperties() in your ownPackageCheck method 201 | * or TinkerLoadResult.getPackageConfigByName 202 | * we will get the TINKER_ID from the old apk manifest for you automatic, 203 | * other config files (such as patchMessage below)is not necessary 204 | */ 205 | configField("patchMessage", "tinker is sample to use") 206 | /** 207 | * just a sample case, you can use such as sdkVersion, brand, channel... 208 | * you can parse it in the SamplePatchListener. 209 | * Then you can use patch conditional! 210 | */ 211 | configField("platform", "all") 212 | /** 213 | * patch version via packageConfig 214 | */ 215 | configField("patchVersion", android.defaultConfig.versionName) 216 | } 217 | //or you can add config filed outside, or get meta value from old apk 218 | //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test")) 219 | //project.tinkerPatch.packageConfig.configField("test2", "sample") 220 | 221 | /** 222 | * if you don't use zipArtifact or path, we just use 7za to try 223 | */ 224 | sevenZip { 225 | /** 226 | * optional,default '7za' 227 | * the 7zip artifact path, it will use the right 7za with your platform 228 | */ 229 | zipArtifact = "com.tencent.mm:SevenZip:1.1.10" 230 | /** 231 | * optional,default '7za' 232 | * you can specify the 7za path yourself, it will overwrite the zipArtifact value 233 | */ 234 | // path = "/usr/local/bin/7za" 235 | } 236 | } 237 | 238 | List flavors = new ArrayList<>(); 239 | project.android.productFlavors.each { flavor -> 240 | flavors.add(flavor.name) 241 | } 242 | boolean hasFlavors = flavors.size() > 0 243 | def date = new Date().format("MMdd-HH-mm-ss") 244 | 245 | /** 246 | * bak apk and mapping 247 | */ 248 | android.applicationVariants.all { variant -> 249 | /** 250 | * task type, you want to bak 251 | */ 252 | def taskName = variant.name 253 | 254 | tasks.all { 255 | if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) { 256 | 257 | it.doLast { 258 | copy { 259 | def fileNamePrefix = "${project.name}-${variant.baseName}" 260 | def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}" 261 | 262 | def destPath = hasFlavors ? file("${bakPath}/${project.name}/${variant.flavorName}") : bakPath 263 | from variant.outputs.outputFile 264 | into destPath 265 | rename { String fileName -> 266 | fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk") 267 | } 268 | 269 | from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt" 270 | into destPath 271 | rename { String fileName -> 272 | fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt") 273 | } 274 | 275 | from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt" 276 | into destPath 277 | rename { String fileName -> 278 | fileName.replace("R.txt", "${newFileNamePrefix}-R.txt") 279 | } 280 | } 281 | } 282 | } 283 | } 284 | } 285 | project.afterEvaluate { 286 | //sample use for build all flavor for one time 287 | if (hasFlavors) { 288 | task(tinkerPatchAllFlavorRelease) { 289 | group = 'tinker' 290 | def originOldPath = getTinkerBuildFlavorDirectory() 291 | for (String flavor : flavors) { 292 | def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release") 293 | dependsOn tinkerTask 294 | def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest") 295 | preAssembleTask.doFirst { 296 | String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15) 297 | project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk" 298 | project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt" 299 | project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt" 300 | 301 | } 302 | 303 | } 304 | } 305 | 306 | task(tinkerPatchAllFlavorDebug) { 307 | group = 'tinker' 308 | def originOldPath = getTinkerBuildFlavorDirectory() 309 | for (String flavor : flavors) { 310 | def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug") 311 | dependsOn tinkerTask 312 | def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest") 313 | preAssembleTask.doFirst { 314 | String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13) 315 | project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk" 316 | project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt" 317 | project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt" 318 | } 319 | 320 | } 321 | } 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | apply from: "config.gradle" 3 | apply from: "upload.gradle" 4 | buildscript { 5 | repositories { 6 | mavenCentral() 7 | jcenter() 8 | google() 9 | 10 | maven { 11 | url 'https://maven.google.com/' 12 | name 'Google' 13 | } 14 | } 15 | dependencies { 16 | classpath 'com.android.tools.build:gradle:2.3.3' 17 | classpath 'com.mcxiaoke.packer-ng:plugin:2.0.0' 18 | classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}" 19 | 20 | // NOTE: Do not place your application dependencies here; they belong 21 | // in the individual module build.gradle files 22 | } 23 | } 24 | 25 | allprojects { 26 | repositories { 27 | mavenCentral() 28 | jcenter() 29 | maven { url "https://jitpack.io" } 30 | google() 31 | maven { 32 | url 'https://maven.google.com/' 33 | name 'Google' 34 | } 35 | } 36 | } 37 | 38 | task clean(type: Delete) { 39 | delete rootProject.buildDir 40 | } 41 | -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * 在主项目的根目录下创建config.gradle文件 3 | * 在这里单独处理统一依赖问题 4 | * 注意需要在根目录的build.gradle中进行引入 5 | */ 6 | ext { 7 | android = [ 8 | compileSdkVersion: 25, 9 | buildToolsVersion: "25.0.3", 10 | minSdkVersion : 16, 11 | targetSdkVersion : 25, 12 | applicationId : "com.wustor.gradlemodule", 13 | versionCode : 9, 14 | versionName : "1.6.3" 15 | 16 | ] 17 | tinker = [ 18 | id : android.versionName, 19 | message : "I am the base apk", 20 | plateform: "all", 21 | version : "1.9.1", 22 | ] 23 | url = [ 24 | "debug" : "debugUrl1", 25 | //"debug" : "debugUrl2", 26 | //"debug" : "debugUrl3", 27 | "release": "releaseUrl1", 28 | // "release": "releaseUrl2" 29 | 30 | 31 | ] 32 | 33 | //Version 34 | supportLibrary = "25.4.0" 35 | tinkerVerison = "1.9.1" 36 | dependencies = [ 37 | /*support库*/ 38 | "multidex" : "com.android.support:multidex:1.0.1", 39 | "cardview" : "com.android.support:cardview-v7:${supportLibrary}", 40 | "constraint" : "com.android.support.constraint:constraint-layout:1.0.2", 41 | "recyclerview" : "com.android.support:recyclerview-v7:${supportLibrary}", 42 | "supportV4" : "com.android.support:support-v4:${supportLibrary}", 43 | "suppoutDesign" : "com.android.support:design:${supportLibrary}", 44 | "supportAppcompat": "com.android.support:appcompat-v7:${supportLibrary}", 45 | /*tinker*/ 46 | "tinker-lib" : "com.tencent.tinker:tinker-android-lib:${tinkerVerison}", 47 | "tinker-anno" : "com.tencent.tinker:tinker-android-anno:${tinkerVerison}", 48 | "packer-ng" : "com.mcxiaoke.packer-ng:helper:2.0.0" 49 | 50 | 51 | ] 52 | excludes = ["com.github.bumptech.glide:glide:4.3.1": 53 | [ 54 | 'com.android.support' : 'support-v4', 55 | 'com.android.support' : 'appcompat-v7', 56 | 'com.squareup.okhttp3': 'okhttp3']] 57 | 58 | } 59 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | TINKER_VERSION=1.8.1 19 | ICON_PATH="" 20 | APK_PATH="" 21 | CONTENT="" 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wustor/GradleModule/928679e32c7a439f9e0f0663dc4ff033752e9182/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Dec 03 11:52:45 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /keystore.properties: -------------------------------------------------------------------------------- 1 | storePassword="" 2 | keyPassword="" 3 | keyAlias="" 4 | storeFile="" 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /upToFir.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # encoding = utf-8 3 | import requests 4 | import sys 5 | 6 | 7 | def upToFir(): 8 | # 打印传递过来的参数数组长度,便于校验 9 | print(len(sys.argv)) 10 | upUrl = sys.argv[1] 11 | appName = sys.argv[2] 12 | bundleId = sys.argv[3] 13 | verName = sys.argv[4] 14 | apiToken = sys.argv[5] 15 | print (apiToken) 16 | iconPath = sys.argv[6] 17 | apkPath = sys.argv[7] 18 | buildNumber = sys.argv[8] 19 | changeLog = sys.argv[9] 20 | queryData = {'type': 'android', 'bundle_id': bundleId, 'api_token': apiToken} 21 | iconDict = {} 22 | binaryDict = {} 23 | # 获取上传信息 24 | try: 25 | response = requests.post(url=upUrl, data=queryData) 26 | json = response.json() 27 | iconDict = (json["cert"]["icon"]) 28 | binaryDict = (json["cert"]["binary"]) 29 | except Exception as e: 30 | print('query:' + e) 31 | 32 | # 上传apk 33 | try: 34 | file = {'file': open(apkPath, 'rb')} 35 | param = {"key": binaryDict['key'], 36 | 'token': binaryDict['token'], 37 | "x:name": appName, 38 | "x:version": verName, 39 | "x:build": buildNumber, 40 | "x:changelog": changeLog.encode("utf-8")} 41 | req = requests.post(url=binaryDict['upload_url'], files=file, data=param, verify=False) 42 | print(req.status_code) 43 | except Exception as e: 44 | print('error_apk:' + e) 45 | 46 | # 上传logo 47 | try: 48 | file = {'file': open(iconPath, 'rb')} 49 | param = {"key": iconDict['key'], 50 | 'token': iconDict['token']} 51 | req = requests.post(url=iconDict['upload_url'], files=file, data=param, verify=False) 52 | print(req.status_code) 53 | except Exception as e: 54 | print('error_icon:' + e) 55 | 56 | 57 | if __name__ == '__main__': 58 | upToFir() 59 | -------------------------------------------------------------------------------- /upload.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | upUrl = "http://api.fir.im/apps" 3 | appName = "创美e药" 4 | bundleId = rootProject.ext.android.applicationId 5 | verName = rootProject.ext.android.versionName 6 | apiToken = "5d30a26ef96ca348e58c1cef17fab7c8" 7 | iconPath = ICON_PATH 8 | apkPath = APK_PATH 9 | buildNumber = rootProject.ext.android.versionCode 10 | changeLog = CONTENT 11 | startUpload = this.&startUpload 12 | } 13 | 14 | //上传至fir 15 | def startUpload() { 16 | println("开始上传至fir") 17 | // def process = "python upToFir.py ${upUrl} ${appName} ${bundleId} ${verName} ${apiToken} ${iconPath} ${apkPath} ${buildNumber} ${changeLog}".execute() 18 | def process = "python upToFir.py $upUrl $appName $bundleId $verName $apiToken $iconPath $apkPath $buildNumber $changeLog".execute() 19 | //获取Python脚本日志,便于出错调试 20 | ByteArrayOutputStream result = new ByteArrayOutputStream() 21 | def inputStream = process.getInputStream() 22 | byte[] buffer = new byte[1024] 23 | int length 24 | while ((length = inputStream.read(buffer)) != -1) { 25 | result.write(buffer, 0, length) 26 | } 27 | println(result.toString("UTF-8")) 28 | println "上传结束 " 29 | } 30 | 31 | --------------------------------------------------------------------------------