Avatar ·

Как узнать, сколько памяти использует моё Android-приложение?

Всем привет! Я начинающий Android-разработчик на Java, и у меня возникла проблема с производительностью приложения. Я заметил, что после нескольких переходов между экранами приложение начинает тормозить, а иногда даже вылетает с OutOfMemoryError. Подозреваю, что это связано с утечками памяти, но я не знаю, как измерить, сколько памяти реально потребляет моё приложение. Пробовал смотреть в Android Studio Profiler, но там слишком много данных, и я не понимаю, на что обращать внимание. Вот пример кода, который, как мне кажется, может быть проблемным. Я загружаю изображения из интернета и сохраняю их в статическом списке для кэша: ```java public class ImageCache { private static List cache = new ArrayList<>(); public static void addImage(Bitmap bitmap) { cache.add(bitmap); } public static Bitmap getImage(int index) { return cache.get(index); } } ``` Я вызываю этот код в Activity при загрузке изображений: ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Загрузка изображения ImageView imageView = findViewById(R.id.imageView); Bitmap bitmap = loadBitmapFromUrl("https://example.com/image.jpg"); ImageCache.addImage(bitmap); imageView.setImageBitmap(bitmap); } } ``` Как мне программно узнать, сколько памяти занимает моё приложение в текущий момент? Может, есть какие-то специальные методы в Android SDK или Java, которые позволяют это сделать? И как вообще правильно отслеживать использование памяти, чтобы находить утечки? Заранее спасибо за помощь! <<>>

Определение использования памяти в Android через Java API

Для измерения памяти вашего приложения в Android есть несколько встроенных инструментов. Основной способ — использовать класс Debug и Runtime из стандартной библиотеки Java. Вот как это сделать правильно.

1. Получение общей выделенной памяти (Heap Size)

Используйте Runtime.getRuntime() для получения информации о куче Java:

public class MemoryUtils {
    public static void printMemoryInfo() {
        Runtime runtime = Runtime.getRuntime();
        long totalMemory = runtime.totalMemory();       // текущий размер кучи
        long freeMemory = runtime.freeMemory();         // свободная память в куче
        long usedMemory = totalMemory - freeMemory;     // используемая память
        long maxMemory = runtime.maxMemory();           // максимальный размер кучи
        
        Log.d("Memory", "Total: " + totalMemory / 1024 / 1024 + " MB");
        Log.d("Memory", "Free: " + freeMemory / 1024 / 1024 + " MB");
        Log.d("Memory", "Used: " + usedMemory / 1024 / 1024 + " MB");
        Log.d("Memory", "Max: " + maxMemory / 1024 / 1024 + " MB");
    }
}

2. Получение памяти через Debug API (более точный способ)

Метод Debug.getMemoryInfo() даёт детальную статистику, включая память native и dalvik:

import android.os.Debug;
import android.os.Debug.MemoryInfo;

public class MemoryMonitor {
    public static void logDetailedMemory() {
        MemoryInfo memoryInfo = new MemoryInfo();
        Debug.getMemoryInfo(memoryInfo);
        
        Log.d("Memory", "Dalvik PSS: " + memoryInfo.dalvikPss + " KB");
        Log.d("Memory", "Native PSS: " + memoryInfo.nativePss + " KB");
        Log.d("Memory", "Total PSS: " + memoryInfo.getTotalPss() + " KB");
        Log.d("Memory", "Total Private Dirty: " + memoryInfo.getTotalPrivateDirty() + " KB");
    }
}

3. Решение вашей проблемы с кэшем изображений

Ваш код с static List<Bitmap> — классическая утечка памяти. Bitmap'ы могут занимать десятки мегабайт, и они никогда не освобождаются. Вот исправленная версия с использованием LruCache (из Android Support Library):

import android.util.LruCache;

public class ImageCache {
    private static LruCache<String, Bitmap> cache;
    
    static {
        // Максимальный размер кэша — 1/8 от доступной памяти
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        
        cache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // Возвращаем размер Bitmap в килобайтах
                return bitmap.getByteCount() / 1024;
            }
        };
    }
    
    public static void addImage(String url, Bitmap bitmap) {
        if (bitmap != null) {
            cache.put(url, bitmap);
        }
    }
    
    public static Bitmap getImage(String url) {
        return cache.get(url);
    }
    
    // Метод для проверки текущего размера кэша
    public static void logCacheStatus() {
        Log.d("Cache", "Current size: " + cache.size() + " KB");
        Log.d("Cache", "Max size: " + cache.maxSize() + " KB");
    }
}

4. Как использовать мониторинг в Activity

Добавьте логирование в ключевые точки вашего приложения:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // Логируем память до загрузки
        MemoryMonitor.logDetailedMemory();
        
        // Используем LruCache вместо статического списка
        Bitmap bitmap = loadBitmapFromUrl("https://example.com/image.jpg");
        ImageCache.addImage("image1", bitmap);
        ImageCache.logCacheStatus();
        
        ImageView imageView = findViewById(R.id.imageView);
        imageView.setImageBitmap(bitmap);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Логируем память после уничтожения Activity
        MemoryMonitor.logDetailedMemory();
    }
}

Рекомендации по предотвращению утечек:

  • Не используйте статические ссылки на Context, Activity или Bitmap — это блокирует сборщик мусора.
  • Всегда очищайте кэш при нехватке памяти. Переопределите onTrimMemory() в вашем Application классе.
  • Используйте WeakReference для колбэков и слушателей.
  • Для больших изображений применяйте библиотеки вроде Glide или Picasso — они автоматически управляют памятью.
<<>>

Альтернативный подход: использование Android Studio Profiler и командной строки

Если вам нужен не программный, а внешний мониторинг, воспользуйтесь встроенным профилировщиком Android Studio или утилитой dumpsys.

1. Android Studio Profiler

Запустите приложение на устройстве или эмуляторе, откройте вкладку "Profiler" (View → Tool Windows → Profiler). Выберите ваше приложение и перейдите на вкладку "Memory". Вы увидите:

  • График использования памяти в реальном времени.
  • Количество выделенных объектов (Java heap, Native heap, Stack, Code).
  • Кнопку "Dump Java Heap" для создания дампа кучи.

Дамп кучи можно открыть в Android Studio и найти объекты, которые не должны быть в памяти (например, старые Activity).

2. Командная строка (ADB)

Используйте команду dumpsys meminfo для получения детальной информации:

# Получить PID вашего приложения
adb shell ps | grep com.yourpackage

# Посмотреть память по PID
adb shell dumpsys meminfo [PID]

# Или по имени пакета
adb shell dumpsys meminfo com.yourpackage

Вывод покажет PSS (Proportional Set Size) для каждого компонента: Dalvik, Native, Graphics, и т.д. Обратите внимание на раздел "Views" и "Activities" — если количество Activity растёт, это признак утечки.

3. Автоматизация с помощью Shell-скрипта

Напишите простой скрипт для периодического снятия показаний:

#!/bin/bash
PACKAGE="com.example.myapp"
while true; do
    adb shell dumpsys meminfo $PACKAGE | grep "TOTAL"
    sleep 5
done

Этот метод удобен для тестирования в цикле, когда вы выполняете определённые действия в приложении.