Android内存泄露案例和解析
Posted 逆水当行舟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android内存泄露案例和解析相关的知识,希望对你有一定的参考价值。
使用过长对象生命周期
静态引用
静态变量存储在方法区,在类加载的时候被加载,除非类被卸载了,或者他会一直存活,直到App进程销毁,也就是说静态变量的生命期等于整个进程的生命期。
private Context mContext;
@OnClick(R.id.btnStaticTest)
void testClick()
finish();
Toast.makeText(mContext, "mContext泄漏", Toast.LENGTH_SHORT).show();
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
mContext持有了MainActivity的引用,因为mContext生命期长于MainActivity所以造成内存泄漏
单例
单例整个程序只保存一个对象,因为其也要通过静态变量来实现,所以他的生命期其实和静态变量一致也是整个进程的存活期。如果在内部持有了一个生命期较短对象的引用,而且没有手动释放的话,那么也会造成内存泄漏。
public class SingleInstance
private Context mContext;
private static SingleInstance instance = new SingleInstance();
public static SingleInstance getInstance()
return instance;
public void setup(Context context)
this.mContext = context
解决办法
使用getApplicationContext代替context
例如在用Toast
的时候,我们会尽量选用getApplicationContext()
,就是利用Application的上下文生命期会贯穿整个App。
Toast.makeText(getApplicationContext(), "mContext泄漏", Toast.LENGTH_SHORT).show();
有的使用,我们无法拿到getApplicationContext()
因为这个方法是在ContextWrapper
这个类里面,Activity继承了这个类他自然可以拿到。可以通过下面这种方式去获取:
public class XApplication extends Application
private static Context mContext;
@Override
public void onCreate()
super.onCreate();
mContext = this;
LeakCanary.install(this);
public static Context getContext()
return mContext;
使用的时候XApplication.getContext()
拿到Context,因为Application
和静态变量生命期一致,所以不会造成内存泄漏,如果Application 在程序切换到后台被后台杀死,再切换回来重建肯定会执行他的onCreate()
方法,再次给mContext赋值,所以不用担心XApplication.getContext()
会报空指针异常。
手动控制释放
例如在Activity中会用到Activity的上下文mContext,那么就可以在onDestroy()
释放引用
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SingleInstance.getInstance().setContext(this);
@Override
protected void onDestroy()
super.onDestroy();
SingleInstance.getInstance().setContext(null);
事实上,这种情况还是经常碰到的,例如注册和反注册广播,还有使用EventBus或者otto的时候
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
EventBus.getDefault().register(this);
@Override
protected void onDestroy()
super.onDestroy();
EventBus.getDefault().unregister(this);
EventBus.getDefault()的实现
static volatile EventBus defaultInstance;
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault()
if (defaultInstance == null)
synchronized (EventBus.class)
if (defaultInstance == null)
defaultInstance = new EventBus();
return defaultInstance;
很明显看出他是一个懒汉式(Lazy Loading)的单例,通过unregister方法实现我们所说的手动释放。
使用非静态内部类
非静态内部类包括以下三种
- 成员内部类
- 局部内部类
- 匿名内部类
非静态内部类会直接持有外部类的引用,如果非静态内部类不被回收,他的外部类也不会被回收从而造成内存泄漏
例如:
成员内部类
class A
private static A a;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
a = new A();
虽然class A
为空,但是因为他存在一个静态实例,这会导致A的类实例不会被GC回收,从而导致外部类MainActivity
不会被回收。
匿名内部类
在点击事件的匿名内部类中,通过Handler.postDelayed()
执行一个延迟任务,任务执行在Runnable匿名内部类中实现。
public class MainActivity extends AppCompatActivity
public static final String TAG = "MainActivity";
public final static int DO_WORK = 1;
private Handler mHandler = new Handler(new Handler.Callback()
@Override
public boolean handleMessage(Message msg)
if (msg.what == DO_WORK)
//do something
return false;
);
@BindView(R.id.btnHandler)
Button btnHandler;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
btnHandler.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
MainActivity.this.finish();
mHandler.sendEmptyMessage(DO_WORK);
mHandler.postDelayed(new Runnable()
@Override
public void run()
Toast.makeText(MainActivity.this, "btnHandler", Toast.LENGTH_SHORT).show();
, 15 * 1000);
);
这里存在2处内存溢出:
1. finish()
掉这个Activity,任务延迟15s,在延后的这15S过程中,造成了15S的内存泄漏。
2. 在public boolean handleMessage(Message msg)
收到发送过来的消息,执行一些任务
修改方式:有两种方案:
手动取消 。 在关闭Activity时(finish/onStop等函数中),取消还在排队的Message:
mHandler.removeCallbacksAndMessages(null);使用弱引用。 使用WeakReference截断StrongReference。问题的症结既然是内部类持有外部类对象的引用,那我不用内部类就行了,直接使用静态成员类。但mHandler又需要与Activity对象交互,那就来个WeakReference,指向外部Activity对象。
public class MainActivity extends AppCompatActivity
public static final String TAG = "MainActivity";
public final static int DO_WORK = 1;
private MyHandler mHandler;
@BindView(R.id.btnHandler)
Button btnHandler;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
ButterKnife.bind(this);
btnHandler.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
MainActivity.this.finish();
mHandler.sendEmptyMessage(DO_WORK);
mHandler.postDelayed(new Runnable()
@Override
public void run()
Toast.makeText(MainActivity.this, "btnHandler", Toast.LENGTH_SHORT).show();
, 15 * 1000);
);
//方案一
@Override
protected void onDestroy()
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
if (mHandler != null)
mHandler = null;
//方案二
private static class MyHandler extends Handler
private WeakReference<AppCompatActivity> wr;
public MyHandler(AppCompatActivity aty)
wr = new WeakReference<>(aty);
@Override
public void handleMessage(Message msg)
super.handleMessage(msg);
if (wr.get() != null)
if (msg.what == DO_WORK)
//dosomething
耗时操作
直接开启子线程,匿名内部类
public class MainActivity extends AppCompatActivity
public static final String TAG = "MainActivity";
private static int count = 0;
@BindView(R.id.btnHandler)
Button btnHandler;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
new Thread(new Runnable()
@Override
public void run()
while (true)
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
count++;
Log.d(TAG, "run: " + count);
).start();
根据我们刚才分析的,使用弱引用来解除,Thread对Activity持有的引用
public class MainActivity extends AppCompatActivity
public static final String TAG = "MainActivity";
private static int count = 0;
@BindView(R.id.btnHandler)
Button btnHandler;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
new MyThread(new Runnable()
@Override
public void run()
while (true)
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
count++;
Log.d(TAG, "run: " + count);
).start();
private class MyThread extends Thread
private WeakReference<Runnable> wr;
public MyThread(Runnable runnable)
super(runnable);
wr = new WeakReference<Runnable>(runnable);
@Override
public synchronized void start()
if (wr.get() == null)
return;
super.start();
但是运行后发现,这样做没有作用。因为我们开始建立的Thread的时候有两个匿名内部类,修改后Runnable还是持有Activity的引用。
这里我们可以利用,线程池的特性在Activity停止的时候取消任务
public class MainActivity extends AppCompatActivity
public static final String TAG = "MainActivity";
private static int count = 0;
@BindView(R.id.btnHandler)
Button btnHandler;
private Future future;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Callable<Boolean> callable = new Callable()
@Override
public Object call() throws Exception
while (true)
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
return false;
count++;
Log.d(TAG, "run: " + count);
;
ExecutorService es = Executors.newSingleThreadExecutor();
future = es.submit(callable);
@Override
protected void onDestroy()
super.onDestroy();
future.cancel(true);
注意:不要使用Runnable取代Callable,这并不能中断任务执行,下面是错误的范例
Runnable runnable = new Runnable()
@Override
public void run()
while (true)
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
count++;
Log.d(TAG, "run: " + count);
;
ExecutorService es = Executors.newSingleThreadExecutor();
future = es.submit(runnable);
这样写 并不能取消执行
以上是关于Android内存泄露案例和解析的主要内容,如果未能解决你的问题,请参考以下文章