tomcat+Gradle全自动打Android apk包方案

最近看到公司IOS的同事做了一个app打包工具给QA使用,极大的方便了QA的工作,也给开发节省了不少精力,不需要频繁的接收QA的要求给QA打包新app做测试,防止编程思路被打包这些琐事给打断。

为了编写方便和跨平台应用,我使用了网页版的交互方式,使用tomcat 8做服务器,这样可以让任意一台手机和电脑通过浏览器就可以轻松的打包然后收到相应的.app文件,界面大概是这个样子

主要的功能是这样的

1、可以自由切换分支,分支号通过下拉列表的形式显示在网页上

2、可以自由切换服务器环境,比如测试服,开发服,正式服等等,上图的staging,dev,live就属于服务器

3、可以自动拉取git分支最新代码

4、可以自动进行apk的签名,apk对齐

大体思路是这样:

1、关于切换分支

因为不同git分支的上的代码不一样,有些依赖库也不一样,通过git命令行直接克隆分支然后用gradle编译是不行的,因为直接克隆下来的代码,尤其是一些iml配置文件在本机是不能直接用的,而且有些依赖库是以com.google.xxx.xxx这样的方式写在gradle.xml里的,这些依赖库本身需要联网进行下载,另外不同的分支用的gradle版本也不一样,需要下载对应的gradle,所以使用一个git 仓库通过check分支来切换这种方式行不通。所以我使用的多个git仓库,每个git仓库放一个分支的代码,切换分支实际上就是通过切换不同的git仓库实现的。这样做之后,下载依赖库和重写iml文件就交给Android
Studio来进行,说白了就是克隆完一个分支之后,先用Android Studio先clean一遍,再用AS打一个apk,这样这个git仓库就和本机的配置契合,可以被命令行打包了。

2、关于切换环境

因为这个项目里服务器的ip地址写死在了.java文件里面,所以只要修改相关java文件里面的ip地址就可以实现服务器的切换。所以我在这里是将各个写有不同ip地址的java文件放在git仓库之外,当打包时根据要打包的环境动态替换项目目录中的.java文件实现环境的切换。

3、关于pull代码,签名,对齐,这些通过直接的git命令和gradle命令执行就好了

4、关于进度的提示:

因为gradle在编译的时候对CPU和内存的开销很大,所以一次只能有一个编译进程执行,所以我就把进度直接用一个静态保存了,获取进度直接获取这个静态的变量的值就行。

在web端就做的很简单,用http请求每隔两秒进行轮询,没有采用高大尚的socket通讯。

先来复习一下gradle相关的命令行指令吧

打包指令:

首先cd到项目根目录下,就是有gradlew.bat这个文件的那个目录,然后执行(gradle命令比Eclipse打包容易多了)

gradle bulid
或 gradlew build
或 gradlew clean build

制作签名文件指令:

keytool -genkeypair -alias mykeyName -keyalg RSA -validity 100 -keystore mydemo.keystore

myKeyName是生成签名文件的别名,非常重要,100是有效期,mydemo.keystore是签名文件的文件名,执行完这一条指令会让你输入一个密码,注意区别密码和前面“alias”(别名)的区别,不要搞混,当时我就搞混了然后浪费了很多时间,下面三张图显示了alias和password的在eclipse和Android Studio打包签名时的截图,帮助你区别alias和password

对apk进行签名:

jarsigner -verbose -keystore E:\QA\file\key -signedjar app-signed.apk -digestalg SHA1 -sigalg MD5withRSA app-release-unsigned.apk mkeyName

首先cd到存放gradle编译好的未签名的apk的目录下

E:\QA\file\key是签名文件的文件路径, app-signed.apk是签好名之后生成apk的文件名,app-release-unsigned.apk是当前文件夹下未签名apk的文件名 mkeyName是签名文件的别名,输入换行符后,控制台会提示你输入签名文件的密码,如果密码正确就会开始一个文件一个文件的进行签名

-digestalg SHA1 -sigalg MD5withRSA是非常重要的,我使用jdk1.8签名后Android 5.0以上机器可以跑,但是5.0一下的机器就装不上,网上说使用jdk1.6以上版本会出现这个签名问题,所以如果你的电脑上装的是jdk1.6以上版本,别忘了加上这句。

对签名好的文件进行对齐,传说对齐可以让安卓系统访问apk包里的资源更快,具体怎样没试过,我对齐apk以后发现apk的大小变大了

首先cd到签名好的apk文件的目录下

zipalign -f -v 4 app-signed.apk app-publish.apk

app-signed.apk是签名好的文件,app-publish.apk是对齐好后生成的新文件

代码时间:

首先是最核心的文件,也就是执行具体命令行指令的java文件

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.SequenceInputStream;

public class PackageNow {
	//记录每个分支git仓库的地址
	public static final String[] BRANCH_0 = {"E:\\QA\\master\\app3","master"};//第0个分支的相关信息,第一个是位置,第二个是分支号,第三个是编译器位置
	public static final String[] BRANCH_1 = {"E:\\QA\\branch1\\app3","QRAII-6916"};//第0个分支的相关信息,第一个是位置,第二个是分支号,第三个是编译器位置
	public static final String[] BRANCH_2 = {"E:\\QA\\branch2\\app3","QRAII-sprint8"};//第0个分支的相关信息,第一个是位置,第二个是分支号,第三个是编译器位置
	public static final String[][] BRANCHES = {BRANCH_0,BRANCH_1,BRANCH_2};

	public static final String GIT_ROOT = "http://username:[email protected]:7171/android/app3.git";//外网的git 地址

	public static final String APK_PATH = "\\app\\build\\outputs\\apk";//进入项目目录后,gradle编译完成后输出apk的目录
	//下面是编译完后生成apk的文件名
	public static final String APP_DEBUG = "app-debug.apk";//打了默认签名的apk
	public static final String APP_SIGNED = "app-signed.apk";//打了正式签名,但是没有4k对齐的apk名字
	public static final String APP_PUBLISH = "app-publish.apk";//打了正式签名,且对齐了的apk名字
	//用于签名apk的签名文件的路径
	public static final String KEY_PATH = "E:\\QA\\file\\key";

	//要根据不同服务器环境替换文件的路径
	public static final String LIVE_FILE = "E:\\QA\\file\\live\\ApplicationConfigurationEntity.java";//记录live环境的java文件的路径,准备用于替换
	public static final String STAGING_FILE = "E:\\QA\\file\\staging\\ApplicationConfigurationEntity.java";
	public static final String DEV_FILE = "E:\\QA\\file\\dev\\ApplicationConfigurationEntity.java";
	public static final String ALPHA1_FILE = "E:\\QA\\file\\alpha1\\ApplicationConfigurationEntity.java";
	public static final String ALPHA2_FILE = "E:\\QA\\file\\alpha2\\ApplicationConfigurationEntity.java";
	public static final String ALPHA3_FILE = "E:\\QA\\file\\alpha3\\ApplicationConfigurationEntity.java";
	public static final String ALPHA4_FILE = "E:\\QA\\file\\alpha4\\ApplicationConfigurationEntity.java";
	public static final String[] environmentNames = {"Staging","Dev","Live"};//获取每个环境的名字,用于给文件命名的,下标是环境的代号
	public static final String[] ENVIRONMENTS = {STAGING_FILE,DEV_FILE,LIVE_FILE};//要根据不同环境替换文件的路径
	public static final String ENV_FILE = "\\app\\src\\main\\java\\com\\xxxx\\xxxxx\\model\\ApplicationConfigurationEntity.java";//要根据环境不同来动态被的项目里替换的java文件

