Android内存泄漏详解和总结
对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary工具来检测应用程序是否存在内存泄漏,LeakCanary则是由Square开源的一款轻量第三方内存泄漏检测工具,当它检测到程序中有内存泄漏的产生时,它将以最直观的方式告诉我们该内存泄漏是由谁产生的和该内存泄漏导致谁泄漏了而不
对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary工具来检测应用程序是否存在内存泄漏,LeakCanary则是由Square开源的一款轻量第三方内存泄漏检测工具,当它检测到程序中有内存泄漏的产生时,它将以最直观的方式告诉我们该内存泄漏是由谁产生的和该内存泄漏导致谁泄漏了而不能回收,供我们复查。
为什么会产生内存泄漏?
当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
内存泄漏对程序的影响?
内存泄漏是造成应用程序OOM的主要原因之一!我们知道Android系统为每个应用程序分配的内存有限,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。
Android中常见的内存泄漏汇总
单例造成的内存泄漏
单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏,由于单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
如下这个典例:
public class AppManager {
private Context context;
private static AppManager manager;
private AppManager(Context context) {
//如果传入Activity的context就可能会造成内存泄露
//this.context = context;
//context使用的是Application的context
this.context = context.getApplicationContext();
}
/**
* 线程安全的单例模式
*
* @return
*/
public static AppManager getInstance(Context context) {
if (manager == null) {
synchronized (AppManager.class) {
if (manager == null) {
manager = new AppManager(context);
}
}
}
return manager;
}
}
如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了
如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。
非静态内部类创建静态实例造成的内存泄漏
有的时候我们可能会在启动频繁的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 {
//...
}
}
这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext。
多线程中匿名内部类/非静态内部类
其实这种也和上面的非静态内部类的静态对象所属于同一种问题,就是生命周期短的对象引用了生命周期长的对象,最后导致对象都不能被回收利用。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Thread(){
@Override
public void run() {
try {
Thread.sleep(40000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
我们退出该Activity时,如果子线程还没有执行完成,那么Activity退出栈时将不能被回收,此时将会造成内存泄漏。
Handler造成的内存泄漏
1.Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例(造成内存泄漏的示例):
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestData();
}
private void requestData(){
//在子线程中请求数据(就不写了),然后把数据发送到UI线程
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列(MessageQueue)中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,会有一条MessageQueue -> Message -> Handler -> Activity的引用链,导致该Activity的内存资源无法及时回收,引发内存泄漏,最终导致你的Activity被持有引用而无法被回收。
2.所以另外一种做法为:(内存泄漏的简单处理,处理的不充分)
public class MainActivity extends AppCompatActivity {
private MineHandler mHandler = new MineHandler (this);
private TextView mTextView ;
private static class MineHandler extends Handler {
private WeakReference reference;
public MyHandler(MainActivity activity) {
reference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("xxx");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
requestData();
}
private void requestData() {
//在子线程中请求数据(就不写了),然后把数据发送到UI线程
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息。
3.我们应该在Activity的onDestroy()时移除消息队列中的消息,更准确的做法如下(完美解决内存泄漏问题):(1)静态的内部类。(2)使用弱引用。(3)退出Activity时移除Handler移除消息队列中所有消息和所有的Runnable。
public class MainActivity extends AppCompatActivity {
private MineHandler mHandler = new MineHandler(this);
private TextView mTextView ;
//(1)静态的内部类。
private static class MineHandler extends Handler {
//(2)使用弱引用。
private WeakReference reference;
public MineHandler(MainActivity activity) {
reference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("xxx");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
requestData();
}
private void requestData() {
//在子线程中请求数据(就不写了),然后把数据发送到UI线程
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
//(3)退出Activity时移除消息队列中所有消息和所有的Runnable。
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
super.onDestroy();
}
}
AsyncTask的内存泄漏
This AsyncTask class should be static or leaks might occur (com.xxx.FadeTimer) less... (Ctrl+F1)
A static field will leak contexts. Non-static inner classes have an implicit reference to their outer class. If that outer class is for example a Fragment or Activity, then this reference means that the long-running handler/loader/task will hold a reference to the activity which prevents it from getting garbage collected. Similarly, direct field references to activities and fragments from these longer running instances can cause leaks. ViewModel classes should never point to Views or non-application Contexts.
解决办法和Handler类似
1.静态的内部类。2.使用弱引用。3.退出Activity时取消AsyncTask。
private MineAsyncTask mineAsyncTask = new MineAsyncTask(this);
//1.创建静态的内部类。
private static class MineAsyncTask extends AsyncTask<Void, Void, String> {
//2.使用弱引用。
private WeakReference<MainActivity> activityReference;
// only retain a weak reference to the activity
MineAsyncTask(MainActivity activity) {
activityReference = new WeakReference<>(activity);
}
@Override
protected String doInBackground(Void... params) {
return "task finished";
}
@Override
protected void onPostExecute(String result) {
// get a reference to the activity if it is still there
MainActivity activity = activityReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
// modify the activity's UI
TextView textView = activity.findViewById(R.id.textview);
textView.setText(result);
// access Activity member variables
activity.mSomeMemberVariable = 321;
}
}
//在需要的地方执行
mineAsyncTask.execute();
@Override
protected void onDestroy() {
//3.退出Activity时取消AsyncTask。
if (mineAsyncTask != null && !mineAsyncTask.isCancelled()) {
mineAsyncTask.cancel(true);
mineAsyncTask = null;
}
super.onDestroy();
}
资源未关闭造成的内存泄漏
对于使用了Bitmap,数据库Cursor,动态的BraodcastReceiver,Stream(OutputStream、InputStream等等),EventBus等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
一些建议
- 对于生命周期比Activity长的对象如果需要应该使用ApplicationContext
- 对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏
- 对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null
- 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期
- 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
- 将内部类改为静态内部类
- 静态内部类中使用弱引用来引用外部类的成员变量
- 在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:
其中:NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建。
如对此有疑问,请联系qq1164688204。
推荐Android开源项目
项目功能介绍:RxJava2和Retrofit2项目,添加自动管理token功能,添加RxJava2生命周期管理,使用App架构设计是MVP模式和MVVM模式,同时使用组件化,部分代码使用Kotlin,此项目持续维护中。
项目地址:https://gitee.com/urasaki/RxJava2AndRetrofit2
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)