Android内存泄漏的八种可能

来源:互联网 发布:刀剑少女2网络出错 编辑:程序博客网 时间:2024/04/30 12:10

Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的代码更为安全。

不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak)。如果不小心,你的Android应用很容易浪费掉未释放的内存,最终导致内存用光的错误抛出(out-of-memory,OOM)。

一般内存泄漏(traditional memory leak)的原因是:当该对象的所有引用都已经释放了,对象仍未被释放。(译者注:Cursor忘记关闭等)

逻辑内存泄漏(logical memory leak)的原因是:当应用不再需要这个对象,当仍未释放该对象的所有引用。

如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。

在Android开发中,最容易引发的内存泄漏问题的是Context。比如Activity的Context,就包含大量的内存引用,例如View Hierarchies和其他资源。一旦泄漏了Context,也意味泄漏它指向的所有对象。Android机器内存有限,太多的内存泄漏容易导致OOM。

检测逻辑内存泄漏需要主观判断,特别是对象的生命周期并不清晰。幸运的是,Activity有着明确的生命周期,很容易发现泄漏的原因。Activity.onDestroy()被视为Activity生命的结束,程序上来看,它应该被销毁了,或者Android系统需要回收这些内存(译者注:当内存不够时,Android会回收看不见的Activity)。

如果这个方法执行完,在堆栈中仍存在持有该Activity的强引用,垃圾回收器就无法把它标记成已回收的内存,而我们本来目的就是要回收它!

结果就是Activity存活在它的生命周期之外。

Activity是重量级对象,应该让Android系统来处理它。然而,逻辑内存泄漏总是在不经意间发生。(译者注:曾经试过一个Activity导致20M内存泄漏)。在Android中,导致潜在内存泄漏的陷阱不外乎两种:

全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。

活在Activity生命周期之外的线程。没有清空对Activity的强引用。

检查一下你有没有遇到下列的情况。

Static Activities

在类中定义了静态Activity变量,把当前运行的Activity实例赋值于这个静态变量。

如果这个静态变量在Activity生命周期结束后没有清空,就导致内存泄漏。因为static变量是贯穿这个应用的生命周期的,所以被泄漏的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
static Activity activity;
 
void setStaticActivity() {
     activity = this;
}
 
View saButton = findViewById(R.id.sa_button);
saButton.setOnClickListener(new View.OnClickListener() {
     @Override public void onClick(View v) {
          setStaticActivity();
          nextActivity();
     }
});

Memory Leak 1 – Static Activity

Static Views

类似的情况会发生在单例模式中,如果Activity经常被用到,那么在内存中保存一个实例是很实用的。正如之前所述,强制延长Activity的生命周期是相当危险而且不必要的,无论如何都不能这样做。

特殊情况:如果一个View初始化耗费大量资源,而且在一个Activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy),像这样,当Activity被销毁时,应当释放资源。(译者注:示例代码中并没有释放内存,把这个static view置null即可,但是还是不建议用这个static view的方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
static view;
 
void setStaticView() {
    view = findViewById(R.id.sv_button);
}
 
View svButton = findViewById(R.id.sv_button);
svButton.setOnClickListener(new View.OnClickListener() {
     @Override public void onClick(View v) {
          setStaticView();
          nextActivity();
     }
});

Memory Leak 2 – Static View

Inner Classes

继续,假设Activity中有个内部类,这样做可以提高可读性和封装性。将如我们创建一个内部类,而且持有一个静态变量的引用,恭喜,内存泄漏就离你不远了(译者注:销毁的时候置空,嗯)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static Object inner;
 
void createInnerClass() {
     class InnerClass {
     }
     inner = new InnerClass();
}
 
View icButton = findViewById(R.id.ic_button);
icButton.setOnClickListener(new View.OnClickListener() {
     @Override public void onClick(View v) {
         createInnerClass();
         nextActivity();
     }
});

Memory Leak 3 – Inner Class

内部类的优势之一就是可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。

Anonymous Classes

相似地,匿名类也维护了外部类的引用。所以内存泄漏很容易发生,当你在Activity中定义了匿名的AsyncTsk

。当异步任务在后台执行耗时任务期间,Activity不幸被销毁了(译者注:用户退出,系统回收),这个被AsyncTask持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void startAsyncTask() {
     new AsyncTask<Void, Void, Void>() {
          @Override protected Void doInBackground(Void... params) {
                while(true);
          }
     }.execute();
}
 
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
     @Override public void onClick(View v) {
          startAsyncTask();
          nextActivity();
     }
});

Memory Leak 4 – AsyncTask

Handler

同样道理,定义匿名的Runnable,用匿名类Handler执行。Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void createHandler() {
new Handler() {
     @Override public void handleMessage(Message message) {
          super.handleMessage(message);
     }
}.postDelayed(new Runnable() {
     @Override public void run() {
     while(true);
}
}, Long.MAX_VALUE >> 1);
}
 
View hButton = findViewById(R.id.h_button);
hButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
           createHandler();
           nextActivity();
      }
});

Memory Leak 5 – Handler

Threads

我们再次通过Thread和TimerTask来展现内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void spawnThread() {
new Thread() {
     @Override public void run() {
          while(true);
     }
}.start();
}
 
View tButton = findViewById(R.id.t_button);
tButton.setOnClickListener(new View.OnClickListener() {
     @Override public void onClick(View v) {
          spawnThread();
          nextActivity();
     }
});

Memory Leak 6 – Thread

TimerTask

只要是匿名类的实例,不管是不是在工作线程,都会持有Activity的引用,导致内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
oid scheduleTimer() {
new Timer().schedule(new TimerTask() {
     @Override
     public void run() {
         while(true);
     }
}, Long.MAX_VALUE >> 1);
}
 
View ttButton = findViewById(R.id.tt_button);
ttButton.setOnClickListener(new View.OnClickListener() {
     @Override public void onClick(View v) {
          scheduleTimer();
          nextActivity();
     }
});

Memory Leak 7 – TimerTask

Sensor Manager

最后,通过Context.getSystemService(int name)可以获取系统服务。这些服务工作在各自的进程中,帮助应用处理后台任务,处理硬件交互。如果需要使用这些服务,可以注册监听器,这会导致服务持有了Context的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
void registerListener() {
      SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
      Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
      sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}
 
View smButton = findViewById(R.id.sm_button);
smButton.setOnClickListener(new View.OnClickListener() {
     @Override public void onClick(View v) {
          registerListener();
          nextActivity();
     }
});

Memory Leak 8 – Sensor Manager

总结

看过那么多会导致内存泄漏的例子,容易导致吃光手机的内存使垃圾回收处理更为频发,甚至最坏的情况会导致OOM。垃圾回收的操作是很昂贵的开销,会导致肉眼可见的卡顿。所以,实例化的时候注意持有的引用链,并经常进行内存泄漏检查。

原 文: Eight Ways Your Android App Can Leak Memory

转载请注明:Android开发中文站 » Android内存泄漏的八种可能

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 感冒吃了阿胶糕怎么办 身上起湿疹很痒怎么办 身上起小疹子痒怎么办 吊带裙带子断了怎么办 裙子洗完缩水了怎么办 衬衫洗后缩水了怎么办 羊毛被洗缩水了怎么办 蕾丝洗后缩水了怎么办 天丝针织衫缩水怎么办? 氨纶面料起球怎么办? 新买的衬衫很硬怎么办 孕晚期睡不好觉怎么办 全棉衣服缩水了怎么办 棉麻的衣服起球怎么办 布艺沙发起球怎么办 加绒的衣服掉毛怎么办 新裤子有刺鼻味怎么办 新买鞋子味道大怎么办 天窗下水管堵了怎么办 雪纺衬衫染色了怎么办 雪纺衣服染色了怎么办 漂白后衣服变黄怎么办 用84泡衣服变红怎么办 84腐蚀过的衣服怎么办 紫檀手链泡水了怎么办 链条包链条太短怎么办 涂银遮光布有毒怎么办 车顶布掉下来了怎么办 公摊面积被占用怎么办 按揭房卖了贷款怎么办 首付了贷款不批怎么办 买的房子烂尾了怎么办 备案的房子烂尾怎么办 漏水把楼下泡了怎么办 电动拉闸门没电怎么办 封阳台空调外机怎么办 门牙有缝怎么办1毫米 玻璃房夏天太晒怎么办 十元人民币缺角怎么办 楼上租房的太吵怎么办 交通事故责任方不赔偿怎么办