免费99精品国产自在现线观看_人妻少妇精品视频区性色_丝袜 屁股 在线 国产_无码视频在线免费观看

和無用代碼說再見!阿里文娛無損代碼覆蓋率統(tǒng)計方案(阿里文娛app)

和無用代碼說再見!阿里文娛無損代碼覆蓋率統(tǒng)計方案(阿里文娛app)

作者 | 阿里巴巴文娛高級無線開發(fā)工程師 孫瓏達

責編 | 屠敏

和無用代碼說再見!阿里文娛無損代碼覆蓋率統(tǒng)計方案(阿里文娛app)

背景

為了適應產品的快速迭代,通常大量的研發(fā)資源會投入在新功能的開發(fā)上,而針對無用功能的治理卻很少被關注。隨著時間的推移,線上應用會積累大量的無用代碼,再加上人員更迭以及功能交接,治理無用代碼的成本越來越高。最終應用安裝包過大,導致應用下載轉化率降低、應用平臺上架受限(例如超過100M的應用不能上架谷歌商店)、研發(fā)效率降低等等。

如何治理無用代碼?首先是代碼靜態(tài)掃描。對于Android應用,ProGuard工具可以在構建階段靜態(tài)分析代碼引用關系,自動裁減掉沒有被引用到的代碼,減少安裝包大小。

當然,只有代碼靜態(tài)掃描是不夠的,因為它不能代表線上用戶實際的使用情況,所以還需要一套線上用戶代碼覆蓋率的統(tǒng)計方案。

下面我將從Android應用的線上代碼覆蓋率統(tǒng)計切入,分享優(yōu)酷的無用代碼治理的技術思考和落地方案。

和無用代碼說再見!阿里文娛無損代碼覆蓋率統(tǒng)計方案(阿里文娛app)

傳統(tǒng)采集方案

首先,在需要統(tǒng)計的代碼處加上統(tǒng)計代碼。當代碼被執(zhí)行時,進行統(tǒng)計和上報。應用的代碼行數(shù)通常都數(shù)以萬計,手動添加顯然是不現(xiàn)實的,所以一般會在構建階段通過面向切面編程(AOP)來插入統(tǒng)計代碼(以下簡稱為插樁),可以借助一些成熟的AOP中間件完成,例如,Jacoco、ASM。

其次,需要思考是,我們期望采集的粒度是什么?一般來說,粒度從細到粗分為:指令、分支、方法、類級別,粒度越細,代碼覆蓋率結果越準確,但性能損耗也越大。例如,如果想采集的粒度為指令級別,就需要對每個指令進行插樁,但這種插裝會導致指令數(shù)也翻倍,安裝包增大并且運行時性能下降。

優(yōu)酷曾嘗試過用Jacoco進行分支粒度的插樁,當時希望覆蓋盡量多的用戶,因為覆蓋的用戶越多結果越準確。但經(jīng)測試,此方案使安裝包增大10M,運行時性能嚴重惡化,果斷放棄了此方案。

為了權衡性能和采集粒度,目前我們一般都采取類級別粒度的插樁,一方面是因為這樣對性能影響較小,另一方面過細的采集粒度反而會加重業(yè)務方治理的難度。但此方案還不夠完美:

1)運行時性能:當類首次加載時會執(zhí)行統(tǒng)計代碼,App啟動過程會加載成千上萬個類 ,會對啟動性能造成一定影響;

2)包大?。河卸嗌賯€類,就會插入多少行統(tǒng)計代碼,對于像優(yōu)酷這種大型App,也會增加不少的安裝包大?。?/p>

3)構建耗時:因為構建過程中需要對每個類進行插樁,增加了構建耗時;

和無用代碼說再見!阿里文娛無損代碼覆蓋率統(tǒng)計方案(阿里文娛app)

新采集方案—SlimLady

? 目標

優(yōu)酷希望有一套方案可以無損地采集線上代碼覆蓋率,核心目標如下:

  1. 運行時性能:無任何影響;

  2. 包大?。簾o任何影響;

  3. 構建耗時:無任何影響;

? 實現(xiàn)

通過研究源碼,發(fā)現(xiàn)可以通過動態(tài)查詢DVM虛擬機已加載類的信息來獲取類級別的代碼覆蓋率,下圖中“覆蓋率采集”部分即為SlimLady采集的原理圖,這里我們只關注這部分,其他部分將在后面的整體方案中進行講解。

和無用代碼說再見!阿里文娛無損代碼覆蓋率統(tǒng)計方案(阿里文娛app)

ClassTable

Java虛擬機規(guī)范規(guī)定,類在使用前要先被虛擬機加載。Android中,是通過ClassLoader來完成類加載的,最后保存在Native層的ClassTable中,所以如果我們獲取了所有ClassLoader的ClassTable對象,就有可能判斷出虛擬機加載了哪些類。

首先,獲取所有的ClassLoader對象。對于APK中的類,如果無特殊聲明,一般都會被默認的PathClassLoader加載;對于動態(tài)加載的類,需要在自定義的ClassLoader中加載,例如Atlas會為每個Bundle創(chuàng)建一個相應的ClassLoader,通過這個ClassLoader來加載Bundle中的類。一旦明確了App中用到了哪些ClassLoder,獲取是易如反掌的

其次,通過ClassLoader來獲取ClassTable的對象的地址。通過Java層ClassLoader類的源碼可知,ClassLoader有一個成員變量classTable(7.0及以上版本),這個變量保存了Native層ClassTable對象的地址,我們可以通過反射獲取這個地址:

