Android中如何实现OEM


     前几天接到个需求,如何根据一个基础的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"
            android:label ="@string/app_name"

     我们只要覆盖drawable-*下对应名字的icon图片和修改values-*路径下strings.xml中对应名字的属性值就行了,为了简单起见在这里以 drawable-hdpi和values-zh-rCN 路径来介绍

AndroidManifest.xml解析


     通过上面的介绍,我们需要从  AndroidManifest.xml获取icon和label两个属性的值,下面是一个简单的解析类,该注意的地方都有注释

/**
 * @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();
       }

}


























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值