實(shí)現(xiàn)zip/tar的壓縮與解壓
java中實(shí)際是提供了對(duì) zip等壓縮格式的支持,但是為什么這里會(huì)用到ant呢?
原因主要有兩個(gè):
1. java提供的類(lèi)對(duì)于包括有中文字符的路徑,文件名支持不夠好,你用其它第三方軟件解壓的時(shí)候就會(huì)存在亂碼。而ant.jar就支持文件名或者路徑包括中文字符。
2. ant.jar提供了強(qiáng)大的工具類(lèi),更加方便于我們對(duì)壓縮與解壓的操作。
注意事項(xiàng):
1. 首先說(shuō)明一下,關(guān)于皮膚或者類(lèi)似于皮膚的zip包,實(shí)際上公司可能會(huì)根據(jù)自己的規(guī)定或需求,自定義壓縮包文件的結(jié)尾,實(shí)際上大多還是zip包的格式. 具體部分的處理大致上是一樣的,因此不再?gòu)?fù)述, 本文給出的例子已經(jīng)有zip包和tar包的解壓縮.
2. 還有要注意的是,此處為提升理解,因此加入zip/tar壓縮,解壓的界面,實(shí)際應(yīng)用中此部分無(wú)需單獨(dú)的界面展示(解壓縮需要一定時(shí)間的話(huà),則為加強(qiáng)用戶(hù)體驗(yàn),加入提示框與進(jìn)度條),請(qǐng)自行編寫(xiě)解壓縮管理類(lèi)進(jìn)行邏輯判斷分別處理.
3. 測(cè)試時(shí)需要講要解壓縮的包導(dǎo)入sdcard目錄下(若為其他目錄,請(qǐng)修改代碼中路徑)
程序主界面及解壓縮的界面:
接下來(lái)是解壓縮核心的代碼:
布局文件: 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) { //進(jìn)入壓縮界面 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) { //進(jìn)入解壓界面 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); //設(shè)置一些默認(rèn)的函數(shù) 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 , "請(qǐng)指定一個(gè)路徑" , 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)) { //如果用戶(hù)沒(méi)有輸入目標(biāo)文件,則生成一個(gè)默認(rèn)的 zipdest = srcfile.getparent(); } system.out.println( "zip name:" +zipdest); //如果是以/結(jié)尾的,則證明用戶(hù)輸入的是一個(gè)目錄 ,需要在后面加上文件名 if (zipdest.endswith(file.separator)) { zipdest+=srcfile.getname()+suffix; } else { //如果壓縮文件名不是以zip/tar結(jié)尾,則加上后綴后 if (!zipdest.endswith(suffix)) { zipdest +=suffix; } } //如果用戶(hù)選擇的是zip,則用 ziputil進(jìn)行壓縮 if (type == antzipactivity.type_zip) { ziputil zipp = new ziputil(); zipp.dozip(srcpath, zipdest); } //如果用戶(hù)選擇的是tar,則用 tarutil進(jìn)行壓縮 else { tarutil tarr = new tarutil(); tarr.dotar(srcpath, zipdest); } //壓縮完成后還是提示用戶(hù) tvtip.settext( "壓縮文件路徑:" +zipdest); toast.maketext( this , "壓縮完成" , toast.length_short).show(); } } |
解壓縮工具類(lèi)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(){ //要構(gòu)造函數(shù)中去初始化我們的緩沖區(qū) this .bufsize = 1024 * 4 ; this .buf = new byte [ this .bufsize]; } /** * 對(duì)傳入的目錄或者是文件進(jìn)行壓縮 * @param srcfile 需要 壓縮的目錄或者文件 * @param destfile 壓縮文件的路徑 */ public void dozip(string srcfile, string destfile) { // zipdirectorypath:需要壓縮的文件夾名 file zipfile = new file(srcfile); try { //生成zipoutputstream,會(huì)把壓縮的內(nèi)容全都通過(guò)這個(gè)輸出流輸出,最后寫(xiě)到壓縮文件中去 this .zipout = new zipoutputstream( new bufferedoutputstream( new fileoutputstream(destfile))); //設(shè)置壓縮的注釋 zipout.setcomment( "comment" ); //設(shè)置壓縮的編碼,如果要壓縮的路徑中有中文,就用下面的編碼 zipout.setencoding( "gbk" ); //啟用壓縮 zipout.setmethod(zipoutputstream.deflated); //壓縮級(jí)別為最強(qiáng)壓縮,但時(shí)間要花得多一點(diǎn) zipout.setlevel(deflater.best_compression); handlefile(zipfile, this .zipout, "" ); //處理完成后關(guān)閉我們的輸出流 this .zipout.close(); } catch (ioexception ioe) { ioe.printstacktrace(); } } /** * 由dozip調(diào)用,遞歸完成目錄文件讀取 * @param zipfile * @param zipout * @param dirname 這個(gè)主要是用來(lái)記錄壓縮文件的一個(gè)目錄層次結(jié)構(gòu)的 * @throws ioexception */ private void handlefile(file zipfile, zipoutputstream zipout,string dirname) throws ioexception { system.out.println( "遍歷文件:" +zipfile.getname()); //如果是一個(gè)目錄,則遍歷 if (zipfile.isdirectory()) { file[] files = zipfile.listfiles(); if (files.length == 0 ) { // 如果目錄為空,則單獨(dú)創(chuàng)建之. //只是放入了空目錄的名字 this .zipout.putnextentry( new zipentry(dirname+zipfile.getname()+file.separator)); this .zipout.closeentry(); } else { // 如果目錄不為空,則進(jìn)入遞歸,處理下一級(jí)文件 for (file file : files) { // 進(jìn)入遞歸,處理下一級(jí)的文件 handlefile(file, zipout, dirname+zipfile.getname()+file.separator); } } } //如果是文件,則直接壓縮 else { fileinputstream filein = new fileinputstream(zipfile); //放入一個(gè)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); } //關(guān)閉zipentry,完成一個(gè)文件的壓縮 this .zipout.closeentry(); } } /** * 解壓指定zip文件 * @param unzipfile 壓縮文件的路徑 * @param destfile 解壓到的目錄 */ public void unzip(string unzipfile, string destfile) { // unzipfilename需要解壓的zip文件名 fileoutputstream fileout; file file; inputstream inputstream; try { //生成一個(gè)zip的文件 this .zipfile = new zipfile(unzipfile); //遍歷zipfile中所有的實(shí)體,并把他們解壓出來(lái) for ( @suppresswarnings ( "unchecked" ) enumeration<zipentry> entries = this .zipfile.getentries(); entries .hasmoreelements();) { zipentry entry = entries.nextelement(); //生成他們解壓后的一個(gè)文件 file = new file(destfile+file.separator+entry.getname()); if (entry.isdirectory()) { file.mkdirs(); } else { // 如果指定文件的目錄不存在,則創(chuàng)建之. file parent = file.getparentfile(); if (!parent.exists()) { parent.mkdirs(); } //獲取出該壓縮實(shí)體的輸入流 inputstream = zipfile.getinputstream(entry); fileout = new fileoutputstream(file); int length = 0 ; //將實(shí)體寫(xiě)到本地文件中去 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 實(shí)現(xiàn)批量打包android應(yīng)用
由于公司運(yùn)維需要以及應(yīng)用中需要加上應(yīng)用推廣的統(tǒng)計(jì),往往要對(duì)應(yīng)二三十個(gè)渠道,按照正常方法一個(gè)一個(gè)的去生成不同渠道包的應(yīng)用,不僅浪費(fèi)了時(shí)間,而且大大降低了效率.
上一篇講到使用ant進(jìn)行zip/tar包的解壓縮,實(shí)際上ant工具不僅僅具有此類(lèi)功能,它更強(qiáng)大的地方在于自動(dòng)化調(diào)用程序完成項(xiàng)目的編譯,打包,測(cè)試等. 類(lèi)似于c語(yǔ)言中的make腳本完成這些工作的批處理任務(wù). 不同于makefile的是,ant是純java編寫(xiě)的,因此具有很好的跨平臺(tái)性.
在此我主要講下如何自動(dòng)構(gòu)建工具ant, 對(duì)應(yīng)用進(jìn)行批量打包, 生成對(duì)應(yīng)不同市場(chǎng)的應(yīng)用:
首先分別看一下用于打包的java工程anttest和需要被打包進(jìn)行發(fā)布的android工程結(jié)構(gòu):
market.txt里保存需要打包的市場(chǎng)標(biāo)識(shí),如:
1
2
3
|
youmeng gfan ....... |
此文件里自行根據(jù)需求添加渠道名稱(chēng).
然后看一下實(shí)現(xiàn)批量打包anttest類(lèi)中的內(nèi)容:
注意:紅色標(biāo)注部分需要進(jìn)行修改:
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;" > // 關(guān)鍵代碼</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" ; //要打包的項(xiàng)目根目錄 private final static string copyapkpath = "d:\\android\\apktest" ; //保存打包apk的根目錄 private final static string signapk = "xxx-release.apk" ; //這里的文件名必須是準(zhǔn)確的項(xiàng)目名! private final static string renameapk = "xxx_" ; //重命名的項(xiàng)目名稱(chēng)前綴(地圖項(xiàng)目不用改) 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批量自動(dòng)化打包開(kāi)始----------" ); starttime = system.currenttimemillis(); date.settimeinmillis(starttime); system.out.println( "開(kāi)始時(shí)間為:" + sdf.format(date.gettime())); bufferedreader br = new bufferedreader( new filereader( "market.txt" )); string flag = null ; while ((flag = br.readline()) != null ) { // 先修改manifest文件:讀取臨時(shí)文件中的@market@修改為市場(chǎng)標(biāo)識(shí),然后寫(xiě)入manifest.xml中 string tempfilepath = projectbasepath + file.separator + "androidmanifest.xml.temp" ; string filepath = projectbasepath + file.separator + "androidmanifest.xml" ; write(filepath, read(tempfilepath, flag.trim())); // 執(zhí)行打包命令 anttest mytest = new anttest(); mytest.init(projectbasepath + file.separator + "build.xml" , projectbasepath); mytest.runtarget( "clean" ); mytest.runtarget( "release" ); // 打完包后執(zhí)行重命名加拷貝操作 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批量自動(dòng)化打包結(jié)束----------" ); endtime = system.currenttimemillis(); date.settimeinmillis(endtime); system.out.println( "結(jié)束時(shí)間為:" + sdf.format(date.gettime())); totaltime = endtime - starttime; system.out.println( "耗費(fèi)時(shí)間為:" + getbeapartdate(totaltime)); } catch (exception e) { e.printstacktrace(); system.out.println( "---------ant批量自動(dòng)化打包中發(fā)生異常----------" ); endtime = system.currenttimemillis(); date.settimeinmillis(endtime); system.out.println( "發(fā)生異常時(shí)間為:" + sdf.format(date.gettime())); totaltime = endtime - starttime; system.out.println( "耗費(fèi)時(shí)間為:" + getbeapartdate(totaltime)); } } /** * 根據(jù)所秒數(shù),計(jì)算相差的時(shí)間并以**時(shí)**分**秒返回 * * @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 + "小時(shí)" + nminute + "分" + nsecond + "秒" ; return beapartdate; } public static string read(string filepath, string replacestr) { bufferedreader br = null ; string line = null ; stringbuffer buf = new stringbuffer(); try { // 根據(jù)文件路徑創(chuàng)建緩沖輸入流 br = new bufferedreader( new filereader(filepath)); // 循環(huán)讀取文件的每一行, 對(duì)需要修改的行進(jìn)行修改, 放入緩沖對(duì)象中 while ((line = br.readline()) != null ) { // 此處根據(jù)實(shí)際需要修改某些行的內(nèi)容 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 { // 關(guān)閉流 if (br != null ) { try { br.close(); } catch (ioexception e) { br = null ; } } } return buf.tostring(); } /** * 將內(nèi)容回寫(xiě)到文件中 * * @param filepath * @param content */ public static void write(string filepath, string content) { bufferedwriter bw = null ; try { // 根據(jù)文件路徑創(chuàng)建緩沖輸出流 bw = new bufferedwriter( new filewriter(filepath)); // 將內(nèi)容寫(xiě)入文件中 bw.write(content); } catch (exception e) { e.printstacktrace(); } finally { // 關(guān)閉流 if (bw != null ) { try { bw.close(); } catch (ioexception e) { bw = null ; } } } } } |
然后是android工程中需要進(jìn)行修改的部分:
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工程名稱(chēng).
如果機(jī)器沒(méi)有配置過(guò)ant環(huán)境變量,可根據(jù)如下步驟進(jìn)行配置:
ant環(huán)境變量設(shè)置:
windows下ant用到的環(huán)境變量主要有2個(gè),ant_home 、path。
設(shè)置ant_home指向ant的安裝目錄。
設(shè)置方法:
1
|
ant_home = d:/apache_ant_1. 7.0 |
將%ant_home%/bin; %ant_home%/lib添加到環(huán)境變量的path中。
設(shè)置方法:
1
|
path = %ant_home%/bin; %ant_home%/lib |