自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

抖音 Android 包體積優(yōu)化探索:基于 ReDex 的 DEX 優(yōu)化落地實(shí)踐

原創(chuàng) 精選
移動(dòng)開發(fā) 移動(dòng)應(yīng)用
抖音是字節(jié)跳動(dòng)規(guī)模最大、運(yùn)行環(huán)境復(fù)雜度最高的應(yīng)用之一。在 ReDex 落地初期,由于對(duì)復(fù)雜度估計(jì)不足,在獨(dú)立灰度和全量灰度期間引起了一些問題,在解決問題的過程中,我們也逐步形成了一套迭代流程以保證優(yōu)化的穩(wěn)定性。

作者 | 馮瑞;廖斌斌;劉豐愷

前言

應(yīng)用安裝包的體積會(huì)顯著影響應(yīng)用的下載速度和安裝速度,按照 Google 的經(jīng)驗(yàn)數(shù)據(jù),包體積每增加 1M 會(huì)造成 0.17%的新增折損。抖音的一些實(shí)驗(yàn)也證明了包體積會(huì)顯著影響下載激活的轉(zhuǎn)化率。

Android 的安裝包是 APK 格式的,在抖音的安裝包中 DEX 的體積占比達(dá)到了 40%以上,所以針對(duì) DEX 的體積優(yōu)化是一種行之有效的包體積優(yōu)化手段。

DEX 本質(zhì)上是由 Java/Kotlin 代碼編譯而成的字節(jié)碼,因此,針對(duì)字節(jié)碼進(jìn)行業(yè)務(wù)無感的通用優(yōu)化成為我們的一個(gè)探索方向。

優(yōu)化結(jié)果

終端基礎(chǔ)技術(shù)團(tuán)隊(duì)和抖音基礎(chǔ)技術(shù)團(tuán)隊(duì)在過去的一年里,利用 ReDex 在抖音包體積優(yōu)化方面取得了一些明顯的收益,這些優(yōu)化也被同步到了其他各大 App 上。

在抖音、頭條和其他應(yīng)用上,我們的優(yōu)化對(duì) APK 體積的縮減普遍達(dá)到了 4%以上,對(duì) DEX 體積的縮減則可以達(dá)到 8% ~ 10%

優(yōu)化思路

在 android 應(yīng)用的構(gòu)建過程中,Java/Kotlin 代碼會(huì)先被編譯成 Class 字節(jié)碼,在這個(gè)階段 gradle 提供了 Transformer 可以進(jìn)行字節(jié)碼的自定義處理,很多插件都是在這個(gè)階段處理字節(jié)碼的。然后,Class 文件經(jīng)過 dexBuilder/mergeDex 等任務(wù)的處理會(huì)生成 DEX 文件,并最終被打進(jìn)安裝包中。整個(gè)過程如下所示:

所以,針對(duì)字節(jié)碼的優(yōu)化是有 2 個(gè)時(shí)機(jī)可以進(jìn)行的:

  • 在 transformer 階段對(duì) Class 字節(jié)碼進(jìn)行優(yōu)化
  • 在 DEX 階段對(duì) DEX 文件進(jìn)行優(yōu)化

顯然,對(duì) DEX 進(jìn)行優(yōu)化是更理想的一種方式,因?yàn)樵?DEX 文件中,除了字節(jié)碼指令外,還存在跨 DEX 引用、字符串池這樣的結(jié)構(gòu),針對(duì)這些 DEX 格式的優(yōu)化是無法在 transformer 階段進(jìn)行的。

在確定了針對(duì) DEX 文件進(jìn)行優(yōu)化的思路后,我們選擇了 facebook 的開源框架 ReDex 作為優(yōu)化工具,并對(duì)其進(jìn)行了定制開發(fā)。

選擇 ReDex 的原因是它提供了豐富的基礎(chǔ)能力,ReDex 的基礎(chǔ)能力包括:

  1. 讀寫及解析 DEX 的能力,同時(shí)可以在一定程度上讀取并解析 xml 和 so 文件
  2. 解析簡單的 proguard keep 規(guī)則并匹配類/方法/成員變量的能力
  3. 對(duì)字節(jié)碼進(jìn)行數(shù)據(jù)流分析的能力,提供了常用的數(shù)據(jù)流分析算法
  4. 對(duì)字節(jié)碼進(jìn)行合法性校驗(yàn)的能力,包括寄存器檢查、類型檢查等
  5. 一系列的字節(jié)碼優(yōu)化項(xiàng),每項(xiàng)優(yōu)化稱為一個(gè) pass,多個(gè) pass 組成 pipeline 對(duì) DEX 進(jìn)行優(yōu)化

