在Android中使用反射到底有多慢?

在Android中使用反射到底有多慢?

反射(Reflection)在Java和安卓開發過程中非常有用,但是反射的使用往往是APP嚴重性能問題的根本原因。下面我們通過分析幾個真實的案例來幫助我們更直觀的理解這個問題。

兩個真實的案例

第一個案例是紐約時報安卓客戶端。在NimbleDroid的幫助下,紐約時報的開發者發現Gson中的type adapter使用了反射,增加了APP700毫秒的啓動時間,他們通過自行實現自定義的type adapter解決了這一問題。

第二個案例是大型圖片分享平臺Photobucket,反射的使用也是它們的一個巨大性能瓶頸:

com.photobucket.android Icicle Graph構造 com.photobucket.api.client.jersey.UserClient constructor 花費了660毫秒

我們可以看到構造 com.photobucket.api.client.jersey.UserClient 花費了660毫秒。進一步分析冰柱圖,我們可以看到延遲的來源是對反射的使用:

com.photobucket.android Iricle Graph存在大量反射的調用,例如:java.lang.Class.getGenericInterfaces

getGenericInterfaces() 函數會返回一個類型直接實現的接口類型,這裏對它進行了5次調用,每次耗時大約81毫秒。當然,如果只看單次調用花費的時間可能影響不大,但是所有調用的總時間就很長了,僅僅是對這個方法的調用就造成了大約600毫秒的啓動延遲。現在讓我們更深入的分析一下爲什麼這個方法的調用會耗費如此長的時間。

我們發現,這個庫支持開發者通過註解(annotation)來定義REST API,但問題是這個庫並沒有在編譯期間去處理這些註解,而是在運行時進行通過反射進行處理,從性能的角度來看,這一點是災難性的。

微基準測試(Micro-benchmarks)

我們創建了一個簡單地測試代碼來測試反射究竟有多慢。

我們在 android.app.Activity 類中反覆進行了10,000次操作,代碼如下:

[代碼]java代碼:

?
1
2
3
4
Class<!--?--> clazz = android.app.Activity.class;
for(inti = 0; i < 10000; i++) {
    clazz.getFields();
}


我們還創建了兩個測試用例,通過創建一個空的類型的實例,來測試反射造成的開銷,代碼如下:

[代碼]java代碼:

?
1
2
3
4
5
6
7
8
9
try{
    for(inti = 0; i < 1_000_000; i++) {
        DummyItem.class.newInstance();
    }
}catch(InstantiationException e) {
    e.printStackTrace();
}catch(IllegalAccessException e) {
    e.printStackTrace();
}


下面是我們的測試結果(時間單位均爲毫秒,用的是有真正使用的手機,所以結果更接近真實用戶體驗):

  NEXUS 5 (6.0) ART GALAXY S5 (5.0) ART GALAXY S3 mini (4.1.2) Dalvik
getFields 1108 1626 27083
getDeclaredFields 347 951 7687
getGenericInterfaces 16 23 2927
getGenericSuperclass 247 298 665
makeAccessible 14 147 449
getObject 21 167 127
setObject 21 201 161
createDummyItems 312 358 774
createDummyItemsWithReflection 1332 6384 2891

顯然,在Android中使用反射非常慢,使用反射的測試執行分別需要1332毫秒、6384毫秒、2891毫秒,而不使用反射則只需要312毫秒、358毫秒、774毫秒。一個有趣的現象是,安卓5.0 ART環境下,高端設備使用反射耗費的時間,比安卓4.1 Dalvik環境下的低端設備更長,只有安卓6.0 ART環境才提升了反射的性能,但是反射仍然非常慢。

更多的真實案例

ActiveAndroid是另一個使用了反射技術的庫,讓我們通過測試一些Google Play商店的APP來分析反射對APP啓動速度的影響。

下面是Scribd的測試結果:

Scribd Iricle Graph調用 com.activeandroid.ActiveAndroid.initialize 耗費1093毫秒

Myntra也存在同樣的問題:

Myntra Iricle Graph調用 com.activeandroid.ActiveAndroid.initialize 耗費1421毫秒

我們可以看到,ActiveAndroid的初始化耗時超過1秒。1秒其實已經很長了,尤其是考慮到用戶對APP啓動時間的期望是2秒以內

總結一下,在Android中使用反射非常之慢。爲了向用戶提供最流暢的用戶體驗,我們強烈建議:

儘可能避免反射的使用(以及使用了反射的第三方庫),尤其是使用類型反射來對Java對象進行序列化操作。


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