安卓方案類-遊戲發行切包資源索引衝突解決方案

做者

你們好,我叫小鑫,也能夠叫我蠟筆小鑫😊;java

本人17年畢業於中山大學,於2018年7月加入37手遊安卓團隊,曾經就任於久邦數碼擔任安卓開發工程師;數組

目前是37手遊安卓團隊的海外負責人,負責相關業務開發;同時兼顧一些基礎建設相關工做。緩存

背景

遊戲發行切包過程當中,常常碰到渠道、研發、發行方,三方資源在合併過程當中,資源ID衝突致使程序異常的問題,此類問題經過getIdentifier方式規避或者修改衝突資源ID的方式能夠處理,但成本較高,本文旨在提出一種在切包過程當中自動化處理資源衝突的解決方案微信

一、public.xml介紹

一、public.xml這個文件是哪來的?

該文件是apktool在反編譯apk時,根據apk包中的resources.arsc文件生成。markdown

沒看過resource.arsc? (本身拖個apk到IDE看吧)app

二、public.xml有什麼做用

publc.xml是aapt在打包資源時用來固定資源id的,若是資源在public.xml中有對應的id了,那麼打包資源時就用已經有的id。優化

三、public.xml中的id的格式

共四個字節32位,第一個字節表明PackgeID,第二個字節表明TypeID,後兩個字節表明資源值spa

一般系統資源PackageID是01,而咱們本身的資源PackageID是7fcode

TypeID,好比attr爲01,string爲02。可是並不固定,並不必定attr就是01。可是在public.xml中,同類型的該字節必定是同樣的,不然回編譯會失敗。orm

二、R類介紹

R類這裏有個知識點,library模塊中生成的R類中的成員的值不是常量,不帶final。app模塊生成的R類的值是常量值。而常量值在java編譯時會被優化,最終代碼中輸出的就是常量值,而不是R.id.xxx這樣。而library的由於是變量,不會被優化,代碼中會保留R.id.xxx

R類和public.xml的關係

從本質上講,其實並無啥關係。可是因爲在代碼中咱們會使用R.id去查找資源,這就關聯上了。若是都用getIdentifier的方式先獲取id,那把R類刪了也沒事。

public.xml打包後對應的就是resources.arsc中的值,而資源值生成Java類,這個類就是R類。也就是說平時使用R類,就是用裏面的索引值去到resources.arsc中找到對應資源位置,再去加載。

三、切包融合過程當中R類和public.xml的處理

切包過程當中,R類屬於代碼,採用直接覆蓋的方式,可是因爲咱們生成的R類跟母包的R類其實值會是不一樣的。

下文中的cp指遊戲研發方,即咱們的SDK的接入方。

而public.xml是用的cp的,爲何用cp的?由於cp創建的是app工程,R類是常量值,若是咱們把母包中public.xml中已有的值給改了,萬一母包中用了,那就gg了

因爲R類在library中使用的時候是個變量,保留了R.id.xxx這種形式,解決方法就有了,糾正R類中的值跟public.xml對應,這樣就能繼續愉快的使用R.id.xxx了。

咱們的切包過程有幾個步驟:

反編譯母包(指接入咱們SDK的乙方)====》合併渠道資源====》合併入新sdk的資源(跳過研發更新咱們的sdk的過程哈)

一、在反編譯母包的時候解析public.xml的值,存下來。

private void init() {
       List<Element> elements = mDocument.getRootElement().elements();
       for (Element element : elements) {
           String type = element.attribute(TYPE).getStringValue();
           String name = element.attribute(NAME).getStringValue();
           String id = element.attribute(ID).getStringValue();
           Map<String, String> typeMap = mTypeMap.get(type);
           if (typeMap == null) {
               typeMap = new HashMap<>();
               typeMap.put(name, id);
               mTypeMap.put(type, typeMap);
           } else {
               typeMap.put(name, id);
           }
       }
}
複製代碼

二、合併渠道資源的時候,將渠道資源中的public.xml(以channelPublic代指)合併到母包的public.xml(以matrixPublic代指)中

合併策略:

a、channelPublic中有,而matrixPublic中沒有,增長到matrixPublic中

好比增長以下數據到matrixPublic中

<public type="attr" name="iconSrc" id="0x7f0200a8" />
複製代碼

若是該type在matrixPublic中已經存在:

首先要獲取到attr在matrixPublic中的PackageId+TypeId。在一個public.xml文件中,同類型好比attr對應的PackageId+TypeId是不能變的,不然回編譯失敗。所以要添加數據時,數據的PackageId+TypeId須要糾正爲matrixPublic的值。

其次資源值,不能和已有的資源值重複,正常狀況下public.xml中的值是aapt生成的有序的,這裏能夠掃描matrixPublic中attr類型值的最大值,而後加一做爲新加的iconSrc的id值

若是該type在matrixPublic中不存在(假設母包中matrixPublic中不存在attr類型)

