Android 多渠道打包方案,Android打包方案
常規Build
我們先來回顧一下通過Ant或者Gradle進行多渠道批量打包,通常是在AndroidManifest中配置:
<meta-data android:name="CHANNEL" android:value="xxx" />
meta-data通過配置value來動態改變渠道名稱,然後我們可以在代碼中這樣去擷取Channel
private String getChannelNameFromManifest(){try { return (String) mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA).metaData.get("CHANNEL"); } catch (NameNotFoundException e) { e.printStackTrace(); }}
Ant 在多渠道包上配置起來相對麻煩一點,如果你感興趣的話可以參考這個部落格 謙虛的天下-《App自動化之使用Ant編譯項目多渠道打包》。
而Gradle作為Android官方構建工具,批量打包自然會相對簡單一點,就像下面這樣簡單配置一下即可:
/** * ------------------------------------------------------- * Flavors * -------------------------------------------------------- */productFlavors { intl { applicationId "com.smartphone.linux1s1s.international" versionName createVersionName(versionNameInit) manifestPlaceholders = [deeplinkScheme: "nlbtn2goint"] } gloav { applicationId "com.smartphone.linux1s1s.android" versionName createVersionName(versionNameInit) manifestPlaceholders = [deeplinkScheme: "nlbtn2go"] }}
配置可以如此簡單,但是批量打包還是繞不過從新構建這個過程,用一句通俗的話說就是,如果我需要1000個渠道包,每個渠道包需要5分鐘,那麼批量完成這些包需要5000分鐘,Android在開發Gradle的時候可能完全想不到這個問題,因為除了天朝有這麼多渠道市場外,其他國家都是望其項背,囉嗦幾句後,咱們還是得面對現實,解決這個耗時的打包問題。
這裡從最簡單的情況說起,如果這麼多渠道僅僅是區分不同的Channel,其他都相同。如果還有別的細微區別,這裡暫且不考慮,話說回來,即使是這些細微的區別可能也僅僅是個別渠道,所以最笨的解決方案是從新構建這些個別渠道,其他都可以通過捷徑來解決。
這個捷徑是什麼呢,當然是避免從新構建Build,如果不從新構建那麼如果做到區分不同的渠道呢?
最佳化Build
我們先來看一下APK的構造
這個壓縮目錄中META-INF目錄中的檔案原則上是不參與簽名的,所以我們可以通過在該檔案中放入Channel標示而逃過從新構建,這樣我們就可以直接通過檔案操作來完成批量打包,而檔案的複製解壓壓縮操作所消耗的時間和從新構建消耗的時間相比,簡直可以忽略不計,上面就是整個打包方案的核心思想,接下來我們來實施上面的核心思想。
實際操作
在META-INF這個目錄中添加一個以xxx_channelName_channelId命名的空檔案,以xiaomi渠道舉個例子如下所示:
然後再將解壓後並添加相應檔案的目錄重新壓縮為.apk格式的檔案即可,上面的工作完成了,接下來就是大量新增檔案過程了,這裡給出Python指令碼以供參考
Python指令碼
#!/usr/bin/python# coding=utf-8import zipfileimport shutilimport os# 空檔案 便於寫入此空檔案到apk包中作為channel檔案src_empty_file = 'xxxxx.txt'#建立一個空檔案(不存在則建立)with open(src_empty_file, 'w') as f: pass# 擷取目前的目錄中所有的apk源包src_apks = []# python3 : os.listdir()即可,這裡使用相容Python2的os.listdir('.')for file in os.listdir('.'): if os.path.isfile(file): extension = os.path.splitext(file)[1][1:] if extension in 'apk': src_apks.append(file)# 擷取渠道列表channel_file = 'channelConfig.txt'with open(channel_file) as f: for src_apk in src_apks: # file name (with extension) src_apk_file_name = os.path.basename(src_apk) # 分割檔案名稱與尾碼 temp_list = os.path.splitext(src_apk_file_name) # name without extension src_apk_name = temp_list[0] # 尾碼名,包含. 例如: ".apk " src_apk_extension = temp_list[1] # 建立組建目錄,與檔案名稱相關 output_dir = 'output_' + src_apk_name + '/' # 目錄不存在則建立 if not os.path.exists(output_dir): os.mkdir(output_dir) # 遍曆渠道號並建立對應渠道號的apk檔案 for line in f.readlines(): # 擷取當前渠道號,因為從渠道檔案中獲得帶有\n,所有strip一下 target_channel = line.strip() # 擷取渠道號,從中分離出渠道name和渠道id target_channel_split = target_channel.split('_') target_channel_name = target_channel_split[0] #target_channel_id = target_channel_split[1] # 拼接對應渠道號的apk target_apk = output_dir + src_apk_name + '-release-' + target_channel_name + src_apk_extension # 拷貝建立新apk shutil.copy(src_apk, target_apk) # zip擷取建立立的apk檔案 zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED) # 初始化渠道資訊 empty_channel_file = "META-INF/xxxx_{channel}".format(channel = target_channel) # 寫入渠道資訊 zipped.write(src_empty_file, empty_channel_file) # 關閉zip流 zipped.close()
如有問題,歡迎交流。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。