博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved !
签名过程:
1、创建签名
keytool -genkey -v -keystore stone.keystore -alias stone -keyalg RSA -keysize 2048-validity 10000 生成签名文件
2、为apk签名
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore stone.keystore unsigned.apk stone 不生成新文件
3、检测apk是否签名
jarsigner -verbose -certs -verify signed.apk
4、优化apk-优化资源请求效率
zipalign -f -v 4 signed_unaligned.apk signed_aligned.apk
以上是Apk的编译过程,可以根据这个过程来编译脚本,在服务器端自动编译apk
以下是7.0以前打包方式的总结。
今天谈一下Androdi三种打包方式,Ant、Gradle、Python。
当然最开始打包用Ant 很方便,后来转Studio开发,自带很多Gradle插件就用了它,然后随着打包数量越多,打包时间成了需要考虑的事,前两者平均打一个包要花费2-3分钟,打30个就要差不多2个小时;而前两者打包的思路主要是,替换AndroidManifest.xml的meta-data下的value值,然后重新编译 注:不管Ant还是Gradle,下面这句都要加入AndroidManifest.xml
而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文件如下
主要作用:声明主项目和依赖项目,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来了
此文件配置获得打包命令,打包渠道,以及修改文件名,最后打包的过程《完》
-------------------------------------------------------------我是分割线----------------------------------------------------------------------------
二、再讲一下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/
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/// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... // This moves them out of them default location under src/ /... 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.mayi.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 } } // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... // This moves them out of them default location under src/ /... 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.mayi.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即可
其次将Gradle下载后,配置环境将Gradle/bin放入即可。
问题1、https://repo1.maven.org/maven2/com/android/tools/build/gradle/只支持最高2.1.3版本,日期2017.01.16
问题2、2.2.0以下出现, > org.gradle.api.internal.tasks.DefaultTaskInputs$TaskInputUnionFileCollectio
n cannot be cast to org.gradle.api.internal.file.collections.DefaultConfigurable
FileCollection
因此暂时不使用命令行操作。
-------------------------------------------------------------我是分割线----------------------------------------------------------------------------
三、Python打包
1、安装python软件
2、在项目中放入ChannelUtil.java类,用来获得渠道号
3、打好一个包放在与MultiChannelBuildTool.py同级目录
4、在.py同级目录info下的channel.txt添加渠道号
5、点击MultiChannelBuildTool.py即可
文件目录:
新包:
ChannelUtil.java
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
Gradle多渠道打包(动态设定App名称,应用图标,替换常量,更改包名,变更渠道)
https://www.jianshu.com/p/533240d222d3