日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

包體積:Layout 二進(jìn)制文件裁剪優(yōu)化

來源: 責(zé)編: 時(shí)間:2023-09-20 21:56:03 391觀看
導(dǎo)讀一、引言得物App在包體積優(yōu)化方面已經(jīng)進(jìn)行了諸多嘗試,收獲也頗豐,已經(jīng)集成的方案有圖片壓縮、重復(fù)資源刪除、ARSC壓縮等可移步至得物 Android 包體積資源優(yōu)化實(shí)踐。本文將主要介紹基于 XML 二進(jìn)制文件的裁剪優(yōu)化。在正

一、引言

得物App在包體積優(yōu)化方面已經(jīng)進(jìn)行了諸多嘗試,收獲也頗豐,已經(jīng)集成的方案有圖片壓縮、重復(fù)資源刪除、ARSC壓縮等可移步至得物 Android 包體積資源優(yōu)化實(shí)踐。本文將主要介紹基于 XML 二進(jìn)制文件的裁剪優(yōu)化。SzG28資訊網(wǎng)——每日最新資訊28at.com

在正式進(jìn)入裁剪優(yōu)化前,需要先做準(zhǔn)備工作,我們先從上層的代碼看起,看看布局填充的方法。方便我們從始到終了解整個(gè)情況。SzG28資訊網(wǎng)——每日最新資訊28at.com

二、XML 解析流程

在 LayoutInflater 調(diào)用 Inflate 方法后,會(huì)將 XML 中的屬性包裝至 LayoutParams 中最后通過反射使用創(chuàng)建對(duì)應(yīng) View。SzG28資訊網(wǎng)——每日最新資訊28at.com

而在反射前,傳入的 R.layout.xxx 文件是如何完成 XML 解析類的創(chuàng)建,后續(xù)又是如何通過該類完成 XML 中的數(shù)據(jù)解析呢?SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

上層 XML 解析最終會(huì)封裝到 XmlBlock 這個(gè)類中。XmlBlock 封裝了具體 RES 文件的解析數(shù)據(jù)。其中 nativeOpenXmlAsset 返回的就是 c 中對(duì)應(yīng)的文件指針,后續(xù)取值都需要通過這個(gè)指針去操作。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

XmlBlock 內(nèi)部的 Parse 類實(shí)現(xiàn)了 XmlResourceParser ,最終被包裝為 AttributeSet 接口返回。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

例如調(diào)用 AttributeSet 的方法:SzG28資訊網(wǎng)——每日最新資訊28at.com

val attributeCount = attrs.attributeCountfor (i in 0 until attributeCount) {    val result = attrs.getAttributeValue(i)    val name = attrs.getAttributeName(i)    println("name:$name ,value::::$result")}
最終就會(huì)調(diào)用到 XmlResourceParser 中的方法,最終調(diào)用到 Native 中。

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

//core/jni/android_util_XmlBlock.cppSzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

可以看到,我們最終都是通過 ResXmlParser 類傳入對(duì)應(yīng)的 ID 來完成取值。而不是通過具體的屬性名稱來進(jìn)行取值。SzG28資訊網(wǎng)——每日最新資訊28at.com

上面介紹的是直接通過 Attrs 取值的方式,在實(shí)際開發(fā)中我們通常會(huì)使用 TypedArray 來進(jìn)行相關(guān)屬性值的獲取。例如 FrameLayout 的創(chuàng)建工程。SzG28資訊網(wǎng)——每日最新資訊28at.com

public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs,        @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {    super(context, attrs, defStyleAttr, defStyleRes);    final TypedArray a = context.obtainStyledAttributes(            attrs, R.styleable.FrameLayout, defStyleAttr, defStyleRes);    saveAttributeDataForStyleable(context, R.styleable.FrameLayout,            attrs, a, defStyleAttr, defStyleRes);    if (a.getBoolean(R.styleable.FrameLayout_measureAllChildren, false)) {        setMeasureAllChildren(true);    }    a.recycle();}

而 obtainStyledAttributes 方法最終會(huì)調(diào)用到 AssetManager 中的 applyStyle 方法,最終調(diào)用到 Native 的 nitiveApplyStyle 方法。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

