Android性能优化,Startalk会话页GIF内存优化实践

栏目: IOS · Android · 发布时间: 7年前

内容简介: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共享时间线视图

Android性能优化,Startalk会话页GIF内存优化实践

(图片来自developer.android.com)

Android Profiler现在显示一个共享时间线视图,其中包括一个带有CPU、MEMORY和NETWORK使用情况实时图表的时间线。该窗口还包括时间线缩放控件 3 ,用于跳转到实时更新的按钮 4 以及显示活动状态,用户输入事件和屏幕旋转事件 5 的事件时间线, 1 是连接的设备, 2 当前所选进程。

3.问题分析

了解了Android Profiler后,我们通过MEMORY时间线看一下在我们进入会话页后&当会话页有较多较大的GIF时我们的IM APP内存占用对比情况,首先看我们刚进入没有GIF的会话页内存占用如下

Android性能优化,Startalk会话页GIF内存优化实践

说明:

•Total: 当前所选进程占用的总内存大小

•Java:Java 或Kotlin代码分配的对象的内存

•Native :从C或C ++代码分配的对象的内存

•Graphics :用于图形缓冲区队列的内存

•Stack :应用程序中堆栈和Java堆栈使用的内存,这通常与您的应用运行的线程数有关

•Code :应用程序使用代码和资源的内存,例如dex字节码,优化或编译的dex代码,.so库和字体

•Others: 应用程序使用的内存,系统不知道如何分类

接着我们看一下在我进入一个Gif比较多(个别Gif图很大20M左右)会话后,滑动会话页后内存占用如下图:

Android性能优化,Startalk会话页GIF内存优化实践

从MEMORY时间线可以看到Native增加了将近70M, 并且在显示之前已经展示过的Gif时Native内存同样还是在增长,结束会话页后内存一直保持在一定值没有下降 。通过上面的分析得出的结论是在加载Gif的时候程序不断的在申请内存,前面背景中提到我们的Gif时Glide+FrameSequenceDrawable加载的,所以C&C++申请内存的操作于应该时在FrameSequence中,看一下FrameSequenceDrawable源码,发现这三个native 申请内存方法。

Android性能优化,Startalk会话页GIF内存优化实践

再看一下我们程序里面是如何使用的

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查看上面同样的操作内存情况

Android性能优化,Startalk会话页GIF内存优化实践

在我退出会话页若干秒或者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…


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Lighttpd

Lighttpd

Andre Bogus / Packt Publishing / 2008-10 / 39.99

This is your fast guide to getting started and getting inside the Lighttpd web server. Written from a developer's perspective, this book helps you understand Lighttpd, and get it set up as securely an......一起来看看 《Lighttpd》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具