本文共 12735 字,大约阅读时间需要 42 分钟。
博客出自:,转载注明出处! 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.apk4、优化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 2016sdk.dir=AndroidSdk的位置,例如:D:\\Android_Software\\adt-bundle-windows-x86_64-20140702\\sdksdk.platformtools=YourSdkPmsdk.tools=YourSdkToolsapk.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/// 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$TaskInputUnionFileCollection cannot be cast to org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection
因此暂时不使用命令行操作。
-------------------------------------------------------------我是分割线----------------------------------------------------------------------------
三、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-8import zipfileimport shutilimport 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