标签:
博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved !
今天谈一下Androdi三种打包方式,Ant、Gradle、Python。
当然最开始打包用Ant 很方便,后来转Studio开发,自带很多Gradle插件就用了它,然后随着打包数量越多,打包时间成了需要考虑的事,前两者平均打一个包要花费2-3分钟,打30个就要差不多2个小时;而前两者打包的思路主要是,替换AndroidManifest.xml的meta-data下的value值,然后重新编译 注:不管Ant还是Gradle,下面这句都要加入AndroidManifest.xml
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}"/>
而Python打包非常快,几百个包5分钟以内搞定,而它的思路仅是打完一个可发布包后,往apk的META-INF下写入一个含渠道名的文件,由应用去解析这个渠道名即可,不再使用传统的meta-data去标识value值。
编译一般有以下几个步骤:1.用aapt命令生成R.java文件
2.用aidl命令生成相应java文件
3.用javac命令编译java源文件生成class文件
4.用dx.bat将class文件转换成classes.dex文件
5.用aapt命令生成资源包文件resources.ap_
6.用apkbuilder.bat打包资源和classes.dex文件,生成unsigned.apk
7.用jarsinger命令对apk认证,生成signed.apk
-------------------------------------------------------------我是分割线----------------------------------------------------------------------------
一、简单讲一下Ant打包的流程1、安装ant,并配置好环境变量,在ant->lib目录下放入一个ant-contrib-1.0b3.jar
2、在主项目和依赖项目目录下放置build.xml和local.properties(依赖文件只用放sdk_dir就行)
3、在主项目目录下放置custom_rules.xml即可
4、在命令行下,进入要打包的主项目目录下,输入ant deploy即可(如果二次打包要先输入ant clean)
build.xml文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project
name="Project"
default="help" >
<property
name="project_name"
value="Project" />
<property
name="base_jar_name"
value="Common" />
<property
name="aapt.ignore.assets"
value="!.svn:!.git:\x3Cdir\x3E_*:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~:crunch" />
<!--
The local.properties file is created and updated by the ‘android‘ tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems.
-->
<property file="local.properties" />
<!--
The ant.properties file can be created by you. It is only edited by the
‘android‘ tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is ‘src‘.
out.dir
The name of the output directory. Default is ‘bin‘.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the ‘android‘ tool with the ‘update‘ action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!--
if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition
property="sdk.dir"
value="${env.ANDROID_HOME}" >
<isset property="env.ANDROID_HOME" />
</condition>
<!--
The project.properties file is created and updated by the ‘android‘
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.-->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using ‘android update project‘ or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir" />
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import
file="custom_rules.xml"
optional="true" />
<!--
Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read ‘custom‘ instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>
主要作用:声明主项目和依赖项目,sdk的位置、用到的文件如local.properties等local.properties
## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Tue Feb 16 16:07:45 CST 2016
sdk.dir=AndroidSdk的位置,例如:D:\\Android_Software\\adt-bundle-windows-x86_64-20140702\\sdk
sdk.platformtools=YourSdkPm
sdk.tools=YourSdkTools
apk.dir=打出包放的位置-打包前要确定此路径存在,且无中文
app_version=版本号
app_name=版本名称
market_channels=渠道号-以逗号隔开
key.store=密钥存储路径-注意双斜杠\key.store.password=密码
key.alias=别名
key.alias.password=别名密码
最重要的custom_rules.xml来了<?xml version="1.0" encoding="UTF-8"?>
<project name="custom_rules">
<taskdef resource="net/sf/antcontrib/antcontrib.properties">
<classpath>
<pathelement location="ant-libs/ant-contrib-1.0b3.jar" />
</classpath>
</taskdef>
<target name="deploy">
<foreach delimiter="," list="${market_channels}" param="channel"
target="modify_manifest">
</foreach>
</target>
<target name="modify_manifest">
<replaceregexp byline="false" encoding="UTF-8" flags="g">
<regexp
pattern="android:name="UMENG_CHANNEL"([\w\W]*)android:value="(.*)"" />
<substitution
expression="android:name="UMENG_CHANNEL" android:value="${channel}"" />
<fileset dir="" includes="AndroidManifest.xml" />
</replaceregexp>
<property name="out.final.file"
location="${apk.dir}/${app_name}${app_version}@${channel}.apk" />
<antcall target="clean" />
<antcall target="release" />
<antcall target="clean" />
</target>
</project>
此文件配置获得打包命令,打包渠道,以及修改文件名,最后打包的过程《完》
-------------------------------------------------------------我是分割线----------------------------------------------------------------------------
二、再讲一下Gradle打包的流程
1、配置好build.gradle,如下
2、studio命令行:
gradlew assembleDebug --打非正式包
gradlew assembleRelease --打正式包
gradlew assembleWandoujiaRelease -打特定渠道
结束!
android {
signingConfigs {
debug {
keyAlias ‘your_alias_key‘
keyPassword ‘your_key_pwd
storePassword ‘your_store_pwd‘
storeFile file(‘your_store_key‘)
}
release {
keyAlias ‘your_alias_key‘
storeFile file(‘your_store_key‘)
if (System.console() != null) {
keyPassword System.console().readLine("\nKey password: ")
storePassword System.console().readLine("\nKeystore password: ")
}
}
}
buildTypes {
debug {
//多余的参数
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.debug
// 显示Log
buildConfigField "boolean", "LOG_DEBUG", "true"
}
release {
minifyEnabled true//缩小
zipAlignEnabled true
shrinkResources true//删除无用资源
signingConfig signingConfigs.release
// 显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith(‘.apk‘)) {
// 输出apk名称为apkName_v1.0_wandoujia.apk
def fileName = "apkName${defaultConfig.versionName}_${variant.productFlavors[0].name}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
proguardFile ‘your_cfg‘--例:E:/SorkSpace/branches/studio/proguard.cfg
}
}
productFlavors {
baidu {}
tencent {}
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
compileSdkVersion 19
buildToolsVersion ‘22.0.1‘
sourceSets {
main {
manifest.srcFile ‘AndroidManifest.xml‘
java.srcDirs = [‘src‘]
resources.srcDirs = [‘src‘]
aidl.srcDirs = [‘src‘]
renderscript.srcDirs = [‘src‘]
res.srcDirs = [‘res‘]
assets.srcDirs = [‘assets‘]
}
// Move the tests to tests/java, tests/res, etc...
instrumentTest.setRoot(‘tests‘)
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
debug.setRoot(‘build-types/debug‘)
release.setRoot(‘build-types/release‘)
}
defaultConfig {
applicationId ‘com.chemayi.manager‘
versionCode 20
versionName ‘3.0‘
minSdkVersion 10
targetSdkVersion 19
// dex突破65535的限制
multiDexEnabled true
// AndroidManifest.xml 里面UMENG_CHANNEL的value为 ${UMENG_CHANNEL_VALUE}
// manifestPlaceholders = [UMENG_CHANNEL_VALUE: "channel_name"]
}
dexOptions {
incremental true
javaMaxHeapSize "4g"
}
packagingOptions {
exclude ‘META-INF/NOTICE.txt‘
exclude ‘META-INF/LICENSE.txt‘
}
lintOptions {
abortOnError false
}
}
配置比Ant简单多了,当然在命令行也可以打包,只不过将gradle换成gradlew即可
-------------------------------------------------------------我是分割线----------------------------------------------------------------------------
三、Python打包
1、安装python软件
2、在项目中放入ChannelUtil.java类,用来获得渠道号
3、打好一个包放在与MultiChannelBuildTool.py同级目录
4、在.py同级目录info下的channel.txt添加渠道号
5、点击MultiChannelBuildTool.py即可
文件目录:
新包:
package com.blog.util;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
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 = "yourchannel";
private static final String CHANNEL_VERSION_KEY = "yourchannel_version";
private static String mChannel;
/**
* 返回市场。 如果获取失败返回""
* @param context
* @return
*/
public static String getChannel(Context context){
return getChannel(context, "");
}
/**
* 返回市场。 如果获取失败返回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包中获取
ApplicationInfo appinfo = context.getApplicationInfo();
String sourceDir = appinfo.sourceDir;
//默认放在meta-inf/里, 所以需要再拼接一下
String key = "META-INF/" + channelKey;
String ret = "";
ZipFile zipfile = null;
try {
zipfile = new ZipFile(sourceDir);
Enumeration<?> entries = zipfile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = ((ZipEntry) entries.nextElement());
String entryName = entry.getName();
if (entryName.startsWith(key)) {
ret = entryName;
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (zipfile != null) {
try {
zipfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
String[] split = ret.split("_");
String channel = "";
if (split != null && split.length >= 2) {
channel = ret.substring(split[0].length() + 1);
}
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;
}
}
MultiChannelBuildTool.py#!/usr/bin/python
# coding=utf-8
import zipfile
import shutil
import os
# 空文件 便于写入此空文件到apk包中作为channel文件
src_empty_file = ‘info/yourchannel_.txt‘
# 创建一个空文件(不存在则创建)
f = open(src_empty_file, ‘w‘)
f.close()
# 获取当前目录中所有的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 = ‘info/channel.txt‘
f = open(channel_file)
lines = f.readlines()
f.close()
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 lines:
# 获取当前渠道号,因为从渠道文件中获得带有\n,所有strip一下
target_channel = line.strip()
# 拼接对应渠道号的apk
target_apk = output_dir + src_apk_name + "_" + target_channel + 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/yourchannel_{channel}".format(channel = target_channel)
# 写入渠道信息
zipped.write(src_empty_file, empty_channel_file)
# 关闭zip流
zipped.close()
channel.txt baidu
tencent
标签:
原文地址:http://blog.csdn.net/reboot123/article/details/51261558