優化 Android 應用程序的提示和工具
已發表: 2022-03-11Android 設備有很多內核,所以編寫流暢的應用程序對任何人來說都是一項簡單的任務,對吧? 錯誤的。 由於 Android 上的一切都可以通過多種不同的方式完成,因此選擇最佳選項可能很困難。 如果你想選擇最有效的方法,你必須知道幕後發生了什麼。 幸運的是,您不必依賴自己的感覺或嗅覺,因為有很多工具可以通過測量和描述正在發生的事情來幫助您找到瓶頸。 適當優化和流暢的應用程序極大地改善了用戶體驗,同時也減少了電池消耗。
讓我們先看一些數字來考慮優化的真正重要性。 根據 Nimbledroid 的一篇帖子,86% 的用戶(包括我)在使用過一次後由於性能不佳而卸載了應用程序。 如果你正在加載一些內容,你有不到 11 秒的時間向用戶展示它。 只有每三分之一的用戶會給你更多的時間。 因此,您可能還會在 Google Play 上獲得很多差評。
每個用戶一遍又一遍地註意到的第一件事是應用程序的啟動時間。 根據 Nimbledroid 的另一篇文章,在 100 個頂級應用程序中,40 個在 2 秒內啟動,70 個在 3 秒內啟動。 因此,如果可能的話,您通常應該盡快顯示一些內容,並稍微延遲背景檢查和更新。
永遠記住,過早的優化是萬惡之源。 您也不應該在微優化上浪費太多時間。 您將看到優化經常運行的代碼的最大好處。 例如,這包括onDraw()
函數,它每幀運行一次,理想情況下每秒運行 60 次。 繪圖是目前最慢的操作,因此請嘗試僅重繪您必須做的事情。 更多關於這方面的內容將在稍後介紹。
性能提示
足夠的理論,如果性能對您很重要,這裡列出了您應該考慮的一些事項。
1. String 與 StringBuilder
假設您有一個字符串,出於某種原因,您想將更多的字符串附加到它 10,000 次。 代碼可能看起來像這樣。
String string = "hello"; for (int i = 0; i < 10000; i++) { string += " world"; }
您可以在 Android Studio Monitors 上看到某些字符串連接的效率有多低。 有大量垃圾收集 (GC) 正在進行。
這個操作在我相當不錯的設備上大約需要 8 秒,它有 Android 5.1.1。 實現相同目標的更有效方法是使用 StringBuilder,就像這樣。
StringBuilder sb = new StringBuilder("hello"); for (int i = 0; i < 10000; i++) { sb.append(" world"); } String string = sb.toString();
在同一台設備上,這幾乎是立即發生的,不到 5 毫秒。 CPU 和內存可視化幾乎完全平坦,因此您可以想像這種改進有多大。 但請注意,為了實現這種差異,我們必須附加 10,000 個字符串,您可能不經常這樣做。 因此,如果您只添加幾個字符串一次,您將看不到任何改進。 順便說一句,如果你這樣做:
String string = "hello" + " world";
它在內部轉換為 StringBuilder,因此它可以正常工作。
您可能想知道,為什麼第一種方式連接字符串這麼慢? 這是由於字符串是不可變的,因此一旦創建,就無法更改。 即使你認為你正在改變一個字符串的值,你實際上是在用新的值創建一個新的字符串。 在如下示例中:
String myString = "hello"; myString += " world";
你將在內存中得到的不是 1 個字符串“hello world”,而是 2 個字符串。 如您所料,字符串 myString 將包含“hello world”。 然而,原來的值為“hello”的字符串仍然存在,沒有對其進行任何引用,等待被垃圾回收。 這也是為什麼您應該將密碼存儲在 char 數組而不是 String 中的原因。 如果將密碼存儲為字符串,它將以人類可讀的格式保留在內存中,直到下一次 GC 的時間長度不可預測。 回到上面描述的不變性,即使你在使用它之後給它分配另一個值,String 也會留在內存中。 但是,如果您在使用密碼後清空 char 數組,它將從各處消失。
2. 選擇正確的數據類型
在開始編寫代碼之前,您應該確定將用於集合的數據類型。 例如,您應該使用Vector
還是ArrayList
? 好吧,這取決於您的用例。 如果你需要一個線程安全的集合,它一次只允許一個線程使用它,你應該選擇一個Vector
,因為它是同步的。 在其他情況下,您可能應該堅持使用ArrayList
,除非您確實有特定的理由使用向量。
當您想要一個具有獨特對象的集合時,情況如何? 好吧,您可能應該選擇一個Set
。 它們不能按設計包含重複項,因此您不必自己處理。 有多種類型的集合,因此請選擇適合您的用例的集合。 對於一組簡單的唯一項目,您可以使用HashSet
。 如果要保留插入項目的順序,請選擇LinkedHashSet
。 TreeSet
自動對項目進行排序,因此您不必在其上調用任何排序方法。 它還應該有效地對項目進行排序,而無需考慮排序算法。
— Rob Pike 的 5 條編程規則
對整數或字符串進行排序非常簡單。 但是,如果您想按某個屬性對類進行排序怎麼辦? 假設你正在寫一份你吃的飯菜的清單,並存儲它們的名字和時間戳。 您將如何按時間戳從低到高對餐點進行排序? 幸運的是,這很簡單。 在Meal
類中實現Comparable
接口並重寫compareTo()
函數就足夠了。 要按時間戳從最低到最高對餐點進行排序,我們可以這樣寫。
@Override public int compareTo(Object object) { Meal meal = (Meal) object; if (this.timestamp < meal.getTimestamp()) { return -1; } else if (this.timestamp > meal.getTimestamp()) { return 1; } return 0; }
3. 位置更新
有很多應用程序可以收集用戶的位置。 為此,您應該使用 Google Location Services API,它包含許多有用的功能。 關於使用它有單獨的文章,所以我不再贅述。
我只想從性能的角度強調一些要點。
首先,只使用您需要的最精確的位置。 例如,如果您正在做一些天氣預報,您不需要最準確的位置。 基於網絡獲得一個非常粗糙的區域更快,電池效率更高。 您可以通過將優先級設置為LocationRequest.PRIORITY_LOW_POWER
來實現。
您還可以使用名為setSmallestDisplacement()
的LocationRequest
函數。 如果它小於給定值,則以米為單位設置將導致您的應用程序不會收到有關位置更改的通知。 例如,如果您有一張附近附近餐館的地圖,並且您將最小位移設置為 20 米,那麼如果用戶只是在房間裡走動,應用程序將不會請求檢查餐館。 這些請求將毫無用處,因為無論如何附近不會有任何新的餐廳。
第二條規則是僅在您需要時才請求位置更新。 這是不言自明的。 如果您真的在構建該天氣預報應用程序,則無需每隔幾秒鐘請求一次位置,因為您可能沒有如此精確的預報(如果有,請聯繫我)。 您可以使用setInterval()
函數來設置設備更新您的應用程序有關位置的所需時間間隔。 如果多個應用程序不斷請求用戶的位置,則每個應用程序都會在每次新的位置更新時收到通知,即使您設置了更高的setInterval()
也是如此。 為防止您的應用收到過於頻繁的通知,請確保始終使用setFastestInterval()
設置最快的更新間隔。
最後,第三條規則是僅在您需要時才請求位置更新。 如果您每隔 x 秒在地圖上顯示一些附近的對象並且應用程序在後台運行,則您不需要知道新位置。 如果用戶無論如何都看不到它,則沒有理由更新地圖。 確保在適當的時候停止監聽位置更新,最好是在onPause()
中。 然後,您可以在onResume()
中恢復更新。
4. 網絡請求
您的應用很有可能正在使用互聯網下載或上傳數據。 如果是,您有幾個理由要注意處理網絡請求。 其中之一是移動數據,它僅限於很多人,你不應該浪費它。
第二個是電池。 如果使用過多,WiFi 和移動網絡都會消耗大量數據。 假設您要下載 1 kb。 要發出網絡請求,您必須喚醒蜂窩或 WiFi 無線電,然後才能下載數據。 但是,收音機不會在手術後立即進入睡眠狀態。 它將保持相當活躍的狀態大約 20-40 秒,具體取決於您的設備和運營商。
所以你能對它做點啥? 批。 為了避免每隔幾秒鐘就喚醒收音機,請預取用戶在接下來的幾分鐘內可能需要的東西。 批處理的正確方法是高度動態的,具體取決於您的應用程序,但如果可能,您應該在接下來的 3-4 分鐘內下載用戶可能需要的數據。 還可以根據用戶的互聯網類型或充電狀態編輯批處理參數。 例如,如果用戶在充電時使用 WiFi,與用戶使用電池電量不足的移動互聯網相比,您可以預取更多的數據。 考慮所有這些變量可能是一件困難的事情,只有少數人會這樣做。 幸運的是,有 GCM 網絡管理器來救援!
GCM 網絡管理器是一個非常有用的類,具有許多可自定義的屬性。 您可以輕鬆安排重複任務和一次性任務。 在重複任務中,您可以設置最低和最高重複間隔。 這不僅允許批處理您的請求,還允許批處理來自其他應用程序的請求。 收音機每隔一段時間只能被喚醒一次,當它啟動時,隊列中的所有應用程序都會下載並上傳它們應該做的事情。 這個Manager也知道設備的網絡類型和充電狀態,所以你可以做相應的調整。 您可以在本文中找到更多詳細信息和示例,我敦促您檢查一下。 示例任務如下所示:
Task task = new OneoffTask.Builder() .setService(CustomService.class) .setExecutionWindow(0, 30) .setTag(LogService.TAG_TASK_ONEOFF_LOG) .setUpdateCurrent(false) .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED) .setRequiresCharging(false) .build();
順便說一句,從 Android 3.0 開始,如果你在主線程上進行網絡請求,你會得到一個NetworkOnMainThreadException
。 那肯定會警告你不要再這樣做了。
5. 反思
反射是類和對象檢查它們自己的構造函數、字段、方法等的能力。 它通常用於向後兼容,以檢查給定的方法是否可用於特定的操作系統版本。 如果您必須為此使用反射,請確保緩存響應,因為使用反射非常慢。 一些廣泛使用的庫也使用反射,例如用於依賴注入的 Roboguice。 這就是為什麼您應該更喜歡 Dagger 2 的原因。有關反射的更多詳細信息,您可以查看單獨的帖子。
6. 自動裝箱
自動裝箱和拆箱是將原始類型轉換為對像類型的過程,反之亦然。 在實踐中,這意味著將 int 轉換為 Integer。 為此,編譯器在內部使用Integer.valueOf()
函數。 轉換不僅慢,對像也比它們的原始等價物佔用更多的內存。 讓我們看一些代碼。
Integer total = 0; for (int i = 0; i < 1000000; i++) { total += i; }
雖然這平均需要 500 毫秒,但重寫它以避免自動裝箱將大大加快速度。
int total = 0; for (int i = 0; i < 1000000; i++) { total += i; }
該解決方案的運行時間約為 2 毫秒,速度提高了 25 倍。 如果你不相信我,你可以測試一下。 每個設備的數字顯然會有所不同,但它仍然應該快得多。 這也是一個非常簡單的優化步驟。
好的,您可能不會經常像這樣創建 Integer 類型的變量。 但是更難避免的情況呢? 就像在地圖中一樣,您必須使用對象,例如Map<Integer, Integer>
? 看看許多人使用的解決方案。
Map<Integer, Integer> myMap = new HashMap<>(); for (int i = 0; i < 100000; i++) { myMap.put(i, random.nextInt()); }
在地圖中插入 100k 個隨機整數大約需要 250 毫秒才能運行。 現在看一下 SparseIntArray 的解決方案。
SparseIntArray myArray = new SparseIntArray(); for (int i = 0; i < 100000; i++) { myArray.put(i, random.nextInt()); }
這需要的時間要少得多,大約 50 毫秒。 它也是提高性能的更簡單的方法之一,因為不需要做任何復雜的事情,而且代碼也保持可讀性。 雖然使用第一個解決方案運行一個清晰的應用程序佔用了我 13MB 的內存,但使用原始整數佔用了 7MB 以下的內存,所以只有一半。
SparseIntArray 只是可以幫助您避免自動裝箱的很酷的集合之一。 像Map<Integer, Long>
這樣的映射可以被SparseLongArray
替換,因為映射的值是Long
類型。 如果您查看SparseLongArray
的源代碼,您會發現一些非常有趣的東西。 在引擎蓋下,它基本上只是一對數組。 您也可以類似地使用SparseBooleanArray
。
如果您閱讀了源代碼,您可能已經註意到一條註釋說SparseIntArray
可能比HashMap
慢。 我一直在嘗試很多,但對我來說SparseIntArray
在內存和性能方面總是更好。 我想這仍然取決於您選擇哪個,試驗您的用例,看看哪個最適合您。 使用地圖時,一定要SparseArrays
。
7. OnDraw
正如我上面所說,當您優化性能時,您可能會看到優化經常運行的代碼的最大好處。 經常運行的函數之一是onDraw()
。 它負責在屏幕上繪製視圖,您可能不會感到驚訝。 由於設備通常以 60 fps 的速度運行,因此該功能每秒運行 60 次。 每幀有 16 毫秒的時間來完全處理,包括它的準備和繪製,所以你應該真正避免慢功能。 只有主線程可以在屏幕上繪製,所以你應該避免對其進行昂貴的操作。 如果您將主線程凍結幾秒鐘,您可能會看到臭名昭著的應用程序無響應 (ANR) 對話框。 對於調整圖像大小、數據庫工作等,請使用後台線程。

我見過一些人試圖縮短他們的代碼,認為這樣會更有效率。 這絕對不是要走的路,因為更短的代碼並不意味著更快的代碼。 在任何情況下,您都不應該通過行數來衡量代碼的質量。
在onDraw()
中應該避免的一件事是分配像 Paint 這樣的對象。 在構造函數中準備好所有東西,這樣繪製的時候就準備好了。 即使您優化了onDraw()
,您也應該盡可能頻繁地調用它。 有什麼比調用優化函數更好的呢? 好吧,根本不調用任何函數。 如果你想繪製文本,有一個非常簡潔的輔助函數叫做drawText()
,你可以在其中指定文本、坐標和文本顏色等內容。
8. ViewHolders
你可能知道這個,但我不能跳過它。 Viewholder 設計模式是一種使滾動列表更平滑的方法。 它是一種視圖緩存,可以大大減少對findViewById()
的調用,並通過存儲視圖來膨脹視圖。 它可能看起來像這樣。
static class ViewHolder { TextView title; TextView text; public ViewHolder(View view) { title = (TextView) view.findViewById(R.id.title); text = (TextView) view.findViewById(R.id.text); } }
然後,在適配器的getView()
函數中,您可以檢查是否有可用的視圖。 如果沒有,您創建一個。
ViewHolder viewHolder; if (convertView == null) { convertView = inflater.inflate(R.layout.list_item, viewGroup, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.title.setText("Hello World");
你可以在互聯網上找到很多關於這種模式的有用信息。 它也可以用於列表視圖中包含多種不同類型元素的情況,例如某些部分標題。
9.調整圖像大小
很有可能,您的應用將包含一些圖像。 如果您從網上下載一些 JPG,它們可能具有非常大的分辨率。 但是,將顯示它們的設備會小得多。 即使您使用設備的相機拍照,也需要在顯示之前縮小它的尺寸,因為照片分辨率比顯示器的分辨率大很多。 在顯示圖像之前調整圖像大小是至關重要的。 如果您嘗試以全分辨率顯示它們,您很快就會耗盡內存。 在 Android 文檔中有很多關於有效顯示位圖的文章,我會嘗試總結一下。
所以你有一個位圖,但你對它一無所知。 在您的服務中有一個名為 inJustDecodeBounds 的有用的位圖標誌,它允許您找出位圖的分辨率。 假設您的位圖為 1024x768,而用於顯示它的 ImageView 僅為 400x300。 您應該繼續將位圖的分辨率除以 2,直到它仍然大於給定的 ImageView。 如果這樣做,它會將位圖縮小 2 倍,為您提供 512x384 的位圖。 下採樣位圖使用的內存減少了 4 倍,這將極大地幫助您避免著名的 OutOfMemory 錯誤。
既然你知道該怎麼做,你就不應該這樣做。 … 至少,如果您的應用嚴重依賴圖像,並且不僅僅是 1-2 張圖像,則不會。 絕對避免手動調整大小和回收圖像之類的東西,為此使用一些第三方庫。 最受歡迎的是 Square 的 Picasso、Universal Image Loader、Facebook 的 Fresco,或者我最喜歡的 Glide。 它周圍有一個龐大的活躍開發者社區,因此您也可以在 GitHub 的問題部分找到很多樂於助人的人。
10.嚴格模式
嚴格模式是一個非常有用的開發者工具,很多人都不知道。 它通常用於檢測來自主線程的網絡請求或磁盤訪問。 您可以設置嚴格模式應該尋找什麼問題以及它應該觸發什麼懲罰。 谷歌示例如下所示:
public void onCreate() { if (DEVELOPER_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } super.onCreate(); }
如果您想檢測 Strict Mode 可以找到的每個問題,您也可以使用detectAll()
。 與許多性能提示一樣,您不應盲目嘗試修復嚴格模式報告的所有內容。 只需調查它,如果您確定這不是問題,請不要理會它。 還要確保僅將嚴格模式用於調試,並始終在生產版本中禁用它。
調試性能:專業的方式
現在讓我們看看一些可以幫助您找到瓶頸的工具,或者至少可以顯示出問題所在。
1.安卓監視器
這是一個內置在 Android Studio 中的工具。 默認情況下,您可以在左下角找到 Android Monitor,您可以在其中切換 2 個選項卡。 Logcat 和監視器。 Monitors 部分包含 4 個不同的圖表。 網絡、CPU、GPU 和內存。 它們是不言自明的,所以我將快速瀏覽它們。 這是在解析一些下載的 JSON 時所拍攝的圖表的屏幕截圖。
網絡部分以 KB/s 顯示傳入和傳出流量。 CPU 部分以百分比顯示 CPU 使用率。 GPU 監視器顯示渲染 UI 窗口的幀所需的時間。 這是這 4 個監視器中最詳細的監視器,因此如果您想了解更多詳細信息,請閱讀此內容。
最後,我們有內存監視器,您可能會使用最多。 默認情況下,它顯示當前的可用和已分配內存量。 您也可以使用它強制進行垃圾收集,以測試使用的內存量是否下降。 它有一個名為 Dump Java Heap 的有用功能,它將創建一個 HPROF 文件,該文件可以使用 HPROF 查看器和分析器打開。 這將使您能夠查看您分配了多少對象,哪些對象佔用了多少內存,以及哪些對象可能導致內存洩漏。 學習如何使用這個分析器並不是最簡單的任務,但它是值得的。 您可以使用內存監視器做的下一件事是進行一些定時分配跟踪,您可以根據需要啟動和停止。 它在許多情況下可能很有用,例如在滾動或旋轉設備時。
2.GPU過度繪製
這是一個簡單的幫助工具,一旦您啟用了開發者模式,您就可以在開發者選項中激活它。 選擇Debug GPU overdraw,“Show overdraw area”,你的屏幕會出現一些奇怪的顏色。 沒關係,這就是應該發生的事情。 顏色表示特定區域被透支的次數。 真彩色意味著沒有透支,這是你應該瞄準的。 藍色表示透支一,綠色表示二,粉紅色三,紅色四。
雖然看到真實的顏色是最好的,但您總會看到一些過度繪製,尤其是在文本、導航抽屜、對話框等周圍。 所以不要試圖完全擺脫它。 如果您的應用程序呈藍色或綠色,那可能沒問題。 但是,如果您在一些簡單的屏幕上看到太多紅色,您應該調查發生了什麼。 如果您繼續添加它們而不是替換它們,則可能有太多的碎片堆疊在一起。 正如我上面提到的,繪圖是應用程序中最慢的部分,因此如果要在其上繪製超過 3 層,那麼繪製東西是沒有意義的。 隨意使用它查看您最喜歡的應用程序。 您會看到即使下載量超過 10 億的應用程序也有紅色區域,所以當您嘗試優化時,請放輕鬆。
3.GPU渲染
這是開發人員選項中的另一個工具,稱為 Profile GPU 渲染。 選擇它後,選擇“在屏幕上作為條形圖”。 您會注意到屏幕上出現了一些彩色條。 由於每個應用程序都有單獨的欄,奇怪的是狀態欄有自己的欄,如果你有軟件導航按鈕,它們也有自己的欄。 無論如何,當您與屏幕交互時,條形圖會更新。
這些條由 3-4 種顏色組成,根據 Android 文檔,它們的大小確實很重要。 越小越好。 在底部你有藍色,它代表用於創建和更新視圖的顯示列表的時間。 如果這部分太高,說明有很多自定義視圖繪製,或者在onDraw()
函數中做了很多工作。 如果您使用的是 Android 4.0+,您會在藍色條上方看到一個紫色條。 這表示將資源傳輸到渲染線程所花費的時間。 然後是紅色部分,它表示 Android 的 2D 渲染器向 OpenGL 發出命令以繪製和重繪顯示列表所花費的時間。 頂部是橙色條,表示 CPU 等待 GPU 完成工作的時間。 如果它太高,應用程序在 GPU 上做了太多的工作。
如果你足夠好,橙色之上還有一種顏色。 它是代表 16 毫秒閾值的綠線。 由於您的目標應該是以 60 fps 的速度運行您的應用程序,因此您有 16 毫秒的時間來繪製每一幀。 如果你不成功,一些幀可能會被跳過,應用程序可能會變得生澀,用戶肯定會注意到。 特別注意動畫和滾動,這是最重要的平滑度。 即使您可以使用此工具檢測到一些跳幀,但它並不能真正幫助您找出問題的確切位置。
4.層次查看器
這是我最喜歡的工具之一,因為它非常強大。 您可以從 Android Studio 通過 Tools -> Android -> Android Device Monitor 啟動它,或者它也在您的 sdk/tools 文件夾中作為“monitor”。 您還可以在那裡找到一個獨立的 hierarachyviewer 可執行文件,但由於它已被棄用,您應該打開監視器。 無論您打開 Android 設備監視器,切換到 Hierarchy Viewer 透視圖。 如果您沒有看到分配給您的設備的任何正在運行的應用程序,您可以採取一些措施來修復它。 還可以嘗試查看這個問題線程,有各種各樣的問題和各種解決方案的人。 有些東西也應該對你有用。
使用 Hierarchy Viewer,您可以獲得視圖層次結構的非常簡潔的概覽(顯然)。 如果您在單獨的 XML 中查看每個佈局,您可能很容易發現無用的視圖。 但是,如果您繼續組合佈局,則很容易混淆。 像這樣的工具可以很容易地發現,例如,一些 RelativeLayout,它只有 1 個孩子,另一個 RelativeLayout。 這使得其中一個可移動。
避免調用requestLayout()
,因為它會導致遍歷整個視圖層次結構,以找出每個視圖應該有多大。 如果測量值有衝突,層次結構可能會被多次遍歷,如果發生在某些動畫中,肯定會跳過一些幀。 如果您想了解更多有關 Android 如何繪製視圖的信息,可以閱讀此內容。 讓我們看一下 Hierarchy Viewer 中的一個視圖。
右上角包含一個按鈕,用於在獨立窗口中最大化特定視圖的預覽。 在它下面,您還可以在應用程序中看到視圖的實際預覽。 下一項是一個數字,它表示給定視圖有多少個子視圖,包括視圖本身。 如果您選擇一個節點(最好是根節點)並按“獲取佈局時間”(3 個彩色圓圈),您將再填充 3 個值,同時出現標記為測量、佈局和繪圖的彩色圓圈。 測量階段代表測量給定視圖所花費的時間,這可能並不令人震驚。 佈局階段是關於渲染時間,而繪製是實際的繪製操作。 這些值和顏色是相互關聯的。 綠色 1 表示視圖呈現在樹中所有視圖的前 50% 中。 黃色表示在樹中所有視圖中較慢的 50% 中渲染,紅色表示給定視圖是最慢的視圖之一。 由於這些值是相對的,所以總會有紅色的。 你根本無法避免它們。
在值下,您有類名稱,例如“TextView”、對象的內部視圖 ID 和視圖的 android:id,您在 XML 文件中設置。 我敦促您養成為所有視圖添加 ID 的習慣,即使您沒有在代碼中引用它們。 這將使在 Hierarchy Viewer 中識別視圖變得非常簡單,並且如果您的項目中有自動化測試,它還將使定位元素更快。 這將為您和您的同事編寫它們節省一些時間。 向 XML 文件中添加的元素添加 ID 非常簡單。 但是動態添加的元素呢? 好吧,事實證明它也很簡單。 只需在您的值文件夾中創建一個 ids.xml 文件並輸入必填字段。 它看起來像這樣:
<resources> <item name="item_title" type="id"/> <item name="item_body" type="id"/> </resources>
然後在代碼中,您可以使用setId(R.id.item_title)
。 再簡單不過了。
在優化 UI 時,還有一些事情需要注意。 您通常應該避免深層次結構,而更喜歡淺層次,也許是寬層次。 不要使用不需要的佈局。 例如,您可能可以將一組嵌套的LinearLayouts
替換為RelativeLayout
或TableLayout
。 隨意嘗試不同的佈局,不要總是使用LinearLayout
和RelativeLayout
。 還可以在需要時嘗試創建一些自定義視圖,如果做得好,它可以顯著提高性能。 例如,您是否知道 Instagram 不使用 TextViews 來顯示評論?
您可以在 Android Developers 網站上找到有關 Hierarchy Viewer 的更多信息,其中包含不同窗格的描述、使用 Pixel Perfect 工具等。我要指出的另一件事是在 .psd 文件中捕獲視圖,這可以通過“捕獲窗口圖層”按鈕。 每個視圖都將位於一個單獨的圖層中,因此在 Photoshop 或 GIMP 中隱藏或更改它非常簡單。 哦,這是為每個視圖添加 ID 的另一個原因。 這將使圖層具有真正有意義的名稱。
您會在開發人員選項中找到更多調試工具,因此我建議您激活它們並查看它們在做什麼。 什麼可能出錯?
Android 開發者網站包含一組性能最佳實踐。 它們涵蓋了很多不同的領域,包括我還沒有真正談論過的內存管理。 我默默地忽略了它,因為處理內存和跟踪內存洩漏是一個完全不同的故事。 使用第三方庫來有效地顯示圖像會有很大幫助,但如果您仍然有內存問題,請查看 Square 製作的 Leak canary,或閱讀此內容。
包起來
所以,這是個好消息。 壞消息是,優化 Android 應用程序要復雜得多。 有很多方法可以做任何事情,因此您應該熟悉它們的優缺點。 通常沒有任何銀彈解決方案只有好處。 只有了解幕後發生的事情,您才能選擇最適合您的解決方案。 僅僅因為您最喜歡的開發人員說某些東西很好,並不一定意味著它是最適合您的解決方案。 有更多的領域需要討論,還有更多更高級的分析工具,所以我們下次可能會討論它們。
確保您向頂級開發人員和頂級公司學習。 您可以在此鏈接中找到數百個工程博客。 這顯然不僅僅是 Android 相關的東西,所以如果你只對 Android 感興趣,你必須過濾特定的博客。 我強烈推薦 Facebook 和 Instagram 的博客。 儘管 Android 上的 Instagram 用戶界面存在問題,但他們的工程博客有一些非常酷的文章。 對我來說,很容易看到每天處理數億用戶的公司是如何完成事情的,所以不閱讀他們的博客似乎很瘋狂,這真是太棒了。 世界變化非常快,所以如果你不不斷地嘗試改進、向他人學習和使用新工具,你就會被甩在後面。 正如馬克吐溫所說,不讀書的人比不讀書的人沒有優勢。