原因及解決方法為:1、使用靜態內部類,避免線程造成的記憶體洩漏;2、使用快取的convertView建構Adapter,避免使用ListView造成的記憶體洩漏;3、退出程式前,clear集合裡的東西,置為null,避免集合容器中的記憶體外洩等。
本教學操作環境:windows7系統、Dell G3電腦。
#1、單例造成的記憶體洩漏
由於單例的靜態特性使得其生命週期和應用的生命週期一樣長,如果一個物件已經不再需要使用了,而單例物件還持有該物件的引用,就會使得該物件不能被正常回收,從而導致了內存洩漏。
範例:防止單例導致記憶體洩漏的實例
// 使用了单例模式 public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context; } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; } }
2、非靜態內部類別建立靜態實例造成的記憶體洩漏
例如,有時候我們可能會在啟動頻繁的Activity中,為了避免重複創建相同的資料資源,可能會出現如下寫法:
public class MainActivity extends AppCompatActivity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mResource == null){ mResource = new TestResource(); } //... } class TestResource { //... } }
3、Handler造成的記憶體洩漏
#範例:建立匿名內部類別的靜態物件
public class MainActivity extends AppCompatActivity { private final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // ... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { // ... handler.sendEmptyMessage(0x123); } }); } }
1、從Android的角度
當Android應用程式啟動時,該應用程式的主執行緒會自動建立一個Looper對象和與之關聯的MessageQueue。當主執行緒中實例化一個Handler物件後,它就會自動與主執行緒Looper的MessageQueue關聯。所有發送到MessageQueue的Messag都會持有Handler的引用,所以Looper會據此回呼Handle的handleMessage()方法來處理訊息。只要MessageQueue中有未處理的Message,Looper就會不斷的從中取出並交給Handler處理。另外,主線程的Looper物件會伴隨該應用程式的整個生命週期。
2、 Java角度
在Java中,非靜態內部類別和匿名類別內部類別都會潛在持有它們所屬的外部類別的引用,但是靜態內部類別卻不會。
對上述的範例進行分析,當MainActivity結束時,未處理的訊息持有handler的引用,而handler又持有它所屬的外部類別也就是MainActivity的引用。這條引用關係會一直維持直到訊息處理,這樣阻止了MainActivity被垃圾回收器回收,造成了記憶體洩漏。
解決方法:將Handler類別獨立出來或使用靜態內部類,這樣便可以避免記憶體洩漏。
4、線程造成的記憶體洩漏
範例:AsyncTask和Runnable
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new MyRunnable()).start(); new MyAsyncTask(this).execute(); } class MyAsyncTask extends AsyncTask<Void, Void, Void> { // ... public MyAsyncTask(Context context) { // ... } @Override protected Void doInBackground(Void... params) { // ... return null; } @Override protected void onPostExecute(Void aVoid) { // ... } } class MyRunnable implements Runnable { @Override public void run() { // ... } } }
AsyncTask和Runnable都使用了匿名內部類,那麼它們將持有其所在Activity的隱式引用。如果任務在Activity銷毀之前還未完成,那麼將導致Activity的記憶體資源無法被回收,從而造成記憶體洩漏。
解決方法:將AsyncTask和Runnable類別獨立出來或使用靜態內部類,這樣便可以避免記憶體洩漏。
5、資源未關閉造成的記憶體洩漏
對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源,應該在Activity銷毀時及時關閉或註銷,否則這些資源將不會被回收,從而造成記憶體洩漏。
1)例如在Activity中register了一個BraodcastReceiver,但在Activity結束後沒有unregister該BraodcastReceiver。
2)資源性物件例如Cursor,Stream、File檔案等往往都用了一些緩衝,我們在不使用的時候,應該及時關閉它們,以便它們的緩衝及時回收記憶體。它們的緩衝不僅存在於 java虛擬機器內,也存在於java虛擬機器外。如果我們只是把它的引用設為null,而不關閉它們,往往會造成記憶體洩漏。
3)對於資源性物件在不使用的時候,應該呼叫它的close()函數將其關閉掉,然後再設定為null。在我們的程式退出時一定要確保我們的資源性物件已經關閉。
4)Bitmap物件不在使用時呼叫recycle()釋放記憶體。 2.3以後的bitmap應該是不需要手動recycle了,記憶體已經在java層了。
6、使用ListView時造成的記憶體洩漏
初始時ListView會從BaseAdapter中根據目前的螢幕佈局實例化一定數量的View對象,同時ListView會將這些View物件快取起來。當向上捲動ListView時,原先位於最上面的Item的View物件會被回收,然後被用來建構新出現在下面的Item。這個建構過程就是由getView()方法完成的,getView()的第二個形參convertView就是被快取起來的Item的View物件(初始化時快取中沒有View物件則convertView是null)。
建構Adapter時,沒有使用快取的convertView。
解決方法:在建構Adapter時,使用快取的convertView。
7、集合容器中的記憶體外洩
#我們通常把一些物件的引用加入了集合容器(例如ArrayList)中,當我們不需要該物件時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。
解決方法:在退出程式之前,將集合裡的東西clear,然後置為null,再退出程式。
8、WebView造成的洩漏
當我們不要使用WebView物件時,應該呼叫它的destory()函數來銷毀它,並釋放其佔用的內存,否則其長期佔用的記憶體也不能被回收,造成記憶體外洩。
解決方法:為WebView另外開啟一個進程,透過AIDL與主執行緒進行通信,WebView所在的進程可以根據業務的需要選擇合適的時機進行銷毀,從而達到記憶體的完整釋放。
更多電腦相關知識,請造訪常見問題欄位!
以上是記憶體洩漏的原因及解決方法是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!