//https://android.googlesource.com/platform/frameworks/base/+/6d0e2c9cb948a10137e6b5a4eb00e601622fe8ee/core/jni/android_util_AssetManager.cppstatic jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject clazz,                                                        jlong themeToken,                                                        jint defStyleAttr,                                                        jint defStyleRes,                                                        jlong xmlParserToken,                                                        jintArray attrs,                                                        jintArray outValues,                                                        jintArray outIndices){...        const jsize xmlAttrIdx = xmlAttrFinder.find(curIdent);        if (xmlAttrIdx != xmlAttrEnd) {            // We found the attribute we were looking for.            block = kXmlBlock;            xmlParser->getAttributeValue(xmlAttrIdx, &value);            DEBUG_STYLES(ALOGI("-> From XML: type=0x%x, data=0x%08x",                    value.dataType, value.data));        }...}//https://android.googlesource.com/platform/frameworks/base/+/6d0e2c9cb948a10137e6b5a4eb00e601622fe8ee/libs/androidfw/ResourceTypes.cppssize_t ResXMLParser::getAttributeValue(size_t idx, Res_value* outValue) const{    if (mEventCode == START_TAG) {        const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;        if (idx < dtohs(tag->attributeCount)) {            const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)                (((const uint8_t*)tag)                 + dtohs(tag->attributeStart)                 + (dtohs(tag->attributeSize)*idx));            outValue->copyFrom_dtoh(attr->typedValue);            if (mTree.mDynamicRefTable != NULL &&                    mTree.mDynamicRefTable->lookupResourceValue(outValue) != NO_ERROR) {                return BAD_TYPE;            }            return sizeof(Res_value);        }    }    return BAD_TYPE;}

三、XML 二進(jìn)制文件格式

你寫的代碼是這個(gè)樣子,App 打包過程中通過 AAPT2 工具處理完 XML文件,轉(zhuǎn)換位二進(jìn)制文件后就是這個(gè)樣子。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

要了解這個(gè)二進(jìn)制文件,使用 命令行 hexdump  查看:SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

在二進(jìn)制文件中,不同數(shù)據(jù)類型分塊存儲(chǔ),共同組成一個(gè)完整文件。我們可以通過依次讀取每個(gè)字節(jié),來獲取對(duì)應(yīng)的信息。要準(zhǔn)確讀取信息,就必須清楚它的定義規(guī)則和順序,確保可以正確讀取出內(nèi)容。SzG28資訊網(wǎng)——每日最新資訊28at.com

https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/libs/androidfw/include/androidfw/ResourceTypes.hSzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

每一塊(Chunk)都按固定格式生成,最基礎(chǔ)的定義有:SzG28資訊網(wǎng)——每日最新資訊28at.com

Type:類型 分類,對(duì)應(yīng)上面截圖中的類型SzG28資訊網(wǎng)——每日最新資訊28at.com

headerSize:頭信息大小SzG28資訊網(wǎng)——每日最新資訊28at.com

Size:總大小 (headerSize+dataSize)通過這個(gè)值,你可以跳過該 Chunk 的內(nèi)容,如果 Size 和 headerSize 一致,說明該 Chunk 沒有數(shù)據(jù)內(nèi)容。SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

StringPoolChunk

SzG28資訊網(wǎng)——每日最新資訊28at.com

所有的 Chunk 中,都包含 ResChunk ,作為基礎(chǔ)信息。這里以 StringPoolChunk 舉例:

SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

在 StringPool 中,除了基礎(chǔ)的 ResChunk ,還額外包含以下信息:SzG28資訊網(wǎng)——每日最新資訊28at.com

stringCount: 字符串常量池的總數(shù)量SzG28資訊網(wǎng)——每日最新資訊28at.com

styleCount: style 相關(guān)的的總數(shù)量SzG28資訊網(wǎng)——每日最新資訊28at.com

Flag: UTF_8 或者 UTF_16 的標(biāo)志位  我們這里默認(rèn)就是 UTF_8SzG28資訊網(wǎng)——每日最新資訊28at.com

stringsStart:字符串開始的位置SzG28資訊網(wǎng)——每日最新資訊28at.com

stylesStart:styles 開始的位置SzG28資訊網(wǎng)——每日最新資訊28at.com

字符串從 stringStart 的位置相對(duì)開始,兩個(gè)字節(jié)來表示長(zhǎng)度,最后以 0 結(jié)束。SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

XmlStartElementChunk

SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

startElementChunk 是布局 XML 中核心的標(biāo)簽封裝對(duì)象,里面記錄了Namespace ,Name,Attribute 及相關(guān)的 Index 信息,其中 Attribute 中有用自己的 Name Value等具體封裝。SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

ResourceMapChunk

ResourceMapChunk是一個(gè) 32 位的 Int 數(shù)組,在我們編寫的 XML 中沒有直觀體現(xiàn),但是在編譯為二進(jìn)制文件后,它的確存在,也是我們后續(xù)能執(zhí)行裁剪屬性名的重要依據(jù):它與 String Pool 中的資源定義相匹配。SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

NameSpaceChunk

SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

NameSpaceChunk 就是對(duì) Namespace 的封裝,主要包含了前綴(Android Tools  App),和具體的 URL。SzG28資訊網(wǎng)——每日最新資訊28at.com

ResourceType.h 文件中定義了所以需要使用的類型,也是面向?qū)ο蟮姆庋b形式。后面講解析時(shí),也會(huì)根據(jù)每種數(shù)據(jù)類型進(jìn)行具體的解析處理。SzG28資訊網(wǎng)——每日最新資訊28at.com

四、XML 解析過程舉例

我們以獲取 StringPool 的場(chǎng)景來舉例二進(jìn)制文件的解析過程,通過這個(gè)過程,可以掌握字節(jié)讀取的具體實(shí)現(xiàn)。解析過程其實(shí)就是從 0 開始的字節(jié)偏移量獲取。每次讀取多少字節(jié),依賴前面 ResourceTypes.h 中的格式定義。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

第一行SzG28資訊網(wǎng)——每日最新資訊28at.com

00000000  03 00 08 00 54 02 00 00   01 00 1c 00 e4 00 00 00  |....T...........| 00 03       XML 類型 00 08       header size 54 02 00 00 Chunksize (0254 596) 00 01 : StringPool 00 1c headersize (28) 00 00 00 e4 :Chunksize (228) SzG28資訊網(wǎng)——每日最新資訊28at.com

第二行 SzG28資訊網(wǎng)——每日最新資訊28at.com

00000010  0b 00 00 00 00 00 00 00    00 01 00 00 48 00 00 00  |............H...| 00 00 00 0b : stringCount (getInt) 11 00 00 00 00 : styleCount (getInt) 0 00 00 01 00 : flags (getInt)  1 使用 UTF-8 00 00 00 48 : StringStart (getInt) 72SzG28資訊網(wǎng)——每日最新資訊28at.com

第三行 SzG28資訊網(wǎng)——每日最新資訊28at.com

00000020  00 00 00 00 00(indx 36) 00 00 00  0b 00 00 00 17 00 00 00  |................| 00 00 00 00 : styleStart(getInt) 0  (StringPoolChunk 中最后一個(gè)字段獲取) 00(index 36) 00 00 00 : readStrings 第一次偏移 0 (72 + 8 從 index 80 開始)SzG28資訊網(wǎng)——每日最新資訊28at.com

0b 00 00 00: readStrings 第二次偏移 11 (80+11 從 91 開始)SzG28資訊網(wǎng)——每日最新資訊28at.com

00 00 00 17:readString 第三次偏移 23 (80 +23 從 103 開始)SzG28資訊網(wǎng)——每日最新資訊28at.com

第四行SzG28資訊網(wǎng)——每日最新資訊28at.com

00000030  1c 00 00 00 2b 00 00 00    3b 00 00 00 42 00 00 00  |....+...;...B...|SzG28資訊網(wǎng)——每日最新資訊28at.com

00 00 00 1c:readString 第四次偏移 28 (80+28 從 108 開始)SzG28資訊網(wǎng)——每日最新資訊28at.com

00 00 00 2b:readString 第五次偏移 43SzG28資訊網(wǎng)——每日最新資訊28at.com

第六行 SzG28資訊網(wǎng)——每日最新資訊28at.com

00000050  08(index 80) 08 74 65 78 74 53 69  7a 65 00 09(index 91) 09 74 65 78  |..textSize...tex|SzG28資訊網(wǎng)——每日最新資訊28at.com

第七行 SzG28資訊網(wǎng)——每日最新資訊28at.com

00000060  74 43 6f 6c 6f 72 00 02(index 103)  02 69 64 00 0c(index 108) 0c 6c 61  |tColor...id...la|SzG28資訊網(wǎng)——每日最新資訊28at.com

第八行SzG28資訊網(wǎng)——每日最新資訊28at.com

00000070  79 6f 75 74 5f 77 69 64  74 68 00 0d 0d 6c 61 79  |yout_width...lay|SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

工具介紹SzG28資訊網(wǎng)——每日最新資訊28at.com

