Android OOM 排查與解決——圖片載入優化
1、OOM 引起與表現
在 Android 這種移動裝置上,如果程式碼沒有處理好,很容易引發記憶體持續佔用與洩漏,導致 OOM(OutOfMemoryError)
異常,進而導致 App 程式 Crash 掛掉。
在 Android 開發中,一個典型的 OOM 異常如下:
一旦碰上了這類錯誤,我們往往需要去排查記憶體了。導致 OOM 的一些情況比較常見,大多數情況下,大家可能遇到的都是同一種情況:
- Activity 洩漏導致;
- 層次龐大複雜的 View 檢視導致;
- 大量圖片持續佔用導致;
- 其他資源持續未釋放導致。
2、Android Studio 檢視記憶體佔用
在 Android Studio 裡面,我們可以在 Monitors
視窗中,實時對 App 記憶體進行監控,我們可以看出 App 的 HeapSize
、已經使用的記憶體大小
、剩餘記憶體大小
以及峰值變化
。有了這些資訊,我們可以在某個頁面開啟和關閉時進行監控,從而對比該頁面佔用記憶體變化,可以很方便的定位問題。
在這張圖中,如果已使用的記憶體大小(Allocated
)接近到 HeapSize
的大小,App 將會處於非常危險的狀態中,很有可能下一個操作就會直接導致 OOM,通過 Android Studio,我們可以防患於未然,在 Debug 階段進行預防。
3、adb 檢視記憶體佔用
adb
工具也是一個非常有用的工具,我們可以通過它來檢視 App 記憶體佔用。
3.1、檢視 JVM 的 HeapSize
等引數
通過命令 adb shell getprop dalvik.vm.heapsize
可以直接檢視 Dalvik 虛擬機器為 App 規定的最大 HeapSize
:
一般來說,App 可達到的最大 HeapSize 為 dalvik.vm.heapgrowthlimit
所規定的大小。但是如果我們在 AndroidManifest.xml
中為 Application 新增 android:largeHeap="true"
屬性,App 可達到的最大 HeapSize 則被調整為 dalvik.vm.heapsize
規定的值。
雖然新增 android:largeHeap="true"
屬性將大大降低 OOM 的概率,但除非萬不得已的情況下,否則不要使用該屬性。出現 OOM 後,我們首先應該排查整個 App,找出記憶體瓶頸予以解決。
3.2、檢視 App 記憶體佔用
通過命令 adb shell dumpsys meminfo [package_name]
可以檢視 App 所佔用記憶體:
通過這個命令,App 所佔資源情況一目瞭然,甚至我們可以看到整個 App 中 View 個數、Activity 個數——這對於排查 Activity 洩漏和優化 View 層級也是非常有幫助的。
4、圖片載入導致 OOM
而在一個 App 中,圖片處理不恰當往往是 OOM 錯誤出現的元凶——因為 App 中所有圖片動輒佔用幾十 M 的記憶體。如果我們能優先著手排查這一塊,將會對 App 的記憶體優化帶來 最直接最明顯
的改觀。而圖片的不恰當處理操作一般有如下一些:
- 直接載入
超大尺寸
圖片; - 圖片載入後
未及時釋放
; - 在頁面中,同時載入
非常多
的圖片;
4.1、超大尺寸圖片處理
現在的手機攝像頭畫素比較高,攝製出來的照片尺寸非常大,比如在一款還算老舊的手機上面,拍攝的圖片尺寸竟然達到了 2368 x 4224
!因為採用 jpeg 格式的緣故,這張圖片在磁碟上才1.9M,但如果我們不加任何處理,按原尺寸載入到記憶體中,佔用的記憶體將會非常可觀。
所以,針對大圖的載入,比較常用的方法是進行 DownSampling(向下取樣)
,許多部落格或技術站點對該方案有詳細的描述,在此不再贅述,簡單原理用程式碼表述如下:
public static int calcInSampleSize(
int width, int height, int requestWidth, int requestHeight) {
int inSampleSize = 1;
if (requestWidth <= 0 || requestHeight <= 0) {
return inSampleSize;
}
if (width > requestWidth || height > requestHeight) {
int widthRatio = Math.round((float) width / (float) requestWidth);
int heightRatio = Math.round((float) height / (float) requestHeight);
inSampleSize = Math.min(widthRatio, heightRatio);
}
return inSampleSize;
}
public static Bitmap decodeBitmapFromUri(
Context context, Uri uri, int requestWidth, int requestHeight) {
BitmapFactory.Options options = getResourceOptions(context, uri);
options.inSampleSize = calcInSampleSize(
options.outWidth, options.outHeight, requestWidth, requestHeight);
options.inJustDecodeBounds = false;
// ...
ContentResolver resolver = context.getContentResolver();
Bitmap bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(uri), new Rect(), options);
// ...
return bitmap;
}
這樣,如果我們要載入一張圖片到 View 上,我們可以通過 view.getMeasuredWidth() 和 view.getMeasuredHeight()
得到 View 的寬和高,然後按這個大小進行取樣,得到的 Bitmap 將會是尺寸適合的圖片,不會佔用額外記憶體,圖片在 View 上展示出來質量也比較高。
4.2、及時釋放圖片
一般不要靜態快取圖片,就算有快取,也可以結合 LRU
機制來保證快取圖片的個數和佔用記憶體。Android SDK 已經提供了 LruCache
類來實現 LRU 機制。
4.3、避免同時載入大量圖片
避免同一時間載入大量的圖片,也可以為我們的記憶體優化提供不小的收益。比如,在一個 ScrollView
中有非常多的 ImageView,這時候,佔用的記憶體往往非常客觀,因為就算一些 View 我們在螢幕視野裡面看不到,它還是持續佔用記憶體。我們可以通過 RecyclerView
或者 ListView
來予以替換,從而達到記憶體優化的效果。
在我的開發過程中,就遇到了這樣一個例子。一個頁面用 ScrollView 來佈局,裡面有 26 張左右的圖片,這時候,整個 App 的記憶體佔用長期達到了 90M
左右!一直徘徊在 OOM 邊緣。在我把這個頁面用 RecyclerView 替換掉 ScrollView 後,整個 App 記憶體竟然下降了 40M
之多!!!整個 App 變得非常順滑。
5、採用開源庫載入圖片
現在已經有非常多的圖片載入庫供我們使用了,比較流行的有:Fresco
、Universal-Image-Loader
、Picasso
、Volley
等等。這些開源庫一般來說,對記憶體的優化已經比較全面了,比我們自己手工管理記憶體來的好。所以,可以根據專案的實際情況靈活選用。
比如,我目前所使用的 Fresco
庫,就可以靈活設定圖片尺寸,避免載入大尺寸的圖片(setResizeOptions
):
public static void displayImage(DraweeView draweeView, Uri uri) {
Size size = getAppropriateSize(draweeView);
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(size.mWidth, size.mHeight))
.setAutoRotateEnabled(true)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setImageRequest(request)
.setOldController(draweeView.getController())
.build();
draweeView.setController(controller);
}
private static Size getAppropriateSize(View view) {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
if (width <= 0 || height <= 0) {
width = view.getWidth();
height = view.getHeight();
}
Size size = MiscUtils.getScreenSize();
if (width <= 0 || height <= 0 || width > size.mWidth || height > size.mHeight) {
width = size.mWidth;
height = size.mHeight;
}
return new Size(width, height);
}
當然,我們還要在 ImagePipelineConfig
中開啟 DownSampling
(setDownsampleEnabled(true)
):
public static void initFresco(Context context) {
ImagePipelineConfig config = ImagePipelineConfig
.newBuilder(context)
.setDownsampleEnabled(true)
.build();
Fresco.initialize(context, config);
}
相關文章
- iOS效能優化 - 網路圖片載入優化iOS優化
- 【前端優化】js圖片懶載入及優化前端優化JS
- 前端優化之圖片懶載入前端優化
- Flutter圖片載入優化深入探索Flutter優化
- Android效能優化——圖片優化(二)Android優化
- Android 圖片載入框架Android框架
- Android圖片載入框架Fresco使用詳解Android框架
- 要優雅!Android中這樣載入大圖片和長圖片Android
- Android 高效安全載入圖片Android
- Android記憶體優化之圖片優化Android記憶體優化
- Android優化--Fragment懶載入Android優化Fragment
- Android偽圖片載入進度效果Android
- Android 基礎之圖片載入(二)Android
- 圖片無法載入的情況下的優化優化
- Swift 專案總結 08 GIF 圖片載入優化Swift優化
- 前端效能優化-圖片懶載入(防抖、節流)前端優化
- Android Web3j OOM解決AndroidWebOOM
- Flutter載入圖片與GlideFlutterIDE
- Android 圖片高斯模糊解決方案Android
- 圖片載入失敗解決方案 以及canvas即時生成提示圖片Canvas
- 解耦圖片載入庫解耦
- Flutter 入門與實戰(七):使用 cached_image_network 優化圖片載入體驗Flutter優化
- Android 載入網路圖片 以及實現圓角圖片效果Android
- 頁面圖片預載入與懶載入策略
- 圖片優化優化
- 深入探索Glide圖片載入框架:做了哪些優化?如何管理生命週期?怎麼做大圖載入?IDE框架優化
- Android9.0使用Glide載入圖片問題AndroidIDE
- Android開發教程 - 使用Data Binding(七)使用BindingAdapter簡化圖片載入AndroidAPT
- 前端效能優化 --- 圖片優化前端優化
- 如何讓Android 支援HEIF 圖片解碼和載入(免費的方法)Android
- Android 圖片載入庫Glide知其然知其所以然之載入AndroidIDE
- Html2canvas——圖片空白的幾種排查解決方案HTMLCanvas
- Android常用圖片載入庫介紹及對比Android
- 圖片懶載入
- 圖片載入事件事件
- 預載入圖片
- Flutter 圖片載入Flutter
- 【學習】Vue 載入優化以及元件非同步載入缺點解決方案Vue優化元件非同步
- 效能優化04-圖片優化優化