http://blog.csdn.net/tibib/article/details/8811759
前几天接到个需求,如何根据一个基础的Android App来生成100个或更多的App,要求App icon和App name都不一样(可能还会有配置文件)。这个有点类似于为App贴上自己的标签,但具体功能由别人提供,有点类似于OEM,下面来分析下如何实现
仔细想一下其实这个就是apk的编译和反编译的应用,再加上个签名(不签名的话无法使用)。只不过是用代码实现罢了
准备工作
1、配置好Java开发环境
2、下载google提供的apk编译和反编译工具 (包含apktool.jar、apktool.bat、aapt.exe三个文件)
3、下载google提供的签名工具(包含sign.bat、signapk.jar两个文件)
icon覆盖和strings文件修改
我们都知道,在Android应用中应用的icon和应用的名称是在AndroidManifest.xml中指定的,应用名称的话有可能直接写死,但多数是这种情况
- android:icon ="@drawable/ic_launcher"
- ndroid:label ="@string/app_name"
我们只要覆盖drawable-*下对应名字的icon图片和修改values-*路径下strings.xml中对应名字的属性值就行了,为了简单起见在这里以drawable-hdpi和values-zh-rCN路径来介绍
- /**
- * @author Tibib
- *
- */
- public class AndroidManifestParser {
- public String NS = "http://schemas.android.com/apk/res/android" ;
- public AppInfo parse(InputStream in) throws Exception {
- try {
- //使用pull解析库
- XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
- NS = parser.getNamespace();
- //设置使用 namespaces特性
- parser.setFeature(XmlPullParser. FEATURE_PROCESS_NAMESPACES , true );
- parser.setInput(in, "UTF-8" );
- parser.nextTag();
- return readAppInfo(parser);
- } catch (Exception e){
- e.printStackTrace();
- throw e;
- } finally {
- in.close();
- }
- }
- private AppInfo readAppInfo(XmlPullParser parser) throws Exception{
- AppInfo appInfo = new AppInfo();
- while (parser.next() != XmlPullParser. END_TAG) {
- if (parser.getEventType() != XmlPullParser. START_TAG) {
- continue ;
- }
- String name = parser.getName();
- // Starts by looking for the General tag
- if ("application" .equals(name)){
- String attrLabelValue = parser.getAttributeValue( NS, "label" );
- String attrIconValue = parser.getAttributeValue( NS, "icon" );
- appInfo.setAppName(attrLabelValue.split( "/" )[1]);
- appInfo.setIconName(attrIconValue.split( "/" )[1]);
- }
- else {
- skip(parser);
- }
- }
- return appInfo;
- }
- // Skips tags the parser isn't interested in. Uses depth to handle nested tags. i.e.,
- // if the next tag after a START_TAG isn't a matching END_TAG, it keeps going until it
- // finds the matching END_TAG (as indicated by the value of "depth" being 0).
- private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
- if (parser.getEventType() != XmlPullParser. START_TAG) {
- throw new IllegalStateException();
- }
- int depth = 1;
- while (depth != 0) {
- switch (parser.next()) {
- case XmlPullParser. END_TAG:
- depth--;
- break ;
- case XmlPullParser. START_TAG:
- depth++;
- break ;
- }
- }
- }
- }
修改strings.xml中name属性为app_name(具体名称看配置)的值
- /**
- * @author Tibib
- *
- */
- public class XmlModifyUtil {
- /**
- * 使用的是 jdom库
- */
- public static void modifyXML(File modifyXmlFile, String appNameAttrValue,
- String appNameText) {
- OutputStreamWriter bos = null ;
- try {
- SAXBuilder builder = new SAXBuilder();
- if (modifyXmlFile.exists()) {
- Document document = (Document) builder.build(modifyXmlFile);
- Element root = document.getRootElement();
- List<Element> stringChildList = root.getChildren( "string");
- for (Element element : stringChildList) {
- String nameAttrValue = element.getAttribute("name" )
- .getValue();
- if (nameAttrValue.equals(appNameAttrValue)) {
- element.setText(appNameText);
- }
- }
- String xmlFileData = new XMLOutputter().outputString(document);
- // strings.xml默认是UTF-8格式
- bos = new OutputStreamWriter(
- new FileOutputStream(modifyXmlFile), "UTF-8" );
- bos.write(xmlFileData);
- bos.flush();
- } else {
- System. out .println("File does not exist" );
- }
- } catch (Exception ex) {
- ex.printStackTrace();
- } finally {
- if (bos != null ) {
- try {
- bos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
执行编译和签名命令
我把反编译和签名工具都放在了同一目录,并且事先把基础apk反编译好,现在只需要用代码来执行编译和签名命令就行了。在Java中可以通过
Runtime类来执行DOS命令
- private static void createApk(String apkName) throws IOException, InterruptedException {
- File dir = new File(wpPath );
- // 编译命令,其中azbz是基础apk反编译后的文件夹
- String backCommand = "cmd /c apktool.bat b azbz " +apkName+".apk" ;
- // 签名命令
- String signCommand = "cmd /c java -jar signapk.jar platform.x509.pem platform.pk8 "+apkName+ ".apk " +apkName+"_signed.apk" ;
- // 这个命令执行完成会生成一个未签名的 apk
- Runtime backR = Runtime. getRuntime();
- Process backP = backR.exec(backCommand, null , dir);
- // 等待执行完再往下执行
- backP.waitFor();
- // 签名 apk, 这里使用的google提供的证书
- Runtime signR = Runtime. getRuntime();
- Process signP = signR.exec(signCommand, null , dir);
- signP.waitFor();
下面是随手写的一个生成两个icon和名称不同的Apk例子
- public class ExecDosCommand {
- static String wpPath_app = "E:" +File. separator+ "decode apk"+File. separator+ "azbz" +File.separator ;
- static String iconPath = wpPath_app +"res" +File. separator+ "drawable-hdpi"+File. separator ;
- static String stringPath = wpPath_app +"res" +File. separator+ "values-zh-rCN"+File. separator +"strings.xml" ;
- static String manifestPath = wpPath_app+ "AndroidManifest.xml";
- static String wpPath = "E:" + File. separator + "decode apk"+File. separator;
- public static void main(String[] args) throws Exception {
- AndroidManifestParser parser = new AndroidManifestParser();
- AppInfo appInfo = parser.parse( new FileInputStream( manifestPath));
- for (int i = 0; i < 2; i++) {
- coverIcon(appInfo, i);
- modifyAppName(appInfo, i);
- createApk( "修改"+(i+1));
- }
- }
- private static void modifyAppName(AppInfo appInfo, int i) {
- XmlModifyUtil. modifyXML( new File( stringPath ),
- appInfo.getAppName(), "修改" +(i+1));
- }
- private static void coverIcon(AppInfo appInfo, int i)
- throws FileNotFoundException, IOException {
- BufferedOutputStream bos = new BufferedOutputStream(
- new FileOutputStream(iconPath +appInfo.getIconName()+ ".png"));
- BufferedInputStream bis = new BufferedInputStream(
- new FileInputStream(wpPath +File. separator+ "image"+File. separator +"icon" +(i+1)+".png" ));
- byte [] buffer = new byte[1024];
- int temp = 0;
- while ((temp = bis.read(buffer)) != -1 ){
- bos.write(buffer, 0, temp);
- }
- bos.flush();
- bos.close();
- bis.close();
- }
- private static void createApk(String apkName) throws IOException, InterruptedException {
- File dir = new File(wpPath );
- // 编译命令
- String backCommand = "cmd /c apktool.bat b azbz " +apkName+".apk" ;
- // 签名命令
- String signCommand = "cmd /c java -jar signapk.jar platform.x509.pem platform.pk8 "+apkName+ ".apk " +apkName+"_signed.apk" ;
- // 这个命令执行完成会生成一个未签名的 apk
- Runtime backR = Runtime .getRuntime();
- Process backP = backR.exec(backCommand, null , dir);
- // 等待执行完再往下执行
- backP.waitFor();
- // 签名 apk, 这里使用的google提供的证书
- Runtime signR = Runtime .getRuntime();
- Process signP = signR.exec(signCommand, null , dir);
- signP.waitFor();
- }
- }