什么是內(nèi)存泄露
內(nèi)存泄露:程序在向系統(tǒng)申請(qǐng)分配內(nèi)存空間后(new)鸵贬,在使用完畢后未釋放。通俗講脐区,沒有用的對(duì)象一直無法回收的現(xiàn)象就是內(nèi)存泄露绳姨,值得注意的是登澜,我們App隨著內(nèi)存泄露的不斷積累,最終會(huì)導(dǎo)致內(nèi)存溢出OOM(Out Of Memory)飘庄,更嚴(yán)重的導(dǎo)致程序崩潰
內(nèi)存溢出:程序向系統(tǒng)申請(qǐng)的內(nèi)存空間超出了系統(tǒng)能給的脑蠕,比如內(nèi)存只能分配一個(gè)int類型,我卻要塞給他一個(gè)long類型跪削,系統(tǒng)就出現(xiàn)oom谴仙。
在Android中造成內(nèi)存泄露的原因
-
1 單例
public class TestManager
{
private static TestManager mTestManager;
private static Context mContext;
private TestManager(Context context){
this.mContext=context;
}
public static TestManager getmTestManager(Context context){
if(mTestManager !=null){
synchronized (TestManager.class){
if(mTestManager !=null){
mTestManager=new TestManager(context);
}
}
}
return mTestManager;
}
}
然后在MainActivity中使用
private void initData()
{
TestManager testManager = TestManager.getmTestManager(this);
}
需要外部傳入一個(gè) Context 來獲取該類的實(shí)例,如果此時(shí)傳入的 Context 是 Activity 的話切揭,此時(shí)單例就有持有該 Activity 的強(qiáng)引用(直到整個(gè)應(yīng)用生命周期結(jié)束)狞甚。這樣的話,即使該 Activity 退出廓旬,該 Activity 的內(nèi)存也不會(huì)被回收哼审,這樣就造了內(nèi)存泄露,
解決辦法——單例模式引用的對(duì)象的生命周期 = 應(yīng)用生命周期孕豹。將 context.getApplicationContext() 賦值給 mContext涩盾,此時(shí)單例引用的對(duì)象是 Application,而 Application 的生命周期本來就跟應(yīng)用程序是一樣的励背,也就不存在內(nèi)存泄露春霍。
public class TestManager
{
private static TestManager mTestManager;
private static Context mContext;
private TestManager(Context context){
this.mContext=context.getApplicationContext();
}
public static TestManager getmTestManager(Context context){
if(mTestManager !=null){
synchronized (TestManager.class){
if(mTestManager !=null){
mTestManager=new TestManager(context);
}
}
}
return mTestManager;
}
}
-
2.匿名內(nèi)部類/非靜態(tài)內(nèi)部類(匿名類和非靜態(tài)內(nèi)部類最大的共同點(diǎn)就是 都持有外部類的引用)
匿名內(nèi)部類叶眉,在Java當(dāng)中址儒,非靜態(tài)內(nèi)部類默認(rèn)將會(huì)有持有外部類的引用,當(dāng)在內(nèi)部類實(shí)例化一個(gè)靜態(tài)的對(duì)象衅疙,那么莲趣,這個(gè)對(duì)象將會(huì)與App的生命周期一樣長(zhǎng),又因?yàn)榉庆o態(tài)內(nèi)部類一直持有外部的MainActivity的引用饱溢,導(dǎo)致MainActivity無法被回收喧伞,內(nèi)存泄露的代碼如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
可以看到我們?cè)?Activity 中繼承 AsyncTask 自定義了一個(gè)非靜態(tài)內(nèi)部類,在 doInbackground() 方法中做了耗時(shí)的操作,然后在 onCreate() 中啟動(dòng) MyAsyncTask潘鲫。如果在耗時(shí)操作結(jié)束之前翁逞,Activity 被銷毀了,這時(shí)候因?yàn)?MyAsyncTask 持有 Activity 的強(qiáng)引用溉仑,便會(huì)導(dǎo)致 Activity 的內(nèi)存無法被回收挖函,這時(shí)候便會(huì)產(chǎn)生內(nèi)存泄露。
- 解決辦法 :將 MyAsyncTask 變成靜態(tài)內(nèi)部類
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
匿名類和非靜態(tài)內(nèi)部類最大的共同點(diǎn)就是 都持有外部類的引用彼念,因此挪圾,匿名類造成內(nèi)存泄露的原因也跟靜態(tài)內(nèi)部類基本是一樣的浅萧,下面舉個(gè)幾個(gè)比較常見的例子:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ① 匿名線程持有 Activity 的引用逐沙,進(jìn)行耗時(shí)操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// ② 使用匿名 Handler 發(fā)送耗時(shí)消息
Message message = Message.obtain();
mHandler.sendMessageDelayed(message, 60000);
}
上面舉出了兩個(gè)比較常見的例子:
- new 出一個(gè)匿名的 Thread,進(jìn)行耗時(shí)的操作洼畅,如果 MainActivity 被銷毀而 Thread 中的耗時(shí)操作沒有結(jié)束的話吩案,便會(huì)產(chǎn)生內(nèi)存泄露
- new 出一個(gè)匿名的 Handler,這里我采用了 sendMessageDelayed() 方法來發(fā)送消息帝簇,這時(shí)如果 MainActivity 被銷毀徘郭,而 Handler 里面的消息還沒發(fā)送完畢的話,Activity 的內(nèi)存也不會(huì)被回收
解決辦法:
- 繼承 Thread 實(shí)現(xiàn)靜態(tài)內(nèi)部類
- 繼承 Handler 實(shí)現(xiàn)靜態(tài)內(nèi)部類丧肴,以及在 Activity 的 onDestroy() 方法中残揉,移除所有的消息 mHandler.removeCallbacksAndMessages(null);
private MyHandler mHandler = new MyHandler(this);
private static class MyHandler extends Handler{
private WeakReference<Context> reference;
public MyHandler(Context context){
reference = new WeakReference<Context>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity = (MainActivity) reference.get();
if(mainActivity != null){
//業(yè)務(wù)處理邏輯
}
}
}
3.其他情況
除了上述 3 種常見情況外,還有其他的一些情況
1芋浮、需要手動(dòng)關(guān)閉的對(duì)象沒有關(guān)閉
網(wǎng)絡(luò)抱环、文件等流忘記關(guān)閉
手動(dòng)注冊(cè)廣播時(shí),退出時(shí)忘記 unregisterReceiver()
Service 執(zhí)行完后忘記 stopSelf()
EventBus 等觀察者模式的框架忘記手動(dòng)解除注冊(cè)
2纸巷、static 關(guān)鍵字修飾的成員變量
3镇草、ListView 的 Item 泄露