Android内存泄漏处理机制
2018-03-07 15:26
134 查看
1.1、内存泄露、内存溢出:
内存泄露(Memory Leak)指一个无用对象持续占有内存或无用对象的内存得不到及时的释放,从而造成内存空间的浪费例如,当Activity的onDestroy()方法被调用以后,Activity 本身以及它涉及到的 View、Bitmap等都应该被回收。但是,如果有一个后台线程持有对这个Activity的引用,那么Activity占据的内存就不能被回收,严重时将导致OOM,最终Crash。
内存溢出(Out Of Memory)指一个应用在申请内存时,没有足够的内存空间供其使用
1.2、Java 的内存分配:
静态存储区:在程序整个运行期间都存在,编译时就分配好空间,主要用于存放静态数据和常量栈区:当一个方法被执行时会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存
堆区:通常存放 new 出来的对象,由 Java 垃圾回收器回收
1.3、四种引用类型:
强引用(StrongReference):Jvm宁可抛出 OOM (内存溢出)也不会让 GC(垃圾回收) 回收具有强引用的对象软引用(SoftReference):只有在内存空间不足时才会被回收的对象
弱引用(WeakReference):在 GC 时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
虚引用(PhantomReference):任何时候都可以被GC回收,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。可以用来作为GC回收Object的标志。
内存泄漏就是指new出来的Object(强引用)无法被GC回收
1.4、非静态内部类和匿名类:
非静态内部类和匿名类会隐式地持有一个外部类的引用1.5、静态内部类:
外部类不管有多少个实例,都是共享同一个静态内部类,因此静态内部类不会持有外部类的引用二、内存泄漏情况分析
2.1、资源未关闭
在使用Cursor,InputStream/OutputStream,File的过程中往往都用到了缓冲,因此在不需要使用的时候就要及时关闭它们,以便及时回收内存。它们的缓冲不仅存在于 java虚拟机内,也存在于java虚拟机外,如果只是把引用设置为null而不关闭它们,往往会造成内存泄漏。此外,对于需要注册的资源也要记得解除注册,例如:BroadcastReceiver。动画也要在界面不再对用户可见时停止。
2.1、Handler的隐式携带
在使用Handler进行UI更新的时候,会发送message给handler对象处理。而handler隐式携带当前activity。故需要在当前的onDestory中执行:handler.removeCallbacksAndMessages(null);2.3、Thread
线程产生内存泄露的主要原因有两点:线程生命周期的不可控。Activity中的Thread和AsyncTask并不会因为Activity销毁而销毁,Thread会一直等到run()执行结束才会停止,AsyncTask的doInBackground()方法同理非静态的内部类和匿名类会隐式地持有一个外部类的引用
例如如下代码,在onCreate()方法中启动一个线程,并用一个静态变量threadIndex标记当前创建的是第几个线程
public class ThreadActivity extends AppCompatActivity { private final String TAG = "ThreadActivity"; private static int threadIndex; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new Thread(new Runnable() { @Override public void run() { int j = threadIndex; while (true) { Log.e(TAG, "Hi--" + j); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }旋转几次屏幕,可以看到输出结果为:
04-04 08:15:16.373 23731-23911/com.czy.leakdemo E/ThreadActivity: Hi--2 04-04 08:15:16.374 23731-26132/com.czy.leakdemo E/ThreadActivity: Hi--4 04-04 08:15:16.374 23731-23970/com.czy.leakdemo E/ThreadActivity: Hi--3 04-04 08:15:16.374 23731-23820/com.czy.leakdemo E/ThreadActivity: Hi--1 04-04 08:15:16.852 23731-26202/com.czy.leakdemo E/ThreadActivity: Hi--5 04-04 08:15:18.374 23731-23911/com.czy.leakdemo E/ThreadActivity: Hi--2 04-04 08:15:18.374 23731-26132/com.czy.leakdemo E/ThreadActivity: Hi--4 04-04 08:15:18.376 23731-23970/com.czy.leakdemo E/ThreadActivity: Hi--3 04-04 08:15:18.376 23731-23820/com.czy.leakdemo E/ThreadActivity: Hi--1 04-04 08:15:18.852 23731-26202/com.czy.leakdemo E/ThreadActivity: Hi--5 ...即使创建了新的Activity,旧的Activity中建立的线程依然还在执行,从而导致无法释放Activity占用的内存,从而造成严重的内存泄漏LeakCanary的检测结果:
想要避免因为Thread造成内存泄漏,可以在Activity退出后主动停止Thread 例如,可以为Thread设置一个布尔变量threadSwitch来控制线程的启动与停止
public class ThreadActivity extends AppCompatActivity { private final String TAG = "ThreadActivity"; private int threadIndex; private boolean threadSwitch = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new Thread(new Runnable() { @Override public void run() { int j = threadIndex; while (threadSwitch) { Log.e(TAG, "Hi--" + j); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } @Override protected void onDestroy() { super.onDestroy(); threadSwitch = false; } }如果想保持Thread继续运行,可以按以下步骤来:将线程改为静态内部类,切断Activity 对于Thread的强引用
在线程内部采用弱引用保存Context引用,切断Thread对于Activity 的强引用
public class ThreadActivity extends AppCompatActivity { private static final String TAG = "ThreadActivity"; private static int threadIndex; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new MyThread(this).start(); } private static class MyThread extends Thread { private WeakReference<ThreadActivity> activityWeakReference; MyThread(ThreadActivity threadActivity) { activityWeakReference = new WeakReference<>(threadActivity); } @Override public void run() { if (activityWeakReference == null) { return; } if (activityWeakReference.get() != null) { int i = threadIndex; while (true) { Log.e(TAG, "Hi--" + i); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
2.4、Context
在使用Toast的过程中,如果应用连续弹出多个Toast,那么就会造成Toast重叠显示的情况因此,可以使用如下方法来保证当前应用任何时候只会显示一个Toast,且Toast的文本信息能够得到立即更新
/** * 作者: 叶应是叶 * 时间: 2017/4/4 14:05 * 描述: */ public class ToastUtils { private static Toast to b093 ast; public static void showToast(Context context, String info) { if (toast == null) { toast = Toast.makeText(context, info, Toast.LENGTH_SHORT); } toast.setText(info); toast.show(); } }然后,在Activity中使用
public class ToastActivity extends AppCompatActivity { private static int i = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_toast); } public void showToast(View view) { ToastUtils.showToast(this, "显示Toast:" + (i++)); } }先点击一次Button使Toast弹出后,退出ToastActivity,此时LeakCanary又会提示说造成内存泄漏了
当中提及了 Toast.mContext,通过查看Toast类的源码可以看到,Toast类内部的mContext指向传入的Context。而ToastUtils中的toast变量是静态类型的,其生命周期是与整个应用一样长的,从而导致 ToastActivity 得不到释放。因此,对Context的引用不能超过它本身的生命周期。解决办法是改为使用 ApplicationContext 即可,因为ApplicationContext会随着应用的存在而存在,而不依赖于Activity的生命周期
/** * 作者: 叶应是叶 * 时间: 2017/4/4 14:05 * 描述: */ public class ToastUtils { private static Toast toast; public static void showToast(Context context, String info) { if (toast == null) { toast = Toast.makeText(context.getApplicationContext(), info, Toast.LENGTH_SHORT); } toast.setText(info); toast.show(); } }
2.5、集合
有时候我们需要把一些对象加入到集合容器(例如ArrayList)中,当不再需要当中某些对象时,如果不把该对象的引用从集合中清理掉,也会使得GC无法回收该对象。如果集合是static类型的话,那内存泄漏情况就会更为严重。因此,当不再需要某对象时,需要主动将之从集合中移除
相关文章推荐
- Android-浅析Android消息处理机制使用不当造成的内存泄漏
- Android多线程,异步消息处理机制, Thread, AsyncTask 简单总结
- Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
- android事件拦截处理机制图解
- android学习笔记——浅析android消息处理机制
- 通过源码分析Android 的消息处理机制
- Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
- android消息处理机制-------Looper
- Android 异步消息处理机制(Handler 、 Looper 、MessageQueue)源码解析
- Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
- Android事件处理机制实例讲解
- android的窗口机制分析------事件处理
- Android--利用Handler消息转发机制实现倒计时(内含防止内存泄露处理)
- Android开发(12)--多线程处理机制之Handler的使用方法
- Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
- Android 输入事件处理机制
- Android绘图机制与处理技巧(三)——Android图像处理之图形特效处理
- Android异步消息处理机制(2)源码解析
- Android编程实现异步消息处理机制的几种方法总结
- Android中的Looper , Handler , Message的关系,异步消息处理的机制,根据源码分析