資深谷歌安卓工程師對安卓應用開發的建議

擅長Java語言的資深開發者們,多年以來可能是工做在網頁,服務器,和桌面系統等開發領域。這些領域的經驗幫助他們創建起來了本身使用Java語言的模式和本身的Java庫的生態系統。可是移動應用的開發卻和這些領域的java開發有着天壤之別。優秀的安卓應用開發者須要考慮到移動設備的限制,從新學習怎麼樣去使用java語言,怎麼樣去有效地使用實時環境和安卓平臺,而後寫出更好的安卓應用程序。javascript


About the Speaker: Romain Guy

Romain是谷歌的安卓工程師。在加入Robotics以前,他在安卓Framwork組參與了安卓1.0到5.0的開發工做。他如今又從新加入了安卓的新UI和圖形圖像相關的項目。java

About the Speaker: Chet Haase

Chet 也是谷歌的工程師。 他如今是安卓UI Toolkit 組的組長,他擅長於動畫,圖像, UI控件和其餘能帶來安卓更好的用戶體驗的UI組件的開發。他還擅長撰寫和表演喜劇。linux


介紹 (0:00)

本次演講是以安卓平臺組寫的近10篇文章爲基礎的。全部的文章都可以在Medium網站上看到,文章的第一部分請看這裏. 今天咱們會講到這些文章裏面的一些東西,若是你對特定的話題感興趣或者想深刻了解它們,請去閱讀原文。android

爲何移動開發如此艱難? (1:47)

有限的內存 (1:47)

咱們發現谷歌公司裏面的應用開發者有一個大問題,他們對口袋裏天天都攜帶着的安卓手機的本質都有些誤解。這些設備內存,CPU的處理能力和電池的待機能力都很是有限。開發者們必須理解大家的應用不是在設備上惟一運行的應用。內存是很是有限的資源,而且被整個系統共享着。我所在的Android平臺組很是當心地對待這個問題,這也是爲何有的時候咱們建議的一些規則看起來會有一點極端的緣由。咱們須要有全局的眼光來看待這個問題,由於系統會同時運行20、30、40個的進程。當你只爲單一應用開發的時候,牢記這些限制是比較困難的。算法

今天咱們手上的設備每每會有1-3GB的內存,但不是全部的安卓設備都有這麼多的內存的。安卓陣營中有14億的設備,其中許可能是兩三年前的產品。事實上,他們纔是着14億設備的主力軍。大部分的設備不是在美國和西歐,而是在中國和印度這樣的國家。在這些國家裏,安卓設備必須便宜才能吸引更多的消費者。shell

安卓在從4.3到4.4的演進過程當中,咱們使出渾身解數才使安卓系統可以成功地在內存受限的系統上運行起來。很長時間以來,由於人們想在便宜的500MB內存的設備上使用安卓系統,薑餅系統成了他們惟一的選擇。固然,如今的狀況已經大不相同,薑餅設備已經差很少消失殆盡了。可是,由於如今的安卓版本有着龐大的大內存消耗的應用羣,這就須要更強大的框架和平臺。數據庫

這是一件你必須持續思考的事情,可是這很難,由於它不總在你優先考慮的事情的範疇內。你可能知道Knuth的名言「過早的優化是一些罪惡的根源」。雖然我贊成他的觀點,但是當你寫完你的應用,而後打算從應用中再減小50MB的內存的使用,也是一件很是困難的事情。咱們不是說你須要毀掉你的應用系統架構或者犧牲你的測試,可是每一次你能作些改進來改善你的內存使用狀況的時候,請立刻動手。編程

爲了整個設備有更好的總體用戶體驗而開發。當你的應用很大的時候,你會致使系統殺掉別的應用,這樣你就影響到了別人應用的用戶體驗。當別人的應用不注意內存使用狀況的時候,你的應用體驗也不會好。大家須要互相交流而後找到和諧共存的方法,請牢記這條建議。緩存

若是系統須要殺掉除了你的應用以外的全部應用,那麼當你的應用退出之後,其餘應用須要從新創建他們可運行的環境。這樣你的應用會看起來像一個惡意的應用。若是你想要讓用戶重入你的應用時有一個良好的體驗的話,請盡你所能將你的應用保持在後臺運行而且消耗最小的內存。安全

在有些應用當中,實現內存回調接口是很是管用的。你能在API文檔中很是容易的找到onTrim的內存回調函數,而且能實現多個級別的Trim操做。基於你的Trim操做的級別,你能夠釋放一些緩存和bitmaps,也能夠退出一些Activity,或者其餘的任何能幫助你留在後臺運行的措施。

CPU 處理器 (8:07)

移動處理器顯然比桌面和服務器的處理器能力要慢不少。雖然從外界不容易察覺,可是移動處理器大部分時候都是處在過熱降頻保護的狀態。這意味着儘管CPU的頻率很高,可是仍然不能像桌面和服務器的處理器同樣快。當諸如用戶在屏幕上拖拽的時候,CPU能夠達到它的最快處理速度。但是在其餘的狀況下,CPU處理器是跑在待機頻率上的,否則的話,電池不能保證用戶的基本待機時間。

若是你買的是2GHz的臺式機或者筆記本,CPU大部分時候是能跑在那個參數指標上的。但是你若是買的是一個2GHz的手機,CPU只能有時候跑在2GHz上。若是你買的是八核的手機,硬件上你是有八個處理核,可是他們大部分時候不會一塊兒工做。基本上,手機盒子上的參數表和你實際上能達到的參數是有不一致的地方的。這主要是爲了降頻保護CPU,提升電池待機時間和減小發熱量。

GPU 圖像處理器 (10:10)

和CPU同樣,GPU也有保護降頻的功能。紋理加載(Texture uploads)的開銷特別大。全部bitmap的操做或者結果是bitmap的操做都會被加載到GPU。舉個例子說,當你在繪製路徑時,這些東西會轉成bitmap而且做爲紋理加載到GPU,這時系統的開銷就會特別大。你的性能瓶頸就可能在這。

填充率和分辨率之間也是相關聯的. Nexus 6P的分辨率很是高,換句話說,屏幕上有許多的像素點要填充。可是GPU的性能帶寬卻跟不上。系統爲了填充全部的像素點而超負荷的工做。你的要求越高,系統須要的時間就越長。

一個提高UI 性能的技巧就是避免過分刷屏。你須要系統填充屏幕上每次像素的次數越多,當屏幕愈來愈大和分辨率愈來愈高的時候,狀況就會愈來愈糟。若是你有一個Playstation 4或者Xbox One的話,他們在運行那些1080p的遊戲(每秒30幀)時十分卡頓。在咱們的手機上,咱們有更高的分辨率,並且咱們以每秒60幀的速度完成全部的事情;咱們儘量的完成更多的事情而且消耗更少的電量。這就是爲何咱們對圖片的處理性能有着諸多的要求以及對應用優化的建議,由於咱們沒有你想象的那麼強的處理能力和電池電量。

內存 = 性能 (12:29)

若是你有大的應用,那麼就會發生更多的內存頁的置換,更慢的內存分配。實際運行中,系統須要遍從來決定新的對象能夠放到內存何處,這須要花費更多的時間。回收也會須要花費更多的時間;每次須要內存的時候都要遍歷更多的東西。

總體上內存回收機制也被更多地觸發。在咱們的演講中,咱們詳細闡述了內存回收是如何工做的,並且討論了ART和Dalvik的改進。詳情請看 上面的視頻 at 13:06

低端設備 (19:40)

你口袋裏面的設備保證比你用戶的設備要快不少。你認爲的那些老舊設備並無消失。 A 它們依舊在用戶的口袋中,並且用戶打算儘量長地使用它們。B 那些老舊的設備雖然慢可是便宜。這意味着它們能吸引哪些不可以購買最快設備的人們。

流暢的幀頻率 (21:01)

找到一個流暢的幀頻率是十分困難的。你只有16毫秒的時間去完成全部的事情。這些事情包括觸屏事件的處理,計算,佈局,繪製幀,而後交換緩存。16毫秒意味着每秒刷新60幀,這是咱們在安卓系統上要求的,由於咱們是V-Sync。咱們不但願屏幕花屏。花屏在有些遊戲中你能看到,由於它們在一秒內同時有兩個buffer。那個看起來太可怕了,咱們不想讓你這麼作。這就意味着若是你僅僅多花了一點時間,哪怕17毫秒,咱們就會跳過一幀。而後跳過一次V-sync,這樣系統就不是60fps了,而是30fps。咱們叫這種現象爲Jank。