	public static final int totalCount = 997;//git编译控制台输出的总行数,用于判断进度
	public static String progress;//记录当前的打包进度
	public static boolean isPackaging = false;//记录带当前是否在打包,控制同一时刻只有一个打包进程,节省cpu,内存开销
	/**
	 * 根据分支号进行编译
	 * @param branch
	 * @return 返回值是命令行命令
	 */
	public static String[]  packageNow(int branch) {//开始打包
		return new String[]{
			"cd "+BRANCHES[branch][0],//cd 到项目目录
			BRANCHES[branch][0].charAt(0)+":",
			"gradlew build"//正式进行编译
		};
	};
	/**
	 * 对apk进行签名
	 * @param branch
	 * @return 返回值是命令行命令
	 */
	public static String[] signKey(int branch){
		return new String[]{
			"cd "+BRANCHES[branch][0]+APK_PATH,
			BRANCHES[branch][0].charAt(0)+":",
			"jarsigner -verbose -keystore "+KEY_PATH+" -signedjar "+APP_SIGNED+" app-release-unsigned.apk keyPassword",
			"imaginato"//这个是签名文件的密码
		};
	}
	/**
	 * 对app进行4k对齐
	 * @param branch
	 * @return 返回值是命令行命令
	 */
	public static String[] zipAlign(int branch){
		return new String[]{
				"cd "+BRANCHES[branch][0]+APK_PATH,
				BRANCHES[branch][0].charAt(0)+":",
				"zipalign -f -v 4 "+APP_SIGNED+" "+APP_PUBLISH,
			};
	}
	/**
	 * pull最新代码
	 * @param branch
	 * @return 返回值是命令行命令
	 */
	public static String[] gitPull(int branch){//pull 一个分支的代码
		return new String[]{
			"cd "+BRANCHES[branch][0],//cd 到项目目录
			BRANCHES[branch][0].charAt(0)+":",
			"git pull "+GIT_ROOT + " "+BRANCHES[branch][1]
		};
	};

	/**
	 * 执行一条命令行指令
	 * @param orders 命令行命令
	 * @param callBack 控制台每输出一条反馈,会调用一次回调
	 * @throws IOException
	 */
	public static void runOneRow(String[] orders,ReadLineCallBack callBack) throws IOException{
		Process process = Runtime.getRuntime ().exec ("cmd");
		SequenceInputStream sis = new SequenceInputStream (process.getInputStream (), process.getErrorStream ());
		// next command
		OutputStreamWriter osw = new OutputStreamWriter (process.getOutputStream ());
		InputStreamReader isr = new InputStreamReader (sis, "GBK");
		BufferedReader br = new BufferedReader (isr);
		BufferedWriter bw = new BufferedWriter (osw);
		for(String s : orders){
			System.out.println("待执行的语句是"+s);
			bw.write (s);
			bw.newLine ();
		}
		bw.flush ();
		bw.close ();
		osw.close ();
		// read
		String line = null;
		while (null != ( line = br.readLine () ))
		{
			System.out.println (line+"$$");
			callBack.readLine(line);
		}
		br.close ();
		isr.close ();
		process.destroy ();
	}
	/**
	 * 控制台每次返回文本后调用的回调接口
	 * @author Administrator
	 *
	 */
	public interface ReadLineCallBack{
		void readLine(String line);
	}

