Android中flavor的使用

在我的開發中,有這樣的需求,有一個項目,需要適配不同的執法儀設備,這些執法儀都是Android系統的,而且有的有系統簽名,有的沒有,比如我共有四款型號,有三款有系統簽名,每款系統簽名各不一樣,有一款無系統簽名,總結就是我需要使用4個不同簽名用到4個型號上,這就必須要有4個apk,因為一個apk不可能同時擁有4個不同簽名,所以就會導致有如下需求:

  • 生成4個apk,每個apk的簽名不相同,簽名不相同導致應用ID(包名)也不能相同。
  • 使用系統簽名的需要在清單文件中設置 android:sharedUserId="android.uid.system",不使用系統簽名的則不設置。
  • 4個apk的版本號可能不一樣,所以需要分別設置版本信息。
  • 有一款號型是只支持32位CPU的,對應只能使用32位的so,其它的使用64位so。

最開始我是使用變量來表示各種版本和配置,但是每打包一個版本時,就需要修改變量,比如把flag設置為1,對應的配置使用為型號1的配置,然后還要經常修改清單文件,這很麻煩,所以,這時候flavor就派上了用場,可以節省許多寶貴時間。

為4個簽名文件設置對應的配置(下面的配置均使用Groovy語言):

android {signingConfigs {/** 型號1,使用系統簽名 */normal {keyAlias 'aaa'keyPassword 'aaa'storeFile file('aaa.keystore')storePassword 'aaa'}/** 型號2,使用系統簽名 */head {keyAlias 'bbb'keyPassword 'bbb'storeFile file('bbb.keystore')storePassword 'bbb'}/** 型號3,使用系統簽名 */hand {keyAlias 'ccc'keyPassword 'ccc'storeFile file('ccc.jks')storePassword 'ccc'}/** 型號4,使用普通簽名 */hik {keyAlias 'ddd'keyPassword 'ddd'storeFile file('ddd.jks')storePassword 'ddd'}}
}

然后根據需求配置flavor