通過上面的手動(dòng)解析二進(jìn)制文件字節(jié)信息,既然格式如此固定,那多半已經(jīng)有人做過相關(guān)封裝解析類吧,請(qǐng)看JakeWharton:https://github.com/madisp/android-chunk-utilsSzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

API 介紹

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

StringPoolChunk 封裝

SzG28資訊網(wǎng)——每日最新資訊28at.com

String 按之前手動(dòng)解析的思路,是通過偏移量獲取 String 的開始位置及具體長(zhǎng)度,完成不同 String 的讀取。
protected Chunk(ByteBuffer buffer, @Nullable Chunk parent) {  this.parent = parent;  offset = buffer.position() - 2;  headerSize = (buffer.getShort() & 0xFFFF);  chunkSize = buffer.getInt();}//StringPoolChunkprotected StringPoolChunk(ByteBuffer buffer, @Nullable Chunk parent) {  super(buffer, parent);  stringCount = buffer.getInt();  styleCount = buffer.getInt();  flags        = buffer.getInt();  stringsStart = buffer.getInt();  stylesStart  = buffer.getInt();}// StringPoolChunk@Overrideprotected void init(ByteBuffer buffer) {  super.init(buffer);  strings.addAll(readStrings(buffer, offset + stringsStart, stringCount));  styles.addAll(readStyles(buffer, offset + stylesStart, styleCount));}private List<String> readStrings(ByteBuffer buffer, int offset, int count) {  List<String> result = new ArrayList<>();  int previousOffset = -1;  // After the header, we now have an array of offsets for the strings in this pool.  for (int i = 0; i < count; ++i) {    int stringOffset = offset + buffer.getInt();    result.add(ResourceString.decodeString(buffer, stringOffset, getStringType()));    if (stringOffset <= previousOffset) {      isOriginalDeduped = true;    }    previousOffset = stringOffset;  }  return result;}public static String decodeString(ByteBuffer buffer, int offset, Type type) {  int length;  int characterCount = decodeLength(buffer, offset, type);  offset += computeLengthOffset(characterCount, type);  // UTF-8 strings have 2 lengths: the number of characters, and then the encoding length.  // UTF-16 strings, however, only have 1 length: the number of characters.  if (type == Type.UTF8) {    length = decodeLength(buffer, offset, type);    offset += computeLengthOffset(length, type);  } else {    length = characterCount * 2;  }  return new String(buffer.array(), offset, length, type.charset());}

SzG28資訊網(wǎng)——每日最新資訊28at.com

ResourceMapChunk 封裝

SzG28資訊網(wǎng)——每日最新資訊28at.com

資源 ID 對(duì)比 String 顯得更加簡(jiǎn)單,因?yàn)樗拈L(zhǎng)度固定為的 32 位 4 字節(jié),所以用 dataSize 除以 4 就可以得到ResourceMap 的大小,然后依次調(diào)用 buffer.getInt() 方法獲取即可。SzG28資訊網(wǎng)——每日最新資訊28at.com

