博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Ant、Gradle、Python三种打包方式的介绍
阅读量:6977 次
发布时间:2019-06-27

本文共 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.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 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$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-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

你可能感兴趣的文章
《Unity着色器和屏幕特效》——2.2 进阶的透明效果
查看>>
《Adobe Premiere Pro CC经典教程(彩色版)》——2.2 建立项目
查看>>
初级Java程序员所面临的4大挑战
查看>>
《算法基础:打开算法之门》一1.5 拓展阅读
查看>>
移动应用开发者应该关注的 Google I/O 两项更新
查看>>
2014 年美国程序员薪资调查
查看>>
方差,标准差,协方差、期望值
查看>>
java异常笔记
查看>>
区域链实践第一步——区域链测试环境搭建
查看>>
《C语言及程序设计》实践项目——画分支结构流程图
查看>>
Qt 自定义信号与槽
查看>>
百度地图 ip查询 service
查看>>
Java新手如何学习三大框架
查看>>
Learn Jenkins the hard way (0) - Jenkins的罪与罚
查看>>
Hadoop history
查看>>
mysql limit offset
查看>>
statpot:使用mongo+bootstrap+highcharts做统计报表
查看>>
文件上传的渐进式增强
查看>>
Linux Shell 脚本限制ssh最大用户登录数
查看>>
incompatible with sql_mode=only_full_group_by
查看>>