Важно (9.07.22)

Если картинки в постах не отображаются, зайдите в блог через прокси. РКН заблокировал поддомены blogger.com на которые загружались картинки.

воскресенье, 5 марта 2017 г.

Как получить информацию о потреблении ОЗУ?



Задача довольно простая, т.к. в АПИ уже доступно всё необходимое. Раз уж мы коснёмся памяти, то давайте постараемся вывести максимальное количество информации.

Что будет уметь приложение:
  • Вывод общей информации о состоянии памяти устройства
  • Вывод общей информации о состоянии памяти выделенной приложению (процессу)
  • Вывод детальной информации о состоянии памяти  для процесса.
  • (Как бонус) Вывод списка процессов на Андроид 4 – 5.
Update 6.03.17. Не пытайтесь дебажить проект через RAD Studio, поймаете ошибку. Подробности ниже.


АПИ.

Вывод общей информации о состоянии памяти устройства.

Классы:
Методы:
Класс «ActivityManager.MemoryInfo» содержит несколько полей и методов, нас интересуют поля:
  • public long availMem – Объём свободной памяти доступной системе.
  • public boolean lowMemory – Устанавливается в True, если система считает, что в данный момент времени памяти не хватает.
  • public long threshold – Пороговое значение памяти для availMem, при котором, система начинается уничтожать фоновые службы и другие процессы.
  • public long totalMem – Объём памяти устройства.
Все значения в байтах.

Код будет примерно таким:
procedure TFormMain.GetGeneralInfo;
var
  ActivityManager: JActivityManager;
  MemoryInfo: JActivityManager_MemoryInfo;
begin
  ActivityManager := TJActivityManager.Wrap
    (TAndroidHelper.Context.getSystemService(TJContext.JavaClass.ACTIVITY_SERVICE));
  MemoryInfo := TJActivityManager_MemoryInfo.JavaClass.init;
  ActivityManager.getMemoryInfo(MemoryInfo);

  lvGeneralInfo.Items.Clear;
  AddHeaderInListView(lvGeneralInfo, 'API Info');
  AddDataInListView(lvGeneralInfo, 'Total memory:', TUtils.FormatBytes(MemoryInfo.totalMem));
  AddDataInListView(lvGeneralInfo, 'Available memory:', TUtils.FormatBytes(MemoryInfo.availMem));
  AddDataInListView(lvGeneralInfo, 'Threshold:', TUtils.FormatBytes(MemoryInfo.threshold));
  AddDataInListView(lvGeneralInfo, 'Low memory(?):', TUtils.BooleanToString(MemoryInfo.lowMemory));
end;


Вывод общей информации о состоянии памяти выделенной приложению (процессу).
В данной ситуации мы задействуем возможности языка JAVA, а точнее, воспользуемся классом «java.lang.Runtime (Oracle Docs)»(или  «java.lang.Runtime (Android Docs)»).
Нас интересуют методы:
  • static Runtime getRuntime() – Возвращает объект, связанный с текущим приложением
  • long freeMemory() – Возвращает значение, характеризующее количество свободной памяти относительно «totalMemory()».
  • long maxMemory() – Возвращает значение, характеризующее общее количество выделенной памяти для использования приложением.
  • long totalMemory() – Возвращает значение, характеризующее количество выделенной памяти на текущий момент.
Все значения в байтах.

Также рассчитаем количество используемой памяти (Used memory) и количество свободной памяти (Total free memory) относительно «maxMemory()».

Картинка найдена и стырена со стековерфлоу.

Код будет примерно таким:
procedure TFormMain.GetGeneralInfo;
var
  Runtime: JRuntime;
begin
  Runtime := TJRuntime.JavaClass.getRuntime;
  
  AddHeaderInListView(lvGeneralInfo, 'JVM Info (this process)');
  AddDataInListView(lvGeneralInfo, 'Max memory:', TUtils.FormatBytes(Runtime.maxMemory));
  AddDataInListView(lvGeneralInfo, 'Total allocated memory:', TUtils.FormatBytes(Runtime.totalMemory));
  AddDataInListView(lvGeneralInfo, 'Used memory:',
    TUtils.FormatBytes(Runtime.totalMemory - Runtime.freeMemory));
  AddDataInListView(lvGeneralInfo, 'Currently allocated free memory:', TUtils.FormatBytes(Runtime.freeMemory));
  AddDataInListView(lvGeneralInfo, 'Total free memory:',
    TUtils.FormatBytes(Runtime.maxMemory - (Runtime.totalMemory - Runtime.freeMemory)));
end;


Вывод детальной информации о состоянии памяти  для процесса и (Как бонус) Вывод списка процессов на Андроид 4 – 5.

Вернёмся к Андроид АПИ. Эти два пункта я свяжу в один, чтобы не городить почти идентичный код два раза.

Важно! Также необходимо упомянуть причину указания версий Андроида для списка процессов. Причина проста, начиная с 5 версии Андроида, Гугл прикрыл лавочку по отображению списка всех процессов, метод будет возвращать только один процесс – это будет процесс вашего приложения. На некоторых 5 версиях, метод ещё работает. Также вы можете найти в интернете jar библиотеку, которая позволяет получить список процессов на всех 5 версиях Андроида (там используется обходной путь, который в след. версиях Андроида уже закрыт) и достаточно сильно обрезанную информацию о процессах. Но мы на этом не будем заострять внимание, т.к. нам важно научиться получать информацию именно о нашем приложении.


Начнём с получения списка процессов.

Нам снова понадобится класс:
и класс:

Методы:

Важно! На текущий момент, при разборе данных из этого метода происходит утечка памяти. В QC давно уже сообщил, в своём приложении я использовал обходные пути (records и принудительное уменьшение счётчика ссылок). О том как я отловил эту утечку, я расскажу в след. статье.

Update 6.03.17 Не пытайтесь дебажить проект через RAD Studio, словите ошибку. Если очень хочется, то закомментируйте строчку "Process._Release;". Но имейте ввиду, что тогда будет происходить утечка памяти!


Класс «ActivityManager.RunningAppProcessInfo» содержит поля (в списке ниже, только те, которые я рассматриваю в этой статье):
  • public int importance – Относительный уровень важности, который система даёт процессу. Содержит номер одной из констант этого класса. Подробности смотрите тут ActivityManager.RunningAppProcessInfo.html#importance
  • public int importanceReasonCode – Основание для выдачи важности, если имеется.
  • public int lru – Дополнительная подкатегория важности.
  • public int pid – Идентификатор процесса. 0 если ни одного.
  • public String processName – Название процесса.
  • public int uid – Идентификатор пользователя этого процесса.
  • Информацию об остальных полях вы можете почитать в справке ActivityManager.RunningAppProcessInfo.html#lfields

Теперь мы знаем идентификаторы всех процессов, а значит, можем получить детальную информацию о состоянии кучи (Heap) для каждого процесса.

Тот же класс, но другой метод:
Класс «android.os.Debug.MemoryInfo». Все методы и поля можно использовать. Для статьи и своего приложения я использую:
Методы:
  • int getTotalPrivateDirty()
  • int getTotalPss()
  • int getTotalSharedDirty()
Поля:
  • public int dalvikPrivateDirty
  • public int dalvikPss
  • public int dalvikSharedDirty
  • public int nativePrivateDirty
  • public int nativePss
  • public int nativeSharedDirty
  • public int otherPrivateDirty
  • public int otherPss
  • public int otherSharedDirty
Все значения в килобайтах.

Пример кода смотрите на гитхабе, ссылка ниже.

Моё приложение.



Приложение разработано на Delphi Berlin 10.1.2. Для приложения сделаны две дополнительные обёртки, для классов «java.lang.Process» и «java.lang.Runtime». Написано немного грубо, пока не было времени всё поправить.

Код приложения доступен на ГитХабе по ссылке: https://github.com/AndrewEfimov/Memory-info