IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原]如何使用Ant脚本编译出Jar和Apk包

    jiangwei0910410003发表于 2016-02-26 09:44:10
    love 0

    一、前言

    今天我们来看一个非常出名的工具ant,我们知道AndroidStudio中已经集成了gradle了,那么ant已经没有往日的辉煌了,但是他并没有被淘汰,因为在web项目中打出war包的时候也是可以用到的,虽然maven也很火,其实我开始工作已经快三年了,但是真心的还没用过ant脚本,因为在第一年的时候,我没有实际的出过release包,后面又开始用gradle了,所以直接略过了ant脚本了,但是今天因为有一个需求,就是想自动化的打出一个jar包,所以就想到了ant脚本,正好也算是学习了,其实ant脚本网上的资料也很多了,这里其实也不算是教程了,只是自己在实际的过程中学习了一下,就写个文章记录一下,以备后面使用呢。那么今天我这里不会很详细的介绍ant的所有内容,比如ant中的每个标签的含义以及用法啥的,因为那个网上太多了,就不介绍了,这里就直接介绍如何使用ant打出一个jar和apk包,这部分资料其实网上也有,但是都是不全的,自己遇到的问题网上也是没有的,这里就记录一下。

    首先ant脚本工具是apache开发的一个工具,他的下载地址可以去apache官网下载,下载下来是一个压缩包,解压之后,在bin目录下面有一个ant.bat。运行这个即可,但是为了后面的工作方便,我们将这个添加到环境变量中。


    二、使用ant脚本打包jar

    首先我们来看看如何使用ant脚本打出一个jar包

    我们新建一个工程AntExportJar


    在工程的目录下面新建一个build.xml,这个是ant脚本规定的一个入口脚本文件,文件名都是:build.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project name="AntExportJar" basedir="." default="exportJar">
    	<!-- 设置全局变量 -->
    	<property name="src" value="src" />
    	<property name="dist" value="dist" />
    	<property name="app.name" value="ant" />
    	<property name="app.version" value="1.0" />
    	<property name="classes.encode" value="GBK" />
    	<property name="lib" value="libs" />
        
        <property
            name="project-dir"
            value="C:\Users\i\workspace\AntExportJar" />
        <property
            name="sdk-folder"
            value="C:\Users\i\AppData\Local\Android\sdk" />
        <property
            name="platform-folder"
            value="${sdk-folder}\platforms\android-22" />
        <property
            name="android-jar"
            value="${platform-folder}\android.jar" />
        <property
            name="src"
            value="${project-dir}\src" />
        <property
            name="bin"
            value="${project-dir}\bin" />
        <property
            name="libs"
            value="${project-dir}\lib" />
    
        <!-- task -->
        <target name="init" >
            <echo>
    			Initialize...
            </echo>
            <delete dir="${bin}" />
            <mkdir dir="${bin}" />
        </target>
        
        <target name="buildFiles" depends="init">
    		<javac
    	            bootclasspath="${android-jar}"
    	            compiler="javac1.7"
    	            target="1.7"
    	            destdir="${bin}"
    	            encoding="GBK"
    	            includeAntRuntime="true"
    	            listfiles="true">
    	            <src path="${project-dir}"/> 
    	            <classpath>
    	                 <!-- 引用第三方jar包需要引用,用于辅助编译,并没有将jar打包进去。jar的打包在dex命令中。-->
    	                 <fileset dir="${libs}" includes="*.jar" />
    	            </classpath>
    	    </javac>
        </target>
        
    	<!-- 导出jar文件 -->
    	<target name="exportJar" depends="buildFiles">
    		<delete dir="${dist}" />
    		<!-- Create the distribution directory -->
    		<mkdir dir="${dist}" />
    		<!-- Put everything in ${classes} into the MyProject-${DSTAMP}.jar file -->
    		<jar jarfile="${dist}/${app.name}.jar" basedir="${bin}">
    		    <!-- 
    		    <fileset dir="${libs}" includes="**/*.jar" />
    		    -->
    		    <zipfileset excludes="META-INF/*.SF" src="${libs}/Baidu_NativeAd_SDK.jar" />  
                <zipfileset excludes="META-INF/*.SF" src="${libs}/gdt_mob_release.jar" />  
    		</jar>
    	</target>
    </project>
    
    脚本很简单,下面我们就来分析一下:
    <project name="AntExportJar" basedir="." default="exportJar">

    最外围的一个标签是project,是一个工程标签,有名字,还有就是工程的目录baseDir,用点号:"."

    接下来就是定义全局变量,或者是属性值:

    <!-- 设置全局变量 -->
    <property name="src" value="src" />
    <property name="dist" value="dist" />
    <property name="app.name" value="ant" />
    <property name="app.version" value="1.0" />
    <property name="classes.encode" value="GBK" />
    <property name="lib" value="libs" />
    
    <property
    	name="project-dir"
    	value="C:\Users\i\workspace\AntExportJar" />
    <property
    	name="sdk-folder"
    	value="C:\Users\i\AppData\Local\Android\sdk" />
    <property
    	name="platform-folder"
    	value="${sdk-folder}\platforms\android-22" />
    <property
    	name="android-jar"
    	value="${platform-folder}\android.jar" />
    <property
    	name="src"
    	value="${project-dir}\src" />
    <property
    	name="bin"
    	value="${project-dir}\bin" />
    <property
    	name="libs"
    	value="${project-dir}\lib" />
    这样我们在后面就可以使用:${name值} 来使用value值的定义了,所以这里就相当于定义了变量的作用,这里我们看到有一些value值是路径,但是这里我们感觉有一个不好的地方,就是这些路径是写死的,那么我们还可以怎么做能让他变得灵活呢?其实很简单,ant脚本中是可以访问环境变量的,那么我们只要将这些路径定义成环境变量就可以了:

    <property environment="env"/>
    <property name="ANDROID_HOME" value="${env.ANDROID_HOME}" />
    第一行先申明一个环境变量值,这个env是公共的写法,也是ant自带的,他表示当前的环境变量的值,那么后面就可以访问具体的哪些环境变量了,比如这里我配置了ANDROID_HOME这个环境变量,那么就可以用${env.ANDROID_HOME}来访问androidsdk的目录了,和上面的那个直接使用绝对路径的方式是一样的。

    解析来就是定义task了,在ant中task也是最重要的,我们最终运行的都是task,就相当于Java中的main方法一样。ant脚本中可以定义多个task,而且每个task可以有执行的先后顺序的。相当于Java中的方法中调用其他方法一样。

    <!-- task -->
    <target name="init" >
    	<echo>
    		Initialize...
    	</echo>
    	<delete dir="${bin}" />
    	<mkdir dir="${bin}" />
    </target>
    首先这里定义了一个初始化的task,其中echo标签也是很常用的,就是打印信息的,然后是删除目录${bin},这个bin变量在上面已经定义了,然后在创建${bin}目录。

    初始化完之后,开始执行编译工作:

    <target name="buildFiles" depends="init">
    	<javac
    		bootclasspath="${android-jar}"
    		compiler="javac1.7"
    		target="1.7"
    		destdir="${bin}"
    		encoding="GBK"
    		includeAntRuntime="true"
    		listfiles="true">
    		<src path="${project-dir}"/> 
    			<classpath>
    				<!-- 引用第三方jar包需要引用,用于辅助编译,并没有将jar打包进去。jar的打包在dex命令中。-->
    				<fileset dir="${libs}" includes="*.jar" />
    			</classpath>
    	</javac>
    </target>

    这里在此定义一个buildFiles的task,depends的值是表示当前的task在这个depends的task执行完之后在执行,这里就是先执行init的task,然后在执行buildFiles的task,这里的task主要是编译Java成class文件:

    bootclasspath:表示编译依赖的系统库,这里依赖的是android.jar

    compiler:表示编译的java版本

    target:表示编译之后的class的版本,就是能够运行的java版本

    destDir:表示编译之后的class文件的存放目录

    其他的就不说了,这里还有一个重点,也就是我们在编译的时候会遇到的问题,就是我们在编译的时候,会引用到第三发的jar,所以这里我们为了保证能够编译过,这里还必须用classpath标签来引用这些jar,当然这里只是能够保证编译通过,并不会把这些jar也编译到最终我们想要的jar中,这个问题我们后面再说。

    下面在看最后的一个task,就是将编译完之后的class文件打包成jar文件:

    <!-- 导出jar文件 -->
    <target name="exportJar" depends="buildFiles">
    	<delete dir="${dist}" />
    	<!-- Create the distribution directory -->
    	<mkdir dir="${dist}" />
    	<!-- Put everything in ${classes} into the MyProject-${DSTAMP}.jar file -->
    	<jar jarfile="${dist}/${app.name}.jar" basedir="${bin}">
    		<!-- 
    		<fileset dir="${libs}" includes="**/*.jar" />
    		-->
    	<zipfileset excludes="META-INF/*.SF" src="${libs}/Baidu_NativeAd_SDK.jar" />  
    	<zipfileset excludes="META-INF/*.SF" src="${libs}/gdt_mob_release.jar" />  
    	</jar>
    </target>
    这里我们定义了一个exportJar的task,他是在buildFiles的task运行完之后在运行。

    首先删除目标目录${dist},然后在创建一个目录。这个目录就是存放最后编译好的jar文件的目录

    然后就是用jar标签来导出jar文件了:

    jarfile:表示编译完之后存放的jar文件名路径

    basedir:表示需要编译jar的class文件目录

    其他就OK了,但是在实际中我们在编译的过程中会引用到第三方的jar,那么这时候我们把这些jar编译到最终的jar中,说道这里,其实我们在使用Eclipse导出jar的时候,有一个插件可以做到这点:fat-jar,安装完插件之后,右击工程会出现此菜单:


    这样也可以导出第三方的jar.

    那么在ant中我们如何导出第三方的jar呢?

    这里也很简单:

    <zipfileset excludes="META-INF/*.SF" src="${libs}/Baidu_NativeAd_SDK.jar" /> 
    使用zipfileset标签就可以了。

    excludes:表示剔除这个文件,意思就是不要把第三方的jar中的*.SF文件打包进去

    src:表示需要打包的第三方jar

    那么到这里我们就介绍完了ant脚本,其实还有很多标签和功能这里都没有在介绍了,当然这里也不会详细的介绍,如果后面遇到有问题的或者不知道的功能可以搜一下就可以了,下面我们就来跑这个脚本了。

    首先进入到build.xml脚本的根目录下面,然后运行:ant exportJar

    这里的exportJar是上面我们定义的最后一个task的名称,也就是脚本的入口task

    这里还需要注意的一个问题就是,ant运行的目录下面一定要有build.xml,因为这个是ant需要寻找到的文件才可以解析运行。


    运行完之后,我们再看看dist目录下:


    有了这个ant.jar包了,成功了,我们用jd-gui工具查看jar:


    看到了,这里把第三方的两个jar包含进来了吧。


    项目下载地址:http://download.csdn.net/detail/jiangwei0910410003/9444178


    三、使用ant脚本编译apk包

    下面我们继续来介绍如何使用ant脚本编译出一个apk包

    首先我们需要了解的是Android中编译一个apk包的流程和步骤,其实这个我在之前的一篇解析resource.arsc文件格式的文章末尾介绍了一下:http://blog.csdn.net/jiangwei0910410003/article/details/50628894

    1、使用Android SDK提供的aapt.exe生成R.java类文件
    2、使用Android SDK提供的aidl.exe把.aidl转成.java文件(如果没有aidl,则跳过这一步)
    3、使用JDK提供的javac.exe编译.java类文件生成class文件
    4、使用Android SDK提供的dx.bat命令行脚本生成classes.dex文件
    5、使用Android SDK提供的aapt.exe生成资源包文件(包括res、assets、androidmanifest.xml等)
    6、使用Android SDK提供的apkbuilder.bat生成未签名的apk安装文件
    7、使用jdk的jarsigner.exe对未签名的包进行apk签名


    那么这些步骤AndroidSdk在build-tools目录下面全部提供了相对应的工具,这里就来一一介绍一下:

    1、使用aapt命令编译资源文件

    aapt package -f -m -J gen -S res -I D:/android-sdk-windows/platforms/android-16/android.jar -M AndroidManifest.xml 

    这里的命令参数有点多就不全部介绍了,就说明几个:

    -J 后面跟着是gen目录,也就是编译之后产生的R类,存放的资源Id

    -S 后面跟着是res目录,也就是需要编译的资源目录

    -l 后面跟着是系统的库,因为我们在项目资源中会用到系统的一些资源文件,所以这里需要链接一下

    -M 后面跟着是工程的清单文件,需要从这个文件中得到应用的包名,然后产生对应的R文件和包名。


    2、使用javac命令编译源文件

    javac -target 1.6 -bootclasspath D:/android-sdk-windows/platforms/android-17/android.jar -d bin gen\com\example\antdemo\*.java src\com\example\antdemo\*.java

    这里的参数没什么好说的,其实都很简单

    -target:表示编译之后的class文件运行的环境版本

    -bootclasspath:表示编译需要用到的系统库

    -d:表示编译之后的class文件存放的目录

    后面就是需要编译的java文件了,不同的包下面的java文件,可以用空格分开即可,这里需要编译gen目录下面的java文件,和src下面的所有java文件。


    3、使用dx命令,将class文件转化成dex

    dx --dex --output=G:\Code\Android\Workspace\AntDemo\bin\classes.dex G:\Code\Android\Workspace\AntDemo\bin

    这个命令简单,这里就不说了,而且这个命令我在很多篇文章中都用到,他的作用还是很大的。


    4、使用aapt命令生成资源包文件(编码AndroidManifest.xml,resource.arsc等)

    aapt package -f -A assets -S res -I D:/android-sdk-windows/platforms/android-17/android.jar -M AndroidManifest.xml -F bin/AntDemo

    这个命令其实就是将资源文件进行编码成二进制文件,我们之前介绍的一篇文章中,就是解析这些二进制文件:

    http://blog.csdn.net/jiangwei0910410003/article/details/50669898

    这些二进制文件都是有自己的格式的,系统编程二进制文件是为了优化,减小包的大小。

    但是这里需要注意的是assets目录是不会进行二进制编译的。


    5、使用apkbuilder命令来编译apk

    apkbuilder G:\Code\Android\Workspace\AntDemo\bin\AntDemo_unsigned.apk -v -u -z G:\Code\Android\Workspace\AntDemo\bin\AntDemo -f G:\Code\Android\Workspace\AntDemo\bin\classes.dex -rf G:\Code\Android\Workspace\AntDemo\src 

    这里的一些参数也没什么好说的,但是这里需要注意的是,在AndroidSDK的高版本之后,这个命令是找不到了,被遗弃了,所以我们可以从网上下载一个老版本的命令:查看他的类型,其实是调用了androidsdk根目录/tools/lib/sdklib.jar 这个jar包,后面我们在ant脚本中会看到怎么用。



    6、使用keytool来产生一个keystore文件

    keytool -genkey -alias ant_test1 -keyalg RSA -validity 20000 -keystore my.keystore

    这个命令网上也是有说明的,具体参数这里不多说了


    7、使用jarsigner签名apk文件

    jarsigner -keystore G:\Code\Android\Workspace\AntDemo\build\my.keystore -storepass 123456 -keypass 123456 -signedjar G:\Code\Android\Workspace\AntDemo\bin\AntDemo_signed.apk G:\Code\Android\Workspace\AntDemo\bin\AntDemo_unsigned.apk ant_test

    关于这个命令和signapk命令都可以进行签名apk的,具体他们两什么区别可以参考这篇文章:

    http://blog.csdn.net/jiangwei0910410003/article/details/50402000


    好了,到此我们就介绍完了如何使用纯手工命令来编译一个apk文件,不需要借助任何的IDE工具也是可以做到的,其实为什么先介绍这些命令呢?就是为了下面需要介绍的ant脚本,其实这个脚本就是新建这几个task,然后设置到命令环境变量,最后执行这些命令,所以脚本内容这里就不在一一讲解了:

    <?xml version="1.0" encoding="UTF-8"?>
    <project
        name="AntDemo"
        default="release" >
        <!-- tools dir -->
        <property
            name="sdk-folder"
            value="C:\Users\i\AppData\Local\Android\sdk" />
        <property
            name="platform-folder"
            value="${sdk-folder}\platforms\android-22" />
        <property
            name="platform-tools-folder"
            value="${sdk-folder}\build-tools\22.0.1" />
        <property
            name="jdk-folder"
            value="C:\Program Files\Java\jdk1.7.0_71" />
        <property
            name="android-jar"
            value="${platform-folder}\android.jar" />
        <property
            name="tools.aapt"
            value="${platform-tools-folder}\aapt.exe" />
        <property
            name="tools.javac"
            value="${jdk-folder}\bin\javac.exe" />
        <property
            name="tools.dx"
            value="${platform-tools-folder}\dx.bat" />
        <property
            name="tools.apkbuilder"
            value="${sdk-folder}\tools\apkbuilder.bat" />
        <property
            name="tools.jarsigner"
            value="${jdk-folder}\bin\jarsigner.exe" />
    
        <!-- project dir -->
        <property
            name="project-dir"
            value="C:\Users\i\Desktop\AntDemo\AntDemo" />
        <property
            name="res"
            value="${project-dir}\res" />
        <property
            name="gen"
            value="${project-dir}\gen" />
        <property
            name="src"
            value="${project-dir}\src" />
        <property
            name="bin"
            value="${project-dir}\bin" />
        <property
            name="assets"
            value="${project-dir}\assets" />
        <property
            name="libs"
            value="${project-dir}\libs" />
    
    	<!-- file lists -->
        <property
            name="manifest"
            value="${project-dir}\AndroidManifest.xml" />
        <property
            name="java-file-gen"
            value="${gen}\com\example\antdemo\*.java" />
        <property
            name="java-file-src"
            value="${src}\com\example\antdemo\*.java" />
        <property
            name="dex-name"
            value="${bin}\classes.dex" />
        <property
            name="pakcage-temp-name"
            value="${bin}\${ant.project.name}" />
        <property
            name="unsigned-apk-name"
            value="${ant.project.name}_unsigned.apk" />
        <property
            name="unsigned-apk-path"
            value="${bin}\${unsigned-apk-name}" />
        <property
            name="signed-apk-name"
            value="${ant.project.name}.apk" />
        <property
            name="signed-apk-path"
            value="${bin}\${signed-apk-name}" />
        <property
            name="keystore-name"
            value="${project-dir}\my.keystore" />
        <property
            name="keystore-alias"
            value="ant_test" />
    
        <!-- task -->
        <target name="init" >
            <echo>
    			Initialize...
            </echo>
            <delete dir="${bin}" />
            <mkdir dir="${bin}" />
        </target>
    
        <target
            name="gen-R"
            depends="init" >
            <echo>
    			Generating R.java from the resources... 
            </echo>
            <exec
                executable="${tools.aapt}"
                failonerror="true" >
                <arg value="package" />
                <arg value="-f" />
                <arg value="-m" />
                <arg value="-J" />
                <arg value="${gen}" />
                <arg value="-S" />
                <arg value="${res}" />
                <arg value="-M" />
                <arg value="${manifest}" />
                <arg value="-I" />
                <arg value="${android-jar}" />
            </exec>
        </target>
    
        <target
            name="compile"
            depends="gen-R" >
            <echo>
    			Compile...
            </echo>
            <javac
                bootclasspath="${android-jar}"
                compiler="javac1.7"
                target="1.7"
                destdir="${bin}"
                encoding="utf-8"
                includeAntRuntime="true"
                listfiles="true">
                <src path="${project-dir}"/> 
                <classpath>
                     <!-- 引用第三方jar包需要引用,用于辅助编译,并没有将jar打包进去。jar的打包在dex命令中。-->
                     <fileset dir="${libs}" includes="*.jar" />
                </classpath>
            </javac>
        </target>
    
        <target
            name="dex"
            depends="compile" >
            <echo>
    			Generate dex...
            </echo>
            <exec
                executable="${tools.dx}"
                failonerror="true" >
                <arg value="--dex" />
                <arg value="--output=${dex-name}" />
                <arg value="${bin}" /><!-- classes文件位置 -->
                <arg value="${libs}"/><!-- 把libs下所有jar打包 -->
            </exec>
        </target>
    
        <target
            name="package"
            depends="dex" >
            <echo>
       			Package resource and assets...
            </echo>
            <exec
                executable="${tools.aapt}"
                failonerror="true" >
                <arg value="package" />
                <arg value="-f" />
                <arg value="-A" />
                <arg value="${assets}" />
                <arg value="-S" />
                <arg value="${res}" />
                <arg value="-I" />
                <arg value="${android-jar}" />
                <arg value="-M" />
                <arg value="${manifest}" />
                <arg value="-F" />
                <arg value="${pakcage-temp-name}" />
            </exec>
        </target>
    
        <target
            name="build-unsigned-apk"
            depends="package" >
            <echo>
      			Build unsigned apk
            </echo>
            <!--
            <exec
                executable="${tools.apkbuilder}"
                failonerror="true" >
                <arg value="${unsigned-apk-path}" />
                <arg value="-v" />
                <arg value="-u" />
                <arg value="-z" />
                <arg value="${pakcage-temp-name}" />
                <arg value="-f" />
                <arg value="${dex-name}" />
                <arg value="-rf" />
                <arg value="${src}" />
            </exec>
            -->
            
            <java classpath="${sdk-folder}/tools/lib/sdklib.jar" classname="com.android.sdklib.build.ApkBuilderMain">  
                <arg value="${unsigned-apk-path}" />  
                <arg value="-u" />  
                <arg value="-z" />  
                <arg value="${pakcage-temp-name}" />  
                <arg value="-f" />  
                <arg value="${dex-name}" />  
                <arg value="-rf" />    
                <arg value="${src}" />   
                <arg value="-rj"/>  
                <arg value="${libs}"/>   
            </java> 
            
        </target>
        
        <target
            name="sign-apk"
            depends="build-unsigned-apk" >
            <echo>
      			Sign apk
            </echo>
            <exec
                executable="${tools.jarsigner}"
                failonerror="true" >
                <arg value="-keystore" />
                <arg value="${keystore-name}" />
                <arg value="-storepass" />
                <arg value="123456" />
                <arg value="-keypass" />
                <arg value="123456" />
                <arg value="-signedjar" />
                <arg value="${signed-apk-path}" />
                <arg value="${unsigned-apk-path}" />
                <arg value="${keystore-alias}" />
            </exec>
        </target>
    
        <target
            name="release"
            depends="sign-apk" >
            <delete file="${pakcage-temp-name}" />
            <delete file="${unsigned-apk-path}" />
            <echo>
    			APK is released. path:${signed-apk-path}
            </echo>
        </target>
    
    </project>
    使用ant跑一下脚本:ant release


    我们再看一下bin目录下,产生了最终的apk脚本了。


    其实从这里我们看到,我们不借助ant脚本,自己编写一个bat脚本也是可以做到的,就是顺序的执行这7条命令即可。

    项目下载:http://download.csdn.net/detail/jiangwei0910410003/9444220


    到这里我们就介绍完了,如何使用ant脚本打包jar和apk,这里我也是第一次用ant脚本,感觉他的作用还是蛮大的,在编译的大家庭中,他也是一个重要的角色。


    四、技术概要

    1、学习了ant脚本的基本语法和简单标签

    2、如何使用ant脚本打包携带第三方jar的工程为jar

    3、学会了Android中apk包产生的步骤了流程以及各个流程用到的命令

    4、最重要的是学习到了如何不借助任何的IDE工具就可以打出一个apk包


    五、总结

    这篇文章的易读性还是可以的,没什么难点,就是自己在实际的项目中遇到了一些问题,就总结了一下,网上的资料也比较多,也很杂,所以这里也算是记录了一些,当然关于ant脚本更深入的内容,这里并没有介绍了,比如使用ant脚本编译出各个渠道包,这些网上有很多资料,但是写了这篇文章之后最大的感触是,有时候我们太依赖于IDE这样的工具,导致我们队整个项目的编译过程知道的少之又少,所以使用Linux系统会比使用Windows系统学习到的更多,因为Windows系统已经把我们当做傻瓜看待,什么都帮我们做好了,我只要点击下一步就可以了,所以对于技术开发来说,学习到的肯定也是好了。

    PS: 关注微信,最新Android技术实时推送








沪ICP备19023445号-2号
友情链接