Startalk(星語)現已在GitHub上全面開源,邀君一塊兒添磚加瓦~~~
android
Startalk(星語)官方網站:im.qunar.com/new/#/home
git
Startalk(星語)開源代碼地址:github.com/qunarcorp/q…github
***********************************************************************************緩存
1.背景bash
作爲IM的核心部分,會話頁的展現和流暢度十分影響用戶體驗,本次優化的內容正是會話裏面的Gif圖片的展現,Android原生是沒有View直接支持Gif圖片播放的,Startalk使用Glide+FrameSequenceDrawable實現對Gif的支持,可是在使用過程當中發現了一些問題,例如在一個會話裏面Gif圖過多過大,IM在運行一段時間後內存吃緊,形成頁面開始卡頓,甚至OOM等問題,爲了解決這個問題咱們經過Android Studio 3.0開始內置的Android Profiler工具來檢測Memory的變化,從而發現問題所在並實施優化。ide
2.Android Profiler介紹工具
首先看一下Android Profiler共享時間線視圖字體
(圖片來自developer.android.com)
優化
Android Profiler如今顯示一個共享時間線視圖,其中包括一個帶有CPU、MEMORY和NETWORK使用狀況實時圖表的時間線。該窗口還包括時間線縮放控件 3,用於跳轉到實時更新的按鈕 4 以及顯示活動狀態,用戶輸入事件和屏幕旋轉事件 5 的事件時間線,1 是鏈接的設備,2當前所選進程。網站
3.問題分析
瞭解了Android Profiler後,咱們經過MEMORY時間線看一下在咱們進入會話頁後&當會話頁有較多較大的GIF時咱們的IM APP內存佔用對比狀況,首先看咱們剛進入沒有GIF的會話頁內存佔用以下
說明:
•Total:當前所選進程佔用的總內存大小
•Java:從Java或Kotlin代碼分配的對象的內存
•Native:從C或C ++代碼分配的對象的內存
•Graphics:用於圖形緩衝區隊列的內存
•Stack:應用程序中堆棧和Java堆棧使用的內存,這一般與您的應用運行的線程數有關
•Code:應用程序使用代碼和資源的內存,例如dex字節碼,優化或編譯的dex代碼,.so庫和字體
•Others:應用程序使用的內存,系統不知道如何分類
接着咱們看一下在我進入一個Gif比較多(個別Gif圖很大20M左右)會話後,滑動會話頁後內存佔用以下圖:
從MEMORY時間線能夠看到Native增長了將近70M,而且在顯示以前已經展現過的Gif時Native內存一樣仍是在增加,結束會話頁後內存一直保持在必定值沒有降低。經過上面的分析得出的結論是在加載Gif的時候程序不斷的在申請內存,前面背景中提到咱們的Gif時Glide+FrameSequenceDrawable加載的,因此C&C++申請內存的操做於應該時在FrameSequence中,看一下FrameSequenceDrawable源碼,發現這三個native 申請內存方法。
再看一下咱們程序裏面是如何使用的
Glide.with(context)
.load(url)
.asGif()
.toBytes()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.dontAnimate()
.into(new ViewTarget<LoadingImgView, byte[]>(mLoadingImgView) {
@Override
public void onResourceReady(byte[] resource, GlideAnimation<? super byte[]> glideAnimation) {
FrameSequence fs = FrameSequence.decodeByteArray(resource);
FrameSequenceDrawable drawable = new FrameSequenceDrawable(fs);
view.setImageDrawable(drawable);
}
});複製代碼
這段代碼是在會話列表的adapter中執行的,FrameSequence.decodeByteArray(resource)每次這個view展現的時候都會被調用到,也就意味着每次都會申請建立 byte[] resource長度大小的內存,這也是重複顯示同一個Gif時內存不斷增長的緣由。
接下來咱們對這段代碼進行優化,使用Cache策略(LruCache)確保同一個url對應一個FrameSequenceDrawable。
Glide.with(context)
.load(url)
.asGif()
.toBytes()
.diskCacheStrategy(DiskCacheStrategy.ALL)//緩存全尺寸
.dontAnimate()
.into(new ViewTarget<LoadingImgView, byte[]>(mLoadingImgView) {
@Override
public void onResourceReady(byte[] resource, GlideAnimation<? super byte[]> glideAnimation) {
WeakReference<Parcelable> cached = new WeakReference<>(MemoryCache.getMemoryCache(url));
if(cached.get() == null){
FrameSequence fs = FrameSequence.decodeByteArray(resource);
FrameSequenceDrawable drawable = new FrameSequenceDrawable(fs);
drawable.setByteCount(resource.length);
view.setImageDrawable(drawable);
MemoryCache.addObjToMemoryCache(url,drawable);
}else {
if(cached.get() instanceof FrameSequenceDrawable){
FrameSequenceDrawable fsd = (FrameSequenceDrawable)cached.get();
view.setImageDrawable(fsd);
}
}
}
});複製代碼
其中MemoryCache爲LruCache封裝的工具類,同時使用了WeakReference來保證FrameSequenceDrawable更容易被回收,回收的好處是native申請的內存能夠被銷燬釋放
protected void finalize() throws Throwable {
try {
if (mNativeFrameSequence != 0) nativeDestroyFrameSequence(mNativeFrameSequence);
} finally {
super.finalize();
}
}複製代碼
咱們在Application的onTrimMemory(level)方法來清空MemoryCache裏面的緩存,觸發GC(備註:onTrimMemory(level)方法會在程序內存吃緊的時候回調到又不通的level級別),咱們這裏設置 level >= TRIM_MEMORY_RUNNING_MODERATE,這樣在咱們Home出程序的時候會被執行。
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= TRIM_MEMORY_RUNNING_MODERATE) {
QIMSdk.getInstance().clearMemoryCache();
}
}複製代碼
而後咱們從新經過Android Profiler查看上面一樣的操做內存狀況
在我退出會話頁若干秒或者Home出去後,Native內存瞬間降下來了,大概回到進會話前大小。
經過Android Profiler對內存的分析咱們優化了Gif的內存消耗問題,其實經過這個工具咱們還能分析出程序的不足地方,本次針對的主要是Native的內存部分,而咱們內存的另外一大開銷Java堆內存也是咱們優化的重點。
問題:在分析FrameSequenceDrawable源碼的時候咱們發現Android7.0及以上當view隱藏的時候回調不到 setVisible方法,只作了臨時處理,有知道的小夥伴能夠評論回覆我。
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = super.setVisible(visible, restart);
//TODO 7.0及以上特殊處理 暫時沒找到其餘好辦法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
if(visible && !isRunning() && !restart){
restart = true;
}
}
if (!visible) {
super.setVisible(visible, restart);
stop();
} else if (restart || changed) {
stop();
start();
}
return changed;
}複製代碼
****************************************************************************************
Startalk(星語)官方網站:im.qunar.com/new/#/home
Startalk(星語)開源代碼地址:github.com/qunarcorp/q…