系統在60fps和30fps中來回跳躍是件很是糟糕的事情。這會讓你的用戶以爲你的應用很是janky和不一致。也有可能你的應用會一直表現糟糕,一直都是30fps。這就是許多遊戲當它們認爲作不到60fps的時候,它們就一直都是30fps。

實時運行: 語言 ≠ 平臺 (22:48)

幾周前,有人問我 「當有新版本的JDK發佈的時候,你通常作什麼?」。 而後我意識到他們並不理解語言和實時運行環境是不同的。當有新的JDK發佈的時候,我基本不關心。它和安卓運行時一點關係都沒有。咱們使用同一種語言,但那不意味着咱們在運行的時候是同樣的。

當人們使用Java語言的時候,有三個方面的因素構成了整個java體驗。Java編程語言自己,實時運行環境(在有些server環境中叫HotSpot)而後是硬件設備。

對於那些擅長服務器的人來講,服務器的實時運行環境提供諸如移動,壓縮收集器的功能,這些都意味着臨時的內存分配帶來的系統開銷很是小。這些事情和移動指針同樣快。在服務器環境中,的確如此。而後你會理所固然的認爲移動設備有同樣快的處理器,同樣大的內存,因此處理能力就應該和服務器同樣無限制。

在安卓系統中,狀況是很是不同的,特別是你習慣於無限的系統資源和徹底不一樣的實時運行環境的時候。咱們有Dalvik和ART。咱們沒有壓縮,這意味着當你分配一個對象的時候,這個對象就會存在堆裏面。堆會碎片化,也會使得尋找空餘的內存空間變得開銷更大和更困難。在ART環境中,咱們有空閒時的壓縮。ART歷來不能在應用是前臺運行的時候壓縮堆空間,可是當它在後臺運行的時候是能夠壓縮堆空間的。在進程的生存週期裏面,有的時候壓縮是會發生的,這會幫助系統尋找空閒內存。

壓縮在本地代碼上的做用更爲關鍵。若是你的應用使用JNI,而且分配一個指針給一個java對象的時候,那個指針一般會被系統認爲一直有效。在ART環境中,若是你的堆被壓縮了,那指針就會變成野指針。若是你使用JNI,在開始的時候你就必須對這種狀況特別當心。

UI 線程 (26:56)

安卓是一個單線程的UI系統。技術上,你能夠有多個UI線程,但這會帶來不少麻煩。正如你擔憂的那樣,全部的UI控件都是在同一個線程裏。由於是一個UI線程,因此你任何阻塞UI線程的操做都會帶來性能上的影響,jankiness(閃屏)和不一致性。在UI線程上分配內存就更糟了。若是你有後臺運行的線程分配內存,當虛擬機VM阻塞全部的線程(包括UI線程)十幾毫秒的時候,你就會跳過一幀。這樣,你就只有30fps,這會很是糟糕。

存儲 (28:53)

存儲的性能沒有一個定論。有時候人們把數據存在SD卡上,這就會有各類各樣的性能標準。當存儲設備快要滿的時候,即便在同一個存儲設備上也是有不一樣的性能體現的。可是若是你的應用是依賴每一個測試設備的存儲速度的話,那總歸是很差的。寫flash存儲的時候也意味着控制器須要作不少事情,由於它須要收集信息,記錄哪些空間已用和哪些空間沒用。換句話說,若是你的應用在後臺作大量的IO操做的話,你會把這個系統拖慢。你應該有這樣的經驗,當Play Stoer 在後檯安裝應用更新的時候,你的設備會忽然間感受慢了起來。由於它在後臺作磁盤寫操做,因此前臺的應用讀它們本身資源也會變得很慢。

存儲的大小也是各式各樣的,因此不要讓你的應用對特定的存儲大小有依賴。APK的大小也很重要。你應用中資源的大小會影響到你的啓動時間。

優化你的應用資源的辦法有不少。如今在老的版本的安卓上,也有了矢量圖。若是能夠的話,請使用安卓的SVG庫。雖然對於圖標來講不是太合適,但的確能對你的應用起好的做用。你也可使用WebP格式,這會比PNG省下20%-30%的存儲空間。PNG Crush也是一個很好地離線工具。APT作了一部分PNG crush的工做,可是還有不少工具會作的更好,更有效。

網絡 (28:53)

你使用的網絡確定比大多數用戶的網絡要快。他們可能還在用着2G網絡,而且流量很貴。因此你的應用不該該依賴持續的網絡鏈接。也許你應該讓你的應用可以智能地下載它須要的內容或者你的應用不須要依賴網絡下載的內容就能使用,真正的內容能夠晚點再去下載。

開車去Utah的路上,你能夠測試在糟糕的網絡環境下,你的應用的表現如何。或者你能夠用一些模擬糟糕網絡的工具。全部這些應用的測試都是手工進行的。

每一臺設備都是一個村莊 (33:31)

每一臺設備都是一個村莊,這意味着每一個在村莊裏的人都須要共同努力來維護一個好的用戶體驗。你可讓這個體驗特別差,也能夠特別好。若是你的應用想成爲好的體驗的支持者,你能夠試着在你的manifest裏面關閉Service和broadcast receivers。在代碼裏面,當你的應用發現用戶打算使用你的應用的時候,你才動態的開啓你的services和receivers。郵件客戶端就是一個好的例子,當用戶安裝郵件客戶端的時候,全部的一切都應該處於關閉的狀態。一旦用戶點擊了該郵件客戶端而且加入了帳戶的時候,你纔開啓你的services和receivers。而後下次用戶重啓設備的時候,Framework就會記錄下來,你的服務就真正的運行起來了。這個方法很簡單,但也很重要。由於你的services雖然運行起來了,可是沒有人用,你會對別人帶來沒必要要的壞的影響。

另一個現象叫作」公共地帶的悲劇「: 每一個人都認爲他本身的應用是最重要的。若是每一個人都是這樣的觀點,那麼每一個應用都會很是大並且儘量被激活,這樣設備會承受不了。勿以惡小而爲之,勿以善小而不爲。

技巧與建議 (35:53)

瞭解你使用的語言 (35:55)

開發者們可能有了不少年Java的經驗,可是卻不能最好地利用語言來發揮出移動設備的最大性能。

不要使用Java的序列化

序列化自己是很是有用的,這不是咱們如今討論的話題。若是你用過序列化的話,你會發現不是太好用,由於你須要產生UUID,並且它還有限制。序列化也會慢,由於它用到了Reflection。

使用安卓的數據結構

其餘場合Java開發者經常使用到的一些集合類和方法在Android上也許就不合適了。Autoboxing 和 Primitive Java 類型的替代品在Java領域裏使用的很是普遍。集合的迭代器也是很是經常使用的。在Android平臺上,咱們特別建立了一些集合類來替代這些模式。對於Key來講,咱們使用基本類型,因此若是hash表裏面是一個整形做爲key的時候,咱們沒有autoboxing。你能夠以使用SparseArray類,一樣的,ArrayMap和SimpleArrayMap也是HashMap的替代者。

爲了不java package中基礎類型的額外開銷,使用Android的數據結構類型是個不錯的選擇。HashMap裏面的每一個條目都會多用4倍的內存空間,像你的int類型同樣。你放在hashmap裏面的內容越多,你浪費的內存資源也就越多。看看你如今已經使用的數據結構,若是你打算在某些狀況下重寫你本身的數據類的話,不要猶豫。

當心使用XML和JSON

他們相對來講太大了,對於你的某些應用來講,他們或許太結構化了。若是你使用有線設備上的數據格式會更加簡潔,可是至少你能在你作網絡傳輸的時候gzip這些數據。固然,若是你打算序列化你的對象到磁盤上,你可能須要找找其餘的替代方案了。

避免使用JNI

有些時候你須要JNI,可是若是不方便就不要使用它了。JNI有些有趣的事情:每一次你越過JAVA和Native的邊界的時候,咱們都須要檢查參數的有效性,這會對GC的行爲有影響,並且帶來額外的消耗。有的時候這些消耗是很是昂貴的,因此當你使用了不少JNI的調用的時候,你可能在JNI調用自己上花的時間比在你native代碼執行的時間都要多。若是你有些老的JNI代碼,請仔細檢查他們。

有一件你能提升JNI效率的事情就是儘量的把屢次調用集中到一次。避免在JAVA和Native中間來回調用,一次搞定。例如,在Android的Graphic Piplline中,咱們在每次調用JNI的時候傳入了儘量多的參數,從而避免了調用JNI的時候,JNI從JAVA對象中抽取參數的開銷。咱們使用了基礎類型,避免了使用奇怪的對象,咱們傳入的是Left,bottom和right參數,因此咱們不須要又返回到JAVA層了。

基礎類型 vs. 封裝後的基礎類型

