[TOC]
內存優化應該優先去做見效快的地方,圖片內存優化是內存優化的重點,可能一張圖片沒有回收就會造成幾M內存的浪費
# 常規的圖片內存優化方法
我們都知道,圖片所占內存=寬*高*一像素占用內存
所以優化圖片內存主要有以下幾個思路
1.縮放減小寬高
2.減少每個像素所占用的內存
3.內存復用,避免重復分配內存
4.對于大圖,可以采取局部加載的策略
## 減少圖片寬高
有時圖片寬高為`200*200`,而`View`寬高為`100*100`,這種時候如果展示`200*200`的圖片沒有意義,應該對圖片進行縮放
這種情況一般通過`inSampleSize`實現
~~~java
BitampFactory.Options options = new BitmapFactory.Options();
// 設置為4就是寬和高都變為原來1/4大小的圖片
options.inSampleSize = 4;
BitmapFactory.decodeSream(is, null, options);
~~~
## 減少每個像素所占用的內存
在`API29`中,將`Bitmap`分為`ALPHA_8`, `RGB_565`, `ARGB_4444`, `ARGB_8888`, `RGBA_F16`, `HARDWARE`六個等級。
* `ALPHA_8`:不存儲顏色信息,每個像素占1個字節;
* `RGB_565`:僅存儲`RGB`通道,每個像素占2個字節,對`Bitmap`色彩沒有高要求,可以使用該模式;
* `ARGB_4444`:已棄用,用`ARGB_8888`代替;
* `ARGB_8888`:每個像素占用4個字節,保持高質量的色彩保真度,默認使用該模式;
* `RGBA_F16`:每個像素占用8個字節,適合寬色域和`HDR`;
* `HARDWARE`:一種特殊的配置,減少了內存占用同時也加快了`Bitmap`的繪制。
每個等級每個像素所占用的字節也都不一樣,所存儲的色彩信息也不同。同一張100像素的圖片,`ARGB_8888`就占了400字節,`RGB_565`才占200字節
所以在某些場景中,修改圖片格式可以達到減少一半內存的效果
## 內存復用,避免重復分配內存
`Bitmap`所占內存比較大,如果我們頻繁創建與回收`Bitmap`,那么很容易造成內存抖動,所以我們應該盡量復用`Bitmap`內存
在 `Android 3.0(API 級別 11)`開始,系統引入了 `BitmapFactory.Options.inBitmap` 字段。如果設置了此選項,那么采用 `Options` 對象的解碼方法會在生成目標 `Bitmap` 時嘗試復用 `inBitmap`,這意味著 `inBitmap` 的內存得到了重復使用,從而提高了性能,同時移除了內存分配和取消分配。不過 `inBitmap` 的使用方式存在某些限制,在 `Android 4.4(API 級別 19)`之前系統僅支持復用大小相同的位圖,4.4 之后只要 `inBitmap` 的大小比目標 `Bitmap` 大即可
## 大圖局部加載策略
對于圖片加載還有種情況,就是單個圖片非常巨大,并且還不允許壓縮。比如顯示:世界地圖、清明上河圖、微博長圖等
首先不壓縮,按照原圖尺寸加載,那么屏幕肯定是不夠大的,并且考慮到內存的情況,不可能一次性整圖加載到內存中
所以這種情況的優化思路一般是局部加載,通過`BitmapRegionDecoder`來實現
~~~java
//設置顯示圖片的中心區域
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
mImageView.setImageBitmap(bitmap);
復制代碼
~~~
## 小結
上面所說的這些關于`Bitmap`的內存優化策略其實都比較簡單,而且我們在開發中可能很少用到
因為我們常用的圖片框架比如`Glide`已經將這些都封裝在里面了,所以一般情況下我們加載圖片時不需要做這些特殊操作
關于`Glide`對于加載圖片都做了哪些優化,有興趣的同學可以參考:[【帶著問題學】Glide做了哪些優化?](https://juejin.cn/post/6970683481127043085 "https://juejin.cn/post/6970683481127043085")
# 圖片兜底策略
針對因`activity`、`fragment`泄漏導致的圖片泄漏,我們可以在`onDetachedFromWindow`時機進行了監控和兜底,具體流程如下:

通過這種方式可以方便地解決因`Activity`導致的圖片泄漏問題
#線上大圖監控方案
當運營在線上配置了不合理大小的圖片時,如果我們及時發現,也會帶來內存問題
如果圖片本身大小就不合理,我們在這個基礎上談圖片優化也沒有什么意義,因此大圖監控這也是個比較常見的需求
下面介紹幾種大圖監控的方案:
## `ArtHook` 方案
該方案采用`weishu`大佬寫的`epic`庫實現,通過對`ART`虛擬機的`hook`,`hook ImageView`的 `setImageBitmap` 等方法
解析對比方法參數中的 `bitmap` 寬高和 `ImageView` 實例的寬高,也可以獲得`bitmap`的實際大小
如果圖片寬高比`ImageView`寬高大,或者圖片大小超出了閾值,就可以把相關信息上報
這種方案的優點在于:
1.侵入性極低,一次初始化配置即可`hook`全局的目標`View`控件
2.可以獲取代碼調用堆棧,方便開發者快速定位
而缺點則在于:
兼容性存在問題,使用了`hook`系統`API` ,不能用于線上
## `BaseActivity` 方案
大部分應用工程在業務發展的過程中都會沉淀封裝自己的`BaseActivity` ,通過在`BaseActivity onDestroy`中動態地檢測各個`View`控件,從而獲知圖片加載情況
~~~kotlin
class BaseActivity : Activity(){
fun onDestory(){
if(isOpenCheckBitmap){
checkBitmapFromView()
}
}
fun checkBitmapFromView(){
//1、遍歷activity中的各個View控件
//2、獲取View控件加載的Bitmap
//3、對比Bitmap寬高與View寬高
}
}
復制代碼
~~~
這種方案的優點在于:
1.兼容性強,無任何反射
2.加入簡單,沒有什么復雜邏輯
缺點在于:
1.侵入性太強,需要修改`BaseActivity`
2.`BaseActivity.onDestory`本身可能被重寫,并不安全
## `ASM`方案
該方案在編譯流程進行插樁,通過匹配`setImageBitmap` 、 `setBackground` 等關鍵方法,插入`Bitmap`大小檢測邏輯
這種方案優點在于:
1.編譯時期插樁,對開發過程無侵入性
缺點在于:
1.通過插樁的方式打點,可能會增加編譯期耗時
2.`ASM`代碼維護成本較高,使用起來不是那么方便
## `registerActivityLifecycleCallback`方案
通過`registerActivityLifecycleCallback`監聽`Activity`生命周期,在`onStop`時進行`Bitmap`大小檢測的邏輯
~~~kotlin
private fun registerActivityLifecycleCallback(application: Application) {
application.registerActivityLifecycleCallbacks(object :
Application.ActivityLifecycleCallbacks {
override fun onActivityStopped(activity: Activity) {
checkBitmapIsTooBig(childViews)
}
})
}
~~~
這種方案對原始代碼無侵入性,同時使用起來比較簡單,也沒有兼容性問題,應該屬于比較良好的方案
詳細實現可見:[BitmapCanary 誕生](https://juejin.cn/post/6956138531789996040#heading-14 "https://juejin.cn/post/6956138531789996040#heading-14")
# 參考資料
[【從入門到實用】android內存優化深入解析](https://juejin.cn/post/6975876569990447134)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