對於 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();
}
}
複製代碼
最後再來簡單總結一下: