Java操作Ant压缩和解压文件及批量打包Anroid应用
实现zip/tar的压缩与解压
java中实际是提供了对 zip等压缩格式的支持,但是为什么这里会用到ant呢?
原因主要有两个:
1. java提供的类对于包括有中文字符的路径,文件名支持不够好,你用其它第三方软件解压的时候就会存在乱码。而ant.jar就支持文件名或者路径包括中文字符。
2. ant.jar提供了强大的工具类,更加方便于我们对压缩与解压的操作。
注意事项:
1. 首先说明一下,关于皮肤或者类似于皮肤的zip包,实际上公司可能会根据自己的规定或需求,自定义压缩包文件的结尾,实际上大多还是zip包的格式. 具体部分的处理大致上是一样的,因此不再复述, 本文给出的例子已经有zip包和tar包的解压缩.
2. 还有要注意的是,此处为提升理解,因此加入zip/tar压缩,解压的界面,实际应用中此部分无需单独的界面展示(解压缩需要一定时间的话,则为加强用户体验,加入提示框与进度条),请自行编写解压缩管理类进行逻辑判断分别处理.
3. 测试时需要讲要解压缩的包导入sdcard目录下(若为其他目录,请修改代码中路径)
程序主界面及解压缩的界面:
接下来是解压缩核心的代码:
布局文件: antzip.xml:
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <linearlayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="20dip" android:layout_centerinparent="true"> <radiogroup android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <radiobutton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/radiozip" android:checked="true" android:text="zip"/> <radiobutton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/radiotar" android:text="tar" android:layout_marginleft="10dip"/> </radiogroup> <button android:text="压缩" android:id="@+id/button1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingleft="30dip" android:paddingright="30dip"></button> <button android:text="解压" android:id="@+id/button2" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingleft="30dip" android:paddingright="30dip" android:layout_margintop="20dip"></button> </linearlayout> </relativelayout>
antzipactivity:
public class antzipactivity extends activity { public static final string type = "type"; public static final int type_zip = -1; public static final int type_tar = 1; public static final string suffix_zip = ".zip"; public static final string suffix_tar = ".tar"; /** called when the activity is first created. */ private button btndocompress; private button btndecompress; private radiobutton radiozip; private radiobutton radiotar; private boolean iszip = true; @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.antzip); radiozip = (radiobutton)findviewbyid(r.id.radiozip); iszip = true; radiozip.setchecked(true); radiozip.setoncheckedchangelistener(new oncheckedchangelistener() { @override public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) { system.out.println("radiozip:"+ischecked); if(ischecked) { iszip = true; } } }); radiotar = (radiobutton)findviewbyid(r.id.radiotar); radiotar.setoncheckedchangelistener(new oncheckedchangelistener() { @override public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) { system.out.println("radiotar:"+ischecked); if(ischecked) { iszip = false; } } }); btndocompress = (button)findviewbyid(r.id.button1); btndocompress.setonclicklistener(new onclicklistener() { @override public void onclick(view v) { //进入压缩界面 intent i = new intent(antzipactivity.this,dozipactivity.class); i.putextra(type, iszip?type_zip:type_tar); antzipactivity.this.startactivity(i); } }); btndecompress = (button)findviewbyid(r.id.button2); btndecompress.setonclicklistener(new onclicklistener() { @override public void onclick(view v) { //进入解压界面 intent i = new intent(antzipactivity.this,unzipactivity.class); i.putextra(type, iszip?type_zip:type_tar); antzipactivity.this.startactivity(i); } }); } }
dozipactivity:
public class dozipactivity extends activity implements onclicklistener{ private edittext etpath; private edittext etdest; private button btndozip; private textview tvtip; private string srcpath; private string zipdest; private int type; private string suffix; @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); settitle("ant-压缩"); type = getintent().getintextra(antzipactivity.type, antzipactivity.type_zip); suffix = type==antzipactivity.type_zip ? antzipactivity.suffix_zip:antzipactivity.suffix_tar; setcontentview(r.layout.dozip); // etpath = (edittext)findviewbyid(r.id.edittext1); etdest = (edittext)findviewbyid(r.id.edittext2); //设置一些默认的函数 etpath.settext("/sdcard/antzip"); etdest.settext("/sdcard/antzip"+suffix); btndozip = (button)findviewbyid(r.id.button); tvtip = (textview)findviewbyid(r.id.tv_tip); btndozip.setonclicklistener(this); } @override public void onclick(view v) { srcpath = etpath.geteditabletext().tostring(); if(textutils.isempty(srcpath)) { toast.maketext(this, "请指定一个路径", toast.length_short).show(); return; } file srcfile = new file(srcpath); if(!srcfile.exists()) { toast.maketext(this, "指定的压缩包不存在", toast.length_short).show(); return; } zipdest = etdest.geteditabletext().tostring(); if(textutils.isempty(zipdest)) { //如果用户没有输入目标文件,则生成一个默认的 zipdest = srcfile.getparent(); } system.out.println("zip name:"+zipdest); //如果是以/结尾的,则证明用户输入的是一个目录 ,需要在后面加上文件名 if(zipdest.endswith(file.separator)) { zipdest+=srcfile.getname()+suffix; } else { //如果压缩文件名不是以zip/tar结尾,则加上后缀后 if(!zipdest.endswith(suffix)) { zipdest +=suffix; } } //如果用户选择的是zip,则用 ziputil进行压缩 if(type == antzipactivity.type_zip) { ziputil zipp = new ziputil(); zipp.dozip(srcpath, zipdest); } //如果用户选择的是tar,则用 tarutil进行压缩 else { tarutil tarr = new tarutil(); tarr.dotar(srcpath, zipdest); } //压缩完成后还是提示用户 tvtip.settext("压缩文件路径:"+zipdest); toast.maketext(this, "压缩完成", toast.length_short).show(); } }
解压缩工具类ziputil:
public class ziputil { private zipfile zipfile; private zipoutputstream zipout; //压缩zip private int bufsize; //size of bytes private byte[] buf; public ziputil(){ //要构造函数中去初始化我们的缓冲区 this.bufsize = 1024*4; this.buf = new byte[this.bufsize]; } /** * 对传入的目录或者是文件进行压缩 * @param srcfile 需要 压缩的目录或者文件 * @param destfile 压缩文件的路径 */ public void dozip(string srcfile, string destfile) {// zipdirectorypath:需要压缩的文件夹名 file zipfile = new file(srcfile); try { //生成zipoutputstream,会把压缩的内容全都通过这个输出流输出,最后写到压缩文件中去 this.zipout = new zipoutputstream(new bufferedoutputstream( new fileoutputstream(destfile))); //设置压缩的注释 zipout.setcomment("comment"); //设置压缩的编码,如果要压缩的路径中有中文,就用下面的编码 zipout.setencoding("gbk"); //启用压缩 zipout.setmethod(zipoutputstream.deflated); //压缩级别为最强压缩,但时间要花得多一点 zipout.setlevel(deflater.best_compression); handlefile(zipfile, this.zipout,""); //处理完成后关闭我们的输出流 this.zipout.close(); } catch (ioexception ioe) { ioe.printstacktrace(); } } /** * 由dozip调用,递归完成目录文件读取 * @param zipfile * @param zipout * @param dirname 这个主要是用来记录压缩文件的一个目录层次结构的 * @throws ioexception */ private void handlefile(file zipfile, zipoutputstream zipout,string dirname) throws ioexception { system.out.println("遍历文件:"+zipfile.getname()); //如果是一个目录,则遍历 if(zipfile.isdirectory()) { file[] files = zipfile.listfiles(); if (files.length == 0) {// 如果目录为空,则单独创建之. //只是放入了空目录的名字 this.zipout.putnextentry(new zipentry(dirname+zipfile.getname()+file.separator)); this.zipout.closeentry(); } else {// 如果目录不为空,则进入递归,处理下一级文件 for (file file : files) { // 进入递归,处理下一级的文件 handlefile(file, zipout, dirname+zipfile.getname()+file.separator); } } } //如果是文件,则直接压缩 else { fileinputstream filein = new fileinputstream(zipfile); //放入一个zipentry this.zipout.putnextentry(new zipentry(dirname+zipfile.getname())); int length = 0; //放入压缩文件的流 while ((length = filein.read(this.buf)) > 0) { this.zipout.write(this.buf, 0, length); } //关闭zipentry,完成一个文件的压缩 this.zipout.closeentry(); } } /** * 解压指定zip文件 * @param unzipfile 压缩文件的路径 * @param destfile 解压到的目录 */ public void unzip(string unzipfile, string destfile) {// unzipfilename需要解压的zip文件名 fileoutputstream fileout; file file; inputstream inputstream; try { //生成一个zip的文件 this.zipfile = new zipfile(unzipfile); //遍历zipfile中所有的实体,并把他们解压出来 for (@suppresswarnings("unchecked") enumeration<zipentry> entries = this.zipfile.getentries(); entries .hasmoreelements();) { zipentry entry = entries.nextelement(); //生成他们解压后的一个文件 file = new file(destfile+file.separator+entry.getname()); if (entry.isdirectory()) { file.mkdirs(); } else { // 如果指定文件的目录不存在,则创建之. file parent = file.getparentfile(); if (!parent.exists()) { parent.mkdirs(); } //获取出该压缩实体的输入流 inputstream = zipfile.getinputstream(entry); fileout = new fileoutputstream(file); int length = 0; //将实体写到本地文件中去 while ((length = inputstream.read(this.buf)) > 0) { fileout.write(this.buf, 0, length); } fileout.close(); inputstream.close(); } } this.zipfile.close(); } catch (ioexception ioe) { ioe.printstacktrace(); } } }
ant 实现批量打包android应用
由于公司运维需要以及应用中需要加上应用推广的统计,往往要对应二三十个渠道,按照正常方法一个一个的去生成不同渠道包的应用,不仅浪费了时间,而且大大降低了效率.
上一篇讲到使用ant进行zip/tar包的解压缩,实际上ant工具不仅仅具有此类功能,它更强大的地方在于自动化调用程序完成项目的编译,打包,测试等. 类似于c语言中的make脚本完成这些工作的批处理任务. 不同于makefile的是,ant是纯java编写的,因此具有很好的跨平台性.
在此我主要讲下如何自动构建工具ant, 对应用进行批量打包, 生成对应不同市场的应用:
首先分别看一下用于打包的java工程anttest和需要被打包进行发布的android工程结构:
market.txt里保存需要打包的市场标识,如:
youmeng gfan .......
此文件里自行根据需求添加渠道名称.
然后看一下实现批量打包anttest类中的内容:
注意:红色标注部分需要进行修改:
package com.cn.ant; import java.io.bufferedreader; import java.io.bufferedwriter; import java.io.file; import java.io.filereader; import java.io.filewriter; import java.io.ioexception; import java.text.simpledateformat; import java.util.calendar; import org.apache.tools.ant.defaultlogger; import org.apache.tools.ant.project; import org.apache.tools.ant.projecthelper; public class anttest { private project project; public void init(string _buildfile, string _basedir) throws exception { project = new project(); project.init(); defaultlogger consolelogger = new defaultlogger(); consolelogger.seterrorprintstream(system.err); consolelogger.setoutputprintstream(system.out); consolelogger.setmessageoutputlevel(project.msg_info); project.addbuildlistener(consolelogger); // set the base directory. if none is given, "." is used. if (_basedir == null) _basedir = new string("."); project.setbasedir(_basedir); if (_buildfile == null) _buildfile = new string(projectbasepath + file.separator + "build.xml"); // projecthelper.getprojecthelper().parse(project, new // file(_buildfile)); <span style="color:#ff0000;">// 关键代码</span> projecthelper.configureproject(project, new file(_buildfile)); } public void runtarget(string _target) throws exception { // test if the project exists if (project == null) throw new exception( "no target can be launched because the project has not been initialized. please call the 'init' method first !"); // if no target is specified, run the default one. if (_target == null) _target = project.getdefaulttarget(); // run the target project.executetarget(_target); } <span style="color:#ff0000;">private final static string projectbasepath = "d:\\android\\workspace3\\xxx";//要打包的项目根目录 private final static string copyapkpath = "d:\\android\\apktest";//保存打包apk的根目录 private final static string signapk = "xxx-release.apk";//这里的文件名必须是准确的项目名! private final static string renameapk = "xxx_";//重命名的项目名称前缀(地图项目不用改) private final static string placeholder = "@market@";//需要修改manifest文件的地方(占位符) </span> public static void main(string args[]) { long starttime = 0l; long endtime = 0l; long totaltime = 0l; calendar date = calendar.getinstance(); simpledateformat sdf = new simpledateformat("yyyy-mm-dd:hh:mm:ss"); try { system.out.println("---------ant批量自动化打包开始----------"); starttime = system.currenttimemillis(); date.settimeinmillis(starttime); system.out.println("开始时间为:" + sdf.format(date.gettime())); bufferedreader br = new bufferedreader(new filereader("market.txt")); string flag = null; while ((flag = br.readline()) != null) { // 先修改manifest文件:读取临时文件中的@market@修改为市场标识,然后写入manifest.xml中 string tempfilepath = projectbasepath + file.separator + "androidmanifest.xml.temp"; string filepath = projectbasepath + file.separator + "androidmanifest.xml"; write(filepath, read(tempfilepath, flag.trim())); // 执行打包命令 anttest mytest = new anttest(); mytest.init(projectbasepath + file.separator + "build.xml", projectbasepath); mytest.runtarget("clean"); mytest.runtarget("release"); // 打完包后执行重命名加拷贝操作 file file = new file(projectbasepath + file.separator + "bin" + file.separator + signapk);// bin目录下签名的apk文件 file renamefile = new file(copyapkpath + file.separator + renameapk + flag + ".apk"); boolean renametag = file.renameto(renamefile); system.out.println("rename------>"+renametag); system.out.println("file ------>"+file.getabsolutepath()); system.out.println("rename------>"+renamefile.getabsolutepath()); } system.out.println("---------ant批量自动化打包结束----------"); endtime = system.currenttimemillis(); date.settimeinmillis(endtime); system.out.println("结束时间为:" + sdf.format(date.gettime())); totaltime = endtime - starttime; system.out.println("耗费时间为:" + getbeapartdate(totaltime)); } catch (exception e) { e.printstacktrace(); system.out.println("---------ant批量自动化打包中发生异常----------"); endtime = system.currenttimemillis(); date.settimeinmillis(endtime); system.out.println("发生异常时间为:" + sdf.format(date.gettime())); totaltime = endtime - starttime; system.out.println("耗费时间为:" + getbeapartdate(totaltime)); } } /** * 根据所秒数,计算相差的时间并以**时**分**秒返回 * * @param d1 * @param d2 * @return */ public static string getbeapartdate(long m) { m = m / 1000; string beapartdate = ""; int nday = (int) m / (24 * 60 * 60); int nhour = (int) (m - nday * 24 * 60 * 60) / (60 * 60); int nminute = (int) (m - nday * 24 * 60 * 60 - nhour * 60 * 60) / 60; int nsecond = (int) m - nday * 24 * 60 * 60 - nhour * 60 * 60 - nminute * 60; beapartdate = nday + "天" + nhour + "小时" + nminute + "分" + nsecond + "秒"; return beapartdate; } public static string read(string filepath, string replacestr) { bufferedreader br = null; string line = null; stringbuffer buf = new stringbuffer(); try { // 根据文件路径创建缓冲输入流 br = new bufferedreader(new filereader(filepath)); // 循环读取文件的每一行, 对需要修改的行进行修改, 放入缓冲对象中 while ((line = br.readline()) != null) { // 此处根据实际需要修改某些行的内容 if (line.contains(placeholder)) { line = line.replace(placeholder, replacestr); buf.append(line); } else { buf.append(line); } buf.append(system.getproperty("line.separator")); } } catch (exception e) { e.printstacktrace(); } finally { // 关闭流 if (br != null) { try { br.close(); } catch (ioexception e) { br = null; } } } return buf.tostring(); } /** * 将内容回写到文件中 * * @param filepath * @param content */ public static void write(string filepath, string content) { bufferedwriter bw = null; try { // 根据文件路径创建缓冲输出流 bw = new bufferedwriter(new filewriter(filepath)); // 将内容写入文件中 bw.write(content); } catch (exception e) { e.printstacktrace(); } finally { // 关闭流 if (bw != null) { try { bw.close(); } catch (ioexception e) { bw = null; } } } } }
然后是android工程中需要进行修改的部分:
1. 修改local.properties中的sdk根目录:
sdk.dir=d:\\android\\android-sdk-windows-r17\\android-sdk-windows-r17
2. 修改ant.properties中签名文件的路径和密码(如果需要)
key.store=d:\\android\\mykeystore key.store.password=123456 key.alias=mykey key.alias.password=123456
3. 修改androidmanifest.xml.temp
拷贝androidmanifest.xml一份,命名为androidmanifest.xml.temp
将需要替换的地方改为占位符,需与打包工程anttest中的placeholder常量一致
如: <meta-data android:value="@market@" android:name="umeng_channel"/>
4. build.xml中:
<project name="xxx" default="help">,xxx必须为android工程名称.
如果机器没有配置过ant环境变量,可根据如下步骤进行配置:
ant环境变量设置:
windows下ant用到的环境变量主要有2个,ant_home 、path。
设置ant_home指向ant的安装目录。
设置方法:
ant_home = d:/apache_ant_1.7.0
将%ant_home%/bin; %ant_home%/lib添加到环境变量的path中。
设置方法:
path = %ant_home%/bin; %ant_home%/lib