├── .gitignore ├── README.md ├── build.gradle ├── fixed-app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── aliasadi │ │ └── memoryleak │ │ ├── MainActivity.java │ │ └── fixed │ │ ├── AsyncTaskActivity.java │ │ ├── DownloadListener.java │ │ ├── HandlerActivity.java │ │ ├── SingletonActivity.java │ │ ├── SingletonManager.java │ │ ├── StaticAsyncTaskActivity.java │ │ ├── ThreadActivity.java │ │ └── asynctask │ │ ├── BestAsyncTaskActivity.java │ │ └── DownloadTask.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_hello_world.xml │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── leak-app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── aliasadi │ │ └── memoryleak │ │ ├── MainActivity.java │ │ └── leak │ │ ├── AsyncTaskActivity.java │ │ ├── DownloadListener.java │ │ ├── HandlerActivity.java │ │ ├── SingletonActivity.java │ │ ├── SingletonManager.java │ │ ├── StaticAsyncTaskActivity.java │ │ └── ThreadActivity.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_hello_world.xml │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── screenshot ├── dump-memory.png ├── fixed-app.png ├── leakcanary.png ├── leaks.png ├── modules.png ├── profiler.png └── run-app.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | .gradle 4 | /local.properties 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # avoid-memory-leak-android 2 | 3 | This project is all about shows common patterns of memory leaks in Android development and how to fix them 4 | 5 | 6 | [![Android Arsenal]( https://img.shields.io/badge/Android%20Arsenal-avoid--memory--leak--android-green.svg?style=flat )]( https://android-arsenal.com/details/1/6887 ) 7 | [![]( https://img.shields.io/badge/Medium-Everything%20you%20need%20to%20know%20about%20Memory%20Leaks%20in%20Android.-lightgrey )]( https://proandroiddev.com/everything-you-need-to-know-about-memory-leaks-in-android-d7a59faaf46a ) 8 | 9 | #### There is 2 seperated modules: 10 | 11 | 12 | 13 | 1. [leak-app ](https://github.com/AliAsadi/avoid-memory-leak-android/tree/master/leak-app/src/main/java/aliasadi/memoryleak/leak)-> Describe and shows how to cause a leak when we use AsyncTask, Handler, Singleton, Thread. 14 | 15 | 16 | 2. [fixed-app ](https://github.com/AliAsadi/avoid-memory-leak-android/tree/master/fixed-app/src/main/java/aliasadi/memoryleak/fixed)-> Describe and shows how to avoid/fix the leaks 17 | 18 | In Android Studio choose which project you want to run on the top bar. 19 | 20 | 21 | 22 |

23 |

24 | 25 | ## Screenshot 26 |

27 | 28 | 29 | 30 |

31 | 32 | 33 | ## How To Avoid Memory Leak? 34 | 35 | 1. Do not keep long-lived references to a context-activity 36 | 37 | ```Java 38 | public static Context context; 39 | 40 | public SampleClass(Activity activity) { 41 | context = (Context) activity; 42 | } 43 | ``` 44 | 45 | 46 | 2. Try using the context-application instead of a context-activity 47 | 48 | ```Java 49 | Utils.doSomeLongRunningTask(getApplicationContext()); 50 | SingletoneManager.getInstance(getApplicationContext()); 51 | ``` 52 | 53 | 3. Avoid non-static inner classes 54 | 55 | ```Java 56 | public class MainActivity extends Activity { 57 | 58 | private class DownloadTask extends Thread { 59 | //do some work 60 | } 61 | } 62 | ``` 63 | 64 | 4. Avoid strong reference use WeakReference for listeners. 65 | 66 | ```Java 67 | public class DownloadTask extends AsyncTask { 68 | 69 | private WeakReference listener; 70 | 71 | public DownloadTask(DownloadListener listener) { 72 | listener = new WeakReference<>(listener); 73 | } 74 | 75 | @Override 76 | protected Void doInBackground(Void... params) { 77 | ///do some work 78 | } 79 | 80 | @Override 81 | protected void onPostExecute(Void aVoid) { 82 | super.onPostExecute(aVoid); 83 | if (listener.get() != null) { 84 | listener.get().onDownloadTaskDone(); 85 | } 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | 5. Clean/Stop all your handlers, animation listeners onDestroy()/onStop(). 92 | 93 | ```Java 94 | protected void onStop() { 95 | super.onStop(); 96 | handler.clearAllMessages(); 97 | unregisterReceivers(); 98 | view = null; 99 | listener = null; 100 | } 101 | ``` 102 | 103 | 6. Avoid Auto-Boxing 104 | 105 | ```Java 106 | public Integer autoBoxing(){ 107 | Integer result = 5; 108 | return result; 109 | } 110 | ``` 111 | 112 | ```Java 113 | public Integer hiddenAutoBoxing(){ 114 | return 5; 115 | } 116 | ``` 117 | #### How to avoid Auto-Boxing: 118 | 119 | ```Java 120 | public int autoBoxing(){ 121 | int result = 5; 122 | return result; 123 | } 124 | ``` 125 | 126 | ```Java 127 | public int hiddenAutoBoxing(){ 128 | return 5; 129 | } 130 | ``` 131 | 132 | 7. Avoid Auto-Boxing in HashMap - Use SparseArray insead. 133 | 134 | ```Java 135 | public Integer hiddenAutoBoxing(){ 136 | HashMap hashMap = new HashMap<>(); 137 | hashMap.put(5,"Hi Android Academy"); 138 | } 139 | ``` 140 | 141 | #### How to avoid Auto-Boxing in HashMap: 142 | 143 | ```Java 144 | public Integer noKeyAutoBoxing(){ 145 | SparseArray sparseArray = new SparseArray<>(); 146 | sparseArray.put(5,"Hi Android Academy"); 147 | } 148 | ``` 149 | 150 | ```Java 151 | public Integer noValueAutoBoxing(){ 152 | SparseIntArray sparseArray = new SparseIntArray(); 153 | sparseArray.put(5,1000); 154 | } 155 | ``` 156 | 157 | ## Tools which can help you identify leaks 158 | 159 | * [LeakCanary](https://github.com/square/leakcanary) from Square is a good tool for detecting memory leaks in your app 160 | 161 |

162 | 163 |

164 | 165 | * [Profiler](https://developer.android.com/studio/profile/android-profiler) View the Java heap and memory allocations with Memory Profiler 166 | 167 |

168 | 169 |

170 | 171 | ### License 172 | ``` 173 | Copyright (C) 2018 Ali Asadi 174 | Licensed under the Apache License, Version 2.0 (the "License"); 175 | you may not use this file except in compliance with the License. 176 | You may obtain a copy of the License at 177 | 178 | http://www.apache.org/licenses/LICENSE-2.0 179 | 180 | Unless required by applicable law or agreed to in writing, software 181 | distributed under the License is distributed on an "AS IS" BASIS, 182 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 183 | See the License for the specific language governing permissions and 184 | limitations under the License. 185 | ``` 186 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.3.0' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /fixed-app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /fixed-app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId 'aliesaassadi.memoryleak.fixed' 7 | minSdkVersion 19 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:28.0.0' 24 | implementation 'com.squareup.leakcanary:leakcanary-android:2.7' 25 | } 26 | -------------------------------------------------------------------------------- /fixed-app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /fixed-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /fixed-app/src/main/java/aliasadi/memoryleak/MainActivity.java: -------------------------------------------------------------------------------- 1 | package aliasadi.memoryleak; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.view.View; 7 | 8 | import aliasadi.memoryleak.fixed.R; 9 | import aliasadi.memoryleak.fixed.StaticAsyncTaskActivity; 10 | import aliasadi.memoryleak.fixed.AsyncTaskActivity; 11 | import aliasadi.memoryleak.fixed.HandlerActivity; 12 | import aliasadi.memoryleak.fixed.SingletonActivity; 13 | import aliasadi.memoryleak.fixed.ThreadActivity; 14 | 15 | /** 16 | * Created by Ali Asadi on 01/04/2019. 17 | */ 18 | public class MainActivity extends Activity implements View.OnClickListener { 19 | 20 | @Override 21 | protected void onCreate(@Nullable Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | 25 | findViewById(R.id.asyncTask).setOnClickListener(this); 26 | findViewById(R.id.staticAsyncTask).setOnClickListener(this); 27 | findViewById(R.id.thread).setOnClickListener(this); 28 | findViewById(R.id.handler).setOnClickListener(this); 29 | findViewById(R.id.singleton).setOnClickListener(this); 30 | } 31 | 32 | @Override 33 | public void onClick(View v) { 34 | switch (v.getId()) { 35 | 36 | case R.id.asyncTask: 37 | AsyncTaskActivity.start(this); 38 | break; 39 | case R.id.staticAsyncTask: 40 | StaticAsyncTaskActivity.start(this); 41 | break; 42 | case R.id.thread: 43 | ThreadActivity.start(this); 44 | break; 45 | case R.id.handler: 46 | HandlerActivity.start(this); 47 | break; 48 | case R.id.singleton: 49 | SingletonActivity.start(this); 50 | break; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /fixed-app/src/main/java/aliasadi/memoryleak/fixed/AsyncTaskActivity.java: -------------------------------------------------------------------------------- 1 | package aliasadi.memoryleak.fixed; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.AsyncTask; 7 | import android.os.Bundle; 8 | import android.os.SystemClock; 9 | 10 | import android.widget.TextView; 11 | 12 | import aliasadi.memoryleak.fixed.R; 13 | 14 | /** 15 | * Created by Ali Asadi on 06/02/2018. 16 | */ 17 | public class AsyncTaskActivity extends Activity { 18 | 19 | private TextView textView; 20 | 21 | /** 22 | * NOTE : if the task done before rotate/close the activity every thing will be ok without leak. 23 | **/ 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_hello_world); 29 | textView = findViewById(R.id.text_view); 30 | 31 | new DownloadTask().execute(); 32 | } 33 | 34 | public void updateText() { 35 | textView.setText(R.string.hello); 36 | } 37 | 38 | public static void start(Context context) { 39 | Intent starter = new Intent(context, AsyncTaskActivity.class); 40 | context.startActivity(starter); 41 | } 42 | 43 | /** 44 | * to fix this leak we use static class instead of inner class. 45 | * static class does not have reference to the containing activity class 46 | **/ 47 | private static class DownloadTask extends AsyncTask { 48 | 49 | @Override 50 | protected Void doInBackground(Void... params) { 51 | SystemClock.sleep(2000 * 10); 52 | return null; 53 | } 54 | 55 | /** 56 | * Problem: 57 | * we still need a reference to activity or listener to run the updateText() method 58 | * what we should do? go to the next example at @StaticAsyncTask class to know how 59 | * to deal with this issue. 60 | **/ 61 | @Override 62 | protected void onPostExecute(Void aVoid) { 63 | super.onPostExecute(aVoid); 64 | //updateText(); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /fixed-app/src/main/java/aliasadi/memoryleak/fixed/DownloadListener.java: -------------------------------------------------------------------------------- 1 | package aliasadi.memoryleak.fixed; 2 | 3 | /** 4 | * Created by Ali Asadi on 2019-05-29. 5 | */ 6 | public interface DownloadListener { 7 | void onDownloadTaskDone(); 8 | } 9 | -------------------------------------------------------------------------------- /fixed-app/src/main/java/aliasadi/memoryleak/fixed/HandlerActivity.java: -------------------------------------------------------------------------------- 1 | package aliasadi.memoryleak.fixed; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.os.Handler; 8 | import android.os.Message; 9 | 10 | import android.util.Log; 11 | 12 | import aliasadi.memoryleak.fixed.R; 13 | 14 | /** 15 | * Created by Ali Asadi on 06/02/2018. 16 | */ 17 | public class HandlerActivity extends Activity { 18 | 19 | private final DownloadTask downloadTask = new DownloadTask(); 20 | 21 | /** 22 | * The handler attached to the main thread 23 | **/ 24 | private final Handler handler = new TaskHandler(); 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_hello_world); 30 | 31 | /** 32 | * Post a message and delay its execution for 10 minutes. 33 | * 34 | * that's mean post a message to the queue, to be run after the specified amount of time elapses, 35 | * The downloadTask will be run on the thread to which this handler is attached on (MainThread) 36 | * **/ 37 | handler.postDelayed(downloadTask, 1000 * 60 * 10); 38 | } 39 | 40 | @Override 41 | protected void onDestroy() { 42 | super.onDestroy(); 43 | 44 | /** 45 | * Remove any pending posts of Runnable @downloadTask that 46 | * are in the message queue, ot prevent leak. 47 | * **/ 48 | handler.removeCallbacks(downloadTask); 49 | } 50 | 51 | public static void start(Context context) { 52 | Intent starter = new Intent(context, HandlerActivity.class); 53 | context.startActivity(starter); 54 | } 55 | 56 | /** 57 | * Use static class instead of inner class. 58 | * static class does not have reference to the containing activity 59 | **/ 60 | private static class DownloadTask implements Runnable { 61 | @Override 62 | public void run() { 63 | Log.e("HandlerActivity", "in run()"); 64 | } 65 | } 66 | 67 | /** 68 | * Use static class instead of inner class. 69 | * static class does not have reference to the containing activity 70 | **/ 71 | private static class TaskHandler extends Handler { 72 | @Override 73 | public void handleMessage(Message msg) { 74 | Log.e("HandlerActivity", "handle message"); 75 | } 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /fixed-app/src/main/java/aliasadi/memoryleak/fixed/SingletonActivity.java: -------------------------------------------------------------------------------- 1 | package aliasadi.memoryleak.fixed; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | 8 | import aliasadi.memoryleak.fixed.R; 9 | 10 | /** 11 | * Created by Ali Asadi on 06/02/2018. 12 | */ 13 | public class SingletonActivity extends Activity { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_hello_world); 19 | 20 | /** 21 | * Using application context instead (Take a a look at the @SingletonManager). 22 | * **/ 23 | SingletonManager.getInstance(this); 24 | } 25 | 26 | public static void start(Context context) { 27 | Intent starter = new Intent(context, SingletonActivity.class); 28 | context.startActivity(starter); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /fixed-app/src/main/java/aliasadi/memoryleak/fixed/SingletonManager.java: -------------------------------------------------------------------------------- 1 | package aliasadi.memoryleak.fixed; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Created by Ali Asadi on 13/02/2018. 7 | */ 8 | public class SingletonManager { 9 | 10 | private static SingletonManager singleton; 11 | private Context context; 12 | 13 | private SingletonManager(Context context) { 14 | this.context = context; 15 | } 16 | 17 | public synchronized static SingletonManager getInstance(Context context) { 18 | if (singleton == null) { 19 | /** 20 | * Use application Context to prevent leak. 21 | * **/ 22 | singleton = new SingletonManager(context.getApplicationContext()); 23 | } 24 | return singleton; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /fixed-app/src/main/java/aliasadi/memoryleak/fixed/StaticAsyncTaskActivity.java: -------------------------------------------------------------------------------- 1 | package aliasadi.memoryleak.fixed; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.AsyncTask; 7 | import android.os.Bundle; 8 | import android.os.SystemClock; 9 | 10 | import android.widget.TextView; 11 | 12 | import java.lang.ref.WeakReference; 13 | 14 | /** 15 | * Created by Ali Asadi on 06/02/2018. 16 | */ 17 | public class StaticAsyncTaskActivity extends Activity implements DownloadListener { 18 | 19 | /** 20 | * Weak Reference Description: 21 | * 22 | * Weak reference objects, which do not prevent their referents from being 23 | * made finalizable, finalized, and then reclaimed. 24 | * 25 | * Suppose that the garbage collector determines at a certain point in time that an 26 | * object is weakly reachable. 27 | * At that time it will atomically clear all weak references to that object and all 28 | * weak references to any other weakly-reachable 29 | * objects from which that object is reachable through a chain of strong and soft references. 30 | * At the same time it will declare all of the formerly weakly-reachable objects to be 31 | * finalizable. At the same time or at some later time it will enqueue 32 | * those newly-cleared weak references that are registered with reference queues. 33 | * 34 | * MORE -> https://developer.android.com/reference/java/lang/ref/WeakReference.html 35 | **/ 36 | 37 | private TextView textView; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_hello_world); 43 | textView = findViewById(R.id.text_view); 44 | 45 | new DownloadTask(this).execute(); 46 | } 47 | 48 | public static void start(Context context) { 49 | Intent starter = new Intent(context, StaticAsyncTaskActivity.class); 50 | context.startActivity(starter); 51 | } 52 | 53 | @Override 54 | public void onDownloadTaskDone() { 55 | updateText(); 56 | } 57 | 58 | public void updateText() { 59 | textView.setText(R.string.hello); 60 | } 61 | 62 | private static class DownloadTask extends AsyncTask { 63 | 64 | /** 65 | * The WeakReference allows the Activity to be garbage collected. 66 | * garbage collected does not protect the weak reference from begin reclaimed. 67 | **/ 68 | private WeakReference listener; 69 | 70 | private DownloadTask(DownloadListener activity) { 71 | this.listener = new WeakReference<>(activity); 72 | } 73 | 74 | @Override 75 | protected Void doInBackground(Void... params) { 76 | SystemClock.sleep(2000 * 10); 77 | return null; 78 | } 79 | 80 | @Override 81 | protected void onPostExecute(Void aVoid) { 82 | super.onPostExecute(aVoid); 83 | if (listener.get() != null) { 84 | listener.get().onDownloadTaskDone(); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /fixed-app/src/main/java/aliasadi/memoryleak/fixed/ThreadActivity.java: -------------------------------------------------------------------------------- 1 | package aliasadi.memoryleak.fixed; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.os.SystemClock; 8 | 9 | 10 | import aliasadi.memoryleak.fixed.R; 11 | 12 | /** 13 | * Created by Ali Asadi on 06/02/2018. 14 | */ 15 | public class ThreadActivity extends Activity { 16 | 17 | /** 18 | * if the task done before to move to another activity 19 | * or rotate the device every thing will works fine with out leak. 20 | * **/ 21 | 22 | private DownloadTask thread = new DownloadTask(); 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_hello_world); 28 | 29 | thread.start(); 30 | } 31 | 32 | @Override 33 | protected void onDestroy() { 34 | super.onDestroy(); 35 | 36 | /** 37 | * Interrupts/stops this thread. 38 | * **/ 39 | thread.interrupt(); 40 | } 41 | 42 | public static void start(Context context) { 43 | Intent starter = new Intent(context, ThreadActivity.class); 44 | context.startActivity(starter); 45 | } 46 | 47 | /** 48 | * make it static so it does not have referenced to the containing activity class 49 | * **/ 50 | private static class DownloadTask extends Thread { 51 | @Override 52 | public void run() { 53 | while (!isInterrupted()) { 54 | SystemClock.sleep(2000 * 10); 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /fixed-app/src/main/java/aliasadi/memoryleak/fixed/asynctask/BestAsyncTaskActivity.java: -------------------------------------------------------------------------------- 1 | package aliasadi.memoryleak.fixed.asynctask; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | import android.widget.TextView; 7 | 8 | import aliasadi.memoryleak.fixed.DownloadListener; 9 | import aliasadi.memoryleak.fixed.R; 10 | 11 | /** 12 | * Created by Ali Asadi on 06/02/2018. 13 | */ 14 | public class BestAsyncTaskActivity extends Activity implements DownloadListener { 15 | 16 | /** 17 | * NOTE : if the task done before rotate/close the activity every thing will be ok without leak. 18 | **/ 19 | 20 | private TextView textView; 21 | private DownloadTask downloadTask; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_hello_world); 27 | textView = findViewById(R.id.text_view); 28 | downloadTask = new DownloadTask(this); 29 | } 30 | 31 | @Override 32 | protected void onDestroy() { 33 | super.onDestroy(); 34 | /** 35 | * cancel the task so it will no invoke onPostExecute(). 36 | * **/ 37 | downloadTask.cancel(true); 38 | } 39 | 40 | @Override 41 | public void onDownloadTaskDone() { 42 | updateText(); 43 | } 44 | 45 | public void updateText() { 46 | textView.setText(R.string.hello); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /fixed-app/src/main/java/aliasadi/memoryleak/fixed/asynctask/DownloadTask.java: -------------------------------------------------------------------------------- 1 | package aliasadi.memoryleak.fixed.asynctask; 2 | 3 | import android.os.AsyncTask; 4 | import android.os.SystemClock; 5 | 6 | import java.lang.ref.WeakReference; 7 | 8 | import aliasadi.memoryleak.fixed.DownloadListener; 9 | 10 | /** 11 | * Created by Ali Asadi on 06/02/2018. 12 | */ 13 | public class DownloadTask extends AsyncTask { 14 | 15 | /** 16 | * The WeakReference allows the Activity to be garbage collected. 17 | * garbage collected dose not protect the weak reference from begin reclaimed. 18 | **/ 19 | private WeakReference listener; 20 | 21 | public DownloadTask(DownloadListener listener) { 22 | this.listener = new WeakReference<>(listener); 23 | } 24 | 25 | @Override 26 | protected Void doInBackground(Void... params) { 27 | /** 28 | * Check if cancelled. 29 | * **/ 30 | while (!isCancelled()) { 31 | SystemClock.sleep(2000 * 10); 32 | } 33 | return null; 34 | } 35 | 36 | @Override 37 | protected void onPostExecute(Void aVoid) { 38 | super.onPostExecute(aVoid); 39 | if (listener.get() != null) { 40 | listener.get().onDownloadTaskDone(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /fixed-app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /fixed-app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /fixed-app/src/main/res/layout/activity_hello_world.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /fixed-app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 17 | 18 |