多渠道打包本来不想写的,因为比较麻烦,所以没打算写。只是前段时间因为电脑蓝屏重装了系统,导致不能打包了,所以又百度了半天,今天就写下来做个笔记。
废话不多说,进入主题。
配置多渠道打包就要使用友盟统计
build.gradle中添加友盟的依赖包:
//友盟统计 compile 'com.umeng.analytics:analytics:latest.integration'
一、配置AndroidManifest.xml
1、权限(4个权限一定要写, ps:我这里就不写了,因为我这里4个都是重复的,就做下备注. 大家去友盟里面复制下来贴上)
2、友盟配置(value 就是你的Appkey)
android:name="UMENG_APPKEY" android:value="5aXXX2df29d9821bbXXXXXX"/> android:name="UMENG_CHANNEL" android:value="${UMENG_CHANNEL_VALUE}"/>
二、在build.gradle设置productFlavors
记住:渠道包名称不能以数字开头。比如:360的包就不可以, 我在前面加了一个大写C
android{
productFlavors { dandanzhuan { dimension "default" } //蛋蛋赚 yingyongbao { dimension "default" } //腾讯开放平台-应用宝 huawei { dimension "default" } //智汇云应用市场-华为 ppzhushou { dimension "default" } //PP助手 C360 { dimension "default" } //360应用市场 diankai { dimension "default" } //点开广告 oppo { dimension "default" } //oppo应用市场 youmi { dimension "default" } //有米广告 xiaomi { dimension "default" } //小米应用市场 meizu { dimension "default" } //魅族应用市场 xunfei { dimension "default" } //讯飞更新包 mumayi { dimension "default" } //木蚂蚁 vivo { dimension "default" } //vivo } productFlavors.all { flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] }
}
三、配置渠道包的输出路径(jglc是我的项目简称, _v是版本字母的开头字母。例如jglc_v3.1.0_20180425_release_Releases.apk)
//修改生成的最终文件名 applicationVariants.all { variant -> variant.outputs.all { output -> def outputFile = output.outputFile if (outputFile != null && outputFile.name.endsWith('.apk')) { def fileName if (!project.hasProperty('FILE_NAME')) { if (variant.buildType.name == "release") { // 输出apk名称为app_v1.0.0_2015-06-15_playStore.apk _${variant.productFlavors[0].name} fileName = "jglc_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}_${variant.productFlavors[0].name}.apk" } else if (variant.buildType.name == "releaselog") { fileName = "jglc_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}_${variant.productFlavors[0].name}.apk" } else if (variant.buildType.name == "check") { fileName = "jglc_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}_${variant.productFlavors[0].name}.apk" } else if (variant.buildType.name == "debug") { fileName = "jglc_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}_${variant.productFlavors[0].name}.apk" } else if (variant.buildType.name == "dev") { // fileName = outputFile.name fileName = "jglc_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}_${variant.productFlavors[0].name}.apk" } } else { fileName = FILE_NAME } outputFileName = fileName } } }
获取时间代码(位置与android{}同级):
def releaseTime() { return new Date().format("yyyyMMdd", TimeZone.getTimeZone("UTC")) }
接下来我们说下怎么动态获取正式服、开发服、测试服的地址 以及 动态指定版本号版本名
四、在build.gradle里面配置url地址以及版本号、版本名
//默认版本号和版本名 def DEF_VERSION_CODE = 27 def DEF_VERSION_NAME = "3.1.0" //正式环境 def API_RELEASE_HOST = "\"https://www.XXXXXX.cn\"" //开发环境 def API_DEV_HOST = "\"http://develop.XXXXXX.cn\"" //测试环境 def API_TEST_HOST = "\"http://test.XXXXXX.cn\""
defaultConfig { //动态指定版本号版本名 versionCode project.hasProperty('VERSION_CODE') ? Integer.parseInt(VERSION_CODE) : DEF_VERSION_CODE versionName project.hasProperty('VERSION_NAME') ? VERSION_NAME : "${DEF_VERSION_NAME}" flavorDimensions "default" println("versionCode = " + versionCode + " versionName = " + versionName) buildConfigField("String", "API_HOST", "${API_RELEASE_HOST}") }
buildTypes { debug { // 显示Log buildConfigField "boolean", "LOG_DEBUG", "true" buildConfigField "String", "API_HOST", "${API_TEST_HOST}" minifyEnabled false //签名 signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } release { // 不显示Log buildConfigField "boolean", "LOG_DEBUG", "false" buildConfigField "String", "API_HOST", "${API_RELEASE_HOST}" minifyEnabled true //是否混淆 //是否设置zip对齐优化 zipAlignEnabled true // 移除无用的resource文件 shrinkResources true //签名 signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } /* 正式环境显示log */ releaselog { // 显示Log buildConfigField "boolean", "LOG_DEBUG", "true" buildConfigField "String", "API_HOST", "${API_RELEASE_HOST}" minifyEnabled true //是否混淆 //是否设置zip对齐优化 zipAlignEnabled true // 移除无用的resource文件 shrinkResources true //签名 signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } /* 开发环境 */ dev { buildConfigField "boolean", "LOG_DEBUG", "true" buildConfigField "String", "API_HOST", "${API_DEV_HOST}" minifyEnabled false //签名 signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } /* 测试环境 */ check { // 显示Log buildConfigField "boolean", "LOG_DEBUG", "true" buildConfigField "String", "API_HOST", "${API_TEST_HOST}" minifyEnabled true //是否混淆 //是否设置zip对齐优化 zipAlignEnabled true // 移除无用的resource文件 shrinkResources true //签名 signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
moudle里面也要配置(如图):
我的build.gradle的配置
apply plugin: 'com.android.application' apply plugin: 'com.jakewharton.butterknife' android { compileSdkVersion 25 buildToolsVersion '26.0.2' //默认版本号和版本名 def DEF_VERSION_CODE = 27 def DEF_VERSION_NAME = "3.1.0" useLibrary 'org.apache.http.legacy' //正式环境 def API_RELEASE_HOST = "\"https://www.XXXXX.cn\"" //开发环境 def API_DEV_HOST = "\"http://develop.XXXXX.cn\"" //测试环境 def API_TEST_HOST = "\"http://test.XXXXX.cn\"" defaultConfig { applicationId "cn.XXXXX.app" minSdkVersion 15 targetSdkVersion 22 multiDexEnabled true //动态指定版本号版本名 versionCode project.hasProperty('VERSION_CODE') ? Integer.parseInt(VERSION_CODE) : DEF_VERSION_CODE versionName project.hasProperty('VERSION_NAME') ? VERSION_NAME : "${DEF_VERSION_NAME}" flavorDimensions "default" println("versionCode = " + versionCode + " versionName = " + versionName) buildConfigField("String", "API_HOST", "${API_RELEASE_HOST}") ndk { //设置支持的SO库架构 abiFilters "armeabi", "x86"//, 'armeabi-v7a', 'x86_64', 'arm64-v8a' } manifestPlaceholders = [ JPUSH_PKGNAME: applicationId, JPUSH_APPKEY : "88d9754a5b3589d6f09a362e", //JPush上注册的包名对应的appkey. JPUSH_CHANNEL: "developer-default", //暂时填写默认值即可. ] } signingConfigs { release { storeFile file(STORE_FILE); storePassword STORE_PASSWORD keyAlias KEY_ALIAS keyPassword KEY_PASSWORD } debug { storeFile file(STORE_FILE); storePassword STORE_PASSWORD keyAlias KEY_ALIAS keyPassword KEY_PASSWORD } } buildTypes { debug { // 显示Log buildConfigField "boolean", "LOG_DEBUG", "true" buildConfigField "String", "API_HOST", "${API_TEST_HOST}" minifyEnabled false //签名 signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } release { // 不显示Log buildConfigField "boolean", "LOG_DEBUG", "false" buildConfigField "String", "API_HOST", "${API_RELEASE_HOST}" minifyEnabled true //是否混淆 //是否设置zip对齐优化 zipAlignEnabled true // 移除无用的resource文件 shrinkResources true //签名 signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } /* 正式环境显示log */ releaselog { // 显示Log buildConfigField "boolean", "LOG_DEBUG", "true" buildConfigField "String", "API_HOST", "${API_RELEASE_HOST}" minifyEnabled true //是否混淆 //是否设置zip对齐优化 zipAlignEnabled true // 移除无用的resource文件 shrinkResources true //签名 signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } /* 开发环境 */ dev { buildConfigField "boolean", "LOG_DEBUG", "true" buildConfigField "String", "API_HOST", "${API_DEV_HOST}" minifyEnabled false //签名 signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } /* 测试环境 */ check { // 显示Log buildConfigField "boolean", "LOG_DEBUG", "true" buildConfigField "String", "API_HOST", "${API_TEST_HOST}" minifyEnabled true //是否混淆 //是否设置zip对齐优化 zipAlignEnabled true // 移除无用的resource文件 shrinkResources true //签名 signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { disable 'MissingTranslation' abortOnError false } dexOptions { // preDexLibraries true // javaMaxHeapSize "8g" // incremental true // dexInProcess = true } //修改生成的最终文件名 applicationVariants.all { variant -> variant.outputs.all { output -> def outputFile = output.outputFile if (outputFile != null && outputFile.name.endsWith('.apk')) { def fileName if (!project.hasProperty('FILE_NAME')) { if (variant.buildType.name == "release") { // 输出apk名称为app_v1.0.0_2015-06-15_playStore.apk _${variant.productFlavors[0].name} fileName = "jglc_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}_${variant.productFlavors[0].name}.apk" } else if (variant.buildType.name == "releaselog") { fileName = "jglc_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}_${variant.productFlavors[0].name}.apk" } else if (variant.buildType.name == "check") { fileName = "jglc_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}_${variant.productFlavors[0].name}.apk" } else if (variant.buildType.name == "debug") { fileName = "jglc_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}_${variant.productFlavors[0].name}.apk" } else if (variant.buildType.name == "dev") { // fileName = outputFile.name fileName = "jglc_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}_${variant.productFlavors[0].name}.apk" } } else { fileName = FILE_NAME } outputFileName = fileName } } } //配置多版本的apk productFlavors { dandanzhuan { dimension "default" } //蛋蛋赚 yingyongbao { dimension "default" } //腾讯开放平台-应用宝 huawei { dimension "default" } //智汇云应用市场-华为 ppzhushou { dimension "default" } //PP助手 C360 { dimension "default" } //360应用市场 diankai { dimension "default" } //点开广告 oppo { dimension "default" } //oppo应用市场 youmi { dimension "default" } //有米广告 xiaomi { dimension "default" } //小米应用市场 meizu { dimension "default" } //魅族应用市场 xunfei { dimension "default" } //讯飞更新包 mumayi { dimension "default" } //木蚂蚁 vivo { dimension "default" } //vivo } productFlavors.all { flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] } } def releaseTime() { return new Date().format("yyyyMMdd", TimeZone.getTimeZone("UTC")) } repositories { flatDir { dirs 'libs' } google() } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') // compile 'com.mcxiaoke.volley:library:1.0.19' compile(name: 'viewpagerindicator-2.4.2', ext: 'aar') // compile 'com.github.navasmdc:MaterialDesign:1.5@aar' // compile 'it.neokree:MaterialNavigationDrawer:1.3.2' // compile 'me.zhanghai.android.materialprogressbar:library:1.1.4' compile files('libs/Baofoo_SDK_IDC_V2.0.jar') // compile 'com.tencent.bugly:crashreport_upgrade:latest.release' //其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.0.0 compile files('libs/AutoUpdate_SDK.jar') implementation project(':library') // 此处以JPush 3.0.0 版本为例。 // 此处以JCore 1.0.0 版本为例。 // compile 'com.github.barteksc:android-pdf-viewer:2.1.0' compile files('libs/MobCommons-2017.0608.1618.jar') compile files('libs/MobTools-2017.0608.1618.jar') compile files('libs/ShareSDK-Core-3.0.0.jar') compile files('libs/ShareSDK-QQ-3.0.0.jar') compile files('libs/ShareSDK-QZone-3.0.0.jar') compile files('libs/ShareSDK-SinaWeibo-3.0.0.jar') compile files('libs/ShareSDK-Wechat-3.0.0.jar') compile files('libs/ShareSDK-Wechat-Core-3.0.0.jar') compile files('libs/ShareSDK-Wechat-Favorite-3.0.0.jar') compile files('libs/ShareSDK-Wechat-Moments-3.0.0.jar') //其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如2.1.9 compile 'com.android.support:appcompat-v7:23.4.0' compile 'com.android.support:design:23.4.0' compile 'com.android.support:multidex:1.0.1' compile 'de.greenrobot:eventbus:2.4.0' compile 'in.srain.cube:ultra-ptr:1.0.11' compile 'in.srain.cube:cube-sdk:1.0.45.3.2-SNAPSHOT@aar' compile 'in.srain.cube:clog:1.0.2' compile 'com.baoyz.swipemenulistview:library:1.3.0' compile 'com.bigkoo:pickerview:1.0.3' compile 'com.bigkoo:convenientbanner:2.0.5' compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.zcw:togglebutton-library:1.0.0' compile 'com.github.barteksc:android-pdf-viewer:2.7.0-beta' compile 'com.android.support:support-v4:25.3.1' compile 'cn.jiguang.sdk:jpush:3.0.0' compile 'cn.jiguang.sdk:jcore:1.0.0' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.android.support:cardview-v7:23.4.0' compile 'com.tencent.bugly:crashreport:2.6.5' compile 'com.bm.photoview:library:1.4.1' compile 'com.android.support:percent:23.4.0' //retrofit+rxjava+okhttp compile 'com.squareup.retrofit2:retrofit:2.3.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' compile 'com.squareup.okhttp3:okhttp:3.8.0' compile 'com.squareup.okhttp3:logging-interceptor:3.8.0' compile 'io.reactivex.rxjava2:rxjava:2.1.0' compile 'io.reactivex.rxjava2:rxandroid:2.0.1' compile 'com.flyco.dialog:FlycoDialog_Lib:1.0.0' compile 'com.google.zxing:core:3.3.0' compile 'com.github.yidun:captcha-android-demo:2.4' //友盟统计 compile 'com.umeng.analytics:analytics:latest.integration' //butterknife compile 'com.jakewharton:butterknife:8.4.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' //logger compile 'com.orhanobut:logger:2.1.1' }
然后就是你URL工具类的配置,如下图:
public static final String SERVER_HOST = BuildConfig.API_HOST;
基本已经完成了所有的配置,如下图:
在terminal中使用gradle命令打包的前提就是先配置gradle(2种方法)。
1、在你电脑系统环境变量中配置(我的gradle路径)
新建,变量名:GRADLE_HOME
变量值:D:\Gradle_4.3all\gradle-4.3.1-all\gradle-4.3.1
Path中添加: %GRADLE_HOME%\bin
第二种配置gradle就是直接在terminal中输入 gradlew回车,自动去下载,很慢的。
配置完成之后:
在terminal中输入命令行 (比如打C360正式服的渠道包):
window系统:gradle assembleC360Release
mac系统:./gradle assembleC360Release
如果没有配置gradle,会提示:'gradle' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
上面的配置完成之后友盟也收不到用户在哪下载的,因为渠道邀请码还没写,友盟怎么能收集到呢,接下来才是重点。
下面就是具体操作用友盟来收集各个渠道的下载量 (渠道邀请码(XXXX)大家去找运营要),工具类:
package com.XXXX.app.base.util; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.text.TextUtils; import com.umeng.analytics.MobclickAgent; /** * Describe: 友盟统计配置初始化 * Created by ky on 2017/11/13. */ public class UmengUtils { /** * 在Application中做的初始化 */ public static void initUmeng() { MobclickAgent.setDebugMode(true);//开启调试模式(如果不开启debug运行不会上传umeng统计) MobclickAgent.openActivityDurationTrack(false); } /** * 在BaseActivity跟BaseFragmentActivity中的onResume加入 * * @param context */ public static void onResumeToActivity(Context context) { MobclickAgent.onPageStart(context.getClass().getName()); MobclickAgent.onResume(context); } /** * 在BaseActivity跟BaseFragmentActivity中的onPause加入 * * @param context */ public static void onPauseToActivity(Context context) { MobclickAgent.onPageEnd(context.getClass().getName()); MobclickAgent.onPause(context); } /** * 在BaseFragment中的onResume加入 * * @param context */ public static void onResumeToFragment(Context context) { MobclickAgent.onPageStart(context.getClass().getName()); } /** * 在BaseFragment中的onPause加入 * * @param context */ public static void onPauseToFragment(Context context) { MobclickAgent.onPageEnd(context.getClass().getName()); } /** * 获取application中指定的meta-data * * @return 如果没有获取成功(没有对应值, 或者异常),则返回值为空 */ public static String getAppMetaData(Context ctx, String key) { if (ctx == null || TextUtils.isEmpty(key)) { return null; } String resultData = null; try { PackageManager packageManager = ctx.getPackageManager(); if (packageManager != null) { ApplicationInfo applicationInfo = packageManager.getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA); if (applicationInfo != null) { if (applicationInfo.metaData != null) { resultData = applicationInfo.metaData.getString(key); } } } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return resultData; } /** * 判断多渠道打包---渠道邀请码(对应的应用市场的代号) * * @param ctx * @param channel * @return */ public static String getChannel(Context ctx, String channel) { if (ctx == null || TextUtils.isEmpty(channel)) { return null; } String channelValue = null; if (!TextUtils.isEmpty(channel)) { if ("C360".equals(channel)) { channelValue = "XXXX"; } else if ("diankai".equals(channel)) { channelValue = "XXXX"; } else if ("oppo".equals(channel)) { channelValue = "XXXX"; } else if ("youmi".equals(channel)) { channelValue = "XXXX"; } else if ("xiaomi".equals(channel)) { channelValue = "XXXX"; } else if ("dandanzhuan".equals(channel)) { channelValue = "XXXX"; } else if ("yingyongbao".equals(channel)) { channelValue = ""; } else if ("huawei".equals(channel)) { channelValue = "XXXX"; } else if ("ppzhushou".equals(channel)) { channelValue = ""; } else if ("xunfei".equals(channel)) { channelValue = ""; } else if("mumayi".equals(channel)){ channelValue = "XXXX"; } else if("vivo".equals(channel)){ channelValue = "XXXX"; } } return channelValue; } }
具体使用方法(我的项目这里是调用了后台激活渠道的一个接口):
/** * 渠道激活信息 **/ private void getChannelVisit() { Map, Object> mapData = new HashMap , Object>(); String uuid = UUIDS.getUniqueId(getBaseActivity()); mapData.put("act", "promotion_activate"); mapData.put("uuid", uuid); mapData.put("terminal", "android"); String channel = UmengUtils.getAppMetaData(getActivity(), "UMENG_CHANNEL"); String channelValue = UmengUtils.getChannel(getActivity(), channel); if (!TextUtils.isEmpty(channelValue)) { mapData.put("appstore_id", channelValue); } FragmentRequestMode requestMode = new FragmentRequestMode(this); requestMode.params = mapData; requestMode.tag = REQUEST_KEY_CHANNEL_VISIT; Appcontext.verifyCaptcha(requestMode); }
channelValue就是获取的渠道邀请码,传给后台就可以,不用处理返回的数据
这个接口就是在用户下载下来,启动咱们的app时候会收集他在哪个平台上下载的,友盟会做记录。
里面有个工具类是获取手机唯一标识符,工具如下:
package com.XXXXXX.app.base.util; import android.content.Context; import android.os.Build; import android.provider.Settings; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * Android唯一标识生成方案 * Created by ky on 2017/11/13. */ public class UUIDS { /**AndroidId 和 Serial Number*/ public static String getUniqueId(Context context){ String androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); String id = androidID + Build.SERIAL; try { return id; } catch (Exception e) { e.printStackTrace(); return id; } } /**唯一标识 加密使用*/ private static String toMD5(String text) throws NoSuchAlgorithmException { //获取摘要器 MessageDigest MessageDigest messageDigest = MessageDigest.getInstance("MD5"); //通过摘要器对字符串的二进制字节数组进行hash计算 byte[] digest = messageDigest.digest(text.getBytes()); StringBuilder sb = new StringBuilder(); for (int i = 0; i < digest.length; i++) { //循环每个字符 将计算结果转化为正整数; int digestInt = digest[i] & 0xff; //将10进制转化为较短的16进制 String hexString = Integer.toHexString(digestInt); //转化结果如果是个位数会省略0,因此判断并补0 if (hexString.length() < 2) { sb.append(0); } //将循环结果添加到缓冲区 sb.append(hexString); } //返回整个结果 return sb.toString(); } }
终于完成了, 这还是去年配置的,都是拼记忆写的。要不是因为电脑蓝屏,配置没了,我都不愿意写。
如果有错误,联系我我会查找原因,我是去年写的,有漏洞请指出。
运行下: