内容简介:Startalk(星语)现已在GitHub上全面开源,邀君一起添砖加瓦~~~Startalk(星语)官方网站:Startalk(星语)开源代码地址:
Startalk(星语)现已在GitHub上全面开源,邀君一起添砖加瓦~~~
Startalk(星语)官方网站: im.qunar.com/new/#/home
Startalk(星语)开源代码地址: github.com/qunarcorp/q…
***********************************************************************************
1.背景
做为IM的核心部分,会话页的展示和流畅度十分影响用户体验,本次优化的内容正是会话里面的Gif图片的展示,Android原生是没有View直接支持Gif图片播放的,Startalk使用Glide+FrameSequenceDrawable实现对Gif的支持,但是在使用过程中发现了一些问题,例如在一个会话里面Gif图过多过大,IM在运行一段时间后内存吃紧,造成页面开始卡顿,甚至OOM等问题,为了解决这个问题我们通过Android Studio 3.0开始内置的Android Profiler工具来检测Memory的变化,从而发现问题所在并实施优化。
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…
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- SQL Server Sleeping会话占用内存资源浅析?
- java – Spring会话数据Redis – 从Redis Store获取有效会话,当前用户
- google-app-engine – GAE webapp2会话:创建和检查会话的正确过程
- 图解 Session(会话)
- 内网会话劫持
- Tomcat集群之会话保持
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入理解并行编程
[美] Paul E.Mckenney(保罗·E·麦肯尼) / 谢宝友 鲁阳 / 电子工业出版社 / 2017-7-1 / 129
《深入理解并行编程》首先以霍金提出的两个理论物理限制为引子,解释了多核并行计算兴起的原因,并从硬件的角度阐述并行编程的难题。接着,《深入理解并行编程》以常见的计数器为例,探讨其不同的实现方法及适用场景。在这些实现方法中,除了介绍常见的锁以外,《深入理解并行编程》还重点介绍了RCU的使用及其原理,以及实现RCU的基础:内存屏障。最后,《深入理解并行编程》还介绍了并行软件的验证,以及并行实时计算等内容......一起来看看 《深入理解并行编程》 这本书的介绍吧!