Android
Утечки памяти
Поиск…
Общие утечки памяти и способы их устранения
1. Исправьте свои контексты:
Попробуйте использовать соответствующий контекст: например, поскольку тост можно увидеть во многих действиях, а не только в одном, используйте getApplicationContext()
для тостов, и поскольку службы могут продолжать работать, даже если действие закончилось, запустите службу с помощью:
Intent myService = new Intent(getApplicationContext(), MyService.class);
Используйте эту таблицу в качестве краткого руководства для контекста:
Оригинальная статья о контексте здесь .
2. Статическая ссылка на контекст
Серьезной ошибкой памяти является сохранение статической ссылки на View
. Каждый View
имеет внутреннюю ссылку на Context
. Это означает, что старая активность со всей иерархией представления не будет собираться мусором, пока приложение не будет прекращено. При повороте экрана у вас будет ваше приложение дважды в памяти.
Убедитесь, что нет никаких статических ссылок на View, Context или их потомков.
3. Убедитесь, что вы фактически завершаете свои услуги.
Например, у меня есть намеренная служба, которая использует API службы местоположения Google. И я забыл называть googleApiClient.disconnect();
:
//Disconnect from API onDestroy()
if (googleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, GoogleLocationService.this);
googleApiClient.disconnect();
}
4. Проверьте использование изображений и растровых изображений:
Если вы используете библиотеку Square Picasso, я обнаружил, что я .fit()
память, не используя .fit()
, что резко сократило объем памяти с 50 МБ в среднем до менее 19 МБ:
Picasso.with(ActivityExample.this) //Activity context
.load(object.getImageUrl())
.fit() //This avoided the OutOfMemoryError
.centerCrop() //makes image to not stretch
.into(imageView);
5. Если вы используете широковещательные приемники, отмените их регистрацию.
6. Если вы используете java.util.Observer
(шаблон наблюдателя):
Обязательно используйте deleteObserver(observer);
Избегайте утечки активности с помощью AsyncTask
Слово предостережения: AsyncTask имеет много Гочи обособленно от утечки памяти , описанной здесь. Поэтому будьте осторожны с этим API или избегайте его вообще, если вы не полностью понимаете последствия. Существует много альтернатив (Thread, EventBus, RxAndroid и т. Д.).
Одна из распространенных ошибок с AsyncTask
заключается в том, чтобы зафиксировать сильную ссылку на Activity
хоста (или Fragment
):
class MyActivity extends Activity {
private AsyncTask<Void, Void, Void> myTask = new AsyncTask<Void, Void, Void>() {
// Don't do this! Inner classes implicitly keep a pointer to their
// parent, which in this case is the Activity!
}
}
Это проблема, потому что AsyncTask
может легко пережить родительскую Activity
, например, если во время выполнения задачи происходит изменение конфигурации.
Правильный способ сделать это - сделать вашу задачу static
классом, который не захватывает родителя и содержит слабую ссылку на хост. Activity
:
class MyActivity extends Activity {
static class MyTask extends AsyncTask<Void, Void, Void> {
// Weak references will still allow the Activity to be garbage-collected
private final WeakReference<MyActivity> weakActivity;
MyTask(MyActivity myActivity) {
this.weakActivity = new WeakReference<>(myActivity);
}
@Override
public Void doInBackground(Void... params) {
// do async stuff here
}
@Override
public void onPostExecute(Void result) {
// Re-acquire a strong reference to the activity, and verify
// that it still exists and is active.
MyActivity activity = weakActivity.get();
if (activity == null
|| activity.isFinishing()
|| activity.isDestroyed()) {
// activity is no longer valid, don't do anything!
return;
}
// The activity is still valid, do main-thread stuff here
}
}
}
Анонимный обратный вызов в действиях
Каждый раз, когда вы создаете анонимный класс, он сохраняет неявную ссылку на свой родительский класс. Поэтому, когда вы пишете:
public class LeakyActivity extends Activity
{
...
foo.registerCallback(new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
}
Фактически вы отправляете ссылку на ваш экземпляр LeakyActivity для foo. Когда пользователь переходит от вашей LeakyActivity, эта ссылка может помешать экземпляру LeakyActivity быть собранным мусором. Это серьезная утечка, поскольку действия содержат ссылку на всю их иерархию представлений и, следовательно, являются довольно большими объектами в памяти.
Как избежать этой утечки:
Конечно, вы можете избежать анонимных обратных вызовов в действиях. Вы также можете отменить регистрацию всех ваших обратных вызовов в отношении жизненного цикла деятельности. вот так:
public class NonLeakyActivity extends Activity
{
private final BarCallback mBarCallback = new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
@Override
protected void onResume()
{
super.onResume();
foo.registerCallback(mBarCallback);
}
@Override
protected void onPause()
{
super.onPause();
foo.unregisterCallback(mBarCallback);
}
}
Контекст активности в статических классах
Часто вы захотите перенести некоторые классы Android в более простые в использовании служебные классы. Эти классы полезности часто требуют контекста для доступа к ресурсам Android и вашим приложениям. Общим примером этого является оболочка для класса SharedPreferences. Чтобы получить доступ к общим настройкам Androids, необходимо написать:
context.getSharedPreferences(prefsName, mode);
И поэтому может возникнуть соблазн создать следующий класс:
public class LeakySharedPrefsWrapper
{
private static Context sContext;
public static void init(Context context)
{
sContext = context;
}
public int getInt(String name,int defValue)
{
return sContext.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}
}
теперь, если вы вызываете init()
с вашим контекстом активности, LeakySharedPrefsWrapper сохранит ссылку на вашу деятельность, не позволяя ей собирать мусор.
Как избежать:
При вызове статических вспомогательных функций вы можете отправить контекст приложения, используя context.getApplicationContext();
При создании статических вспомогательных функций вы можете извлечь контекст приложения из предоставленного вами контекста (вызов метода getApplicationContext () в контексте приложения возвращает контекст приложения). Итак, исправление нашей обертки просто:
public static void init(Context context)
{
sContext = context.getApplicationContext();
}
Если контекст приложения не подходит для вашего варианта использования, вы можете включить параметр Context в каждую функцию утилиты, вы должны избегать ссылок на эти параметры контекста. В этом случае решение будет выглядеть так:
public int getInt(Context context,String name,int defValue)
{
// do not keep a reference of context to avoid potential leaks.
return context.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}
Обнаружение утечек памяти с помощью библиотеки LeakCanary
LeakCanary - это библиотека Java с открытым исходным кодом для обнаружения утечек памяти в ваших отладочных сборках.
Просто добавьте зависимости в build.gradle
:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
Затем в вашем классе Application
:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
Теперь LeakCanary автоматически отобразит уведомление, когда в вашей отладочной сборке обнаружена утечка памяти активности.
ПРИМЕЧАНИЕ. Код деблокирования не будет содержать ссылки на LeakCanary, кроме двух пустых классов, которые существуют в leakcanary-android-no-op
.
Избегайте утечки активности с помощью прослушивателей
Если вы реализуете или создаете слушателя в Activity, всегда обращайте внимание на жизненный цикл объекта, на котором зарегистрирован слушатель.
Рассмотрим приложение, в котором у нас есть несколько различных действий / фрагментов, заинтересованных в том, что пользователь вошел в систему или вышел из системы. Одним из способов сделать это было бы иметь экземпляр singleton UserController
который можно подписаться, чтобы получить уведомление при изменении состояния пользователя:
public class UserController {
private static UserController instance;
private List<StateListener> listeners;
public static synchronized UserController getInstance() {
if (instance == null) {
instance = new UserController();
}
return instance;
}
private UserController() {
// Init
}
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void logout() {
for (StateListener listener : listeners) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listeners) {
listener.userLoggedIn();
}
}
public interface StateListener {
void userLoggedIn();
void userLoggedOut();
}
}
Затем есть два действия: SignInActivity
:
public class SignInActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
startMainActivity();
}
@Override
public void userLoggedOut() {
showLoginForm();
}
...
public void onLoginClicked(View v) {
userController.login();
}
}
И MainActivity
:
public class MainActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
...
public void onLogoutClicked(View v) {
userController.logout();
}
}
Что происходит с этим примером, так это то, что каждый раз, когда пользователь входит в систему, а затем выходит из системы снова, экземпляр MainActivity
просачивается. Утечка происходит из-за ссылки на активность в UserController#listeners
.
Обратите внимание: даже если мы используем анонимный внутренний класс в качестве слушателя, активность все равно будет протекать:
...
this.userController.registerUserStateChangeListener(new UserController.StateListener() {
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
});
...
Активность все равно будет протекать, потому что анонимный внутренний класс имеет неявную ссылку на внешний класс (в данном случае активность). Вот почему во внешнем классе можно вызвать методы экземпляра из внутреннего класса. Фактически, единственным типом внутренних классов, которые не имеют ссылки на внешний класс, являются статические внутренние классы.
Короче говоря, все экземпляры нестатических внутренних классов содержат неявную ссылку на экземпляр внешнего класса, который их создал.
Существует два основных подхода к решению этого вопроса, добавив метод удаления слушателя из прослушивателей UserController#listeners
или используя WeakReference
для хранения ссылки слушателей.
Альтернатива 1: Удаление слушателей
Начнем с создания нового метода removeUserStateChangeListener(StateListener listener)
:
public class UserController {
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void removeUserStateChangeListener(StateListener listener) {
listeners.remove(listener);
}
...
}
Затем назовем этот метод в методе onDestroy
активности:
public class MainActivity extends Activity implements UserController.StateListener{
...
@Override
protected void onDestroy() {
super.onDestroy();
userController.removeUserStateChangeListener(this);
}
}
С этой модификацией экземпляры MainActivity
больше не просачиваются, когда пользователь входит в систему и выходит из MainActivity
. Однако, если документация не ясна, возможно, что следующий разработчик, который начинает использовать UserController
может пропустить, что требуется отменить регистрацию слушателя, когда действие будет уничтожено, что приведет нас к второму методу предотвращения этих типов утечек.
Альтернатива 2: Использование слабых ссылок
Прежде всего, давайте начнем с объяснения того, что такое слабая ссылка. Слабая ссылка, как следует из названия, содержит слабую ссылку на объект. По сравнению с обычным полем экземпляра, которое является сильной ссылкой, слабые ссылки не останавливают сборщик мусора, GC, от удаления объектов. В приведенном выше примере это позволит MainActivity
быть собранным с мусором после его уничтожения, если UserController
использовал WeakReference
для ссылки на слушателей.
Короче говоря, слабая ссылка говорит GC, что, если никто другой не имеет сильной ссылки на этот объект, продолжайте и удалите его.
Давайте WeakReference
UserController
чтобы использовать список WeakReference
чтобы отслеживать его слушателей:
public class UserController {
...
private List<WeakReference<StateListener>> listeners;
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(new WeakReference<>(listener));
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
WeakReference referencesToRemove = null;
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null && listener == listenerToRemove) {
referencesToRemove = listenerRef;
break;
}
}
listeners.remove(referencesToRemove);
}
public void logout() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedOut();
} else {
referencesToRemove.add(listenerRef);
}
}
}
public void login() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedIn();
} else {
referencesToRemove.add(listenerRef);
}
}
}
...
}
С этой модификацией не имеет значения, удалены ли слушатели или нет, поскольку UserController
содержит сильных ссылок на любого из слушателей. Однако писать этот шаблонный код каждый раз является громоздким. Поэтому создадим общий класс под названием WeakCollection
:
public class WeakCollection<T> {
private LinkedList<WeakReference<T>> list;
public WeakCollection() {
this.list = new LinkedList<>();
}
public void put(T item){
//Make sure that we don't re add an item if we already have the reference.
List<T> currentList = get();
for(T oldItem : currentList){
if(item == oldItem){
return;
}
}
list.add(new WeakReference<T>(item));
}
public List<T> get() {
List<T> ret = new ArrayList<>(list.size());
List<WeakReference<T>> itemsToRemove = new LinkedList<>();
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == null) {
itemsToRemove.add(ref);
} else {
ret.add(item);
}
}
for (WeakReference ref : itemsToRemove) {
this.list.remove(ref);
}
return ret;
}
public void remove(T listener) {
WeakReference<T> refToRemove = null;
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == listener) {
refToRemove = ref;
}
}
if(refToRemove != null){
list.remove(refToRemove);
}
}
}
Теперь давайте WeakCollection<T>
UserController
вместо WeakCollection<T>
:
public class UserController {
...
private WeakCollection<StateListener> listenerRefs;
...
public void registerUserStateChangeListener(StateListener listener) {
listenerRefs.put(listener);
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
listenerRefs.remove(listenerToRemove);
}
public void logout() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedIn();
}
}
...
}
Как показано в приведенном выше примере кода, WeakCollection<T>
удаляет весь шаблонный код, необходимый для использования WeakReference
вместо обычного списка. В довершение всего: если вызов UserController#removeUserStateChangeListener(StateListener)
пропущен, слушатель и все объекты, на которые он ссылается, не будут течь.
Избегайте утечек памяти с помощью анонимного класса, обработчика, задачи таймера, потока
В android каждый разработчик использует Anonymous Class
(Runnable) хотя бы один раз в проекте. Любой Anonymous Class
имеет ссылку на родителя (активность). Если мы выполним долговременную задачу, родительская активность не будет уничтожена до завершения задачи.
В примере используется класс обработчика и Анонимный Runnable
. Память будет протекать, когда мы прекратим действие до завершения Runnable
.
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// do abc long 5s or so
}
}, 10000); // run "do abc" after 10s. It same as timer, thread...
Как мы его решаем?
- Не делайте никаких
WeakReference
сAnonymous Class
или нам нуженStatic class
иWeakReference
в негоWeakReference
(например, активность, просмотр ...).Thread
же сAnonymous Class
. - Отмените
Handler
,Timer
когда действие уничтожено.