Android内存泄露案例和解析
2016-06-08 21:43
387 查看
使用过长对象生命周期
静态引用
静态变量存储在方法区,在类加载的时候被加载,除非类被卸载了,或者他会一直存活,直到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);
这样写 并不能取消执行
相关文章推荐
- c语言内存泄露示例解析
- 解决js函数闭包内存泄露问题的办法
- IE下使用jQuery重置iframe地址时内存泄露问题解决办法
- Android App调试内存泄露之Cursor篇
- js内存泄露的几种情况详细探讨
- Javascript 闭包引起的IE内存泄露分析
- JS闭包、作用域链、垃圾回收、内存泄露相关知识小结
- 深入解析PHP垃圾回收机制对内存泄露的处理
- PHP脚本内存泄露导致Apache频繁宕机解决方法
- 容易造成JavaScript内存泄露几个方面
- 关于js内存泄露的一个好例子
- 总结JavaScript在IE9之前版本中内存泄露问题
- Android垃圾回收机制解决内存泄露问题
- Android编程中避免内存泄露的方法总结
- C语言中的内存泄露 怎样避免与检测
- PHP CURL 内存泄露问题解决方法
- Java中典型的内存泄露问题和解决方法
- Android中Handler引起的内存泄露问题解决办法
- 浅谈Java编程中的内存泄露情况
- 理解Java中的内存泄露及解决方法示例