1. 程式人生 > >App性能優化之內存優化

App性能優化之內存優化

處理器 app https dma subst arraymap lru cache man onmeasure

本文為慕課網《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性能優化之內存優化