我們基于這些能力進(jìn)行了定制和擴(kuò)展,并期望最終建立完善的優(yōu)化體系。

優(yōu)化項(xiàng)

在抖音落地的優(yōu)化項(xiàng),包括 facebook 開源的優(yōu)化和我們自研的優(yōu)化,從其出發(fā)點(diǎn)來看,可以大致分為下面幾種:

  • 通用字節(jié)碼優(yōu)化:通常意義下的編譯優(yōu)化,如常量傳播、內(nèi)聯(lián)等,一般也可在 Transformer 階段實(shí)現(xiàn)
  • DEX 格式優(yōu)化:DEX 中除了字節(jié)碼指令外,還包括字符串池、類/方法引用、debug 信息等等,針對(duì)這些方面的優(yōu)化歸類為 DEX 格式優(yōu)化
  • 針對(duì)編程語言的優(yōu)化:Java/Kotlin 的一些語法糖會(huì)生成大量字節(jié)碼,可以對(duì)這些字節(jié)碼進(jìn)行針對(duì)性的分析和優(yōu)化
  • 提升壓縮率的優(yōu)化:將 DEX 打包成 APK 實(shí)質(zhì)上是個(gè)壓縮的過程,對(duì) DEX 內(nèi)容進(jìn)行針對(duì)性的優(yōu)化可以提升壓縮率,從而產(chǎn)生體積更小的 APK

這幾種優(yōu)化沒有明確的標(biāo)準(zhǔn)和界線,有時(shí)一個(gè) Pass 會(huì)涉及到多種,下面詳細(xì)介紹一下各項(xiàng)優(yōu)化。

通用字節(jié)碼優(yōu)化

ConstantPropagationPass

該 Pass 實(shí)際上包含了常量折疊和常量傳播。

常量折疊是在編譯期簡化常量的過程,比如

y = 7 - 14 / 2
--->
y = 0

常量傳播是在編譯期替代指令中已知常量的過程,比如

int x = 14;
int y = 7 - x / 2;
return y * (28 / x + 2);
--->
int x = 14;
int y = 7 - 14 / 2;
return (7 - 14 / 2) * (28 / 14 + 2);

上面的例子經(jīng)過 常量折疊 + 常量傳播優(yōu)化后就會(huì)簡化為

int x = 14;
int y = 0;
return 0;

再經(jīng)過死代碼刪除就可以最終變?yōu)閞eturn 0。

具體的優(yōu)化過程是:

  1. 對(duì)方法進(jìn)行數(shù)據(jù)流分析,主要針對(duì) const/move 等指令,得出一個(gè)寄存器在某個(gè)位置可能的取值
  2. 根據(jù)分析的結(jié)果,進(jìn)行指令替換或指令刪除,包括:
  • 如果值肯定是非空的,可以將對(duì)應(yīng)的判空去掉,比如 kotlin 生成的 null check 調(diào)用
  • 如果值肯定為空,可以將指令替換為拋空異常
  • 如果值肯定讓某 if 分支走不到,可以刪除對(duì)應(yīng)的分支
  • 如果值是固定的,可以用 const 指令替換對(duì)應(yīng)的賦值或計(jì)算指令

一個(gè)方法經(jīng)過 ConstantPropagationPass 優(yōu)化后,可能會(huì)產(chǎn)生一些死代碼,比如例子中的int y = 0,這也為后續(xù)的死代碼刪除創(chuàng)造了條件。

AnnoKillPass

該 Pass 是用來移除無用注解的。注解主要分為三種類型:

  • SOURCE:java 源碼編譯為 class 字節(jié)碼就不可見,此類注解一般不用過于關(guān)注
  • CLASS:字節(jié)碼通過 dx 工具轉(zhuǎn)成 DEX 就不可見,代碼運(yùn)行時(shí)不需要獲取信息,所以一般來說也不需要關(guān)注,實(shí)測(cè)發(fā)現(xiàn)部分注解仍然存在于 DEX 中,這部分注解可以進(jìn)行優(yōu)化
  • RUNTIME:DEX 中仍然可見,代碼運(yùn)行中可以通過 getAnnotations 等接口獲取注解信息,但是隨著業(yè)務(wù)的迭代,可能獲取注解信息的代碼已經(jīng)去掉,注解卻沒有下掉,這部分注解會(huì)被 ReDex 安全的移除

除此之外,實(shí)際上為了支持某些系統(tǒng)特性,編譯器會(huì)自動(dòng)生成系統(tǒng)注解,雖然注解本身是 RUNTIME 類型,但是可見性是VISIBILITY_SYSTEM

  • AnnotationDefault : 默認(rèn)注解,不能刪除
  • EnclosingClass : 當(dāng)前內(nèi)部類申明時(shí)所在的類
  • EnclosingMethod : 當(dāng)前內(nèi)部類申明時(shí)所在的方法
  • InnerClass : 當(dāng)前內(nèi)部類名稱
  • MemberClasses : 當(dāng)前類的所有內(nèi)部類列表
  • MethodParameters : 方法參數(shù)
  • Signature : 泛型相關(guān)
  • Throws : 異常相關(guān)

舉例說明

編譯器生成 1MainApplication$1這個(gè)匿名內(nèi)部類,帶有 EnclosingMethod 和 InnerClass 注解

系統(tǒng)提供以下接口獲取類相關(guān)的信息,就是通過分析相關(guān)的系統(tǒng)注解來實(shí)現(xiàn)的

  • Class.getEnclosingMethod
  • Class.getSimpleName
  • Class.isAnonymousClass
  • ....

如果代碼中不存在使用這些接口獲取類信息的邏輯,就可以安全的移除這部分注解,從而達(dá)到縮減包大小的目的。

RenameClassesPass

該 Pass 通過縮減類名的字符串長度來減小包體積

比如把類名從La/b/c/d/e;改為LX/a;,可以類名字符串的長度,從而達(dá)到包大小縮減的目的。實(shí)際上 Proguard 本身已經(jīng)提供類似的功能: -repackageclasses 'X',效果如下:

但是-repackageclasses 'X'的處理會(huì)影響 ReDex 的 InterDexPass 的算法邏輯(InterDexPass 可以參考下文),導(dǎo)致收益縮減

  • 收益測(cè)試
  • Proguard-repackageclasses 'X' 收益: 600K+
  • RedexInterDexPass 收益: 400K+
  • 同時(shí)應(yīng)用 Proguard-repackageclasses 'X'? 和 RedexInterDexPass 收益: 40K+

本質(zhì)原因在于 Proguard 重命名后,影響了 InterDexPass 函數(shù)引用權(quán)重分配,導(dǎo)致 InterDex 收益被回收

  • 解決方案
  • InterDexPass 深入分析原理,優(yōu)化權(quán)重算法
  • 先執(zhí)行 InterDexPass,后執(zhí)行類似 Proguard 的-repackageclasses 'X'

權(quán)重算法優(yōu)化相對(duì)來說比較復(fù)雜,同時(shí)存在眾多不可確定性,比如潛在的跟其他優(yōu)化的沖突,所以我們采取了第二種解決方案。

這里需要解決的一個(gè)關(guān)鍵點(diǎn)在于如何確定一個(gè)類名是否可以被安全的重命名,我們采取了一個(gè)比較取巧的方式,ReDex 會(huì)分析 Proguard 傳遞上來 mapping.txt 文件,只要我們保持跟 Proguard 類重命名優(yōu)化一樣的處理策略,就不會(huì)引發(fā)反射/native 調(diào)用/序列化等一系列問題。

但是執(zhí)行起來還是碰到各種千奇百怪的問題,比如 Signature 系統(tǒng)注解失效問題。Signature 注解的內(nèi)容是非標(biāo)準(zhǔn)的類名格式,所以類重命名后簡單回寫字符串或者更新 Type 類型會(huì)導(dǎo)致 Signature 注解失效,最后通過深入解析 Signature 格式規(guī)避了這個(gè)問題。

StringBuilderOutlinerPass

