相同点:都会致使应用运行出现问题、性能降低或崩溃。 不一样点:java
内存泄漏就是指new出来的Object(强引用)没法被GC回收android
非静态内部类和匿名类会隐式地持有一个外部类的引用git
外部类无论有多少个实例,都是共享同一个静态内部类,所以静态内部类不会持有外部类的引用github
在使用Cursor,InputStream/OutputStream,File的过程当中每每都用到了缓冲,所以在不须要使用的时候就要及时关闭它们,以便及时回收内存。它们的缓冲不只存在于 java虚拟机内,也存在于java虚拟机外,若是只是把引用设置为null而不关闭它们,每每会形成内存泄漏。 此外,对于须要注册的资源也要记得解除注册,例如:BroadcastReceiver。动画也要在界面再也不对用户可见时中止。bash
在以下代码中app
public class HandlerActivity extends AppCompatActivity {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
}
}
复制代码
在声明Handler对象后,IDE会给开发者一个提示:ide
This Handler class should be static or leaks might occur.
复制代码
意思是:Handler须要声明为static类型的,不然可能产生内存泄漏工具
这里来进行具体缘由分析: 应用在第一次启动时, 系统会在主线程建立Looper对象,Looper实现了一个简单的消息队列,用来循环处理Message。全部主要的应用层事件(例如Activity的生命周期方法回调、Button点击事件等)都会包含在Message里,系统会把Message添加到Looper中,而后Looper进行消息循环。主线程的Looper存在于整个应用的生命周期期间。 当主线程建立Handler对象时,会与Looepr对象绑定,被分发到消息队列的Message会持有对Handler的引用,以便系统在Looper处理到该Message时能调用Handle的handlerMessage(Message)方法。 在上述代码中,Handler不是静态内部类,因此会持有外部类(HandlerActivity)的一个引用。当Handler中有延迟的的任务或者等待执行的任务队列过长时,因为消息持有对Handler的引用,而Handler又持有对其外部类的潜在引用,这条引用关系会一直保持到消息获得处理为止,致使了HandlerActivity没法被垃圾回收器回收,从而致使了内存泄露。oop
好比,在以下代码中,在onCreate()方法中令handler每隔一秒就输出Log日记post
public class HandlerActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.e(TAG, "Hi");
handler.postDelayed(this, 1000);
}
}, 6000);
}
}
复制代码
查看Handler的源码能够看到,postDelayed方法其实就是在发送一条延时的Message
public final boolean postDelayed(Runnable r, long delayMillis){
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
复制代码
首先要意识到,非静态类和匿名内部类都会持有外部类的隐式引用。当HandlerActivity生命周期结束后,延时发送的Message持有Handler的引用,而Handler持有外部类(HandlerActivity)的隐式引用。该引用会继续存在直到Message被处理完成,而此处并无能够令Handler终止的条件语句,因此阻止了HandlerActivity的回收,最终致使内存泄漏。
此处使用 LeakCanary 来检测内存泄露状况(该工具下边会有介绍) 先启动HandlerActivity后退出,等个三四秒后,能够看到LeakCanary提示咱们应用内存泄漏了
经过文字提示能够看到问题就出在Handler身上
解决办法就是在HandlerActivity退出后,移除Handler的全部回调和消息
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
复制代码
当在开启一个子线程用于执行一个耗时操做后,此时若是改变配置(例如横竖屏切换)致使了Activity从新建立,通常来讲旧Activity就将交给GC进行回收。但若是建立的线程被声明为非静态内部类或者匿名类,那么线程会保持有旧Activity的隐式引用。当线程的run()方法尚未执行结束时,线程是不会被销毁的,所以致使所引用的旧的Activity也不会被销毁,而且与该Activity相关的全部资源文件也不会被回收,所以形成严重的内存泄露。
所以总结来看, 线程产生内存泄露的主要缘由有两点:
例如以下代码,在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继续运行,能够按如下步骤来:
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();
}
}
}
}
}
}
复制代码
在使用Toast的过程当中,若是应用连续弹出多个Toast,那么就会形成Toast重叠显示的状况 所以,可使用以下方法来保证当前应用任什么时候候只会显示一个Toast,且Toast的文本信息可以获得当即更新
/**
* 做者: 叶应是叶
* 时间: 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, 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的引用不能超过它自己的生命周期。
public Toast(Context context) {
mContext = context;
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
复制代码
解决办法是改成使用 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();
}
}
复制代码
有时候咱们须要把一些对象加入到集合容器(例如ArrayList)中,当再也不须要当中某些对象时,若是不把该对象的引用从集合中清理掉,也会使得GC没法回收该对象。若是集合是static类型的话,那内存泄漏状况就会更为严重。 所以,当再也不须要某对象时,须要主动将之从集合中移除
LeakCanary是Square公司开发的一个用于检测内存溢出问题的开源库,能够在 debug 包中轻松检测内存泄露 GitHub地址:LeakCanary
要引入LeakCanary库,只须要在项目的build.gradle文件添加
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
复制代码
Gradle强大的可配置性,能够确保只在编译 debug 版本时才会检查内存泄露,而编译 release 等版本的时候则会自动跳过检查,避免影响性能
若是只是想监测Activity的内存泄漏,在自定义的Application中进行以下初始化便可
/**
* 做者: 叶应是叶
* 时间: 2017/4/4 12:41
* 描述:
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
复制代码
若是还想监测Fragmnet的内存泄漏状况,则在自定义的Application中进行以下初始化
/**
* 做者: 叶应是叶
* 时间: 2017/4/4 12:41
* 描述:
*/
public class MyApplication extends Application {
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
MyApplication application = (MyApplication) context.getApplicationContext();
return application.refWatcher;
}
}
复制代码
而后在要监测的Fragment中的onDestroy()创建监听
public class BaseFragment extends Fragment {
@Override
public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = MyApplication.getRefWatcher();
refWatcher.watch(this);
}
}
复制代码
当在测试debug版本的过程当中出现内存泄露时,LeakCanary将会自动展现一个通知栏显示检测结果