Ant、Gradle、Python三种打包方式的介绍

简介: 博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved !签名过程:1、创建签名keytool -genkey -v -keystore stone.

博客出自: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

<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.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 



目录
相关文章
|
2月前
|
缓存 前端开发 测试技术
(译)Python 官方团队在打包项目中踩过的坑
(译)Python 官方团队在打包项目中踩过的坑
32 2
|
3月前
|
Python Windows
win64系统安装32位的python解释器和打包成exe程序
本文说明了在win64系统环境下,如何安装32位的python解释器,同时对32位虚拟环境下运行的python程序进行打包,将其打包成可执行的exe程序,以图文相结合的方式记录了操作步骤,供大家参考。
264 0
|
4月前
|
小程序 Linux 区块链
Python PyInstaller 打包成 Win、Mac 应用程序(app / exe)
Python PyInstaller 打包成 Win、Mac 应用程序(app / exe)
140 0
|
4月前
|
Python
Python Playwright 打包报错 Please run the following command to download new browsers
Python Playwright 打包报错 Please run the following command to download new browsers
83 0
|
3月前
|
安全 编译器 开发者
Python打包成.exe文件直接运行
Python打包成.exe文件直接运行
113 1
|
1月前
|
存储 UED 开发者
Python语言的软件打包及发布
Python语言的软件打包及发布
|
1月前
|
Linux 数据库连接 数据库
Python如何将项目直接打包为一键整合包
Python如何将项目直接打包为一键整合包
41 0
|
2月前
|
存储 前端开发 API
Python 打包——过去、现在与未来
Python 打包——过去、现在与未来
24 0
|
2月前
|
存储 编译器 开发者
Python 打包的现状:包的三种类型
Python 打包的现状:包的三种类型
24 1
|
2月前
|
自然语言处理 安全 Unix
一键式Excel分词统计工具:如何轻松打包Python脚本为EXE
一键式Excel分词统计工具:如何轻松打包Python脚本为EXE
44 0