該 Pass 是針對(duì) StringBuilder 的 CallSites 進(jìn)行分析縮略的優(yōu)化,與死代碼刪除搭配使用可以有不錯(cuò)的優(yōu)化效果。

為何要優(yōu)化 StringBuilder 呢?在 Java 的代碼開發(fā)過程中,字符串操作幾乎是我們最經(jīng)常做的一件事情,無論是實(shí)際處理字符串拼接還是各種不同數(shù)據(jù)類型之間的拼接操作。而這些拼接操作都會(huì)被 Java 的 de-sugar 優(yōu)化為 StringBuilder 操作。比如:var log = "A" + 1 + "B" + 1.0f + other_var; 會(huì)被優(yōu)化為:

StringBuilder builder = new StringBuilder();
builder.append("A"); builder.append(1);
builder.append("B"); builder.append(1.0f);
builder.append(other_var);
builder.toString();

因此我們對(duì) StringBuilder 的所有 Callsites 進(jìn)行分析,在最好情況下多個(gè)方法調(diào)用可以被優(yōu)化為一個(gè)調(diào)用,這個(gè)方法是一個(gè) outline (外聯(lián))方法,具體的參數(shù)拼接和 toString 被隱藏在函數(shù)內(nèi)部:

invoke-static {v1, v2, v3} Outline;.bind:([Ljava/lang/Object)Ljava/lang/String;

優(yōu)化步驟可以被簡單的分為如下幾個(gè)步驟:

  1. 生成一個(gè)泛型的外聯(lián)方法、以及數(shù)個(gè)特定參數(shù)的方法:我們可以認(rèn)為生成的方法大概是這樣的
@Keep
public static String bind(Object... args) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < args.length ; i++) {
builder.append(args[i]);
}
return builder.toString();
}
  1. 收集StringBuilder 的 CallSites :通過抽象解釋和不動(dòng)點(diǎn)分析,分析所有的 StringBuilder 操作,對(duì) append、new-instance、和 init 方法分類。判斷每次 append 的參數(shù)是不是 immutable 操作,如果增加的 insn 少于減少的 insn 即會(huì)減少代碼,就對(duì)這里進(jìn)行處理。
  2. 生成外聯(lián)方法調(diào)用:由于我們使用了泛型方法來接受參數(shù),因此我們要對(duì)基礎(chǔ)類型生成 ValueOf 的轉(zhuǎn)換操作、并且刪除append 方法前為了防止被錯(cuò)誤優(yōu)化我們還需要插入 move 指令來 copy 原有參數(shù)(這些 move 指令會(huì)被后續(xù)優(yōu)化正確刪除)、如果參數(shù)個(gè)數(shù)還在我們生成的特定 outline 方法范圍內(nèi)我們就可以使用特定方法來生成外聯(lián)函數(shù),其余的將使用泛化的外聯(lián)來接受。

DEX 格式優(yōu)化

InterDexPass

該 Pass 是針對(duì)跨 DEX 引用的優(yōu)化。

跨 DEX 引用是指當(dāng)一個(gè) DEX 需要“使用”到另一個(gè) DEX 中的類/方法/變量時(shí),需要在本 DEX 中保存一份對(duì)應(yīng)的類/方法/變量的 id,如果 2 個(gè) DEX 用到了相同的字符串,那么這個(gè)字符串在 2 個(gè) DEX 都需要進(jìn)行定義。所以,改變類/方法/變量和字符串在 DEX 中的分布,可以減小引用的數(shù)量,從而減小 DEX 的體積。從原理中也可以看出,該優(yōu)化對(duì)單 DEX 的應(yīng)用是無效的。

從上圖可以看到,進(jìn)行類重排后,DEX0 的類引用和方法引用數(shù)量都減少了,DEX 的體積也會(huì)因此減小。

具體的優(yōu)化過程是:

  1. 收集每個(gè)類涉及的所有引用,按照引用數(shù)量和類型計(jì)算出類的權(quán)重
  2. 根據(jù)權(quán)重計(jì)算出每個(gè)類的優(yōu)先級(jí)
  3. 根據(jù)優(yōu)先級(jí)選取一個(gè)類放入 DEX 中,然后調(diào)整剩余類的優(yōu)先級(jí),重復(fù)此步驟直到所有類都被處理

ReBindRefsPass

該 Pass 是針對(duì)方法引用的優(yōu)化,其原理同 InterDexPass。