首先要獲取類型已經被佔用的有哪些,即獲取到matrixPublic中的TypeId,正常狀況也是有序的,獲取出最大的TypeId,加一做爲新Type的起始值。賦值給iconSrc的id值

b、channelPublic中有,而matrixPublic中也有的,不須要處理,保留matrixPublic中的值不變

三、合併入新sdk的資源,在覆蓋完R類,後開始糾正R類的值

掃描R類在PublicAndRHelper中

掃描覆蓋完R類的smali代碼中全部的R類,R$styleable類除外,由於styleable中保存的是一些數組的值,規則不一樣。

/**
     * 掃描代碼中的R類
     * @return
     */
   private void scannerRClass(String path) {
       File smaliFilePath = new File(path);
       for (File file : smaliFilePath.listFiles()) {
           if (file.isDirectory()) {
               scannerRClass(file.getAbsolutePath());
           } else if(file.isFile()){
               if (file.getName().equals("R.smali") || file.getName().startsWith("R$")) {
                   //此處過濾掉styleable文件
                   if (!file.getName().endsWith("R$styleable.smali")) {
                       mRClassFileList.add(file.getAbsolutePath());
                   }
               }
           }
       }
   }
複製代碼

針對每個R類調用糾正R類中方法,糾正R類值在RValueHelper類中

策略:匹配出要糾正的行,獲取到type,name。在public.xml中找出對應的值,糾正。

注意這裏的糾正不要用replace(oldValue,newValue)這種方式,要用替換行的方式,由於存在新值在R類中也存在後,後續替換出問題。好比a替換成b,b替換成c的狀況最終R類中的a和b都被替換成了c

其次是styleable的處理,當掃描到的R是attr類型的時候,判斷是否有styleable類型的存在,若是存在,則緩存下來attr中所作的糾正,用於糾正styleable。

public static void handle(String RFilePath, PublicXmlBean publicXmlBean) {
       File RFile = new File(RFilePath);
       String RStyleFilePath = "";
       Map<String, String> cacheMap = null;
       if (RFile.getName().endsWith("R$attr.smali")) {
           RStyleFilePath = RFilePath.replace("R$attr", "R$styleable");
           File RStyleAbleFile = new File(RStyleFilePath);
           //styleable存在,則把attr文件替換過的值緩存
           if (RStyleAbleFile.exists()) {
               cacheMap = new HashMap<>();
           }
       }
       String rFileContent = FileUtil.read(RFilePath);
       //找到RFile中是屬性的每一行
       ArrayList<String> lines = FileUtil.readAllLines(RFilePath, ".field public static final");
       String regex = ".field public static final (.*):(.*) = (.*)";
       for (String line : lines) {
           Pattern pattern = Pattern.compile(regex);
           Matcher matcher = pattern.matcher(line);
           if (matcher.find()) {
               String type = RFile.getName().replace("R$", "").replace(".smali", "");
               String name = matcher.group(1);
               String resetValue = publicXmlBean.getValue(type, name);
               if (StringUtils.isEmpty(resetValue)) {
                   resetValue = publicXmlBean.addValue(type, matcher.group(1));
               }
               //替換到文件內容中
               rFileContent = rFileContent.replace(line, ".field public static final " + name + ":" + matcher.group(2) + " = " + resetValue);
               if (cacheMap != null) {
                   //換過的值緩存起來
                   cacheMap.put(matcher.group(3), resetValue);
               }
           }
       }
       FileUtil.write(RFilePath, rFileContent);
       if (cacheMap != null) {
           //糾正R$styleable的值
           List<String> styleAbleLines = FileUtil.readAllLines(RStyleFilePath);
           BufferedWriter bw = null;
           try {
               bw = new BufferedWriter(new FileWriter(RStyleFilePath));
               for (String styleAbleLine : styleAbleLines) {
                   for (String key : cacheMap.keySet()) {
                       if (styleAbleLine.contains(key)) {
                           styleAbleLine = styleAbleLine.replace(key, cacheMap.get(key));
                       }
                   }
                   bw.write(styleAbleLine);
                   bw.newLine();
               }
           } catch (IOException e) {
               e.printStackTrace();
           } finally {
               if (bw != null) {
                   try {
                       bw.close();
                   } catch (IOException e) {
                       bw = null;
                   }
               }
           }
       }
   }
複製代碼

至此,糾正完了R類和public.xml的值

四、小結

遊戲發行行業中,切包過程因爲是多方代碼資源的一個合併過程,常常出現資源衝突問題。本方案致力於優化切包過程,自動化地解決資源衝突問題。本方案已申請專利,並在咱們實際業務中使用並穩定運行

歡迎交流

過程當中有問題或者須要交流的同窗,能夠掃描二維碼加好友,而後進羣進行問題和技術的交流等;

企業微信截圖_5d79a123-2e31-42cc-b03f-9312b8b99df3.png

相關文章
相關標籤/搜索