請使用基礎類型來代替封裝後的基礎類型。一個不那麼明顯的事實是:若是你使用那些集合類而且比較它們是否相等的時候,咱們每次都會作autobox。每次都須要分配一個對象去使用,由於它們會被強制轉換成boxed equivalent。這裏有個例外,你可使用big boolean,由於它們只有兩個。

避免使用反射(Reflection)

比避免使用JNI更進一步,我須要如我建議避免使用JNI同樣指出來,animation framework使用了JNI來避免使用反射。這樣固然多了雙重內存分配的開銷,由於咱們須要分配這些對象而且每處都要autobox他們,同時從Java運行環境發起的函數調用才用的內部機制的開銷特別大。

當心使用 finalizers

內部咱們只在頗有限的狀況下才用。關於finalizers有個事實不太明顯的是:它須要兩次完整的GC才能收集完全部的事情。若是你在一個finalizer裏面放置某些assert來收集信息的話,咱們須要運行兩次完整的GC才能回來處理。有些時候這是必要的,可是在其餘的一些狀況下,把這些處理放在離finalizer以外的近點的地方會更方便。否則的話開銷太大了。

網絡 (47:19)

不要過分同步

正如前面描述的那樣,你的用戶的網絡可能不那麼好,或者他們的網絡會很貴。並且,你會給系統帶來負擔。也許你認爲你的應用須要儘量快地獲得那些數據,可是事實上是你會給系統帶來不少的負擔,僅僅爲了保持你的應用處於激活狀態。

容許延時下載

這在信號很差的網絡和收費很貴的網絡下十分重要。你能夠把數據打包起來。把全部須要下載的東西收集在一塊兒,而後作一次同步。

谷歌雲消息 Google Cloud Messaging

GCM收集了不少東西。他會使用傳送層來和後臺服務器傳送數據,因此你也許能夠重用這個系統來避免本身重複工做,也避免了每一個應用都建立本身的socket。

GCM 網絡管理(Job 調度)

這也是個很好的東西能夠利用起來。Job 調度可讓你把你的東西打包起來。你能夠說 「我想在空閒的時候才使用這些事務,或者當我被激活的,或者當我在wifi連上的時候」,而後在普通的時間間隙裏面收集這些事情。這會更有效,對用戶也不會太突兀。

不要輪詢

永遠不要輪詢

只同步你須要的

你知道你的應用裏面發生了什麼,因此僅僅只要同步當前應用須要的數據,而不是把全部可能須要的數據都同步下來。

網絡質量 (50:05)

不要對網絡有任何的假設。爲低端網絡開發,而後保證你的應用在低端網絡下測試過。即便是你在使用模擬器,你也須要保證你在咱們稱爲「糟糕的網絡」的環境下測試過。在這些狀況下,你應用的表現會讓你大吃一驚的。

數據 & 協議 (51:13)

若是你擁有服務器,作全部能幫助到設備的事情。好比改變數據格式,改變發送數據的類型。設備來告訴服務器它打算瀏覽圖片的大小,也是個好方法。咱們看到過內部的應用接收到了比屏幕大小大4倍的圖片,而後在設備端從新處理圖片的大小。這個工做明顯服務器來作比較合適。

使用縮小和壓縮的算法。gzip是個好的選擇。若是你能在HTT上傳送GZIP的數據,請這樣作。這會頗有用,特別是在糟糕的網絡上。GCM也能夠幫忙。它會幫助你保持鏈接,因此,一個好處就是使用它會有更短的延時,由於不用在每次鏈接的時候都作一次握手並且在延時方面,移動網絡是出了名的糟糕的。

存儲 (52:21)

不要寫死你的文件路徑

路徑會改變的,若是用戶想把它們存到別處呢?

僅僅固定相對路徑

你知道你的數據相對你的APK的路徑,這是正確的,安卓有APIs獲得那些路徑。你不須要作hard code的事情,你只須要堅守和你APK實際安裝位置的相對路徑。

使用存儲緩存來處理臨時文件

有APIs能夠調用,請使用它們。

簡單狀況下不要使用SQLite

SQlite的消耗在某種程度上也是很大的,因此若是你只須要些簡單的方式,好比 key-value存儲會更加合適些。

避免使用太多的數據庫

數據庫總體上開銷是很大的。也許你能夠試試只用一個數據庫,而後服務於多個不一樣的用戶。

讓用戶選擇內容存儲位置

當用戶有可移除的存儲設備的時候這些尤其重要。或者他們可使用adoptable storage,這是在棉花糖版本上的新的方法。若是用戶打算採用新的存儲設備,而後把數據都遷移到它上面,你的應用應該容許他們這樣作,這樣你的應用就不會亂掉了。

問和答 (54:00)

問:你提到了一個平滑的策略是下降你的幀頻率,有可能一個應用說: 「我須要30fps而不是60fps嗎?」

Romain:某種程度上。有一些你可使用的API來達到和屏幕同步的目的。你能夠每隔一幀同步一次。在普通的應用中,這會是很困難的,除非你知道你會由於外面各類各樣的設備而有許多的工做量。可是,若是你使用的是OpenGL或者Canvas,你又有本身的線程作渲染,而且你對渲染線程有完整的控制權的時候,狀況就不同了。在這種狀況下,你能夠控制你的幀,你能夠等待,而後你記錄V-Sync,諸如此類的動做。若是你想深刻的瞭解這些高級的技術細節,你能夠看看那些遊戲開發者的文章,他們在作相似的事情。

Chet:通常狀況下,你能夠嘗試60fps,而後你可使用一個叫GPU Profile的工具,這個工具在Android Studio上就有。工具裏有一個彩色的顯示條,你須要保證你持續的呆在綠線如下。你能夠在developers.android.com 查到工具的詳細信息。

Romain: 還有,咱們常常用 Systrace。Systrace是一個底層的,輕量級的,系統層面的監測工具。若是你的應用在任何地方有性能問題,它不須要嵌入在你的代碼中。Systrace不但能顯示你在那你花費了時間,並且還能顯示你的線程是否被調度,CPU的運行頻率,你是否是被從一個CPU轉移到另外一個CPU上(這也會影響到你的性能),是否後臺線程致使了鎖定,或者優先級倒置。文檔在developer.android.com上也能找到。這也是個頗有用的工具。

問:有一個類叫‘android.os.memfile’。 它和linux 共享內存聯繫在一塊兒。當我使用它的時候,我能從linux 內核中獲得300-400MB的內存來共享。因此,我使用它來分享拍攝的視頻而且存到了臨時區域。同時我把它設爲 non-purgable. 由於使用了400MB的內存,Android總會參與進來刷新內存。你對使用這個技術來存儲奇怪的內存有什麼建議嗎,或者不要使用共享內存?

Romain:這是個很是有趣的問題。我沒有什麼想法。我也不知道系統有這樣的行爲,也許你應該問問內核組來理解這裏的行爲。若是是如設計同樣,那麼後果是什麼。對不起,我沒有一個更好的答案。

問:我想指出當Android在切換Activity狀態的時候,會使用共享內存。他們發現若是你一直監視着你的Android設備中共享內存的使用狀態,你能夠準確地猜出Activity是處於什麼狀態,如:是從onResume到onStop,因此這看起來像是個安全漏洞。顯然,安卓再每次作Activity的狀態轉換的時候都使用了共享內存,有時候是12bytes。因此實時監測共享內存的狀態,你就能夠知道Activity是在什麼狀態。

Romain: 若是你一直監視着內存狀態,你可能還能夠知道其餘的發生在設備上的事情。好比看着屏幕。可是,很高興你能分享這點。

問:你以前討論到應該避免使用JNI,也須要避免使用Reflection。而後你提到你在動畫實現中忍受了許多開銷來避免使用Reflection。我想知道若是使用了Reflection,會在動畫對象中發生些什麼呢?

Chet: 咱們沒有用Reflection,咱們用的是JNI。JNI裏面有Reflection的代碼。它是這樣工做的:他會調用到JNI說,「我須要一個方法」。因此,當你想使用一個動畫的屬性叫作「foo」。你傳入了一個串,而後說「好吧,我想找到一個設置的方法叫作setFoo」。進入JNI層後,看起來JNI層會說:「有這樣的方法的簽名嗎?」。若是JNI找不到,就會返回false而後就會使用Reflection。我認爲Reflection的代碼應該永遠都不被調用到。因此JNI須要返回false。我之前也使用過Reflection,只要是我寫的代碼,我會使用已經存在的接口來保證沒有其餘奇怪的事情發生。而後,在使用Reflection的時候我還會有些backup的機制。可是我不認爲Reflection須要這樣用。這種狀況下可使用JNI。在有些代碼中,也許不太明顯,可是若是你找到代碼正確的地方,你會發現一個狀況是「JNI夠用嗎?若是他夠用,就使用JNI好了。」

Romain: 當你使用JNI的時候,你其實能夠有效地訪問到全部的成員和方法,這就是Reflection作的事情。咱們發現使用JNI會比Reflection快上不少。

Chet: 並且部分緣由是內存的消耗。由於至少算上運行的開銷,JNI是不會額外再分配內存的。經過任何機制實現方法查找都不簡單。可是,咱們只會在你第一次調用的時候作方法查找。因此,當你建立動畫對象的時候,第一次你運行的時候,就開始尋找setter或者getter,而後緩存它們。這樣一樣的狀況不會再次發生。

Romain: 另外一個事情是當你經過Reflection調用一個set alpha的函數的時候,你會在方法對象上調用該方法,這須要一個對象。因此當你須要傳入一個Float時,你須要分配內存來封裝(boxing)你的Float。可是若是你用的是JNI,你就不須要boxing。

Chet: 固然還有第三種機制,若是你打算習慣它的話,就是屬性(properties)。這就是咱們爲何很早前就加入屬性(properties)。爲一些特殊的視圖咱們建立了屬性,alpha屬性,translation X屬性, rotation X屬性等等。他們直接使用setters。因此當你使用屬性的時候,他直接調用了靜態的屬性對象。你在視圖的實例中傳遞它們,它會調用視圖的seter,這樣是開銷最小的方法。

問:你剛纔說大家不太關心Java的版本發佈,由於Java語言和實時運行是分開的。可是我仍是很好奇大家如何看待 retrolambda,它讓你使用lambdas,這是Java 8 的功能,可是它把Lambdas編譯成二進制代碼。並且做爲dexing的一部分,它把他們重編譯成匿名類。你有什麼建議嗎?

Romain: 我知道retrolambda,並且我喜歡它。我以爲它太棒了。使用它以前,我會反編譯retrolambda的結果看看發生了什麼。由於使用lambda,我敢確定你會有些討厭的驚喜。我很是確定它們在某些地方會變成匿名類,因此在你會遇到大量的內存分配,分配狀況取決於它們是如何介入的。例如,若是我在一個循環中傳入一個lambda,內存分配會發生什麼呢?因此,我會去看看反彙編代碼,而後在我決定前看看發生了什麼。你知道,匿名類能夠被緩存,或者它會在每次使用的時候分配,我不知道具體會發生那種狀況。我須要看看。做爲一個軟件工程師來講,在決定如何使用前,儘可能的去理解背後到底發生了什麼是很是重要的。由於這樣作,就不會在以後的開發中遇到討厭的驚喜。固然,在某些狀況下,若是你是在用戶點擊一個按鈕時使用了lambda,並不重要,對嗎?當你點擊一個按鈕時,性能不是個特別大的問題。

問:我想知道的是何時大家會支持Java 8, 而後你不須要把他們編譯成匿名類。

Romain: 不知道,實話實說,這幾個月咱們確實討論了很多關於桌面系統的JAVA 8的事情。我使用過streams,parallel streams和lambdas。這些都太棒了。代碼看起來特別棒,寫起來都特別有趣。可是當你意識到在某些狀況下,他們確實會比老的循環更慢些的時候,感受就不同了。再說一遍,當心一些,儘可能去理解你如今正在作的取捨。關於Java 8語言何時會被Android支持,我不知道,由於咱們沒有討論過這個事情。並且即便我知道,我也不能告訴你。

Chet: 我有必要提一下,我最喜歡的一個工具就是DDMS裏面的Allocation Tracker,它如今多是在Monitor的什麼地方。反編譯二進制代碼是一個好的方法,這樣能夠看看到底編譯器幹了什麼,可是運行的時候看看發生了什麼也是個好方法。當咱們剛開始開發動畫框架的時候,咱們使用Allocation Tracker去找到咱們在每一幀的時候分配了些什麼,而後消除它們。因此,若是你用retralambda,你須要肯定在你應用的關鍵點,你不要分配那些從外部看不須要的內存。

問:咱們平常的工做都是feature驅動的,對嗎?內部,咱們如何去維護一個性能的標杆?大家有專職的QA來跟蹤大家的代碼,而後你能夠看棧的trace,而後時刻提醒你,或者做爲一個feature的開發者在大家提交給QA前大家有什麼標準嗎?

Romain: 這是個混合的問題。對於那些很是關心他們應用的性能的工程師來講,這是個他們開發過程當中很是當心的問題。也有QA會關心這個話題,可是這幾年來,Android組寫了不少自動化的測試用例來測試性能。例如,拿Butter項目來講,圖像組爲每個CL作了一個jank的dashboard。他會運行一些像在Gmail中滾動滾動條的用例,而後計算咱們錯過的幀。每一次數字的變更,咱們都會收到一封郵件,而後有人就會受到批評。固然應用的開發者會先收到批評,而後他們會發給咱們框架組說:「大家太爛,大家的東西太慢」。而後咱們會回覆它,這樣會來來回回討論好長時間。

Chet: 咱們也會寫出許多工具而且改進它們。Systrace就是一個咱們內部正在使用而且持續改進的一個很好的工具,以後咱們提供給全部人使用。而且咱們持續迭代地開發它。這樣咱們就像最近作的事情同樣,在一些不明顯的狀況下,給你一些關於你的問題的提醒和信息。

Romain: 事實上,大部分的工具你能夠經過工程的UI看到不少信息。因此諸如Hierarchy Viewer, devel-draw,debug tool 和 GPU profiler都在設備上能夠運行。全部的這些工具都是咱們內部須要的,因此咱們開發出來而且提供給全部人使用。固然,還有不少的事情能夠作,可是關於性能最重要的事情就是大家能寫出自動的測試用例來捕捉它們。這是很是困難的一件事情。咱們暴露了一些內部的計數器,我想咱們有文檔描述它們。在adb shell的dumpsys幫助下,你能夠獲取其中的一些信息,幀的時間戳,janks的次數,特別是在棉花糖和棒棒糖中咱們加入了更多的方法。你能夠作的事情就像UI automater同樣,你能夠建立一個用例,驅動你的應用,而後輸出這些計數器看看可否告訴你些什麼有用的信息。更有效的方式是有個dashboard。它抓取了這些命令的輸出,而後給你畫一個特別好看的圖表。另外一件事情是,當你作些事情的時候想一想你的性能。提及來簡單,作起來難,評判起來更難,理解你在量化什麼還要困難一些。可是,這是惟一的途徑。

問:你提到不要使用序列化。你能再詳細的描述一下嗎?在Activity和Fragment間使用Serializable和Parcelable傳輸數據包含在裏面嗎?

Romain: 咱們是在討論Serializable的接口。Parcelable是Android提供的Serialization的一個變種,是很是有效的,可是僅僅用來進程間的數據傳輸。如今咱們有新的方式了,搜索「persistent parcel」,你就能夠找到它了。可是對了,咱們討論的是Serializable接口。

問:你以前提到flat buffers。有一種說法是若是網絡請求須要很長時間的話,節省100多毫秒不過重要。

Romain: 我以前給的例子比較有意思是由於他用了JSON做爲磁盤上文件存儲格式。這裏的想法是你已經緩存了數據了,因此你已經承受了網絡的開銷,因此能夠優化的地方在你屢次讀取這些數據的時候。在這種狀況下,使用更快的方式是有意義的。可是你是對的,若是解析JSON須要10毫秒,解析你的flat buffer只須要5毫秒,可是網絡傳輸須要200多毫秒,這樣的優化還有做用嗎?顯然,我但願你的應用在後臺線程處理全部的網絡操做。因此這裏討論的更多的是UI的延時,而不是別的。若是你有什麼東西是常常訪問的,你固然須要在flat buffer 這樣的地方里面尋找它。

還有一個Cap’n Proto,也是protobuf v2的開發者開發的。他寫了一些相似flat buffer的東西,可是更加深刻。由於它能夠被做爲有線傳輸格式,能夠被用做RPC機制,並且我相信它有Java 封裝層。這就回到咱們以前的堅持,當你考慮性能的時候,你須要量化標準。肯定你修改的是正確的問題。若是你修改的地方不是個問題可能還無所謂。做爲咱們來講,在你的應用裏面到底什麼會是個問題更難。由於這徹底依賴於你的應用。咱們只能分享咱們在應用中一次又一次看到的問題,再一次強調,這不意味着你的應用也有一樣的問題。可是真的,若是你在磁盤上作serialization,看看其餘的格式吧。總而言之,找到你最好的方式。

相關文章
相關標籤/搜索