在字節(jié)碼中,invoke-virtual/interface指令需要一個(gè)方法引用,在很多情況下,這個(gè)引用指向的是子類或者實(shí)現(xiàn)類的引用,把這個(gè)引用替換成父類和接口的方法引用不會(huì)影響運(yùn)行時(shí)邏輯,同時(shí)會(huì)減少 DEX 中方法引用的數(shù)量。在生成 DEX 的時(shí)候,方法引用的 65536 限制通常是最先遇到的瓶頸,該優(yōu)化也可以緩解這種情況。

如上圖所示,優(yōu)化前 caller 方法的 invoke 指令使用的是子類引用,其偽指令如下所示,需要用到 2 個(gè)引用

new-instance v0, Sub1
invoke-virtual v0, Sub1.a()
new-instance v1, Sub2
invoke-virtual v1, Sub2.a()

優(yōu)化后,invoke 指令都指向其父類應(yīng)用,2 個(gè)引用可以合并為 1 個(gè),減少了 DEX 中的引用數(shù)量

new-instance v0, Sub1
invoke-virtual v0, Base.a()
new-instance v1, Sub2
invoke-virtual v1, Base.a()

針對(duì)編程語言的優(yōu)化

KotlinDataClassPass

該 Pass 是對(duì) Kotlin data class 的優(yōu)化,基本思路是對(duì) data class 的生成代碼進(jìn)行精簡。

解構(gòu)聲明優(yōu)化

Kotlin 中存在解構(gòu)聲明這種語法,可以更方便的創(chuàng)建多個(gè)變量,基本用法如下

data class Person(val name: String,val age: Int)
val (name,age) = person("John",20)

kotlinc 會(huì)為Person類生成 get 方法和 componentN 方法,如下是偽代碼表示

Person {
String name;
Int age;

getName(): String { return name; }
getAge(): Int { return age; }
component1(): String { return name; }
component2(): Int { return age; }
}

// 解構(gòu)聲明編譯為
val name = person.component12 1()
val age = person.component2()

可以看到,get 和 component 的邏輯是一樣的,所以在編譯期,可以進(jìn)行全局的匹配,用 get 替換掉 component,然后再刪除 component。

toString 等生成方法優(yōu)化

kotlin compiler 為 data class 生成的 toString 具有相似的代碼結(jié)構(gòu),因此可以生成一個(gè)輔助方法,然后在所有 data class 的 toString 方法中調(diào)用這個(gè)輔助方法,即外聯(lián),從而減少指令數(shù)量。

equals 和 hashCode 也可以進(jìn)行類似優(yōu)化,但是風(fēng)險(xiǎn)相對(duì)較高,因此單獨(dú)為這些優(yōu)化配置了開關(guān),業(yè)務(wù)方可以視情況開啟。

提升壓縮率的優(yōu)化

RegAllocPass

DEX 及其他文件經(jīng)過壓縮打成 APK,如果能通過改變 DEX 的內(nèi)容來提升壓縮率,那么也會(huì)減小最終的包體積。RegAllocPass 就是通過重新分配寄存器來提升壓縮率的。

dx 生成 DEX 時(shí)使用的是線性寄存器分配算法,其基本步驟是進(jìn)行存活變量分析,然后計(jì)算出每個(gè)變量的活躍區(qū)間,再根據(jù)活躍區(qū)間依次為變量分配寄存器,超出活躍區(qū)間的寄存器可以進(jìn)行再分配,其優(yōu)點(diǎn)是運(yùn)行速度快,但結(jié)果往往不是最優(yōu)的。

比如下面的代碼,dx 分配了 6 個(gè)寄存器,v0 ~ v5

public static double calculateLuminance(@ColorInt int color) {
final double[] result = getTempDouble3Array();
colorToXYZ(color,result);
return result[1] / 100;
}

相對(duì)的,ReDex 使用了圖著色算法進(jìn)行寄存器分配,基本步驟是進(jìn)行存活變量分析,并構(gòu)建沖突圖,沖突圖的每個(gè)節(jié)點(diǎn)是一個(gè)變量,如果 2 個(gè)變量可以同時(shí)存活,就在兩個(gè)節(jié)點(diǎn)之間建立邊,最后為沖突圖著色,每個(gè)顏色代表一個(gè)寄存器,著色完成即寄存器分配完成。著色法相對(duì)更慢,結(jié)果一般更優(yōu)。對(duì)上面同樣的代碼,著色法使用了 4 個(gè)寄存器,v0 ~ v3。

