使用Ant 實現批量打包Android應用

由於公司運維需要以及應用中需要加上應用推廣的統計,往往要對應二三十個渠道,按照正常方法一個一個的去生成不同渠道包的應用,不僅浪費瞭時間,而且大大降低瞭效率.

上一篇講到使用Ant進行Zip/Tar包的解壓縮,實際上Ant工具不僅僅具有此類功能,它更強大的地方在於自動化調用程序完成項目的編譯,打包,測試等. 類似於C語言中的make腳本完成這些工作的批處理任務. 不同於MakeFile的是,Ant是純Java編寫的,因此具有很好的跨平臺性.

 

在此我主要講下如何自動構建工具Ant, 對應用進行批量打包, 生成對應不同市場的應用:

 

首先分別看一下用於打包的Java工程AntTest和需要被打包進行發佈的Android工程結構:

 

 

 

 

 

market.txt裡保存需要打包的市場標識,如:

youmeng

gfan

…….

此文件裡自行根據需求添加渠道名稱.

 

然後看一下實現批量打包AntTest類中的內容:

註意:紅色標註部分需要進行修改:

 

 

 

[java]
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; 
                } 
            } 
        } 
    } 

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));
  // 關鍵代碼
  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);

 }

 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文件的地方(占位符)

 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

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *