App性能优化之内存优化

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了App性能优化之内存优化相关的知识,希望对你有一定的参考价值。

本文为慕课网《App性能优化之内存优化》课程的学习笔记,视频地址 (http://www.imooc.com/video/13670)

## 如何查看一个app在安卓系统中的内存分配情况? 
方法一: 
1.启动android studio和虚拟机,建立连接。 
2.打开cmd窗口,输入adb shell。 
3.输入ps。 
技术分享 
4.可以看到有一个name为应用包名的进程,这就是我们的app所在的进程 
技术分享 
5.为了具体查看app所在进程的内存使用情况,需输入dumpsys meminfo +包名。 
技术分享 
方法二:

    float total_memory=
    Runtime.getRuntime().totalMemory()*1.0f/1024/1024;
    float free_memory=
    Runtime.getRuntime().freeMemory()*1.0f/1024/1024;
    float max_memory=
    Runtime.getRuntime().maxMemory()*1.0f/1024/1024;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

方法三: 
打开android studio的android monitor。 
方法四: 
打开android studio的Tools→Android→Android Device Monitor。 
技术分享

android内存分配与回收方式

  1. 一个App通常就是一个进程,对应一个虚拟机。
  2. GC(垃圾回收器)只在Heap剩余空间不足时才触发垃圾回收。(当GC回收垃圾后Heap剩余空间仍不足,GC会发起系统请求,若GC有很多变量,且GC回收会占用处理器时间,如果处理时间很长,影响app响应)。
  3. GC触发时,所有线程都会暂停,极端情况下发生线程抖动(后面会说)。

App内存限制机制


  1. 每个app分配的最大内存限制,随不同设备而不同。查看方式:`
ActivityManager manager= (ActivityManager)getSystemService(ACTIVITY_SERVICE);
int memory=manager.getMemoryClass();
int large=manager.getLargeMemoryClass();//大部分情况下二者相同
  • 1
  • 2
  • 3
  1. 吃内存大户:图片

切换应用时后台App清理机制


  • app切换时的LRU cache (LRU算法,最近使用的排在最前面,最少可能的被清理掉)
  • 系统清理(或内存变化)时会回调应用里activity的onTrimMemory(int level)方法。这时我们可以判断系统内存是否不足了,如果是就清理掉应用的一些不用的内存来使应用的占用内存变小,减少被系统清理掉的可能性。level对应信息

App内存优化方法

  • 数据结构优化 
    1.频繁的字符串拼接采用StringBuilder而不是通过+的方式(会产生无用的中间字符串内存块,视频中二者拼接同样字符串的总耗时为3ms和8000ms!!!)。 
    2.ArrayMap,SparseMap替换HashMap(HashMap效率不高,占用内存大)。 
    3.内存抖动(变量使用不当引起,比如突然产生很多变量或申请很多内存空间,但很快就做完事情弃之不用了,过了一会又进行上述操作,如果此时Heap不够,GC触发垃圾回收,此时所有线程暂停,内存使用情况会像抖动一样忽高忽低)。 
    4.再小的Class也要消耗0.5KB。 
    5.HashMap的每个entry需要占用额外的32B。
  • 对象复用 
    1.复用系统自带的资源。 
    2.ListView/GridView的ConvertView复用(ViewHolder)。 
    3.避免在onDraw方法里执行对象的创建(onMeasure也会调用多次,推荐在onSizeChanged方法内操作)。
  • 避免内存泄露 
    内存泄露:由于代码瑕疵,导致这块内存虽然停止不用了,但依然被其他东西引用着,导致GC无法对其进行回收。 
    1.内存泄露会导致剩余Heap越来越少,GC频繁触发。(视频中在activity中点击启动线程(简单的休眠5分钟),然后退出进入该activity,启动线程,重复多次,再进入Android Device Monitor,多次点击Cause GC启动GC回收,发现byte-array的count会有所减少,重复上述操作,count停止减少时的值不断增加,说明发生了内存泄露。 原因是线程是自定义内部类,会隐含的引用activity对象,且该线5min内会一直执行,如果换成休眠较短时间会有所改善。解决方法:放在service里执行)。 
    2.尤其是Activity泄露 
    3.用Application Context而不是Activity Context(可能会经常退出),某些View如Dialog一定要Activity Context(Token)。 
    4.Cursor对象用完要及时关闭。

OOM问题优化


1.OOM问题分析

  • OOM的必然性与可解决性,不再赘述。
  • OOM的绝大部分发生在图片。

强引用、软引用的意义

强引用就是平时的写法。 
软引用的用法。(虚引用与之类似)

private Map<String, SoftReference<Bitmap>> imageCache =
            new HashMap<String, SoftReference<Bitmap>>();

    public void addBitmapToCache(String path) {
        // 强引用的Bitmap对象
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        // 软引用的Bitmap对象
        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
        //WeakReference<Bitmap> weakBitmap=new WeakReference<Bitmap>(bitmap);
        TranslateAnimation animation;
        // 添加该对象到Map中使其缓存
        imageCache.put(path, softBitmap);
    }

    public Bitmap getBitmapByPath(String path) {
        // 从缓存中取软引用的Bitmap对象
        SoftReference<Bitmap> softBitmap = imageCache.get(path);
        // 判断是否存在软引用
        if (softBitmap == null) {
            return null;
        }
        // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
        return softBitmap.get();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

考虑如下情景:有些成员变量使用几次后就不使用了,但仍占据着内存空间,它们随着Activity的销毁而被回收,即使GC触发垃圾回收也不会对其进行回收,此时可用把它们放在SoftReference中,放入与读取见上述代码,GC触发垃圾回收时就可对其进行回收了。

2.优化OOM问题的方法

  • 临时Bitmap的优化

1.BitmapFactory.Options类

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(),R.drawable.yin,options);//不直接加载,获取bitmap图片宽高
BitmapFactory.Options options2 = new BitmapFactory.Options();
        options2.inSampleSize = scale;
Bitmap bitmap1=
BitmapFactory.decodeResource(getResources(),R.drawable.yin,options2);//scale越大,图片越模糊,所占内存越小
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
//RGB_565让ARGB只占两个字节,大小缩小一倍,且变化不明显
BitmapFactory.Options options=new BitmapFactory.Options();
        options.inPreferredConfig= Bitmap.Config.RGB_565;
Bitmap bitmap1=
BitmapFactory.decodeResource(getResources(),R.drawable.yin,options);
  • 1
  • 2
  • 3
  • 4
  • 5
//BitmapRegionDecoder类可以实现范围选取图片细节
BitmapRegionDecoder decoder=
BitmapRegionDecoder.newInstance(,false);
BitmapFactory.Options options2 = 
new BitmapFactory.Options();
bitmap=decoder.decodeRegion(new Rect(width/2-SCREEN_WIDTH/2+shiftpx,
                height/2-SCREEN_HEIGHT/2,width/2+SCREEN_WIDTH/2+shiftpx,
                height/2+SCREEN_HEIGHT/2),options2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.通过软引用。优点是让系统在内存不足时可以直接回收,缺点是回收没有优先级,可能回收的不是用过的而是将要显示的。

public class BitmapCache {
    static private BitmapCache cache;
    private ArrayMap<String,MySoftRe> hashRef;
    //软引用被回收后,回收对象放在这,可以查看哪些被回收了
    private ReferenceQueue<Bitmap> queue;

    private BitmapCache(){
            hashRef=new ArrayMap<>();
            queue=new ReferenceQueue<>();
        }
            /*
            继承SoftReference,使得每一个实例都具有可识别的标识
             */
    private class MySoftRe extends SoftReference<Bitmap>{
         private String key="";

         public MySoftRe(Bitmap referent, ReferenceQueue<? super Bitmap> q,String key) {
            super(referent, q);
            this.key=key;
        }
    }

    public static BitmapCache getInstance(){
        if (cache==null){
            cache=new BitmapCache();
        }
        return cache;
    }

    /*
    以软引用的方式对一个bitmap对象的实例进行引用并保存该引用
     */
    public void addCacheBitmap(String key, Bitmap bitmap){
        cleanCache();
        MySoftRe msf=new MySoftRe(bitmap,queue,key);
        hashRef.put(key,msf);
    }


    public Bitmap getBitmap(String key){
        Bitmap bitmap=null;
        try {
            if (hashRef.containsKey(key)){
                MySoftRe msf=hashRef.get(key);
                bitmap=msf.get();
            }
            return bitmap;
        }catch (NullPointerException e){
            return null;
        }
    }

    private void cleanCache() {
        MySoftRe msf=null;
        while ((msf= (MySoftRe) queue.poll())!=null){
            hashRef.remove(msf.key);
        }
    }

    public void clearCache(){
        cleanCache();
        hashRef.clear();
        System.gc();
        System.runFinalization();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

3.使用LRU Cache。

public class MemoryCache {

    private static final String TAG="jason";
    //LinkedHashMap专门用来构建LRU算法,但是线程不安全
    private Map<String,Bitmap> cache= Collections.synchronizedMap(
            new LinkedHashMap<String, Bitmap>(8,0.75f,true));
    private long size=0;//MemoryCache已经分配的大小
    private long limit=1000000;

    public MemoryCache(){
        setLimit(Runtime.getRuntime().maxMemory()/4);
    }

    private void setLimit(long l) {
        limit=l;
        Log.d(TAG,"MemoryCache will use up to"+limit/1024/1024+"MB");
    }

    public Bitmap get(String id){
        try {
            if (!cache.containsKey(id)){
                return null;
            }
            return cache.get(id);
        }catch (NullPointerException e){
            return null;
        }
    }

    public void put(String id,Bitmap bitmap){
        try {
            if (cache.containsKey(id)){
                size-=getSizeInBytes(cache.get(id));
            }
            cache.put(id,bitmap);
            size+=getSizeInBytes(bitmap);
            checkSize();
        }catch (Throwable th){
            th.printStackTrace();
        }
    }

    private void checkSize() {
        Log.i(TAG,"cache size="+size+"length="+cache.size());
        if (size>limit){
            Iterator<Map.Entry<String,Bitmap>> iterator=cache.entrySet().iterator();
            while (iterator.hasNext()){
                Map.Entry<String,Bitmap> entry=iterator.next();
                size-=getSizeInBytes(entry.getValue());
                iterator.remove();
                if (size<=limit){
                    break;
                }
            }
            Log.d(TAG,"Clean cache,new size="+cache.size());
        }
    }

    private long getSizeInBytes(Bitmap bitmap) {
        if (bitmap==null) {
            return 0;
        }
        return bitmap.getRowBytes()*bitmap.getHeight();
    }

    public void clear(){
        cache.clear();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
 
 




























以上是关于App性能优化之内存优化的主要内容,如果未能解决你的问题,请参考以下文章

App性能优化之内存优化

Android app性能优化大汇总之内存性能优化

Android性能优化系列之App启动优化

Google官方 详解 Android 性能优化史诗巨著之内存篇

Android性能优化第(八)篇---App启动速度优化之耗时检测处理

iOS性能优化之内存篇