DEX 中的方法使用的寄存器越少,其內(nèi)容重復(fù)率就越高,壓縮率也會(huì)更大,從而減小了包體積。

抖音落地

抖音是字節(jié)跳動(dòng)規(guī)模最大、運(yùn)行環(huán)境復(fù)雜度最高的應(yīng)用之一。在 ReDex 落地初期,由于對(duì)復(fù)雜度估計(jì)不足,在獨(dú)立灰度和全量灰度期間引起了一些問題,在解決問題的過程中,我們也逐步形成了一套迭代流程以保證優(yōu)化的穩(wěn)定性。下面介紹一下我們遇到過的典型問題及當(dāng)前的迭代流程。

遇到的問題

兼容性問題

一般來說,只要按照字節(jié)碼規(guī)范進(jìn)行優(yōu)化,就不會(huì)有兼容性問題,因?yàn)?dalvik/art 也是按照規(guī)范去校驗(yàn)和運(yùn)行字節(jié)碼的,即使進(jìn)行了錯(cuò)誤的優(yōu)化,引起的問題也應(yīng)該是共性問題。但很多事都有例外,ReDex 就在某品牌手機(jī)的部分 Android 5.x 的機(jī)型上遇到了問題。

從 log 和一些 hook 來看,某品牌手機(jī)對(duì) 5.x 的 art 做了大量的魔改,可以推斷其魔改存在一些問題,導(dǎo)致對(duì)正確的字節(jié)碼的校驗(yàn)和運(yùn)行也可能出現(xiàn)問題。一個(gè)可能的原因是:在 ReDex 進(jìn)行優(yōu)化時(shí),會(huì)對(duì)一些方法體的指令順序進(jìn)行重排,這種重排是不影響方法的邏輯的,但是可能會(huì)改變一部分指令,魔改后的 art 在校驗(yàn)這樣的方法時(shí)可能會(huì)報(bào) verify error,引起 crash。

最終通過黑名單配置跳過了這些方法的優(yōu)化規(guī)避了問題,在后續(xù)的優(yōu)化過程中,沒有再遇到類似的問題。

復(fù)雜場景優(yōu)化問題

抖音業(yè)務(wù)復(fù)雜,代碼寫法多樣,給靜態(tài)分析和優(yōu)化增加了一些難度,也更容易遇到問題。下面是 2 個(gè)典型問題:

1.空方法優(yōu)化問題 代碼中可能存在一些空方法,排除掉反射和 natvie 調(diào)用等場景后,剩下的空方法應(yīng)該是可以刪除的。但是在做優(yōu)化時(shí),卻遇到了 crash,如以下代碼

object XXXSDKHelper {
init {
initXXXSDK()
}
fun fakeInit() {
}
}

// 初始化任務(wù)
public class XXInitTask implements Runnable {
@Override
public void run() {
XXXSDKHelper.INSTANCE.fakeInit();
}
}

在初始化代碼中調(diào)用fakeInit,它是一個(gè)空方法,調(diào)用它的目的是觸發(fā)XXSDKHelper類加載從而執(zhí)行init語句塊,如果刪除了這個(gè)空方法,就會(huì)導(dǎo)致初始化未執(zhí)行,在后續(xù)的流程中拋空指針。

2.復(fù)雜反射問題

對(duì)于 Class.forname(...)等簡單的反射用法,靜態(tài)分析是可以分析出來的,但是對(duì)一些經(jīng)過字符串拼接或者嵌套之后的反射,靜態(tài)分析很難分析到。因此,對(duì)可能會(huì)被反射的代碼進(jìn)行優(yōu)化需要非常小心,通常來說,匿名內(nèi)部類是不會(huì)通過反射調(diào)用的,基于此前提,我們進(jìn)行了匿名內(nèi)部類的重命名優(yōu)化,但是在灰度后,發(fā)現(xiàn)某些第三方 SDK 會(huì)通過復(fù)雜的運(yùn)行時(shí)邏輯對(duì)匿名內(nèi)部類進(jìn)行了反射調(diào)用,最終導(dǎo)致了 ClassNotFoundError。

