实现zip/tar的压缩与解压
java中实际是提供了对 zip等压缩格式的支持,但是为什么这里会用到ant呢?
原因主要有两个:
1. java提供的类对于包括有中文字符的路径,文件名支持不够好,你用其它第三方软件解压的时候就会存在乱码。而ant.jar就支持文件名或者路径包括中文字符。
2. ant.jar提供了强大的工具类,更加方便于我们对压缩与解压的操作。
注意事项:
1. 首先说明一下,关于皮肤或者类似于皮肤的zip包,实际上公司可能会根据自己的规定或需求,自定义压缩包文件的结尾,实际上大多还是zip包的格式. 具体部分的处理大致上是一样的,因此不再复述, 本文给出的例子已经有zip包和tar包的解压缩.
2. 还有要注意的是,此处为提升理解,因此加入zip/tar压缩,解压的界面,实际应用中此部分无需单独的界面展示(解压缩需要一定时间的话,则为加强用户体验,加入提示框与进度条),请自行编写解压缩管理类进行逻辑判断分别处理.
3. 测试时需要讲要解压缩的包导入sdcard目录下(若为其他目录,请修改代码中路径)
程序主界面及解压缩的界面:
接下来是解压缩核心的代码:
布局文件: antzip.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
<?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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
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里保存需要打包的市场标识,如:
1
2
3
|
youmeng gfan ....... |
此文件里自行根据需求添加渠道名称.
然后看一下实现批量打包anttest类中的内容:
注意:红色标注部分需要进行修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
|
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根目录:
1
|
sdk.dir=d:\\android\\android-sdk-windows-r17\\android-sdk-windows-r17 |
2. 修改ant.properties中签名文件的路径和密码(如果需要)
1
2
3
4
|
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的安装目录。
设置方法:
1
|
ant_home = d:/apache_ant_1. 7.0 |
将%ant_home%/bin; %ant_home%/lib添加到环境变量的path中。
设置方法:
1
|
path = %ant_home%/bin; %ant_home%/lib |