	/**
	 * 开始打包函数
	 * @param appLocation 打包完成将apk发送到哪去的文件路径
	 * @param branch //分支序号
	 * @param environment //服务器环境序号
	 * @param sign //是否进行自动签名,如果进行签名那么签名完会再执行一步对齐操作
	 * @throws IOException
	 */
	public static void buildPackage(String appLocation,int branch,int environment,final boolean sign) throws IOException{
		System.out.println("即将打包的分支号是"+branch);
		progress ="正在进行编译";

		final int[]rowCount_progress = {0,0};//第0个记录当前是第几行,第1个记录进度百分比
		File targetEnvFile = new File(BRANCHES[branch][0]+ENV_FILE);//工作空间里的环境配置文件
		File sourceEnvFile = new File(ENVIRONMENTS[environment]);//写好环境的外头的配置文件
		copyFile(sourceEnvFile, targetEnvFile);
		runOneRow(packageNow(branch), new ReadLineCallBack() {

			public void readLine(String line) {
				// TODO Auto-generated method stub
				rowCount_progress[0]++;
				if(!progress.equals("编译完成") &&!progress.equals("error:编译失败"))progress = "Progress:"+String.valueOf(Math.round((float)rowCount_progress[0]/(float)totalCount*100)+"%");
				if(line.startsWith(":")||line.startsWith("Reading")||line.startsWith("Note"))return;
				if(line.startsWith("BUILD SUCCESSFUL")){
					if(!sign)isPackaging = false;
					System.out.println("编译成功啦");
					progress = "编译完成";//如果不需要签名,那么现在已经成功了
				}else if(line.startsWith("BUILD FAILED")){
					progress = "error:编译失败";
					isPackaging = false;
				}
			}
		});
		if(!progress.equals("编译完成")){
			System.out.println("没有编译成功"+progress);
			progress = "error:没有编译成功";
			return;
		}
		System.out.println("一共有"+rowCount_progress[0]+"行");
		if(sign){//如果需要签名
			System.out.println("准备进行签名"+BRANCHES[branch][0]);
			runOneRow(signKey(branch), new ReadLineCallBack() {

				public void readLine(String line) {
					// TODO Auto-generated method stub
					if(!line.startsWith("jar 已签名"))return;
					System.out.println("签名成功");
					progress = "签名成功!!";
				}
			});
			if(!progress.equals("签名成功!!")){//检测签名是否添加成功
				progress = "error:签名失败!!!";
				return;
			}
			progress = "正在进行apk对齐";
			//准备进行4k对齐
			runOneRow(zipAlign(branch), new ReadLineCallBack() {

				public void readLine(String line) {
					// TODO Auto-generated method stub
					if(line.startsWith("Unable to open")){
						System.out.println("4k对齐失败");
						progress = "4k对齐失败!!!";
						return;
					}
					if(!line.equals("Verification succesful"))return;
					System.out.println("4k对齐成功");
					progress = "4k对齐成功";
				}
			});
			if(!progress.equals("4k对齐成功")){//检测4k对齐是否成功
				progress = "error:4k对齐失败";
				return;
			}
		}
		progress = "正在准备传送文件";
		File sourceFile = new File(BRANCHES[branch][0]+APK_PATH+"\\"+(sign?APP_PUBLISH:APP_DEBUG));//需要签名和不需要签名给出的带key的app名字不一样
		System.out.println("源文件"+sourceFile.getAbsolutePath()+sourceFile.exists());
		System.out.println("源文件的大小是"+sourceFile.length());
		if(sourceFile.exists() && sourceFile.isFile() && sourceFile.length()>100000){
			File targetFile = new File(appLocation);
			if(copyFile(sourceFile, targetFile))System.out.println("文件复制成功"+targetFile.length());
			progress = "succeed";//在这里算彻底的成功
		}else {
			progress = "error:失败";
		}
		isPackaging = false;
	}

