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

}


























發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章