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…