对于 Android Developer 来讲,不少开源库都是属于开发必备的知识点,从使用方式到实现原理再到源码解析,这些都须要咱们有必定程度的了解和运用能力。因此我打算来写一系列关于开源库源码解析和实战演练的文章,初定的目标是 EventBus、ARouter、LeakCanary、Retrofit、Glide、OkHttp、Coil 等七个知名开源库,但愿对你有所帮助 😇😇java
公众号:字节数组android
系列文章导航:数组
上篇文章对 LeakCanary 进行了一次比较全面的源码解析,按流程来讲本篇文章应该是属于实战篇的,但是因为某些缘由就不打算写实战内容了(其实就是本身有点菜,写不太出来),就仍是来写一篇关于内存泄露相关的扩展阅读吧 😂😂markdown
Java 的一个很显著的优势就在于内存自动回收机制,Java 经过垃圾收集器(Garbage Collection,GC)来自动管理内存的回收过程,而无需开发者来主动释放内存。这种自动化行为有效地节省了开发人员的开发成本,但也让一些开发者误觉得 Java 就不存在内存泄漏的问题了,或者是误认为内存泄露应该是 GC 或者 JVM 层面来关心和解决的问题。这种想法是不正确的,由于内存泄露大多时候是因为程序自己存在缺陷而致使的,GC 和 JVM 并没有法精准理解程序的实现初衷,因此仍是须要由开发人员来主动解决问题app
内存泄露(Memory Leak) 和 内存溢出(Out Of Memory) 两个概念常常会一块儿被说起,二者有相互关联的地方,但实质上仍是有着很大的区别:异步
二者都会致使应用运行出现问题、性能降低或崩溃。不一样点主要在于:async
对于一个存在内存泄露的程序来讲,即便每次仅会泄露少许内存,程序的可用内存也是会逐步下降,在长期运行事后,程序也是隐藏着崩溃的危险ide
为了判断程序是否存在内存泄露的状况,咱们首先必须先了解 Java 是如何管理内存的,Java 的内存管理就是对象的分配和释放过程oop
在 Java 中,咱们都是经过关键字 new 来申请内存空间并建立对象的(基本类型除外),全部的对象都在堆 (Heap)中分配空间。总的来讲,Java 的内存区域能够分为三类:源码分析
对象的释放则由 GC 来完成。GC 负责监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等行为。当某个对象被 GC 判断为再也不被引用了时,GC 就会回收并释放该对象对应的内存空间
一个对象的引用方式能够分为四种:
而一个对象再也不被引用的标记就是其再也不被强引用,JVM 会经过引用计数法或者是可达性分析等方法来判断一个对象是否还被强引用着
在 Java 中,内存泄露的就意味着发生了这么一种状况:一个对象是可达的,存在其它对象强引用着该对象,但该对象是无用的,程序之后不会再使用这些对象。知足这种状况的对象就意味着该对象已经泄露,该对象不会被 GC 所回收(由于该对象可达,还未达到 GC 的标准),然而却一直持续占用着内存。例如,因为非静态内部类会持有对外部类的隐式引用,因此当非静态内部类在被回收以前,外部类也没法被回收
如下列举九种常见的内存泄露场景及相应的解决方案,内容来自于国外的一篇文章:9 ways to avoid memory leaks in Android
若是在 Activity 中注册了 BroadcastReceiver 而忘记了 unregister 的话,BroadcastReceiver 就将一直持有对 Activity 的引用,即便 Activity 已经执行了 onDestroy
public class BroadcastReceiverLeakActivity extends AppCompatActivity {
private BroadcastReceiver broadcastReceiver;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
}
private void registerBroadCastReceiver() {
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//your receiver code goes here!
}
};
registerReceiver(broadcastReceiver, new IntentFilter("SmsMessage.intent.MAIN"));
}
@Override
protected void onStart() {
super.onStart();
registerBroadCastReceiver();
}
@Override
protected void onStop() {
super.onStop();
/* * Uncomment this line in order to avoid memory leak. * You need to unregister the broadcast receiver since the broadcast receiver keeps a reference of the activity. * Now when its time for your Activity to die, the Android framework will call onDestroy() on it * but the garbage collector will not be able to remove the instance from memory because the broadcastReceiver * is still holding a strong reference to it. * */
if(broadcastReceiver != null) {
unregisterReceiver(broadcastReceiver);
}
}
}
复制代码
开发者必须谨记在 Activity.onStop()
的时候调用 unregisterReceiver
。但须要注意的是,若是 BroadcastReceiver 是在 onCreate()
中进行注册的,那么当应用进入后台并再次切换回来时,BroadcastReceiver 将不会被再次注册。因此,最好在 Activity 的 onStart()
或者 onResume()
方法中进行注册,而后在 onStop()
时进行注销
看下面的示例代码,将 TextView 声明为了静态变量(不管出于什么缘由)。无论是直接仍是间接经过静态变量引用了 Activity 或者 View,在 Activity 被销毁后都没法对其进行垃圾回收
public class StaticReferenceLeakActivity extends AppCompatActivity {
/* * This is a bad idea! */
private static TextView textView;
private static Activity activity;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
textView = findViewById(R.id.activity_text);
textView.setText("Bad Idea!");
activity = this;
}
}
复制代码
永远不要经过静态变量来引用 Activity、View 和 Context
看下面的例子,定义了一个 Singleton 类,该类须要传递 Context 以便从本地存储中获取一些文件
public class SingletonLeakExampleActivity extends AppCompatActivity {
private SingletonSampleClass singletonSampleClass;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* * Option 1: Do not pass activity context to the Singleton class. Instead pass application Context */
singletonSampleClass = SingletonSampleClass.getInstance(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
/* * Option 2: Unregister the singleton class here i.e. if you pass activity context to the Singleton class, * then ensure that when the activity is destroyed, the context in the singleton class is set to null. */
singletonSampleClass.onDestroy();
}
}
复制代码
public class SingletonSampleClass {
private Context context;
private static SingletonSampleClass instance;
private SingletonSampleClass(Context context) {
this.context = context;
}
public synchronized static SingletonSampleClass getInstance(Context context) {
if (instance == null) instance = new SingletonSampleClass(context);
return instance;
}
public void onDestroy() {
if(context != null) {
context = null;
}
}
}
复制代码
此时若是没有主动将 SingletonSampleClass 包含的 context 置空的话,就将致使内存泄露。那如何解决这个问题?
看下面的例子,定义了一个 LeakyClass 类,你须要传递 Activity 才能重定向到新的 Activity
public class InnerClassReferenceLeakActivity extends AppCompatActivity {
/* * Mistake Number 1: * Never create a static variable of an inner class * Fix I: * private LeakyClass leakyClass; */
private static LeakyClass leakyClass;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
new LeakyClass(this).redirectToSecondScreen();
/* * Inner class is defined here * */
leakyClass = new LeakyClass(this);
leakyClass.redirectToSecondScreen();
}
/* * Mistake Number 2: * 1. Never create a inner variable of an inner class * 2. Never pass an instance of the activity to the inner class */
private class LeakyClass {
private Activity activity;
public LeakyClass(Activity activity) {
this.activity = activity;
}
public void redirectToSecondScreen() {
this.activity.startActivity(new Intent(activity, SecondActivity.class));
}
}
}
复制代码
如何解决这个问题?
public class InnerClassReferenceLeakActivity extends AppCompatActivity {
/* * Mistake Number 1: * Never create a static variable of an inner class * Fix I: */
private LeakyClass leakyClass;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
new LeakyClass(this).redirectToSecondScreen();
/* * Inner class is defined here * */
leakyClass = new LeakyClass(this);
leakyClass.redirectToSecondScreen();
}
/* * How to fix the above class: * Fix memory leaks: * Option 1: The class should be set to static * Explanation: Instances of anonymous classes do not hold an implicit reference to their outer class * when they are "static". * * Option 2: Use a weakReference of the textview or any view/activity for that matter * Explanation: Weak References: Garbage collector can collect an object if only weak references * are pointing towards it. * */
private static class LeakyClass {
private final WeakReference<Activity> messageViewReference;
public LeakyClass(Activity activity) {
this.activity = new WeakReference<>(activity);
}
public void redirectToSecondScreen() {
Activity activity = messageViewReference.get();
if(activity != null) {
activity.startActivity(new Intent(activity, SecondActivity.class));
}
}
}
}
复制代码
匿名内存类带来的内存泄漏问题和上一节内容相同,解决办法以下所示:
public class AnonymousClassReferenceLeakActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
textView = findViewById(R.id.activity_text);
textView.setText(getString(R.string.text_inner_class_1));
findViewById(R.id.activity_dialog_btn).setVisibility(View.INVISIBLE);
/* * Runnable class is defined here * */
new Thread(new LeakyRunnable(textView)).start();
}
private static class LeakyRunnable implements Runnable {
private final WeakReference<TextView> messageViewReference;
private LeakyRunnable(TextView textView) {
this.messageViewReference = new WeakReference<>(textView);
}
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
TextView textView = messageViewReference.get();
if(textView != null) {
textView.setText("Runnable class has completed its work");
}
}
}
}
复制代码
看下面的示例,经过 AsyncTask 来获取一个字符串值,该值用于在 onPostExecute()
方法中更新 textView
public class AsyncTaskReferenceLeakActivity extends AppCompatActivity {
private TextView textView;
private BackgroundTask backgroundTask;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
/* * Executing AsyncTask here! * */
backgroundTask = new BackgroundTask(textView);
backgroundTask.execute();
}
/* * Couple of things we should NEVER do here: * Mistake number 1. NEVER reference a class inside the activity. If we definitely need to, we should set the class as static as static inner classes don’t hold * any implicit reference to its parent activity class * Mistake number 2. We should always cancel the asyncTask when activity is destroyed. This is because the asyncTask will still be executing even if the activity * is destroyed. * Mistake number 3. Never use a direct reference of a view from acitivty inside an asynctask. * */
private class BackgroundTask extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... voids) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "The task is completed!";
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
textView.setText(s);
}
}
}
复制代码
如何解决这个问题?
public class AsyncTaskReferenceLeakActivity extends AppCompatActivity {
private TextView textView;
private BackgroundTask backgroundTask;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
/* * Executing AsyncTask here! * */
backgroundTask = new BackgroundTask(textView);
backgroundTask.execute();
}
/* * Fix number 1 * */
private static class BackgroundTask extends AsyncTask<Void, Void, String> {
private final WeakReference<TextView> messageViewReference;
private BackgroundTask(TextView textView) {
this.messageViewReference = new WeakReference<>(textView);
}
@Override
protected String doInBackground(Void... voids) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "The task is completed!";
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
/* * Fix number 3 * */
TextView textView = messageViewReference.get();
if(textView != null) {
textView.setText(s);
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
/* * Fix number 2 * */
if(backgroundTask != null) {
backgroundTask.cancel(true);
}
}
}
复制代码
看下面的例子,经过 Handler 在五秒后更新 UI
public class HandlersReferenceLeakActivity extends AppCompatActivity {
private TextView textView;
/* * Mistake Number 1 * */
private Handler leakyHandler = new Handler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
/* * Mistake Number 2 * */
leakyHandler.postDelayed(new Runnable() {
@Override
public void run() {
textView.setText(getString(R.string.text_handler_1));
}
}, 5000);
}
复制代码
如何解决这个问题?
public class HandlersReferenceLeakActivity extends AppCompatActivity {
private TextView textView;
/* * Fix number I * */
private final LeakyHandler leakyHandler = new LeakyHandler(this);
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
leakyHandler.postDelayed(leakyRunnable, 5000);
}
/* * Fix number II - define as static * */
private static class LeakyHandler extends Handler {
/* * Fix number III - Use WeakReferences * */
private WeakReference<HandlersReferenceLeakActivity> weakReference;
public LeakyHandler(HandlersReferenceLeakActivity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
HandlersReferenceLeakActivity activity = weakReference.get();
if (activity != null) {
activity.textView.setText(activity.getString(R.string.text_handler_2));
}
}
}
private static final Runnable leakyRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
}
复制代码
Thread 和 TimerTask 也可能会致使内存泄露问题
public class ThreadReferenceLeakActivity extends AppCompatActivity {
/* * Mistake Number 1: Do not use static variables * */
private static LeakyThread thread;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
createThread();
redirectToNewScreen();
}
private void createThread() {
thread = new LeakyThread();
thread.start();
}
private void redirectToNewScreen() {
startActivity(new Intent(this, SecondActivity.class));
}
/* * Mistake Number 2: Non-static anonymous classes hold an * implicit reference to their enclosing class. * */
private class LeakyThread extends Thread {
@Override
public void run() {
while (true) {
}
}
}
复制代码
如何解决这个问题?
public class ThreadReferenceLeakActivity extends AppCompatActivity {
/* * FIX I: make variable non static * */
private LeakyThread leakyThread = new LeakyThread();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
createThread();
redirectToNewScreen();
}
private void createThread() {
leakyThread.start();
}
private void redirectToNewScreen() {
startActivity(new Intent(this, SecondActivity.class));
}
@Override
protected void onDestroy() {
super.onDestroy();
// FIX II: kill the thread
leakyThread.interrupt();
}
/* * Fix III: Make thread static * */
private static class LeakyThread extends Thread {
@Override
public void run() {
while (!isInterrupted()) {
}
}
}
}
复制代码
对于 TimerTask 也能够遵循相同的原则,修复内存泄漏的示例以下所示:
public class TimerTaskReferenceLeakActivity extends Activity {
private CountDownTimer countDownTimer;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
startTimer();
}
/* * Mistake 1: Cancel Timer is never called * even though activity might be completed * */
public void cancelTimer() {
if(countDownTimer != null) countDownTimer.cancel();
}
private void startTimer() {
countDownTimer = new CountDownTimer(1000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
final int secondsRemaining = (int) (millisUntilFinished / 1000);
//update UI
}
@Override
public void onFinish() {
//handle onFinish
}
};
countDownTimer.start();
}
}
复制代码
如何解决这个问题?
public class TimerTaskReferenceLeakActivity extends Activity {
private CountDownTimer countDownTimer;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
startTimer();
}
public void cancelTimer() {
if(countDownTimer != null) countDownTimer.cancel();
}
private void startTimer() {
countDownTimer = new CountDownTimer(1000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
final int secondsRemaining = (int) (millisUntilFinished / 1000);
//update UI
}
@Override
public void onFinish() {
//handle onFinish
}
};
countDownTimer.start();
}
/* * Fix 1: Cancel Timer when * activity might be completed * */
@Override
protected void onDestroy() {
super.onDestroy();
cancelTimer();
}
}
复制代码
最后再来简单总结一下: