1. 程式人生 > >一個專案如何編譯多個不同簽名、包名、資源等,的apk?

一個專案如何編譯多個不同簽名、包名、資源等,的apk?

簡介

如題所示!本篇文章就是為了解決這種問題。方便打包和執行的時候能做到無需手動替換配置,即可打包想要的apk。打包的時候,只需選一下想打哪種配置的apk就OK啦。 (^o^)/~

先來看,有需求如下:

  1. 同一個專案
  2. 不同的apk圖示
  3. 不同的伺服器域名
  4. 不同的包名
  5. 不同的名稱
  6. 不同的簽名
  7. 不同的第三方key
  8. 不同的版本名版本號

解決思路

  1. 當然最直接的方式不過於每次打不同包的時候都去替換對應的配置,這種方式的麻煩之處不言而喻。
  2. 將所有配置,資源等都配置入專案中,打包的時候,根據選擇渠道打包不同配置的apk。(本篇文章就是要講怎麼這麼做的)
  3. 相信還有其他的。。。

相關的幾個要點

  1. 首先我們需要知道productFlavors
    來配置渠道,這裡我將渠道用來表示哪種apk,如下我需要配置四種應用:
productFlavors {
  userquhua {}
  quhua {}
  cuntuba {}
  xemh {}
}
  1. 如果我們選擇了某一個渠道,那麼執行打包的時候會根據渠道名選擇資原始檔(可結合第6點一起看)
  2. 簽名可在signingConfigs中配置多個(我將所有簽名檔案放在了專案跟目錄的key資料夾中),這樣我們就可以通過signingConfigs指定預製好的簽名配置。
signingConfigs {
    userquhuaRelease {
        storeFile file("../key/xxx1.keystore")
        storePassword "xxxxxx"
        keyAlias "alias"
        keyPassword "xxxxxx"
    }

    quhuaRelease {
        storeFile file("../key/xxx2.keystore")
        storePassword "xxxxxx"
        keyAlias "alias"
        keyPassword "xxxxxx"
    }

    cuntubaRelease {
        storeFile file("../key/xxx3.keystore")
        storePassword "xxxxxx"
        keyAlias "alias"
        keyPassword "xxxxxx"
    }

    xemhRelease {
        storeFile file("../key/xxx4.keystore")
        storePassword "xxxxxx"
        keyAlias "alias"
        keyPassword "xxxxxx"
    }
}
  1. 可在build.gradle中配置動態配置java程式碼呼叫的常量資料(如:通過該方式我們可根據不同渠道動態配置第三方appid,或其他需要根據渠道而改變的資料)
  • 比如:我們在defaultConfig {} 中定義了:
buildConfigField "String", "SERVER_URL", '"http://xx.xxxx.com/"'
  • 此時,您看一下清單檔案中manifest標籤裡的,package的值,假如是:
com.xxx.xx
  • 那麼,您就可以在java程式碼中通過匯入檔案:
import com.xxx.xx.BuildConfig;
  • 然後呼叫
BuildConfig.SERVER_URL

它的值就是上邊配置的字串:http://xx.xxxx.com/

  • 您可以進入BuildConfig看一看,裡面還包含了一些當前的包名版本號等資訊。
  1. 在渠道配置那裡可以配置對應的包名版本名簽名等等 如下所示:
// 省略其他配置...
android {
 // 省略其他配置...
 productFlavors {
     userquhua {
         applicationId "com.xxx.xx"
         versionCode 1
         versionName "1.0.0"
         signingConfig signingConfigs.userquhuaRelease // 配置簽名

         String qq_id = '"xxxxxxxxx"' //配置qq appid
         buildConfigField "String",           "QQ_ID", qq_id
         buildConfigField "String",           "WX_ID", '"wxxxxxxxxxxxxxxxxx"' // 配置微信appid
         manifestPlaceholders = [
           qq_id: qq_id,
           JPUSH_PKGNAME : applicationId,
           JPUSH_APPKEY : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", //JPush 上註冊的包名對應的 Appkey.
           JPUSH_CHANNEL : "developer-default",
         ]
     }
 }

 buildTypes {
   release {
     // 省略其他配置...
       signingConfig null  // 置空
   }

   debug {
     // 省略其他配置...
       signingConfig null // 置空
   }
 }
}
  • 這樣,如果我們打包userquhua這個渠道,看第2點中介紹選擇userquhuaDebug。
  • 然後,最好clean一下專案、然後我們執行專案。
  • 該app的包名就是com.xxx.xx,版本號為1,版本名為1.0.0
  • 通過BuildConfig呼叫QQ_ID靜態常量,就是該渠道里配置的值,WX_ID同理。
  • manifestPlaceholders配置也可以這樣配置。
  • 簽名問題經過個人反覆嘗試(然後半天就過去了 ̄へ ̄),最終簽名如上配置。需要注意buildTypes中的簽名配置signingConfig如果不設定為null,那麼打包的是有還是以內建的簽名打包。
  1. 資原始檔替換 再看到第2點的介紹,我們選擇執行渠道後,會預設匹配對應渠道下的資源。下面我將xemh渠道的資源目錄全部展開一下。
  • 如上圖這樣,只需要資源名字和app目錄對應的檔名字一樣即可替換。
  • strings.xml裡的應用名,只需要將對應app_name修改既可替換app下strings的app_name,其他不用替換的不用寫就行。
  1. 打正式包的時候選好渠道,就可以打包不同配置的apk,當然您也可以使用命令的方式。

其他配置記錄

獲取當前時間

static def releaseTime() {
    return new Date().format("yyyy-MM-dd-HH.mm", TimeZone.getTimeZone("GMT+8"))
}

打包的時候,修改檔名,以方便區別渠道和版本打包時間

applicationVariants.all {
    variant ->
        variant.outputs.all {
            outputFileName = "${variant.productFlavors[0].name}-v${variant.productFlavors[0].versionName}-${releaseTime()}.apk"
        }
}
  • ${variant.productFlavors[0].name}當前渠道名
  • ${variant.productFlavors[0].versionName}當前版本名
  • ${releaseTime()}當前時間

其他需要注意事項

如果您在清單檔案AndroidManifest.xml中,有那種以包名開頭命名的那種。因為如果包名都改了,有些也需要動態的改變。可以用${applicationId}代替。在打包的時候,會自動替換成當前包名。

比如,類似下配置:

<permission
    android:name="com.xxx.xx.permission.JPUSH_MESSAGE"
    android:protectionLevel="signature" />
<uses-permission android:name="com.xxx.xx.permission.JPUSH_MESSAGE" />
<receiver
    android:name=".push.MyJPushMessageReceiver"
    android:enabled="true"
    android:exported="false" >
    <intent-filter>
        <action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" />
        <category android:name="com.xxx.xx" />
    </intent-filter>
</receiver>
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.xxx.xx.provider"
    android:exported="false"
    tools:replace="android:authorities"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

可改為:

<permission
    android:name="${applicationId}.permission.JPUSH_MESSAGE"
    android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.JPUSH_MESSAGE" />
<receiver
    android:name=".push.MyJPushMessageReceiver"
    android:enabled="true"
    android:exported="false" >
    <intent-filter>
        <action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" />
        <category android:name="${applicationId}" />
    </intent-filter>
</receiver>
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    tools:replace="android:authorities"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

當然值得注意的是,在程式碼中我們也不能把包名寫死了,可通過BuildConfig得到當前包名

我的完整配置,供參考

有關隱私資訊的都用xxx替換了

  1. 專案根目錄的build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {

    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        classpath "io.github.prototypez:save-state:0.1.7"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven { url "https://jitpack.io" }
        maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }
        flatDir {
            dirs 'libs'
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

ext{
    minSdkVersion               = 16
    targetSdkVersion            = 27
    compileSdkVersion           = 27
    buildToolsVersion           = '27.1.1'

    supportLibraryVersion       = '27.1.1'
    xmvpVersion                 = '1.2.2'
    retrofit2Version            = '2.3.0'
    okhttp3Version              = '3.8.1'
    butterknifeVersion          = '8.6.0'
    rx2Version                  = '2.0.2'
    CircleProgressDialogVersion = '1.0.2'
    smarttabVersion             = '[email protected]'
    adapterHelperVersion        = '2.9.41'
    glideVersion                = '4.7.1'
    roundedimageviewVersion     = '2.3.0'
    eventbusVersion             = '3.0.0'
    dispatcherVersion           = '2.4.0'
    picture_libraryVersion      = 'v2.2.3'
    statusbarutilVersion        = '1.5.1'
    okhttpUtilsVersion          = '3.8.0'
    constraintVersion           = '1.1.3'
    flexboxVersion              = '1.0.0'
}
  1. app目錄下的build.gradle
apply plugin: 'com.android.application'
apply plugin: 'save.state'

static def releaseTime() {
    return new Date().format("yyyy-MM-dd-HH.mm", TimeZone.getTimeZone("GMT+8"))
}

android {
    compileSdkVersion rootProject.compileSdkVersion
//    buildToolsVersion rootProject.buildToolsVersion
    defaultConfig {
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
        // config the JSON processing library
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ serializer : "gson" ]
            }
        }

        ndk {
            abiFilters "armeabi-v7a"
        }
        renderscriptTargetApi 25
        renderscriptSupportModeEnabled true

    }
    signingConfigs {
        userquhuaRelease {
            storeFile file("../key/xxx.keystore")
            storePassword "xxxxxx"
            keyAlias "xxx"
            keyPassword "xxxxxx"
        }

        quhuaRelease {
            storeFile file("../key/xxx.keystore")
            storePassword "xxxxxxx"
            keyAlias "xxx"
            keyPassword "xxxxxxx"
        }

        cuntubaRelease {
            storeFile file("../key/xxx.keystore")
            storePassword "xxxxxxx"
            keyAlias "xxx"
            keyPassword "xxxxxxx"
        }

        xemhRelease {
            storeFile file("../key/xxx.keystore")
            storePassword "xxxxxxx"
            keyAlias "xxx"
            keyPassword "xxxxxxx"
        }
    }
    flavorDimensions "default"
    productFlavors {
        userquhua {
            applicationId "com.xxx.xx"
            versionCode 22
            versionName "1.7.5"
            signingConfig = signingConfigs.userquhuaRelease

            String qq_id = '"xxxxxx"'
            buildConfigField "String",           "QQ_ID", qq_id // qq appId
            buildConfigField "String",         "SINA_ID", '"xxxxxx"' // 新浪appId
            buildConfigField "String",           "WX_ID", '"xxxxxx"' // 微信 appId
            buildConfigField "String",           "UM_ID", '"xxxxxx"' // 友盟
            buildConfigField "String",       "WX_SECRET", '"xxxxxx"' // 微信 secret
            buildConfigField "String",   "SINA_REDIRECT", '"http://open.weibo.com/apps/xxxxxx/privilege/oauth"' // 新浪

            buildConfigField "String",   "ADHUB_INIT_ID", '"xxxxxx"' // 廣告sdk初始化id
            buildConfigField "String", "ADHUB_SPLASH_ID", '"xxxxxx"' // 開屏廣告id
            buildConfigField "String", "ADHUB_BANNER_ID", '"xxxxxx"' // banner廣告id

            buildConfigField "String",      "SERVER_URL", '"http://xxx.xxx.com/"'
            buildConfigField "String",        "LOGO_URL", '"http://file.xxx.com/img/xxx.png"'

            manifestPlaceholders = [
                    qq_id: qq_id,
                    JPUSH_PKGNAME : applicationId,
                    JPUSH_APPKEY : "xxxxxx", //JPush 上註冊的包名對應的 Appkey.
                    JPUSH_CHANNEL : "developer-default", //暫時填寫預設值即可.
            ]
        }

        quhua {
            applicationId "com.xxx.xx"
            versionCode 1
            versionName "1.0.0"
            signingConfig = signingConfigs.quhuaRelease

            String qq_id = '"xxxxxx"'
            buildConfigField "String",           "QQ_ID", qq_id
            buildConfigField "String",         "SINA_ID", '"xxxxxx"'
            buildConfigField "String",           "WX_ID", '"xxxxxx"'
            buildConfigField "String",           "UM_ID", '"xxxxxx"'
            buildConfigField "String",       "WX_SECRET", '"xxxxxx"'
            buildConfigField "String",   "SINA_REDIRECT", '"http://open.weibo.com/apps/xxxxxx/privilege/oauth"'

            buildConfigField "String",   "ADHUB_INIT_ID", '"xxxxxx"' // 廣告sdk初始化id
            buildConfigField "String", "ADHUB_SPLASH_ID", '"xxxxxx"' // 開屏廣告id
            buildConfigField "String", "ADHUB_BANNER_ID", '"xxxxxx"' // banner廣告id

            buildConfigField "String",      "SERVER_URL", '"http://xx.xxx.com/"'
            buildConfigField "String",        "LOGO_URL", '"http://file.xxx.com/img/xxx.png"'

            manifestPlaceholders = [
                    qq_id: qq_id,
                    JPUSH_PKGNAME : applicationId,
                    JPUSH_APPKEY : "xxxxxx", //JPush 上註冊的包名對應的 Appkey.
                    JPUSH_CHANNEL : "developer-default", //暫時填寫預設值即可.
            ]
        }

        cuntuba {
            applicationId "com.xxx.xx"
            versionCode 1
            versionName "1.0.0"
            signingConfig = signingConfigs.cuntubaRelease

            String qq_id = '"xxxxxx"'
            buildConfigField "String",           "QQ_ID", qq_id
            buildConfigField "String",         "SINA_ID", '"xxxxxx"'
            buildConfigField "String",           "WX_ID", '"xxxxxx"'
            buildConfigField "String",           "UM_ID", '"xxxxxx"'
            buildConfigField "String",       "WX_SECRET", '"xxxxxx"'
            buildConfigField "String",   "SINA_REDIRECT", '"http://open.weibo.com/apps/xxxxxx/privilege/oauth"'

            buildConfigField "String",   "ADHUB_INIT_ID", '"xxxxxx"' // 廣告sdk初始化id
            buildConfigField "String", "ADHUB_SPLASH_ID", '"xxxxxx"' // 開屏廣告id
            buildConfigField "String", "ADHUB_BANNER_ID", '"xxxxxx"' // banner廣告id

            buildConfigField "String",      "SERVER_URL", '"http://xxx.xxxx.com/"'
            buildConfigField "String",        "LOGO_URL", '"http://file.xxx.com/img/xxx.png"'

            manifestPlaceholders = [
                    qq_id: qq_id,
                    JPUSH_PKGNAME : applicationId,
                    JPUSH_APPKEY : "xxxxxx", //JPush 上註冊的包名對應的 Appkey.
                    JPUSH_CHANNEL : "developer-default", //暫時填寫預設值即可.
            ]
        }

        xemh {
            applicationId "com.xxx.xx"
            versionCode 1
            versionName "1.0.0"
            signingConfig = signingConfigs.xemhRelease

            String qq_id = '"xxxxxx"'
            buildConfigField "String",           "QQ_ID", qq_id
            buildConfigField "String",         "SINA_ID", '"xxxxxx"'
            buildConfigField "String",           "WX_ID", '"xxxxxx"'
            buildConfigField "String",           "UM_ID", '"xxxxxx"'
            buildConfigField "String",       "WX_SECRET", '"xxxxxx"'
            buildConfigField "String",   "SINA_REDIRECT", '"xxxxxx"'

            buildConfigField "String",   "ADHUB_INIT_ID", '"xxxxxx"' // 廣告sdk初始化id
            buildConfigField "String", "ADHUB_SPLASH_ID", '"xxxxxx"' // 開屏廣告id
            buildConfigField "String", "ADHUB_BANNER_ID", '"xxxxxx"' // banner廣告id

            buildConfigField "String",      "SERVER_URL", '"http://xx.xxx.com/"'
            buildConfigField "String",        "LOGO_URL", '"http://file.xxxxxx.com/img/xxxxxx.png"'

            manifestPlaceholders = [
                    qq_id: qq_id,
                    JPUSH_PKGNAME : applicationId,
                    JPUSH_APPKEY : "xxxxxx", //JPush 上註冊的包名對應的 Appkey.
                    JPUSH_CHANNEL : "developer-default", //暫時填寫預設值即可.
            ]
        }
    }

    applicationVariants.all {
        variant ->
            variant.outputs.all {
                outputFileName = "${variant.productFlavors[0].name}-v${variant.productFlavors[0].versionName}-${releaseTime()}.apk"
            }
    }

    buildTypes {
        release {
            // 不顯示Log
            buildConfigField "boolean", "LOG_DEBUG", "false"
            signingConfig null
            minifyEnabled true
            zipAlignEnabled true
            // 移除無用的resource檔案
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        debug {
            // 顯示Log
            buildConfigField "boolean", "LOG_DEBUG", "true"
            signingConfig null
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
        }
    }
    packagingOptions {
        exclude 'META-INF/DEPENDENCIES.txt'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE.txt'
    }
    compileOptions {
        targetCompatibility JavaVersion.VERSION_1_8
        sourceCompatibility JavaVersion.VERSION_1_8
    }

    dexOptions {

        javaMaxHeapSize "4g" //此處可根據電腦本身配置 數值越大 當然越快

        preDexLibraries = false

    }
}

repositories {
    flatDir {
        dirs 'libs', '../adpoymer/libs'
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "com.android.support:appcompat-v7:$supportLibraryVersion"
    implementation "com.android.support:recyclerview-v7:$supportLibraryVersion"
    implementation "com.android.support:support-v4:$supportLibraryVersion"
    implementation "com.android.support:design:$supportLibraryVersion"
    implementation "com.android.support.constraint:constraint-layout:$constraintVersion"

    //新增retrofit2 的依賴 新增這個依賴就預設添加了okhttp依賴
    compile "com.squareup.retrofit2:retrofit:$retrofit2Version"
    compile "com.squareup.retrofit2:converter-gson:$retrofit2Version"
    compile "com.squareup.retrofit2:adapter-rxjava2:$retrofit2Version"
    compile "com.squareup.okhttp3:logging-interceptor:$okhttp3Version"
    compile "com.jakewharton:butterknife:$butterknifeVersion"
    annotationProcessor "com.jakewharton:butterknife-compiler:$butterknifeVersion"
    compile "io.reactivex.rxjava2:rxandroid:$rx2Version"
    compile "com.github.xujiaji:xmvp:$xmvpVersion"
    implementation "com.github.autume:CircleProgressDialog:$CircleProgressDialogVersion"
    compile "com.ogaclejapan.smarttablayout:library:$smarttabVersion"
    compile "com.github.CymChad:BaseRecyclerViewAdapterHelper:$adapterHelperVersion"

    compile "com.github.bumptech.glide:glide:$glideVersion"
    annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"

    compile "com.makeramen:roundedimageview:$roundedimageviewVersion"
    compile "org.greenrobot:eventbus:$eventbusVersion"
    annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$dispatcherVersion"
    compile "com.jaeger.statusbarutil:library:$statusbarutilVersion"
    compile("com.github.hotchemi:permissionsdispatcher:$dispatcherVersion") {
        exclude module: "support-v13"
    }
    implementation "com.github.LuckSiege.PictureSelector:picture_library:$picture_libraryVersion"
    implementation 'me.drakeet.library:crashwoodpecker:2.1.1'
    implementation 'com.github.chenupt.android:springindicator:[email protected]'
    debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
    implementation 'com.umeng.sdk:common:1.5.3'
    implementation 'com.umeng.sdk:analytics:7.5.3'

    implementation 'com.liulishuo.filedownloader:library:1.7.5'

    implementation project(':banner')
    implementation project(':xdialog')
    implementation project(':shareutil')
    implementation project(':update')
    implementation project(':pay')
//    implementation project(':adhub')
    implementation project(':imagewatcher')
    implementation files('libs/lite-orm-1.9.2.jar')
    implementation 'jp.wasabeef:blurry:2.1.1'
    implementation "com.google.android:flexbox:$flexboxVersion"

    implementation 'cn.jiguang.sdk:jpush:3.1.6'  // 此處以JPush 3.1.6 版本為例。
    implementation 'cn.jiguang.sdk:jcore:1.2.5'  // 此處以JCore 1.2.5 版本為例。

    compile(name: 'sdk-release', ext: 'aar')
    compile(name: 'open_ad_sdk', ext: 'aar')
    compile(name: 'adpoymer-3.4.35', ext: 'aar')
    implementation 'pl.droidsonroids.gif:android-gif-drawable:1.0.+'
}

結束

就這樣就可以解放大量勞動力啦!每次專案打包各種軟體,選一下就ojbk,哈哈哈~ 如果有些配置在其他渠道沒有的,也可通過BuildConfig在java中判斷如果是某某渠道那麼遮蔽。 原文地址:https://blog.xujiaji.com/post/android-project-one-for-more over