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

 

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

 

 

 

 

 

 

你可能感兴趣的:(开发工具)