Android 批量打包利器
因為添加了渠道號,對應不同的渠道包,此時,動不動就幾十個包,實在讓人頭疼,此時,需要引入自動打包功能。
首先,列舉出援引的博客內容
美團Android自動化之旅—生成渠道包
http://tech.meituan.com/mt-apk-packaging.html
美團Android自動化之旅—適配渠道包
http://tech.meituan.com/mt-apk-adaptation.html
Android批量打包提速 - 1分鐘900個市場不是夢
http://www.baiduhome.net/lib/view/open1418262988402.html
Android批量打包-如何一秒內打幾十個apk渠道包
http://blog.csdn.net/johnny901114/article/details/48714849
在國內Android常用渠道可能多達幾十個,如:
谷歌市場、騰訊應用寶、百度手機助手、91手機商城、360應用平臺、豌豆莢、安卓市場、小米、魅族商店、oppo手機、聯想樂商、中興匯天地、華為、安智、應用匯、木螞蟻、3G安卓市場(久邦開發者發布系統)
uc應用商店、蘇寧應用、淘寶手機助手、蘑菇市場、搜狗市場、搜狗助手、機鋒、易用匯(金立手機)、中國聯通沃商、中國移動MM、中國電信天翼、億優市場、歷趣世界、冒泡堂、網訊安卓開發者平臺、桌樂、網易、泡椒網、十字貓、酷傳、安粉、安卓園、安卓之家
所以在工作中,當項目開發、測試完畢后就需要針對不同的渠道打出對應的apk安裝包。為了統計每個渠道效果,我們可以使用Umeng sdk或者百度的sdk。這些sdk的使用我就不再這里贅述了,請看相應的開發文檔即可。本文以友盟統計為例。
批量打包方式一:Gradle方式
我相信現在應該很多開發環境都是AndroidStudio了,對Gradle相對還是熟悉的。如果您使用的是Eclipse也沒有關系,用AndroidStudio導入Eclipse工程,或者把gradle配置放在Eclipse工程下(因為AndroidStudio和Eclipse的工程目錄有些差別,把對應的目錄配置對即可)
首先我們使用AndroidStudio新建一個工程,名叫AndroidBatchApk,工程結構如下:
打開AndroidManifest.xml文件 添加友盟的渠道配置如下:
在MainActivity 顯示渠道名代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String channel = ManifestUtil.getMetaDataFromAppication(this, "UMENG_CHANNEL");
//String channel = ManifestUtil.getUmengChannel(this);
((TextView) findViewById(R.id.tv_channel)).setText(channel);
}
} - 1
<meta-data
android:name="UMENG_APPKEY"
android:value="Your UMENG_APPKEY" />
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" /> //${UMENG_CHANNEL_VALUE}是個占位符 - 1
打開app目錄下的build.gradle文件,修改成如下形式:
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
packagingOptions {
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/LICENSE.txt'
}
//簽名
signingConfigs {
release {
//storeFile file("../yourapp.keystore")
storeFile file("keystore_apk.jks")
storePassword "123456"
keyAlias "apk"
keyPassword "123456"
}
}
buildTypes {
release {
// 不顯示Log
//buildConfigField "boolean", "LOG_DEBUG", "false"
//minifyEnabled true //混淆
zipAlignEnabled true //內存對齊
shrinkResources true //移除無用的resource文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
android.applicationVariants.all { variant ->
def stringsFile = new File(variant.outputs[0].processResources.assetsDir, "abc.txt")
stringsFile.mkdir()
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
def fileName = "APK_${releaseTime()}_${variant.productFlavors[0].name}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
}
}
lintOptions {
checkReleaseBuilds false
abortOnError false
ignoreWarnings true
}
// 渠道列表
productFlavors {
_360 {}
_91 {}
QQ {}
appChina {}
baidu {}
google {}
//.....
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
def releaseTime() {
return new Date().format("yyyy-MM-dd HH-mm-ss", TimeZone.getTimeZone("GMT+8"))
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.1'
} - 1
上面的配置,我們測試打6個包,分別是google 、_360、 _91、 appChina、 QQ、 baidu
打開cmd命令行 進入工程所在的目錄,輸入命令:gradle build 不出意外將看到如下成功界面:
并且在output目錄下生成了我們要的apk包(AndroidBatchApk\app\build\outputs\apk)
現在用的安裝我們的生成apk文件,安裝google渠道的apk。
到這里我們就通過gradle方式成功的批量打包了,
時間我們只花費了25秒,但是這是最簡單的工程,如果是實際的開發中,我們的項目會很大,打包的時間也會花費很長時間,我現在公司的項目,通過這種方式打包,需要30、40分鐘左右,這也是挺長的。時間上并不占優勢。但是比我們用工具一個個的打apk強太多了。下面為大家界面一種更高效的打包方式。
批量打包方式二:Python批量打包
首先配置好Python,我用的是Python2.7版本。使用該方式,不把渠道名稱放在AndroidManifest.xml 里,而是新建一個空文件,文件名就是渠道名稱。該文件放在apk目錄的META-INF里。META-INF目錄下默認文件列表如下:
現在我們要解決兩個問題:
我們解決第一個問題。首先我們通過AndroidStudio或者Eclipse打一個正式環境的apk安裝包,不需要有渠道。然后按照渠道列表 復制出各個渠道的,然后往apk文件里寫入文件為渠道名的空文件。我們使用Python代碼來實現該功能,代碼如下:
import sys,os,shutil,zipfile,time
apkVersion="1.4.700000.0107"
srcFileName="source.apk"
destDir=os.path.abspath('.')
file=open("channel.txt")
def writeChannelToApk(filename,channel):
z=zipfile.ZipFile(filename,'a',zipfile.ZIP_DEFLATED)
empty_channel_file="META-INF/channel_{channe}".format(channe=channel)
target_file="channel.apk"
z.write(target_file,empty_channel_file)
z.close()
print "writeChannelToApkchannel"+channel+","+filename+"\n"
def cpFile(srcPath,fileName):
destPath = destDir + os.path.sep + fileName
if os.path.exists(srcPath) and not os.path.exists(destPath):
shutil.copy(srcPath,destPath)
if not os.path.exists(srcFileName):
print "sourcefile"+srcFileName+"notexists"
sys.exit(1)
start = time.clock()
for line in file:
channel=line.strip('\n').strip()
targetFileName="andedu_"+channel+"_"+apkVersion+".apk"
print "copyfile:"+targetFileName
cpFile(srcFileName,targetFileName)
writeChannelToApk(targetFileName,channel)
end = time.clock()
print("The function run time is : %.03f seconds" %(end-start)) 上面是我編寫的Python代碼,根據代碼我們需要三個文件,一個我們打出的apk文件(source.apk 當然名字可以改)、一個空apk文件(channel.apk)和渠道列表文件(channel.txt) 目錄如下:
渠道文件內容如下:
360
appChina
wandoujia
91
baidu
3G
eoe
anzhi
163
hiapk
jifeng
xiaomi
meizu
oppo
lenovo
在命令行輸入:python batch_apk.py 回車,或者雙擊python batch_apk.py 文件
瞬間完成打包工作。
那么如何在apk里面讀取channel呢?
package com.ummeng.getchannel;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import com.linkage.mobile72.studywithme.Consts;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.preference.PreferenceManager;
import android.text.TextUtils;
public class ChannelUtil {
private static final String CHANNEL_KEY = "channel";
private static final String CHANNEL_VERSION_KEY = "channel_version";
private static String mChannel;
/**
* 返回市場。 如果獲取失敗返回""
* @param context
* @return
*/
public static String getChannel(Context context){
return getChannel(context, "00000");
}
/**
* 返回市場。 如果獲取失敗返回defaultChannel
* @param context
* @param defaultChannel
* @return
*/
public static String getChannel(Context context, String defaultChannel) {
//內存中獲取
if(!TextUtils.isEmpty(mChannel)){
return mChannel;
}
//sp中獲取
mChannel = getChannelBySharedPreferences(context);
if(!TextUtils.isEmpty(mChannel)){
return mChannel;
}
//從apk中獲取
mChannel = getChannelFromApk(context, CHANNEL_KEY);
if(!TextUtils.isEmpty(mChannel)){
//保存sp中備用
saveChannelBySharedPreferences(context, mChannel);
return mChannel;
}
//全部獲取失敗
return defaultChannel;
}
/**
* 從apk中獲取版本信息
* @param context
* @param channelKey
* @return
*/
private static String getChannelFromApk(Context context, String channelKey) {
// //從apk包中獲取
String channel = "";
ApplicationInfo appinfo = context.getApplicationInfo();
String sourceDir = appinfo.sourceDir;
ZipFile zipfile = null;
final String start_flag = "META-INF/channel_";
try {
zipfile = new ZipFile(sourceDir);
Enumeration<?> entries = zipfile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = ((ZipEntry) entries.nextElement());
String entryName = entry.getName();
if (entryName.contains(start_flag)) {
channel = entryName.replaceAll(start_flag, "");
return channel;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (zipfile != null) {
try {
zipfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return channel;
}
/**
* 本地保存channel & 對應版本號
* @param context
* @param channel
*/
private static void saveChannelBySharedPreferences(Context context, String channel){
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
Editor editor = sp.edit();
editor.putString(CHANNEL_KEY, channel);
editor.putInt(CHANNEL_VERSION_KEY, getVersionCode(context));
editor.commit();
}
/**
* 從sp中獲取channel
* @param context
* @return 為空表示獲取異常、sp中的值已經失效、sp中沒有此值
*/
private static String getChannelBySharedPreferences(Context context){
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
int currentVersionCode = getVersionCode(context);
if(currentVersionCode == -1){
//獲取錯誤
return "";
}
int versionCodeSaved = sp.getInt(CHANNEL_VERSION_KEY, -1);
if(versionCodeSaved == -1){
//本地沒有存儲的channel對應的版本號
//第一次使用 或者 原先存儲版本號異常
return "";
}
if(currentVersionCode != versionCodeSaved){
return "";
}
return sp.getString(CHANNEL_KEY, "");
}
/**
* 從包信息中獲取版本號
* @param context
* @return
*/
private static int getVersionCode(Context context){
try{
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
}catch(NameNotFoundException e) {
e.printStackTrace();
}
return -1;
}
} 將channel存儲到了sp里面,或者放到Application 內,方便使用。
最后,友盟AndroidManifest.xml
<meta-data
android:name="UMENG_CHANNEL"
android:value="00000" > 用來設置渠道號,此時可以替換為代碼設置AnalyticsConfig.setChannel(BaseApplication.getInstance().getChannel());
來自: http://www.cnblogs.com/liaolandemengxiang/p/5109873.html