android {flavorDimensions "version"productFlavors {normal {dimension "version"versionCode 202508180versionName "1.1.0"// 應用id沒指定,則和原來的保持一樣signingConfig signingConfigs.normal// 使用32位sondk.abiFilters "armeabi-v7a"// 指定清單文件中的sharedUserIdmanifestPlaceholders = [sharedUid: "android.uid.system"]}head {dimension "version"versionCode 202508080versionName "1.0.0"applicationIdSuffix ".head" // 修改應用ID,在原來包名基礎上添加.headsigningConfig signingConfigs.headndk.abiFilters "arm64-v8a"// 指定清單文件中的sharedUserIdmanifestPlaceholders = [sharedUid: "android.uid.system"]}hand {dimension "version"versionCode 202508110versionName "1.0.0"applicationIdSuffix ".hand" // 修改應用ID,在原來包名基礎上添加.handsigningConfig signingConfigs.handndk.abiFilters "arm64-v8a"// 指定清單文件中的sharedUserIdmanifestPlaceholders = [sharedUid: "android.uid.system"]}hik {dimension "version"versionCode 202508180versionName "1.0.0"applicationIdSuffix ".hik" // 修改應用ID,在原來包名基礎上添加.hiksigningConfig signingConfigs.hikndk.abiFilters "arm64-v8a"// 指定清單文件中的sharedUserId,設置為空即為普通應用,不使用系統簽名的manifestPlaceholders = [sharedUid: ""] }}
}

從這里可以看到,通過flavor,我們可以很方便的給每個變體設置不一樣的版本號、應用ID、簽名、so、sharedUserId等,flavor支持的配置遠不止這些,如果你還有更多配置需要,自行問AI即可。

這里第一個flavor我們沒有配置應用ID,則它和默認的保持一樣,比如:

android {defaultConfig {applicationId "com.example.hello"}
}

其它的flavor則添加了后綴,比如:applicationIdSuffix ".hik",則它實際使用的應用ID為:com.example.hello.hik。按道理每個flavor都添加后綴比較好看一點,為什么第一個我沒添加,這是因為在做這一款型號的開發的時候,我不知道它有這么多型號,所以當時就使用了com.example.hello包名,且已經上線了,后來來了幾款型號說也要適配,所以此時這個包名已經是不能修改的了。

還有這里指定的manifestPlaceholders = [sharedUid: "android.uid.system"],它會自動注入清單文件,清單文件內容如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:sharedUserId="${sharedUid}">

這里需要注意的是,flavor中指定的簽名配置只對release版本生效,對于系統簽名,即使是debug版本,我們也希望使用系統簽名,因為有些api,必須使用系統簽名才能調用的,如果debug版本使用了Android Studio自帶的debug.keystore,則會拋出異常,所以我們可以配置不使用自帶的debug.keystore,如下:

android {buildTypes {debug {// 注:這里的簽名配置會覆蓋productFlavors中設置的簽名配置,所以要想使用productFlavors中配置的簽名,則這里不能配置簽名// debug簽名,即使我們不配置signingConfig,但它默認其實是配置了使用Android默認的debug.keystore簽名的,所以要想debug的變體// 也使用productFlavors中配置的簽名,則需要在這里手動把signingConfig設置為null,這樣構造debug變體時才會使用productFlavors中的簽名。minifyEnabled falsesigningConfig null // 禁用默認簽名proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}
}

flavor配置好之后,開發就簡單了許多,比如當我需要開發hik版本時,我就在構建變體中選擇hik版本即可,然后調試的時候就直接點運行按鈕,則hik的Debug版本就會運行到設備上,如下:

在這里插入圖片描述
當需要打包某個版本時,直接使用gradle命令,我們可以先在gradle面板中執行tasks命令來查看當前項目都有哪些命令,如下:
在這里插入圖片描述
如上圖,在右上角選擇我們的app模塊(不選擇其實也沒問題,選擇了就更好一些,表明只看app模塊的可用任務),然后在輸入框中輸入tasks然后回車,結果如下:
在這里插入圖片描述
Build tasks分組下,assemble開頭的命令則為打包apk的命令:

命令構建范圍輸出數量典型用途
assemble所有風味 × 所有構建類型8個APK全設備全版本打包(CI/CD)
assembleDebug所有風味 × Debug4個APK所有設備的調試測試版本
assembleRelease所有風味 × Release4個APK所有設備的正式發布版本
assembleHeadhead風味 × 所有構建類型2個APK特定設備的調試+正式版本

其實tasks任務并沒有完全打印所有的assemble命令,比如我就想打包一個hik風味的release版本,則可以用:assembleHikRelease,如果只要debug,則為assembleHikDebug

總結就是:assemble可單獨使用,也可加風味,也可加構建類型,也可都加,在輸入命令時,這太長了又容易輸錯,所以可以使用縮寫,比如我要打包風味為normalrelease版本,完整命令為:assembleNormalRelease,縮寫為aNR,對于HeadHandHik,它們都是H開頭,所以可以再加多第二個字母來區別,比如要打包Hikrelease版本,則可以用:aHiR

不知道是不是我的Android插件版本不對,我執行assemble命令生成的apk位置在app/build/intermediates/apk目錄下,截圖如下:
在這里插入圖片描述
執法assemble命令來打包所有版本時,也是可以用縮寫的,截圖如下:
在這里插入圖片描述
生成所有debug版本:gradle aD,這與androidDependencies沖突了,則改用:gradle asD,反正不用記,先執行,沖突了會報錯,然后再改了再執行即可,效果如下:
在這里插入圖片描述
生成所有release版本:gradle aR,效果如下:
在這里插入圖片描述
生成hik風味的debug與release版本:gradle aHi,效果如下:
在這里插入圖片描述
生成hik風味的release版本:gradle aHiR,效果如下:
在這里插入圖片描述
生成hik風味的debug版本:gradle aHiD,效果如下:
在這里插入圖片描述
有時候在代碼中,還需要根據變體做特殊處理,比如我的某個變體使用普通簽名,則它不能調用那些需要系統簽名的API,在代碼中判斷當前是哪個變體也很簡單,我們是給應用ID添加的后綴,則判斷后綴即可,如下:

class MyApplication : Application() {companion object {var isNormal = falsevar isHead = falsevar isHand = falsevar isHik = false}fun onCreate() {when  {packageName.endsWith(".hello")   -> isNormal = truepackageName.endsWith(".head") -> isHead = truepackageName.endsWith(".hand") -> isHand = truepackageName.endsWith(".hik")  -> isHik = true}}
}

flavor的一個經典應用就是同一個項目提供免費版本和付費版本,也可以理解為基礎版本和高級版本,高級版本需要收費。由于近年來kotlin語言做為build.gradle.kts語言越來越流行了,所以下面使用kotlin語言進行示例演示:

android {flavorDimensions += "version"productFlavors {create("free") {dimension = "version"applicationId = "cn.android666.audiorecorder.free"versionCode = 1versionName = "1.0-free"}create("paid") {dimension = "version"applicationId = "cn.android666.audiorecorder.paid"versionCode = 1versionName = "1.0-paid"}}}

在flavor配置中,還可以為Debug和Release分別設置服務器IP、端口等,這樣通過切換變體就能實現服務器的切換,無需要手動修改。假設免費版和收費版使用的服務器IP和端口都是一樣的,但是debug版本和release版本不一樣,其實這種情況就只和構建類型相關,和flavor不相關了,所以在構建類型中定義即可,如下:

android {buildTypes {debug {// Debug版本使用公司內部服務器buildConfigField("String", "SERVER_IP", "\"192.168.10.100\"")buildConfigField("int", "SERVER_PORT", "3000")}release {// Release版本使用生產環境服務器buildConfigField("String", "SERVER_IP", "\"47.98.123.156\"")buildConfigField("int", "SERVER_PORT", "80")}}buildFeatures {buildConfig = true}}

假設情況有變了,debug版本和release版本的服務器ip端口是一樣的,只是免費版本和付費版不相同,這就跟構建類型不相關了,而是跟flavor相關了,所以就不要在構建類型中配置ip和端口了,而應該以在flavor中配置,如下:

android {flavorDimensions += "version"productFlavors {create("free") {dimension = "version"applicationId = "cn.android666.audiorecorder.free"versionCode = 1versionName = "1.0-free"// 免費版服務器配置buildConfigField("String", "SERVER_IP", "\"47.102.56.122\"")buildConfigField("int", "SERVER_PORT", "3000")}create("paid") {dimension = "version"applicationId = "cn.android666.audiorecorder.paid"versionCode = 1versionName = "1.0-paid"// 付費版服務器配置buildConfigField("String", "SERVER_IP", "\"47.102.56.123\"")buildConfigField("int", "SERVER_PORT", "8080")}}buildFeatures {buildConfig = true}
}

假設情況又有變了,對于免費版本和付費版本,它們分別使用不同的服務器,且它們的debug版本和release版本也是使用不同的服務器,此時不但和構建類型相關,還和和flavor相關,這種情況屬于flavor和構建類型相交差的情形,聲明在構建類型配置中不合適,聲明在flavor配置中也不合適,這需要動態設置,示例如下:

android {buildTypes {debug {isMinifyEnabled = false}release {isMinifyEnabled = falseproguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")}}flavorDimensions += "version"productFlavors {create("free") {dimension = "version"applicationId = "cn.android666.audiorecorder.free"versionCode = 1versionName = "1.0-free"}create("paid") {dimension = "version"applicationId = "cn.android666.audiorecorder.paid"versionCode = 1versionName = "1.0-paid"}}applicationVariants.all {val variant = thisvar serverIp = "\"192.168.1.100\""  // 默認服務器var serverPort = "8080"             // 默認端口// 根據變體名稱配置不同的服務器IP和端口when (variant.name) {"freeDebug" -> {serverIp = "\"192.168.192.128\""     // 免費版調試服務器serverPort = "3000"                 // 免費版調試端口}"freeRelease" -> {serverIp = "\"47.98.123.156\""      // 免費版生產服務器serverPort = "80"                   // 免費版生產端口}"paidDebug" -> {serverIp = "\"192.168.192.100\""     // 付費版調試服務器serverPort = "4000"                 // 付費版調試端口}"paidRelease" -> {serverIp = "\"47.102.56.123\""      // 付費版生產服務器serverPort = "8080"                 // 付費版生產端口}}variant.buildConfigField("String", "SERVER_IP", serverIp)variant.buildConfigField("int", "SERVER_PORT", serverPort)}buildFeatures {buildConfig = true}
}

在代碼中訪問服務器IP和端口:

Log.i("TAG", "Server IP: ${BuildConfig.SERVER_IP}, Port: ${BuildConfig.SERVER_PORT}")

運行不同的變體就能得到不同的服務器IP和端口,無需每次都手動修改代碼,這樣大大節省了寶貴時間。

這里需要注意的是,我們在build.gradle.kts中指定的int類型時不要設置為Int,如下:

variant.buildConfigField("Int", "SERVER_PORT", serverPort)

這樣生成的BuildConfig.java代碼如下:

public final class BuildConfig {public static final boolean DEBUG = Boolean.parseBoolean("true");public static final String APPLICATION_ID = "cn.android666.audiorecorder.free";public static final String BUILD_TYPE = "debug";public static final String FLAVOR = "free";public static final int VERSION_CODE = 1;public static final String VERSION_NAME = "1.0-free";// Field from the variant APIpublic static final String SERVER_IP = "192.168.192.128";// Field from the variant APIpublic static final Int SERVER_PORT = 3000;
}

雖然語法上是錯的,但是它還是生成了,這是Java代碼,不是Kotlin,Java中是沒有Int類型的,只有小寫的int,有時候不注意,用kotlin習慣了,一下子轉不過來,明明Int生成了,但是為什么使用的時候報錯,如下:
在這里插入圖片描述
報錯原因就是Java中沒有Int類型只有int類型。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/93599.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/93599.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/93599.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何使用DeepSeek解析長pdf的文本

直接使用python工具解析pdf文件&#xff0c;可能因為格式兼容問題&#xff0c;導致解析出的文本幾乎不可讀。 這里嘗試使用Deepseek解析pdf文件&#xff0c;這里僅考慮文本&#xff0c;不考慮其他要素。 1 解析第一步 將pdf作為附件上傳到deepseekchat界面&#xff0c;輸入如…

W3C CSS 活動

W3C CSS 活動 引言 CSS(層疊樣式表)是現代網頁設計中不可或缺的技術之一。W3C(萬維網聯盟)作為全球互聯網標準制定的主要組織,定期舉辦各類CSS相關的活動,旨在促進CSS技術的發展與普及。本文將詳細介紹W3C CSS活動,包括活動內容、參與方式以及活動意義。 活動內容 1…

React Native 與 UniApp 對比

React Native 優點: 由 Facebook 開發維護&#xff0c;社區生態強大 使用 JavaScript/TypeScript 開發&#xff0c;學習曲線相對平緩 真正的原生渲染&#xff0c;性能接近原生應用 支持熱重載&#xff0c;開發效率高 可訪問所有原生平臺 API 大型應用案例豐富(如 Faceboo…

Dijkstra和多層圖 0

眾所周知&#xff0c;Dijkstra經常拿來解決不帶負權和環的單元最短路。我們先來看一下他的實現過程 (由于樸素版用的不多&#xff0c;我們直接上堆優化) 模板 #include<bits/stdc.h> #define mf(x,y) make_pair(x,y)//x距離&#xff0c;y節點 using namespace std; …

【驅動】RK3576:桌面操作系統基本概念

1、桌面操作系統 我們常說的Ubuntu、Debian、麒麟、統信等都是總包工頭; 他們把linux內核、根文件系統(遵循 Linux 標準文件系統層次結構FHS)、包管理(軟件、庫)、桌面環境(GNOME、Xfce等)、初始化系統(Systemd)、各種服務與守護進程、安全組件等整合成一個完整的桌面…

sfc_os!SfcQueueValidationRequest函數分析之sfc_os!IsFileInQueue

第一部分&#xff1a;1: kd> kc# 00 sfc_os!SfcQueueValidationRequest 01 sfc_os!SfcWatchProtectedDirectoriesWorkerThread 02 kernel32!BaseThreadStart1: kd> dvRegVal 0x01129164ChangeType 5vrd 0x012bfef0Status 0n1988337684vrdexisting 0x012bffdc//// if…

100202Title和Input組件_編輯器-react-仿低代碼平臺項目

文章目錄1 開發兩個問卷組件1.1 Title組件1.2 Input組件1.3 畫布靜態展示TItle和Input2 Ajax獲取問卷數據&#xff0c;并存儲到Redux store2.1 API接口2.2 組件列表存儲到Redux store統一管理2.3 重構useLoadQuestionData3 在畫布顯示問卷列表&#xff0c;點擊可選中3.1 Redux獲…

設置計劃任務自動備份mysql

windows系統下1.創建mysql自動備份腳本mysqlback.bat需將此腳本存放在mysql的bin文件夾下。確保此腳本執行成功了在進行第2步做計劃任務。echo off REM 定義備份目錄backup_dir、備份的文件名filename set "backup_dirD:\mysqlback" set "filenamemysqlback_%da…

飛機起落架輪軸深孔中間段電解擴孔內輪廓檢測 - 激光頻率梳 3D 輪廓檢測

摘要&#xff1a;飛機起落架輪軸深孔中間段電解擴孔內輪廓檢測存在精度要求高、結構復雜等挑戰。本文針對電解擴孔特殊工藝特征&#xff0c;探討激光頻率梳 3D 輪廓檢測技術的應用&#xff0c;分析其檢測原理、技術優勢及在輪軸深孔檢測中的實踐&#xff0c;為電解擴孔內輪廓高…

【軟考中級網絡工程師】知識點之入侵防御系統:筑牢網絡安全防線

目錄一、入侵防御系統基礎概念1.1 定義與作用1.2 與其他安全設備的關系二、入侵防御系統工作原理剖析2.1 數據包捕獲與預處理2.2 深度包檢測&#xff08;DPI&#xff09;技術2.3 威脅特征匹配2.4 行為分析與機器學習輔助檢測2.5 威脅處理與響應機制三、入侵防御系統功能全面解析…

Python爬蟲實戰:研究scrapfly-scrapers庫,構建電商/新聞/社交媒體數據采集系統

1. 引言 1.1 研究背景與意義 在大數據與人工智能技術深度滲透各行業的背景下,數據已成為企業決策、學術研究、產品創新的核心驅動力。互聯網作為全球最大的信息載體,蘊含海量結構化與非結構化數據(如電商商品信息、新聞資訊、社交媒體動態等),其價值挖掘依賴高效的數據采…

Python爬蟲反爬檢測失效問題的代理池輪換與請求頭偽裝實戰方案

Python爬蟲反爬檢測失效問題的代理池輪換與請求頭偽裝實戰方案 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般絢爛的技術棧中&#xff0c;我是那個永不停歇的色彩收集者。 &#x1f98b; 每一個優化都是我培育的花朵&#xff0c;每一個特性都是…

【原理】C#構造函數可以標記為Static嗎

【從UnityURP開始探索游戲渲染】專欄-直達 實例構造函數&#xff08;Instance Constructor&#xff09;不能標記為static但C#提供了一種特殊的? 靜態構造函數&#xff08;Static Constructor&#xff09;專門用于初始化靜態成員。下面依次介紹他們&#xff1a; 1. ?實例構造…

數據結構--樹(3)

數據結構基礎&#xff08;13&#xff09; 文章目錄數據結構基礎&#xff08;13&#xff09;--樹樹的存儲結構樹的存儲方式1&#xff1a;雙親表示法&#xff08;順序存儲&#xff09;樹的存儲方式2&#xff1a;孩子表示法樹的存儲方式3&#xff1a;孩子兄弟表示法樹轉二叉樹森林…

sys.stdin讀取鍵盤輸入【持續更新~】

背景sys.stdin主要用來讀取鍵盤的一行或者多行輸入&#xff0c;讀取后表達形式為字符串。下文主要探討sys.stdin.readline()的使用&#xff0c;sys.stdin.read()參考&#xff1a;sys.stdin.readline()是逐行讀取&#xff0c;通常會配合.strip()清除首尾的換行符/空格sys.stdin.…

近閾值技術引領者:STM32U3系列的能效與安全革新

引言 當電池供電設備已深度融入生活的每一個角落&#xff0c;功耗控制與續航能力儼然成為制約技術演進的核心瓶頸。在此背景下&#xff0c;超低功耗新系列STM32U3憑借前沿的近閾值設計理念&#xff0c;為受功耗瓶頸限制的設備提供了突破性解決方案&#xff0c;也為能耗管理開啟…

Vue3 中的 provide 和 inject 詳解:實現跨組件通信

一、provide 和 inject 概述在 Vue3 中&#xff0c;provide 和 inject 是一對用于實現跨層級組件通信的 API&#xff0c;它們解決了 props 需要逐層傳遞的繁瑣問題。1.1 基本概念provide (提供)&#xff1a;在祖先組件中提供數據inject (注入)&#xff1a;在任意后代組件中注入…

Kafka 零拷貝(Zero-Copy)技術詳解

文章目錄1. 什么是零拷貝2. Kafka 如何實現零拷貝2.1 sendfile 系統調用2.2 mmap 內存映射3. 傳統拷貝 vs 零拷貝3.1 傳統文件傳輸流程3.2 零拷貝文件傳輸流程4. Kafka 零拷貝的具體實現4.1 消息消費時的零拷貝4.2 日志段文件的零拷貝5. 零拷貝帶來的性能優勢6. 零拷貝的適用場…

Vue 中 v-for 的使用及 Vue2 與 Vue3 的區別

v-for 基本用法v-for 是 Vue 中用于循環渲染列表的指令&#xff0c;基本語法如下&#xff1a;運行<!-- Vue2 和 Vue3 通用基本語法 --> <div v-for"(item, index) in items" :key"item.id">{{ index }} - {{ item.name }} </div>Vue2 和…

本地搭建dify+deepseek智能體

今天開始搭建智能體&#xff0c;學習一下&#xff0c;也是公司轉型所需。(Windows下的docker安裝給我差點干破防了&#xff0c;安裝了一周docker才成功。我真就要放棄的時候&#xff0c;又意外成功了/(ㄒoㄒ)/~~)0、準備階段 配置Windows10的基本配置。 按下鍵盤Windows鍵&…