復(fù)雜場景的優(yōu)化問題有些是業(yè)務(wù)代碼不規(guī)范造成的,但更多的是優(yōu)化前提(空方法可以刪除/匿名內(nèi)部類不會(huì)被反射)不成立所導(dǎo)致,所以在進(jìn)行優(yōu)化時(shí)首先需要對(duì)假設(shè)進(jìn)行謹(jǐn)慎的驗(yàn)證。

迭代流程

為了減少穩(wěn)定性問題,我們總結(jié)了 ReDex Pass 的迭代流程。

在對(duì)一項(xiàng) Pass 有了初步構(gòu)思后,組內(nèi)會(huì)進(jìn)行可行性討論,如果理論上可行就進(jìn)入開發(fā)和驗(yàn)證階段,之后同步進(jìn)行至少 2 輪的獨(dú)立灰度驗(yàn)證和業(yè)務(wù)方 Pass 評(píng)審,最后進(jìn)行全量灰度驗(yàn)證。其中任意一個(gè)環(huán)節(jié)發(fā)現(xiàn)問題,都會(huì)重新進(jìn)行整個(gè)流程。

通過這個(gè)流程,我們大大減少了穩(wěn)定性問題遺留到灰度階段的可能,在不斷完善迭代流程的同時(shí)我們也在探索通過加強(qiáng)單元測(cè)試、自動(dòng)化測(cè)試等方式來提升質(zhì)量。

后續(xù)規(guī)劃

ReDex 仍然在持續(xù)迭代中,未來我們會(huì)在以下幾個(gè)方向繼續(xù)進(jìn)行深入探索:

  1. 更多包體積優(yōu)化的探索和迭代,同時(shí)探索字節(jié)碼優(yōu)化在性能提升方面的可能性
  2. 提升字節(jié)碼質(zhì)量
  • 更加嚴(yán)格的合法性校驗(yàn);ReDex 之前已經(jīng)檢測(cè)出若干自定義插件和 proguard 的問題,將問題攔截在了編譯期,后續(xù)會(huì)繼續(xù)提升該能力
  • 建立更加完善的質(zhì)量驗(yàn)證體系;ReDex 作為編譯期的全局字節(jié)碼優(yōu)化方案,如果保證優(yōu)化后的字節(jié)碼質(zhì)量一直是個(gè)痛點(diǎn),我們會(huì)繼續(xù)在單元測(cè)試、自動(dòng)化測(cè)試等方向探索質(zhì)量提升的手段
  1. 增加編譯期監(jiān)控,更加快速便捷的解決編譯期字節(jié)碼問題,提升接入體驗(yàn)
  2. 其他應(yīng)用方向探索;如方法插樁、某些條件下的死代碼掃描等。
責(zé)任編輯:未麗燕 來源: 字節(jié)跳動(dòng)技術(shù)團(tuán)隊(duì)
相關(guān)推薦

2022-05-07 15:51:47

Android資源文件文件名

2024-06-13 17:10:16

2022-06-06 12:19:08

抖音功耗優(yōu)化Android 應(yīng)用

2022-03-29 13:27:22

Android優(yōu)化APP

2022-06-07 15:33:51

Android優(yōu)化實(shí)踐

2023-07-19 22:17:21

Android資源優(yōu)化

2023-11-03 17:02:18

抖音直播畫質(zhì)優(yōu)化

2022-07-19 16:47:53

Android抖音

2023-03-03 15:43:23

抖音世界杯畫質(zhì)優(yōu)化

2022-07-06 13:02:00

高延時(shí)電商直播主播互動(dòng)

2022-04-28 15:07:41

抖音內(nèi)存泄漏Android

2022-10-28 13:41:51

字節(jié)SDK監(jiān)控

2024-11-13 08:47:24

2023-04-14 15:31:55

SwiftToolchain

2022-08-26 16:24:19

抖音體系化建設(shè)項(xiàng)目

2022-08-31 14:42:32

抖音包體積特效中臺(tái)

2013-03-27 09:17:17

Android開發(fā)AndroidList

2022-04-28 09:36:47

Redis內(nèi)存結(jié)構(gòu)內(nèi)存管理

2023-10-31 12:50:35

智能優(yōu)化探索
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)