android 工作筆記 內存優化問題


工作中的學習終結,寫成博客以供以后學習

Android的虛擬機是基於寄存器的Dalvik,它的最大堆大小一般是16M,有的機器為24M。我們平常看到的OutOfMemory的錯誤,通常是堆內存溢出。

android常見的內存溢出一般有以下幾點問題
1.資源性對象沒有關閉(例如cursor,file流等)
2.適配器adapter中沒有復用contentview
3.廣播調用registerReceiver()后未調用unregisterReceiver().
4.Bitmap使用后未調用recycle()。
5.static關鍵字等。
1.資源性對象比如(Cursor,File文件等)往往都用了一些緩沖,我們在不使用的時候,應該及時關閉它們,以便它們的緩沖及時回收內存。它們的緩沖不僅存在於java虛擬機內,還存在於java虛擬機外。如果我們僅僅是把它的引用設置為null,而不關閉它們,往往會造成內存泄露。因為有些資源性對象,比如SQLiteCursor(在析構函數finalize(),如果我們沒有關閉它,它自己會調close()關閉),如果我們沒有關閉它,系統在回收它時也會關閉它,但是這樣的效率太低了。因此對於資源性對象在不使用的時候,應該調用它的close()函數,將其關閉掉,然后才置為null.在我們的程序退出時一定要確保我們的資源性對象已經關閉。
  程序中經常會進行查詢數據庫的操作,但是經常會有使用完畢Cursor后沒有關閉的情況。如果我們的查詢結果集比較小,對內存的消耗不容易被發現,只有在常時間大量操作的情況下才會復現內存問題,這樣就會給以后的測試和問題排查帶來困難和風險。

Cursor cursor = null;   
 try {   
   cursor = mContext.getContentResolver().query(uri,null, null,null,null);   
   if(cursor != null) {   
       cursor.moveToFirst();   
       //do something 
   }   
 } catch (Exception e) {   
   e.printStackTrace();     
 } finally {   
   if (cursor != null) {   
      cursor.close();   
   }   
   有一種情況下,我們不能直接將Cursor關閉掉,這就是在CursorAdapter中應用的情況,但是注意,CursorAdapter在Acivity結束時並沒有自動的將Cursor關閉掉,因此,你需要在onDestroy函數中,手動關閉。 
@Override  
protected void onDestroy() {         
   if (mAdapter != null && mAdapter.getCurosr() != null) {   
       mAdapter.getCursor().close();   
   }   
   super.onDestroy();    
}   

CursorAdapter中的changeCursor函數,會將原來的Cursor釋放掉,並替換為新的Cursor,所以你不用擔心原來的Cursor沒有被關閉。

Thread(線程)回收:

線程中涉及的任何東西GC都不能回收(Anything reachable by a thread cannot be GC’d ),所以線程很容易造成內存泄露。
如下面代碼所示:

[java] view plaincopy 
Thread t = new Thread() {  
    public void run() {  
        while (true) {  
            try {  
                Thread.sleep(1000);  
                System.out.println("thread is running...");  
            } catch (InterruptedException e) {  

            }  
        }  
    }  
};  
t.start();  
t = null;  
System.gc();  

如上在線程t中每間隔一秒輸出一段話,然后將線程設置為null並且調用System.gc方法。
最后的結果是線程並不會被回收,它會一直的運行下去。

因為運行中的線程是稱之為垃圾回收根(GC Roots)對象的一種,不會被垃圾回收。當垃圾回收器判斷一個對象是否可達,總是使用垃圾回收根對象作為參考點。
2.下面是示例代碼,減少iew對象的創建會節省很多內存

 public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}

static class ViewHolder {
TextView text;
ImageView icon;
}

3.廣播經常用到,方便了我們進程間通信
可以通過代碼的方式注冊:
IntentFilter postFilter = new IntentFilter();
postFilter.addAction(getPackageName() + “.background.job”);
this.registerReceiver(receiver, postFilter);

**當我們Activity中使用了registerReceiver()方法注冊了BroadcastReceiver,一定要在Activity的生命周期內調用unregisterReceiver()方法取消注冊
也就是說registerReceiver()和unregisterReceiver()方法一定要成對出現,通常我們可以重寫Activity的onDestory().**
4.接下來就是我們的內存消耗大戶bitmap
1.Bitmap類有一個方法recycle(),從方法名可以看出意思是回收。
Bitmap類的構造方法都是私有的,所以開發者不能直接new出一個Bitmap對象,只能通過BitmapFactory類的各種靜態方法來實例化一個Bitmap。仔細查看BitmapFactory的源代碼可以看到,生成Bitmap對象最終都是通過JNI調用方式實現的。所以,加載Bitmap到內存里以后,是包含兩部分內存區域的。簡單的說,一部分是Java部分的,一部分是C部分的。這個Bitmap對象是由Java部分分配的,不用的時候系統就會自動回收了,但是那個對應的C可用的內存區域,虛擬機是不能直接回收的,這個只能調用底層的功能釋放。所以需要調用recycle()方法來釋放C部分的內存。從Bitmap類的源代碼也可以看到,recycle()方法里也的確是調用了JNI方法了的。
那如果不調用recycle(),是否就一定存在內存泄露呢?也不是的。Android的每個應用都運行在獨立的進程里,有着獨立的內存,如果整個進程被應用本身或者系統殺死了,內存也就都被釋放掉了,當然也包括C部分的內存。
Android對於進程的管理是非常復雜的。簡單的說,Android系統的進程分為幾個級別,系統會在內存不足的情況下殺死一些低優先級的進程,以提供給其它進程充足的內存空間。在實際項目開發過程中,有的開發者會在退出程序的時候使用Process.killProcess(Process.myPid())的方式將自己的進程殺死,但是有的應用僅僅會使用調用Activity.finish()方法的方式關閉掉所有的Activity。
2.設置一定的采樣率。
如果圖片像素過大,使用BitmapFactory類的方法實例化Bitmap的過程中,需要大於8M的內存空間,就必定會發生OutOfMemory異常。這個時候該如何處理呢?如果有這種情況,則可以將圖片縮小,以減少載入圖片過程中的內存的使用,避免異常發生。
使用BitmapFactory.Options設置inSampleSize就可以縮小圖片。屬性值inSampleSize表示縮略圖大小為原始圖片大小的幾分之一。即如果這個值為2,則取出的縮略圖的寬和高都是原始圖片的1/2,圖片的大小就為原始大小的1/4。
如果知道圖片的像素過大,就可以對其進行縮小。那么如何才知道圖片過大呢?
使用BitmapFactory.Options設置inJustDecodeBounds為true后,再使用decodeFile()等方法,並不會真正的分配空間,即解碼出來的Bitmap為null,但是可計算出原始圖片的寬度和高度,即options.outWidth和options.outHeight。通過這兩個值,就可以知道圖片是否過大了。

private ImageView preview;   
BitmapFactory.Options options = new BitmapFactory.Options();   
options.inSampleSize = 2;//圖片寬高都為原來的二分之一,即圖片為原來的四分之一   
Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);   
preview.setImageBitmap(bitmap);   
第三、巧妙的運用軟引用(SoftRefrence) 
有些時候,我們使用Bitmap后沒有保留對它的引用,因此就無法調用Recycle函數。這時候巧妙的運用軟引用,可以使Bitmap在內存快不足時得到有效的釋放。如下例: 

private class MyAdapter extends BaseAdapter {

private ArrayList> mBitmapRefs = new ArrayList>();   
public View getView(int i, View view, ViewGroup viewGroup) {   
    View newView = null;   
    if(view != null) {   
        newView = view;   
    } else {   
        newView =(View)mInflater.inflate(R.layout.image_view, false);   
    }   

    Bitmap bitmap = BitmapFactory.decodeFile(mValues.get(i).fileName);   
    mBitmapRefs.add(new SoftReference(bitmap));     //此處加入ArrayList   
    ((ImageView)newView).setImageBitmap(bitmap);   

    return newView;   
}   

}
5.static int intVal = 42;
static String strVal = “Hello, world!”;

編譯器會生成一個叫做clinit的初始化類的方法,當類第一次被使用的時候這個方法會被執行。方法會將42賦給intVal,然后把一個指向類中常量表 的引用賦給strVal。當以后要用到這些值的時候,會在成員變量表中查找到他們。 下面我們做些改進,使用“final”關鍵字:

static final int intVal = 42;
static final String strVal = “Hello, world!”;

現在,類不再需要clinit方法,因為在成員變量初始化的時候,會將常量直接保存到類文件中。用到intVal的代碼被直接替換成42,而使用strVal的會指向一個字符串常量,而不是使用成員變量。
將一個方法或類聲明為final不會帶來性能的提升,但是會幫助編譯器優化代碼。舉例說,如果編譯器知道一個getter方法不會被重載,那么編譯器會對其采用內聯調用。
你也可以將本地變量聲明為final,同樣,這也不會帶來性能的提升。使用“final”只能使本地變量看起來更清晰些(但是也有些時候這是必須的,比如在使用匿名內部類的時候)。

靜態方法代替虛擬方法
如果不需要訪問某對象的字段,將方法設置為靜態,調用會加速15%到20%。這也是一種好的做法,因為你可以從方法聲明中看出調用該方法不需要更新此對象的狀態。

減少不必要的全局變量
盡量避免static成員變量引用資源耗費過多的實例,比如Context
因為Context的引用超過它本身的生命周期,會導致Context泄漏。所以盡量使用Application這種Context類型。 你可以通過調用Context.getApplicationContext()或 Activity.getApplication()輕松得到Application對象。

避免創建不必要的對象
最常見的例子就是當你要頻繁操作一個字符串時,使用StringBuffer代替String。
對於所有所有基本類型的組合:int數組比Integer數組好,這也概括了一個基本事實,兩個平行的int數組比 (int,int)對象數組性能要好很多。
總體來說,就是避免創建短命的臨時對象。減少對象的創建就能減少垃圾收集,進而減少對用戶體驗的影響。

避免內部Getters/Setters
在Android中,虛方法調用的代價比直接字段訪問高昂許多。通常根據面向對象語言的實踐,在公共接口中使用Getters和Setters是有道理的,但在一個字段經常被訪問的類中宜采用直接訪問。

避免使用浮點數
通常的經驗是,在Android設備中,浮點數會比整型慢兩倍。

关注微信公众号

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com