	/**
	 * 一个简单的复制文件函数
	 * @param sourceFile
	 * @param targetFile
	 * @return
	 */
	public static boolean copyFile(File sourceFile,File targetFile){
		long beginTime = System.currentTimeMillis();

		if(sourceFile==null||targetFile==null)return false;
		System.out.println("目标地址"+targetFile.getAbsolutePath());
		try {
			if(!targetFile.exists()){//如果目标文件不存在就新建
				targetFile.createNewFile();
			}else {//如果目标文件存在就删除,然后新建一个
				targetFile.delete();
				targetFile.createNewFile();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        FileInputStream fis;
        FileOutputStream fos;
		try {
			fis = new FileInputStream(sourceFile);
			fos = new FileOutputStream(targetFile);
		} catch (FileNotFoundException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			return false;
		}
        byte[] b = new byte[1024];
        int len = 0;
        try {
	        while ((len = fis.read(b)) != -1) {
				fos.write(b, 0, len);
	        }
        } catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
        try {
			fos.flush();
			fis.close();
	        fos.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
        long endTime = System.currentTimeMillis();
        System.out.println("采用传统IO FileInputStream 读取,耗时:"+ (endTime - beginTime));
        return true;
	}

}

调用打包函数是通过html发送一个ajax请求,由servlet调用java代码控制命令行进行打包,下面给出servlet的代码。这里在一个打包请求到达后,先检测是否由用户正在打包,如果有人正在打包,就返回一个错误,如果可以打包的话,根据系统当前时间计算一个文件名,并将文件名发送给客户端,供客户端在打包完毕之后下载

import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.imaginato.tools.PackageNow;

/**
 * Servlet implementation class PackageOL
 */
public class PackageOL extends HttpServlet {
	private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public PackageOL() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		System.out.println("有人访问在线打包servlet"+PackageNow.isPackaging);
		int branch = 0;//分支选择
		int environment = 0;//环境选择
		boolean needSign = false;
		try {
			branch = new Integer(request.getParameter("branch"));
			environment = new Integer(request.getParameter("environment"));
			if(request.getParameter("needSign")!=null && request.getParameter("needSign").length()>0)needSign = true;
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			System.out.println("没有传来分支号!!!");
			return;
		}
		System.out.println("当前选择分支"+branch+"  环境"+environment+"  是否签名"+needSign);

		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
		String fileName = "AppII"+PackageNow.environmentNames[environment]+"_"+dateFormat.format(new Date(System.currentTimeMillis())).concat(".apk");//QravedIIStaging_20160508.apk
		String appLocation = getServletConfig().getServletContext().getRealPath("/").concat("release\\").concat(fileName);//将最终要给客户的文件的服务器路径发给打包类,让打包类打包完以后将文件复制到这个目录下

		PrintWriter out = response.getWriter();
		if(PackageNow.isPackaging){
			System.out.println("现在正在打包");
			out.write("packaging");
			out.flush();
			out.close();
			return;
		}else {
			out.write(fileName);//将准备放过去的文件名返回
		}
		out.flush();
		out.close();
		PackageNow.isPackaging = true;
		PackageNow.buildPackage(appLocation,branch,environment,needSign);
	}

}

下面贴出web页面的HTML代码,里面包含了布局和ajax请求,这里发给服务器的分支号是发的序号,环境也是序号,这个序号就是上面PackageNow.java里面静态字符串数组的下标

<!DOCTYPE html>
<html>
  <head>
    <title>index.html</title>

    <meta name="keywords" content="keyword1,keyword2,keyword3">
    <meta name="description" content="this is my page">
    <meta name="content-type" content="text/html"; charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script src="./js/bootstrap.min.js"></script>
    <link rel="stylesheet" type="text/css" href="./css/bootstrap.min.css">-->

  </head>

  <body>
<div class="container-fluid">    

	<div class="jumbotron">
	  <h1>欢迎使用Qraved Android HTML5 自动打包工具!</h1>
	  <p></p>
	  <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
	</div>
	<div style="margin-left: auto;margin-right: auto">
   <div class="checkbox">
    <label>
      <input id = "autoSign" type="checkbox"> 自动签名
    </label>
  </div>
    <div class="dropdown">
	  <button id=currentBranch class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
	    	当前分支:sprint8
	  </button>
	  <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
	    <li class=branch  code=0><a href="#">master</a></li>
	    <li class=branch  code=1><a href="#">sprint7</a></li>
	    <li class=branch  code=2><a href="#">sprint8</a></li>
	  </ul>
	</div>

	<div class="btn-group" data-toggle="buttons">
	  <label class="btn btn-primary active env"code="0">
	    <input type="radio" name="options" id="option1"  autocomplete="off" checked> staging
	  </label>
	  <label class="btn btn-primary env"code="1">
	    <input type="radio" name="options" id="option2" autocomplete="off"> dev
	  </label>
	  <label class="btn btn-primary env" code="6">
	    <input type="radio" name="options" id="option4" autocomplete="off"> live
	  </label>
	</div>

	<div>
    	<span id="process">准备打包</span>
    </div>
    <div>
		<button type="button" class="btn btn-primary" id=pull>拉取最新代码</button>
		<button type="button" class="btn btn-success" id="startPackage">开始打包</button>
		<button type="button" class="btn btn-info" id="download">下载apk</button>
  	</div>
  	</div>
  </div>
  </body>
  <script type="text/javascript">
  var showDialog = false;//这个是当前页面是否已经显示过一遍打板成功
  var choosedBranch = "2";//这个记录当前选定的分支号,有三种选择,0,1,2,默认为2
  var environment = "0";
  var autoSign = false;
  var isPulling = false;
  	$("#startPackage").click(function(){
  		console.log("点击了开始打包");
  		$.get("./PackageOL", "branch="+choosedBranch+"&environment="+environment+(autoSign?"&needSign=true":""), function(data, textStatus, req) {
  			console.log("发来的data=="+data);
  			if(data=="packaging"){
  				$("#process").text("正在打包中,请稍等");
  				window.alert("当前有其他用户正在打板,请稍候")
  				return;
  			}else if(data=="0"){
  				$("#process").text("准备就绪");
  			}else{
  				$("#process").text("开始打包");
  				showDialog = false;
  				$("#download").unbind("click").click(function() {
			  		console.log("点击了下载");
			  		window.open("./release/"+data);//正常的话,返回的data是文件名
  				});
  			}
  		});
  		var task = window.setInterval(function() {
  			console.log("发送获取进度的请求");
  			$.get("./PackageProcess", "", function(data, textStatus, req) {
  				console.log("ajax返回data是"+data);
  				if(data=="succeed"){
  					$("#process").text("打包成功,请点击下载");
  					if(showDialog==false){
  						window.alert("打包成功,请点击下载");
  						showDialog = true;
  					}
  					window.clearInterval(task);
  					return;
  				}else{
  					if(data.substr(0, 5)=="error"){
  						window.clearInterval(task);
  						showDialog = true;
  						window.alert("打包失败,原因"+data);
  					}
  					$("#process").text(data);
  				}
  			});
  		}, 2500);
  	});
  	//下面是控制选择分支的下啦菜单
	$(".branch").click(function() {
		var li = this;
		$("#currentBranch").text("当前分支:"+$(li).text());
		choosedBranch = $(li).attr("code");
		console.log("当前选定"+choosedBranch);
	});
  	$("#pull").click(function() {
  		console.log("准备pull最新代码"+choosedBranch);
  		if(isPulling){
  			window.alert("上次代码还没有pull完");
  			return;
  		}
  		$("#process").text("正在拉取代码,请稍等");
  		isPulling = true;
  		$.get("./PullCodeServlet", "branch="+choosedBranch, function(data, textStatus, req) {
  			console.log("pull代码的data是"+data);
  			if(data=="succeed")window.alert("pull 代码成功");
  			else window.alert("pull 代码失败");
  			$("#process").text("代码拉取完毕");
  			isPulling = false;
  		});
  	});
  	$(".env").click(function() {
  		var button = this;
  		environment = $(button).attr("code");
  		console.log("environment是"+environment);
  	});
  	$("#autoSign").click(function() {
  		autoSign = !autoSign;
  	});

  </script>
</html>

还有拉取代码,观察进度的servlet,限于篇幅这里就先不贴了

时间: 2024-10-12 08:26:27

tomcat+Gradle全自动打Android apk包方案的相关文章

Gradle实战:Android多渠道打包方案汇总

查看原文:http://blog.csdn.net/u010818425/article/details/52319382 Gradle实战系列文章: <Gradle基本知识点与常用配置> <Gradle实战:不同编译类型的包同设备共存> <Gradle实战:发布aar包到maven仓库> <Gradle实战:执行sql操作hive数据库> 本文将延续之前几篇博客的风格,先从基本概念入手,这有助于我们对后文的理解: 在后续的代码中如果忘了某个概念的具体意义,

理解使用Gradle编译打包Android apk

本篇的目的:理解Gradle构建过程,解读Android Gradle插件的配置 阅读本文一定是要使用过Gradle生成apk,文中不会讲如何安装运行Gradle,如有需要可先看文末的参考文章. APK包是一个ZIP压缩包,从Java源代码.资源文件到生成这个APK,经过了编译打包一系列特定的过程,这个过程可以参看<使用Ant打包Android应用--apk生成过程>,也可以从自己的旧版SDK文档(/docs/tools/building/index.html)中找到.而这一系列特定的过程,重

Gradle实战:发布aar包到maven仓库

查看原文:http://blog.csdn.net/u010818425/article/details/52441711 Gradle实战系列文章: <Gradle基本知识点与常用配置> <Gradle实战:Android多渠道打包方案汇总> <Gradle实战:不同编译类型的包同设备共存> <Gradle实战:执行sql操作hive数据库> aar简介 aar文件是Google为Android开发所设计的一种library格式,全名为Android Ar

Android减包 - 减少APK大小

本文是对Google官方文档 Reduce APK Size 的翻译,点击"阅读原文"可以查看英文原文. 译者简介:damonxia(夏正冬),天天P图Android工程师 用户经常会避免下载看起来体积较大的应用,特别是在不稳定的2G.3G网络或者在以字节付费的网络.这篇文章描述了怎样减少你的APK大小,这会让更多的用户愿意下载你的应用. 理解APK的结构 在讨论怎样减少应用大小之前,先了解APK的结构是有用的.一个APK文件就是ZIP包,其中包含了组成你的应用的所有文件,比如Java

Android APK加固技术方案调研

@author ASCE1885的 Github 简书 微博 CSDN 最近项目中需要实现自己的APK加固方案,因此就有了这一篇调研报告. 软件安全领域的攻防向来是道高一尺魔高一丈,攻防双方都处于不断的演变和进化过程中,因此软件加固技术需要长期持续的研究与投入. 目前成熟的第三方解决方案 1. 娜迦 针对Android平台下的APP被逆向分析,破解,植入木马病毒后,用户敏感信息泄露或者被钓鱼网站劫持,NAGA Android保护采用防止静态分析与防止动态调试全面防护的思路,在未保护程序运行的不同

Android 项目利用 Android Studio 和 Gradle 打包多版本APK

在项目开发过程中,经常会有需要打包不同版本的 APK 的需求. 比如 debug版,release版,dev版等等. 有时候不同的版本中使用到的不同的服务端api域名也不相同. 比如 debug_api.com,release_api.com,dev_api.com等等. 不同的版本对应了不同的 api 域名,还可能对应不同的 icon 等. 如果每次都在打包前修改我们都手动来修改,这样实在是不够方便. 但如果我们使用了 Android Studio 和 Gradle,这个麻烦就可以轻松省去.

手动打Android Wear Apk包

如果使用Google官方推荐的集成开发IDE Android Studio开发,会自动生成两个工程,一个是手机端的,一个是手表端的. 但是如果用Eclipse环境,如果要将手表的apk打到手机apk中,需要注意以下几点: 1.在wearable app中声明的permission在mobile app中也要加上: 2.确保wearable app和mobile app有同样的包名和版本号: 3.将签名的wearable app放到mobile app工程的res/raw目录下,假设wearabl

Android 4.4(KitKat)中apk包的安装过程

原文地址:http://blog.csdn.net/jinzhuojun/article/details/25542011 其实对于apk包的安装,4.4和之前版本没大的差别.Android中app安装主要有以下几种情况:系统启动时安装,adb命令安装,Google Play上下载安装和通过PackageInstaller安装.安装的最核心方法是scanPackageLI(),以上几个安装方式最后都是调用这个函数完成主要工作的,区别在于在此之前的处理过程不同.本文以前两种为主,简要介绍这四种安装

Android APK及导出JAR包的代码混淆

像Android开发基于java语言的,很容易被别人反编译出来,一下就相当于裸奔了,特别是用于商业用途的时候,防止反编译是必要的措施.而代码混淆是一种很好防止反编译的方式. 1.APK的代码混淆. 在eclipse的开发环境下,使用android SDK 自带的proguard混淆工具. 主要用到红线框中的两个文件. 在project.properties文件中,要指定混淆的文件.如图: 然后,详细的proguard-project.txt的写法如下: -dontwarn 是让指定包名不提示警告