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