一、前言->需求
最近公司的项目需要试上线,安卓包会放到多个渠道进行推广,玩家会进行下载安装登录,后台为了得到渠道包的下载使用数据,就会给每个渠道包加入了不同的渠道ID以便统计数据。那问题就来了,每出一个新版本的包,要快速生成几十上百个渠道包,那改怎么办,不可能手动去改ID再去生成吧,这样会很费时费力还容易出错,同时文件还要命名好。所以我就花了两天的时间开发这么个工具解决该问题。另外使其他人方便打包,所以需要增加http功能方便其他人来使用。
二、开始->思路构想
1、使用apktool d -s xxxx.apk 拆包可获得一个xxxx文件夹,里面可以得到明文的AndroidMenifest.xml文件。
2、找到对应的meta-data参数用正则匹配规则进行替换掉。
3、使用apktool b xxxx -o new_xxxx.apk 生成未签名的apk包,注意这个包还是不能安装在手机上的,需要签名才行。
4、使用jarsigner -keystore -keypass istormkj -storepass istormkj -signedjar new_xxxx_singed.apk new_xxxx.apk alise
5、步骤完成。
三、选择的语言与开发
因为时间比较急,所以就直接选择了Java语言进行开发,选择Java的原因是开发快,工具类库比较多,网上资料也相当的丰富,好了,开始编码。
先准备一个配置文件,文件包括:启动http端口,渠道包的版本号、应用ID、应用子ID、主渠道号、子渠道号,登录平台等,参数根据自己决定。如下图
<configuration> <option name="httpPort" value="8089"/> <option name="version" value="1.0.8"/> <option name="app_id" value="1039"/> <option name="appchi_id" value="10139"/> <option name="channel_id" value="1000124"/> <option name="channelchi_ids" value="10002102,10002106,10002108"/> <option name="login_env" value="test_113"/> </configuration>
上面的参数key命名建议与AndriodMenifest.xml文件中的Meta-data一致,方便写代码进行修改,我的AndriodMenifest.xml部分内容如下:
<meta-data android:name="app_id" android:value="1039"/> <meta-data android:name="appchi_id" android:value="10139"/> <meta-data android:name="channel_id" android:value="1000124"/> <meta-data android:name="channelchi_id" android:value="10002106"/> <meta-data android:name="login_env" android:value="cg_xz_wan_001"/>
启动服务后,通过http:127.0.0.1:8089/ 可以看到渠道方面的配置参数,如下,这里是可以进行打包(渠道包)和设置渠道参数的操作
============>打多渠道服务器<============ 一、使用方法: 1、查看配置:http://192.168.2.160:8089/ 2、开始打包:http://192.168.2.160:8089/?a=b 3、设置渠道:http://192.168.2.160:8089/?a=c&key=value&... ------------------------------------------------------------------------------------------------------------- {"appchi_id":"10139","httpPort":"8089","channelchi_ids":"10002102,10002106,10002108","version":"1.0.8","app_id":"1039","channel_id":"1000124","login_env":"test_113"}
请求 http:127.0.0.1:8089/?a=b 会进行打包 ,道具是进行解包,下面的代码是一个解包的类。
package gateway; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Timer; import java.util.TimerTask; import config.FileSystem; /** * * @author hzd * */ public class DecompressionApk extends TimerTask { private String serverDir; public DecompressionApk(String serverDir) { this.serverDir = serverDir; } @Override public void run() { String[] files = FileSystem.ls(serverDir); for(int i = 0; i < files.length; ++i) { String file = files[i]; if(file.endsWith("-debug.apk") || file.endsWith("-release.apk") ) // { FileSystem.delete(serverDir + "/" + file.substring(0,file.lastIndexOf("."))); String cmd = serverDir + "/apktool.bat d -s " + serverDir + "/" + file; Process p = null; try { p = Runtime.getRuntime().exec(cmd); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } BufferedReader in = new BufferedReader(new InputStreamReader( p.getInputStream())); String s = ""; String line = null; try { while ((line = in.readLine()) != null) { s += line + "\n"; } } catch (IOException e) { e.printStackTrace(); } System.out.println("cmd:========\n" + s); new Timer().schedule(new MakeChannelsAndUnsigneApk(serverDir, file),1000); break; } } System.out.println("========解压Apk完成============="); } }
解压完后,进行生成渠道用的AndroidMenifest.xml文件,把每个文件放到渠道号为名的文件夹中,然后现进行打未签名的apk名。
代码如下:
package gateway; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Timer; import java.util.TimerTask; import java.util.regex.Matcher; import java.util.regex.Pattern; import config.Config; import config.DateTime; import config.FileSystem; /** * * @author hzd * */ public class MakeChannelsAndUnsigneApk extends TimerTask { private String serverDir; private String file; public MakeChannelsAndUnsigneApk(String serverDir,String file) { this.serverDir = serverDir; this.file = file; } @Override public void run() { FileSystem.delete(serverDir + "/AllChannels"); FileSystem.delete(serverDir + "/UnSignePackages"); String[] files = FileSystem.ls(serverDir + "/SignedPackages/"); for(int i = 0; i < files.length; ++i) { String file = files[i]; if(file.endsWith(".apk")) { FileSystem.delete(serverDir + "/SignedPackages/" + file); } } String version = Config.getString("version").replaceAll("\\.",""); String app_id = Config.getString("app_id"); String appchi_id = Config.getString("appchi_id"); String channel_id = Config.getString("channel_id"); String channelchi_ids = Config.getString("channelchi_ids"); String[] channelChiIds = channelchi_ids.split(","); String xmlFile = serverDir + "/" + file.substring(0,file.lastIndexOf("."))+"/AndroidManifest.xml"; String xmlStr = FileSystem.read(xmlFile); String packname = "com.istorm.hllad"; Pattern pattern = Pattern.compile("com.istorm.\\w+ad"); Matcher m = pattern.matcher(xmlStr); while(m.find()) { packname = m.group(); break; } xmlStr = xmlStr.replaceFirst("android:name=\"app_id\" android:value=\"\\d{4,8}\"", "android:name=\"app_id\" android:value=\""+app_id+"\""); xmlStr = xmlStr.replaceFirst("android:name=\"appchi_id\" android:value=\"\\d{4,8}\"", "android:name=\"appchi_id\" android:value=\""+appchi_id+"\""); xmlStr = xmlStr.replaceFirst("android:name=\"channel_id\" android:value=\"\\d{4,8}\"", "android:name=\"channel_id\" android:value=\""+channel_id+"\""); for (int i = 0; i < channelChiIds.length; i++) { String channelchi_id = channelChiIds[i]; String newXmlStr = xmlStr.replaceFirst("android:name=\"channelchi_id\" android:value=\"\\d{8}\"", "android:name=\"channelchi_id\" android:value=\""+channelchi_id+"\""); String newXmlFileName = serverDir + "/AllChannels/"+ channelchi_id +"/AndroidManifest.xml"; FileSystem.write(newXmlFileName, newXmlStr, false); } HttpRequestHandler.pack_cur_counts = 0; HttpRequestHandler.pack_all_counts = channelChiIds.length; // 开启定时去打第一个包 for(int i = 0; i < channelChiIds.length; i++) { String channelchi_id = channelChiIds[i]; String subFileName = file.substring(0, file.lastIndexOf(".")); String newXmlFileName = serverDir + "/AllChannels/" + channelchi_id + "/AndroidManifest.xml"; String dstXmlFileName = serverDir + "/" + file.substring(0, file.lastIndexOf(".")) + "/AndroidManifest.xml"; FileSystem .write(dstXmlFileName, FileSystem.read(newXmlFileName), false); String outFileName = packname.substring(packname.lastIndexOf(".") + 1, packname.length()); outFileName = outFileName.substring(0, 3); if (subFileName.indexOf("debug") > -1) { outFileName += "_d_"; } else { outFileName += "_r_"; } outFileName += channelchi_id + "_" + version + "_" + DateTime.date("MMddHHmm") + ".apk"; String cmd = serverDir + "/apktool.bat b " + serverDir + "/" + file.substring(0, file.lastIndexOf(".")) + " -o " + serverDir + "/UnSignePackages/" + outFileName; String s = ""; try { Process p = Runtime.getRuntime().exec(cmd); BufferedReader in = new BufferedReader(new InputStreamReader( p.getInputStream())); String line = null; while ((line = in.readLine()) != null) { s += line + "\n"; } } catch (IOException e) { e.printStackTrace(); } System.out.println("cmd==" + s); System.out.println("===打未签名包:" + outFileName + "..............ok!"); new Timer().schedule(new MakeSingedApk(serverDir, outFileName),1000); } } }
最后就是要进行签包了,相关的代码如下:
package gateway; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Timer; import java.util.TimerTask; import config.FileSystem; /** * * @author hzd * */ public class DecompressionApk extends TimerTask { private String serverDir; public DecompressionApk(String serverDir) { this.serverDir = serverDir; } @Override public void run() { String[] files = FileSystem.ls(serverDir); for(int i = 0; i < files.length; ++i) { String file = files[i]; if(file.endsWith("-debug.apk") || file.endsWith("-release.apk") ) // { FileSystem.delete(serverDir + "/" + file.substring(0,file.lastIndexOf("."))); String cmd = serverDir + "/apktool.bat d -s " + serverDir + "/" + file; Process p = null; try { p = Runtime.getRuntime().exec(cmd); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } BufferedReader in = new BufferedReader(new InputStreamReader( p.getInputStream())); String s = ""; String line = null; try { while ((line = in.readLine()) != null) { s += line + "\n"; } } catch (IOException e) { e.printStackTrace(); } System.out.println("cmd:========\n" + s); new Timer().schedule(new MakeChannelsAndUnsigneApk(serverDir, file),1000); break; } } System.out.println("========解压Apk完成============="); } }
四、演示例子
需要代码的同学们可以加我QQ:296464231(注明加我原因)