ResourceMap封裝過程:private List<Integer> enumerateResources(ByteBuffer buffer) {  // id 固定為 4 個(gè)字節(jié)  int resourceCount = (getOriginalChunkSize() - getHeaderSize()) / RESOURCE_SIZE;   List<Integer> result = new ArrayList<>(resourceCount);  int offset = this.offset + getHeaderSize();  buffer.mark();  buffer.position(offset);  for (int i = 0; i < resourceCount; ++i) {    result.add(buffer.getInt());  }  buffer.reset();  return result;}

SzG28資訊網(wǎng)——每日最新資訊28at.com

XmlStartElementChunk 封裝

SzG28資訊網(wǎng)——每日最新資訊28at.com

protected XmlStartElementChunk(ByteBuffer buffer, @Nullable Chunk parent) {  super(buffer, parent);  // 獲取namespace的id  namespace = buffer.getInt();  // 獲取名稱  name = buffer.getInt();  // 獲取屬性索引的開始位置  attributeStart = (buffer.getShort() & 0xFFFF);  // 獲取索引的總大小  int attributeSize = (buffer.getShort() & 0xFFFF);  // 強(qiáng)制檢查 attributeSize 的值是否為固定值,  Preconditions.checkState(attributeSize == XmlAttribute.SIZE, // 20       "attributeSize is wrong size. Got %s, want %s", attributeSize, XmlAttribute.SIZE);  attributeCount = (buffer.getShort() & 0xFFFF);  // The following indices are 1-based and need to be adjusted.  idIndex = (buffer.getShort() & 0xFFFF) - 1;  classIndex = (buffer.getShort() & 0xFFFF) - 1;  styleIndex = (buffer.getShort() & 0xFFFF) - 1;}private List<XmlAttribute> enumerateAttributes(ByteBuffer buffer) {  List<XmlAttribute> result = new ArrayList<>(attributeCount);  int offset = this.offset + getHeaderSize() + attributeStart;  int endOffset = offset + XmlAttribute.SIZE * attributeCount;  buffer.mark();  buffer.position(offset);  while (offset < endOffset) {    result.add(XmlAttribute.create(buffer, this));    offset += XmlAttribute.SIZE;  }  buffer.reset();  return result;}
/** * Creates a new {@link XmlAttribute} based on the bytes at the current {@code buffer} position. * * @param buffer A buffer whose position is at the start of a {@link XmlAttribute}. * @param parent The parent chunk that contains this attribute; used for string lookups. */public static XmlAttribute create(ByteBuffer buffer, XmlNodeChunk parent) {  int namespace = buffer.getInt(); // 4  int name = buffer.getInt(); // 4  int rawValue = buffer.getInt(); // 4  ResourceValue typedValue = ResourceValue.create(buffer);  return new AutoValue_XmlAttribute(namespace, name, rawValue, typedValue, parent);}public static ResourceValue create(ByteBuffer buffer) {  int size = (buffer.getShort() & 0xFFFF); //2  buffer.get();  // Unused // Always set to 0. 1    Type type = Type.fromCode(buffer.get());//1  int data = buffer.getInt(); // 4  return new AutoValue_ResourceValue(size, type, data);}

六、細(xì)節(jié)問題

SzG28資訊網(wǎng)——每日最新資訊28at.com

Style 對(duì)應(yīng)問題

SzG28資訊網(wǎng)——每日最新資訊28at.com

在 StringPool 中我們可以看到除了 String 的定義,這里還有 Style 的定義,那么這個(gè) Style 到底對(duì)應(yīng)什么呢?經(jīng)過一番測(cè)試后,Style 其實(shí)對(duì)應(yīng) Strings.xml 中定義的富文本內(nèi)容,例如:
<string name="spannable_string">  This is a <b>bold</b> text, this is an <i>italic</i></string>

這種內(nèi)容,最后在 解析 Arsc 文件時(shí),就會(huì)有有 Style 相關(guān)的屬性。SzG28資訊網(wǎng)——每日最新資訊28at.com

我們注意主要聚焦于 Layout 文件,所以這里不再展開分析。SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

字節(jié)存儲(chǔ)方式

SzG28資訊網(wǎng)——每日最新資訊28at.com

Little Endian:低位字節(jié)序SzG28資訊網(wǎng)——每日最新資訊28at.com

Big Endian:高位字節(jié)序SzG28資訊網(wǎng)——每日最新資訊28at.com

在 Little Endian 字節(jié)序中,數(shù)據(jù)的最低有效字節(jié)存儲(chǔ)在內(nèi)存地址的最低位置,而最高有效字節(jié)則存儲(chǔ)在內(nèi)存地址的最高位置。這種字節(jié)序的優(yōu)點(diǎn)是可以更好地利用內(nèi)存,能夠更容易地處理低位字節(jié)和高位字節(jié)的組合,尤其是在處理較大的整數(shù)和浮點(diǎn)數(shù)時(shí)比較快速。SzG28資訊網(wǎng)——每日最新資訊28at.com

在 Big Endian 字節(jié)序中,數(shù)據(jù)的最高有效字節(jié)存儲(chǔ)在內(nèi)存地址的最低位置,而最低有效字節(jié)則存儲(chǔ)在內(nèi)存地址的最高位置。這種字節(jié)序雖然更符合人類讀寫的方式,但在高效率方面卻不如 Little Endian 字節(jié)序。SzG28資訊網(wǎng)——每日最新資訊28at.com

舉例 0x12345678SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

七、裁剪優(yōu)化實(shí)現(xiàn)

SzG28資訊網(wǎng)——每日最新資訊28at.com

Namespace 移除

SzG28資訊網(wǎng)——每日最新資訊28at.com

將字符串池中命名空間字符串替換成""空串,以達(dá)到減少文件內(nèi)容的效果。有上面 NameSpaceChunk 中的介紹可知,nameSpace 的前綴及具體 URL 都存放在 StringPool 中,具體內(nèi)容(字節(jié),偏移量)封裝在  NameSpaceChunk 中。所以需要執(zhí)行兩個(gè)步驟:1. 移除 NameSpaceChunk 對(duì)象 2. 置空 StringPool 中的字符串內(nèi)容。

第一步使用 Android-Chunk-Utils 代碼如下,第二步和屬性名移除并列執(zhí)行。SzG28資訊網(wǎng)——每日最新資訊28at.com

FileInputStream(resourcesFile).use { inputStream ->    val resouce = ResourceFile.fromInputStream(inputStream)    val chunks = sChunk.chunks    // 過濾出所有的 NameSpaceChunk 對(duì)象    val result = chunks.values.filter { it is XmlNamespaceChunk }    // 移除    chunks.values.removeAll(result.toSet())}

SzG28資訊網(wǎng)——每日最新資訊28at.com

屬性名移除

SzG28資訊網(wǎng)——每日最新資訊28at.com

將字符串池中每一個(gè)字符串替換成""空字符串。

StringPoolChunk 中記錄了 XML 中的所有組件名稱及其屬性,而每個(gè)屬性對(duì)應(yīng)的具體 ID ,則是固定的,在ResourceMapChunk 中,由 Index 一一對(duì)應(yīng)。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

舉個(gè)例子,在這個(gè)布局文件中, Layout_width 的在 StringPool 中的索引是 6 ,對(duì)應(yīng)在 ResourceMapChunk 中是 16842996 的值,轉(zhuǎn)換十六進(jìn)制后:10100f4,與 public.xml 中定義的屬性 ID 完全對(duì)應(yīng)。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

通過上面源碼的介紹,每個(gè)屬性(Attr)包含一個(gè)對(duì)應(yīng)的整型 ID 值,獲取其屬性值時(shí)都會(huì)通過該 ID 值來獲取。所以對(duì)應(yīng)的屬性名理論上可以移除。具體代碼如下:SzG28資訊網(wǎng)——每日最新資訊28at.com

private fun handleStringPoolValue(strings: MutableList<String>, resources: MutableList<Int>?, stringPoolChunk: StringPoolChunk, emptyIndexs: MutableList<Int>) {    strings.forEachIndexed { i, k ->        val res = resources        // 默認(rèn)屬性置空        if (res != null && i < res.size) {            stringPoolChunk.setString(i, "")            emptyIndexs.add(i)        }        // 命名空間置空        else if (k == "http://schemas.android.com/apk/res/android") {            stringPoolChunk.setString(i, "")            emptyIndexs.add(i)        } else if (k == "http://schemas.android.com/apk/res-auto") {            stringPoolChunk.setString(i, "")            emptyIndexs.add(i)        } else if (k == "http://schemas.android.com/tools") {            stringPoolChunk.setString(i, "")            emptyIndexs.add(i)        } else if (k == "android") {            stringPoolChunk.setString(i, "")            emptyIndexs.add(i)        } else if (k == "app") {            stringPoolChunk.setString(i, "")            emptyIndexs.add(i)        } else if (k == "tools") {            stringPoolChunk.setString(i, "")            emptyIndexs.add(i)        }    }}

SzG28資訊網(wǎng)——每日最新資訊28at.com

Stringpool 偏移量修改

SzG28資訊網(wǎng)——每日最新資訊28at.com

經(jīng)過上面兩個(gè)步驟后,StringPool 已經(jīng)得到優(yōu)化,但是觀察新的二進(jìn)制文件會(huì)發(fā)現(xiàn),目前總大小為:00 00 01 c4 (452) ,優(yōu)化前為 00 00 02 54(596)。經(jīng)過進(jìn)一步分析可以發(fā)現(xiàn),StringPool 中字符串的偏移量還可以優(yōu)化。

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

第二行SzG28資訊網(wǎng)——每日最新資訊28at.com

00000010  0b 00 00 00 00 00 00 00  00 01 00 00 48 00 00 00  |............H...|SzG28資訊網(wǎng)——每日最新資訊28at.com

第三行SzG28資訊網(wǎng)——每日最新資訊28at.com

00000020  00 00 00 00 00(index 36) 00 00 00  03 00 00 00 06 00 00 00  |................|SzG28資訊網(wǎng)——每日最新資訊28at.com

第四行SzG28資訊網(wǎng)——每日最新資訊28at.com

00000030  09 00 00 00 0c 00 00 00  0f 00 00 00 12 00 00 00  |................|SzG28資訊網(wǎng)——每日最新資訊28at.com

第六行SzG28資訊網(wǎng)——每日最新資訊28at.com

00000050  00(index 80) 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|SzG28資訊網(wǎng)——每日最新資訊28at.com

可以看到空字符串第一個(gè)偏移量是 0: 72+8 80 ,從 80 開始,每一個(gè)空字符串都由一組 00 00 00 來表示,這里也會(huì)有冗余的存儲(chǔ)占用。那這里是否可以控制偏移量,就用一組 00 00 00 來表示呢?答案是可以的, Android-Chunk-Utils 工具類已經(jīng)給我們提供了策略支持。ResourceFile.toByteArray 回寫方法就提供了 Shrink 參數(shù)。SzG28資訊網(wǎng)——每日最新資訊28at.com

FileOutputStream(resourcesFile).use {    it.write(newResouce.toByteArray(true))}@Overridepublic byte[] toByteArray(boolean shrink) throws IOException {  ByteArrayDataOutput output = ByteStreams.newDataOutput();  for (Chunk chunk : chunks) {    output.write(chunk.toByteArray(shrink));  }  return output.toByteArray();}
//StringPoolChunk 中的具體寫入實(shí)現(xiàn)@Overrideprotected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)    throws IOException {  ByteArrayOutputStream baos = new ByteArrayOutputStream();  int stringOffset = 0;  ByteBuffer offsets = ByteBuffer.allocate(getOffsetSize());  offsets.order(ByteOrder.LITTLE_ENDIAN);  // Write to a temporary payload so we can rearrange this and put the offsets first  try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) {    stringOffset = writeStrings(payload, offsets, shrink);    writeStyles(payload, offsets, shrink);  }  output.write(offsets.array());  output.write(baos.toByteArray());  if (!styles.isEmpty()) {    header.putInt(STYLE_START_OFFSET, getHeaderSize() + getOffsetSize() + stringOffset);  }}private int writeStrings(DataOutput payload, ByteBuffer offsets, boolean shrink)    throws IOException {  int stringOffset = 0;  Map<String, Integer> used = new HashMap<>();  // Keeps track of strings already written  for (String string : strings) {    // Dedupe everything except stylized strings, unless shrink is true (then dedupe everything)    if (used.containsKey(string) && (shrink || isOriginalDeduped)) {      // 如果支持優(yōu)化,將復(fù)用之前的數(shù)據(jù)和 offest      Integer offset = used.get(string);      offsets.putInt(offset == null ? 0 : offset);    } else {      byte[] encodedString = ResourceString.encodeString(string, getStringType());      payload.write(encodedString);      used.put(string, stringOffset);      offsets.putInt(stringOffset);      stringOffset += encodedString.length;    }  }

經(jīng)過三步優(yōu)化,重新更新 XML 文件后再次確定二進(jìn)制信息,獲取 Chunck 的總大小為:00 00 01 b0 (432),對(duì)比原始 XML 文件,一共減少 164 (28% )。當(dāng)然這個(gè)減少數(shù)據(jù)量取決于 XML 中標(biāo)簽及屬性的數(shù)量,越復(fù)雜的 XML 文件,縮減率越高。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

SzG28資訊網(wǎng)——每日最新資訊28at.com

效果對(duì)比

SzG28資訊網(wǎng)——每日最新資訊28at.com

裁剪前裁剪前SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

裁剪后SzG28資訊網(wǎng)——每日最新資訊28at.com

八、API 兼容調(diào)整

雖然理論上說移除布局的屬性后對(duì)于正常的流程無影響,但是,該有的問題總還是會(huì)有的,真一個(gè)問題都沒有那才讓人心里不踏實(shí),接下來看兼容的一些異常情況。

SzG28資訊網(wǎng)——每日最新資訊28at.com

TabLayout 獲取 Height 的場(chǎng)景

SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

這個(gè)寫法同時(shí)使用了 Namespace 和 特定屬性,布局初始化時(shí)直接就會(huì) Crash 。后面掃描了所有使用 getAttributeValue 方法的類,篩選確定后進(jìn)行統(tǒng)一代碼調(diào)整。SzG28資訊網(wǎng)——每日最新資訊28at.com

int[] systemAttrs = {android.R.attr.layout_height};TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs);try {    // 如果定義的是 WRAP_CONTENT 或者 MATCH_PARENT 這里會(huì)異常,然后通過 getInt 獲取值(-1 -2)    mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT);} catch (Exception e) {    // e.printStackTrace();    mHeight = a.getInt(0, ViewGroup.LayoutParams.WRAP_CONTENT);}

SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片庫獲取 SRC 的場(chǎng)景

SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片庫內(nèi)部默認(rèn)支持了 ImageView 的 SRC 屬性,具體獲取方式使用了 getAttributeResourceValue 的方法。

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

因?yàn)閳D片庫調(diào)用的地方做了默認(rèn) Catch 捕獲異常,所以 App 沒有 Crash ,但是對(duì)于使用 SRC 屬性設(shè)置的圖片資源無法正常顯示。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

調(diào)整為:SzG28資訊網(wǎng)——每日最新資訊28at.com

if (attrs != null) {    TypedArray a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.theme});    if (a.hasValue(0)) {        int[] attr = new int[]{R.attr.colorControlNormal, android.R.attr.textColorPrimary};        TypedArray array = context.obtainStyledAttributes(attrs.getAttributeResourceValue(0, 0), attr);        try {            mNavigationIconTintColor = array.getColor(0, Color.BLACK);            mTitleTextColor = array.getColor(1, Color.BLACK);        } finally {            array.recycle();        }    }    a.recycle();}

修改后發(fā)現(xiàn)依然有問題,對(duì)比異常的不同頁面發(fā)現(xiàn)以下區(qū)別:SzG28資訊網(wǎng)——每日最新資訊28at.com

<androidx.appcompat.widget.Toolbar    android:id="@+id/toolbar"    android:layout_width="match_parent"    android:layout_height="?attr/actionBarSize"    app:layout_collapseMode="pin"    app:popupTheme="@style/ThemeToolbarWhite"    app:theme="@style/ThemeToolbarWhite"><androidx.appcompat.widget.Toolbar    android:id="@+id/toolbar"    android:layout_width="match_parent"    android:layout_height="?attr/actionBarSize"    android:theme="@style/ThemeToolbarWhite"    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"    app:title="領(lǐng)取數(shù)字藏品"    app:titleTextColor="@android:color/white" />

使用 Android 命名空間生成的屬性是系統(tǒng)自帶屬性,定義在 public.xml 中,使用 App 生成的屬性是自定義屬性,打包到 Arsc 的 Attr 中。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

所以,上面僅判斷 Android.R.attr.theme 不夠,還需要增加 R.attr.theme 。SzG28資訊網(wǎng)——每日最新資訊28at.com

TypedArray a = context.obtainStyledAttributes(attrs, new int[]{R.attr.theme, android.R.attr.theme});

九、收益

最后,確定下總體包體積優(yōu)化收益:SzG28資訊網(wǎng)——每日最新資訊28at.com

移除 Namespace 及屬性值后:SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

優(yōu)化空字符串后:SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

由于這是 Apk 解壓后的所有文件匯總收益,重新壓縮打包 Apk 后,包體積整體收益在 2.2 M左右。SzG28資訊網(wǎng)——每日最新資訊28at.com

圖片SzG28資訊網(wǎng)——每日最新資訊28at.com

十、總結(jié)

本文介紹了得物App的包體積優(yōu)化工作,講解了針對(duì)XML二進(jìn)制文件的裁剪優(yōu)化。文章首先概述了XML解析流程和XML二進(jìn)制文件格式,然后介紹了解析過程中的一些工具以及細(xì)節(jié)問題,探討了裁剪優(yōu)化實(shí)現(xiàn)以及API的兼容調(diào)整,最后呈現(xiàn)了包體積優(yōu)化的收益。SzG28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-10542-0.html包體積:Layout 二進(jìn)制文件裁剪優(yōu)化

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com

上一篇: 為什么在 C++14 中刪除了 get 函數(shù)?

下一篇: 冠閔信息CEO顏偉志談MSP發(fā)展趨勢(shì):利用各種工具持續(xù)交付高度自動(dòng)化的服務(wù)

標(biāo)簽:
  • 熱門焦點(diǎn)
Top 主站蜘蛛池模板: 镇康县| 读书| 绍兴县| 宜城市| 栾城县| 桃江县| 甘德县| 富民县| 舟曲县| 仪陇县| 兰考县| 哈巴河县| 关岭| 嘉荫县| 汝城县| 英德市| 离岛区| 将乐县| 泰安市| 白水县| 高雄市| 库尔勒市| 河西区| 瑞金市| 庆安县| 安阳县| 静安区| 沁源县| 建宁县| 西藏| 新竹市| 桂平市| 佳木斯市| 怀柔区| 象州县| 徐水县| 英吉沙县| 上犹县| 张家界市| 榆社县| 浦江县|