ClassLoader classLoader = XXX;
Field classTableField = ClassLoader.class.getDeclaredField("classTable");
classTableField.setAccessible(true);
long classTableAddr = classTableField.getLong(classLoader);

但在9.0的系統(tǒng)中成員變量classTable被加入了深灰名單,限制了直接反射,需要通過系統(tǒng)類進行反射繞過此限制:

ClassLoader classLoader = XXX;
Method metaGetDeclaredField = Class.class.getDeclaredMethod("getDeclaredField", String.class);
Field classTableField = (Field) metaGetDeclaredField.invoke(ClassLoader.class, "classTable");
classTableField.setAccessible(true);
long classTableAddr = classTableField.getLong(classLoader);

至此,我們就獲得了所有的ClassTable對象的地址,里面保存了全部的類加載信息。

類名列表

通過閱讀源碼發(fā)現(xiàn)ClassTable有個方法可以通過類名查詢類是否被加載過(下節(jié)將詳細介紹),這樣我們只需要獲得所有類名的列表,再調用那個方法,即可以判斷類是否被加載過。

APK中的類名列表可以通過DexFile進行獲取,如下:

List<String> classes = new ArrayList<>;
DexFile df = new DexFile(context.getPackageCodePath);
for (Enumeration<String> iter = df.entries; iter.hasMoreElements; ) {
classes.add(iter.nextElement);
}

同理,動態(tài)加載的類也可以通過DexFile獲取;

類是否被加載

通過閱讀源碼發(fā)現(xiàn)class_table.cc中,ClassTable有個Lookup方法,傳入類名和類名的hash值,返回類對象的地址,如下:

mirror::Class* ClassTable::Lookup(const char* descriptor, size_t hash)

如果返回值為ptr,說明沒有加載過此類,否則,說明加載過。

mirror::Class* ClassTable::Lookup(const char* descriptor, size_t hash)

獲取此方法地址的方法:

  1. 加載so:class_table.cc在libart.so中,所有我們需要用dlopen加載libart.so獲得此so的handler。其實在加載前,libart.so在當前進程一定已經(jīng)被加載過了,此次加載只是為了獲得handler,并不耗時;

  2. 符號表:通過readelf查詢Lookup的符號:_ZN3art10ClassTable6LookupEPKcj;

  3. 方法指針:調用dlsym,傳入handler和符號表,即可以找到Lookup方法的地址;

注:從7.0系統(tǒng)開始,Google禁止了調用系統(tǒng)Native的API,這里我們通過/proc/self/maps找到libart.so的地址,將里面的符號表進行拷貝,進而繞過此限制;

至此,我們就可以通過調用ClassTable的Lookup方法,傳入類名和hash值,判斷類是否被加載過了。

總結

這樣,我們就能知道某一時刻有哪些類被加載過,對其上傳,進行聚合和處理,再通過對比所有類名列表,就能得到代碼覆蓋率數(shù)據(jù)了。此方案不需要插樁,所以可以無損地采集覆蓋率。

和無用代碼說再見!阿里文娛無損代碼覆蓋率統(tǒng)計方案(阿里文娛app)

新方案整體設計

上面提到的采集方案是整個方案的核心,除此之外還有上下游的配套流程,整體方案的設計如下圖:

和無用代碼說再見!阿里文娛無損代碼覆蓋率統(tǒng)計方案(阿里文娛app)

1)APK分發(fā):通過構建中心構建出最新的APK,分發(fā)給用戶;

2)觸發(fā)采集:用戶安裝應用,在使用過程中,將APP退后臺10s后,通過采樣率計算是否命中,若命中,則觸發(fā)代碼覆蓋率采集

3)配置下發(fā):在需要時,可以通過配置中心下發(fā)配置來動態(tài)調整功能開關、采樣率等配置;

4)數(shù)據(jù)采集:代碼覆蓋率采集中間件(SlimLady)統(tǒng)計出被加載的類,將已加載的類名保存在文件中,進行壓縮,將壓縮后的數(shù)據(jù)傳給上傳中間件;

5)數(shù)據(jù)上傳:上傳中間件將數(shù)據(jù)上傳到云端;

6)數(shù)據(jù)下載:服務器定期對云端數(shù)據(jù)進行下載;

7)類信息提供:服務器從構建中心獲取類信息,包括所有類名列表和混淆文件;

8)數(shù)據(jù)解析:服務器按版本對代碼覆蓋率數(shù)據(jù)進行解壓、反混淆、聚合統(tǒng)計,聚合統(tǒng)計后的結果包含了被加載過的類及次數(shù),與所有類名列表進行對比,即可以知道哪些類沒有被加載過,將結果保存至數(shù)據(jù)庫;

9)結果聚合:網(wǎng)頁端從數(shù)據(jù)庫讀取聚合結果,按模塊展示代碼覆蓋率、模塊熱度、模塊大小等信息。

和無用代碼說再見!阿里文娛無損代碼覆蓋率統(tǒng)計方案(阿里文娛app)

總結

本方案突破了傳統(tǒng)的插樁埋點統(tǒng)計,動態(tài)獲取虛擬機信息,無損地采集代碼覆蓋率。有了代碼覆蓋率數(shù)據(jù),能做的治理有很多,例如:下線無用代碼、模塊;瘦身或下線調用低頻、體積大的模塊;在集成階段添加代碼覆蓋率卡口等等。

相關新聞

聯(lián)系我們
聯(lián)系我們
在線咨詢
分享本頁
返回頂部