原因及解決方法為:1、使用靜態(tài)內(nèi)部類,避免線程造成的內(nèi)存泄漏;2、使用緩存的convertView構(gòu)造Adapter,避免使用ListView造成的內(nèi)存泄漏;3、退出程序前,clear集合里的東西,置為null,避免集合容器中的內(nèi)存泄露等。
本教程操作環(huán)境:windows7系統(tǒng)、Dell G3電腦。
常見(jiàn)的內(nèi)存泄露造成的原因
1、單例造成的內(nèi)存泄漏
由于單例的靜態(tài)特性使得其生命周期和應(yīng)用的生命周期一樣長(zhǎng),如果一個(gè)對(duì)象已經(jīng)不再需要使用了,而單例對(duì)象還持有該對(duì)象的引用,就會(huì)使得該對(duì)象不能被正?;厥?,從而導(dǎo)致了內(nèi)存泄漏。
示例:防止單例導(dǎo)致內(nèi)存泄漏的實(shí)例
// 使用了單例模式 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、非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏
例如,有時(shí)候我們可能會(huì)在啟動(dòng)頻繁的Activity中,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源,可能會(huì)出現(xiàn)如下寫法:
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造成的內(nèi)存泄漏
示例:創(chuàng)建匿名內(nèi)部類的靜態(tài)對(duì)象
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的角度
當(dāng)Android應(yīng)用程序啟動(dòng)時(shí),該應(yīng)用程序的主線程會(huì)自動(dòng)創(chuàng)建一個(gè)Looper對(duì)象和與之關(guān)聯(lián)的MessageQueue。當(dāng)主線程中實(shí)例化一個(gè)Handler對(duì)象后,它就會(huì)自動(dòng)與主線程Looper的MessageQueue關(guān)聯(lián)起來(lái)。所有發(fā)送到MessageQueue的Messag都會(huì)持有Handler的引用,所以Looper會(huì)據(jù)此回調(diào)Handle的handleMessage()方法來(lái)處理消息。只要MessageQueue中有未處理的Message,Looper就會(huì)不斷的從中取出并交給Handler處理。另外,主線程的Looper對(duì)象會(huì)伴隨該應(yīng)用程序的整個(gè)生命周期。
2、 Java角度
在Java中,非靜態(tài)內(nèi)部類和匿名類內(nèi)部類都會(huì)潛在持有它們所屬的外部類的引用,但是靜態(tài)內(nèi)部類卻不會(huì)。
對(duì)上述的示例進(jìn)行分析,當(dāng)MainActivity結(jié)束時(shí),未處理的消息持有handler的引用,而handler又持有它所屬的外部類也就是MainActivity的引用。這條引用關(guān)系會(huì)一直保持直到消息得到處理,這樣阻止了MainActivity被垃圾回收器回收,從而造成了內(nèi)存泄漏。
解決方法:將Handler類獨(dú)立出來(lái)或者使用靜態(tài)內(nèi)部類,這樣便可以避免內(nèi)存泄漏。
4、線程造成的內(nèi)存泄漏
示例: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都使用了匿名內(nèi)部類,那么它們將持有其所在Activity的隱式引用。如果任務(wù)在Activity銷毀之前還未完成,那么將導(dǎo)致Activity的內(nèi)存資源無(wú)法被回收,從而造成內(nèi)存泄漏。
解決方法:將AsyncTask和Runnable類獨(dú)立出來(lái)或者使用靜態(tài)內(nèi)部類,這樣便可以避免內(nèi)存泄漏。
5、資源未關(guān)閉造成的內(nèi)存泄漏
對(duì)于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷,否則這些資源將不會(huì)被回收,從而造成內(nèi)存泄漏。
1)比如在Activity中register了一個(gè)BraodcastReceiver,但在Activity結(jié)束后沒(méi)有unregister該BraodcastReceiver。
2)資源性對(duì)象比如Cursor,Stream、File文件等往往都用了一些緩沖,我們?cè)诓皇褂玫臅r(shí)候,應(yīng)該及時(shí)關(guān)閉它們,以便它們的緩沖及時(shí)回收內(nèi)存。它們的緩沖不僅存在于 java虛擬機(jī)內(nèi),還存在于java虛擬機(jī)外。如果我們僅僅是把它的引用設(shè)置為null,而不關(guān)閉它們,往往會(huì)造成內(nèi)存泄漏。
3)對(duì)于資源性對(duì)象在不使用的時(shí)候,應(yīng)該調(diào)用它的close()函數(shù)將其關(guān)閉掉,然后再設(shè)置為null。在我們的程序退出時(shí)一定要確保我們的資源性對(duì)象已經(jīng)關(guān)閉。
4)Bitmap對(duì)象不在使用時(shí)調(diào)用recycle()釋放內(nèi)存。2.3以后的bitmap應(yīng)該是不需要手動(dòng)recycle了,內(nèi)存已經(jīng)在java層了。
6、使用ListView時(shí)造成的內(nèi)存泄漏
初始時(shí)ListView會(huì)從BaseAdapter中根據(jù)當(dāng)前的屏幕布局實(shí)例化一定數(shù)量的View對(duì)象,同時(shí)ListView會(huì)將這些View對(duì)象緩存起來(lái)。當(dāng)向上滾動(dòng)ListView時(shí),原先位于最上面的Item的View對(duì)象會(huì)被回收,然后被用來(lái)構(gòu)造新出現(xiàn)在下面的Item。這個(gè)構(gòu)造過(guò)程就是由getView()方法完成的,getView()的第二個(gè)形參convertView就是被緩存起來(lái)的Item的View對(duì)象(初始化時(shí)緩存中沒(méi)有View對(duì)象則convertView是null)。
構(gòu)造Adapter時(shí),沒(méi)有使用緩存的convertView。
解決方法:在構(gòu)造Adapter時(shí),使用緩存的convertView。
7、集合容器中的內(nèi)存泄露
我們通常把一些對(duì)象的引用加入到了集合容器(比如ArrayList)中,當(dāng)我們不需要該對(duì)象時(shí),并沒(méi)有把它的引用從集合中清理掉,這樣這個(gè)集合就會(huì)越來(lái)越大。如果這個(gè)集合是static的話,那情況就更嚴(yán)重了。
解決方法:在退出程序之前,將集合里的東西clear,然后置為null,再退出程序。
8、WebView造成的泄露
當(dāng)我們不要使用WebView對(duì)象時(shí),應(yīng)該調(diào)用它的destory()函數(shù)來(lái)銷毀它,并釋放其占用的內(nèi)存,否則其長(zhǎng)期占用的內(nèi)存也不能被回收,從而造成內(nèi)存泄露。
解決方法:為WebView另外開啟一個(gè)進(jìn)程,通過(guò)AIDL與主線程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷毀,從而達(dá)到內(nèi)存的完整釋放。