Wie kann ich den Speicherplatz meiner Android-Anwendung programmgesteuert finden?
Ich hoffe, dass es einen Weg gibt, dies zu tun. Wie bekomme ich außerdem den freien Speicher des Telefons?
Beachten Sie, dass die Speichernutzung auf modernen Betriebssystemen wie Linux ein extrem komplizierter und schwer verständlicher Bereich ist. Tatsächlich ist die Wahrscheinlichkeit, dass Sie tatsächlich die Zahlen richtig interpretieren, die Sie erhalten, äußerst gering. (So gut wie jedes Mal, wenn ich mir mit anderen Ingenieuren die Speicherbelegungszahlen ansehe, gibt es eine lange Diskussion darüber, was sie eigentlich bedeuten, was nur zu einer vagen Schlussfolgerung führt.)
Hinweis: Wir haben jetzt eine viel umfangreichere Dokumentation zu Speicherverwaltung Ihrer App , die einen Großteil des Materials hier abdeckt und aktueller mit dem Stand von Android ist .
Als Erstes lesen Sie wahrscheinlich den letzten Teil dieses Artikels, in dem erläutert wird, wie der Speicher unter Android verwaltet wird:
Änderungen der Service-API ab Android 2.
Jetzt ist ActivityManager.getMemoryInfo()
unsere API auf höchster Ebene, um die gesamte Speichernutzung zu untersuchen. Dies ist hauptsächlich dazu da, um einer Anwendung zu zeigen, wie nahe das System rückt, um keinen Speicher mehr für Hintergrundprozesse zu haben, und damit zu beginnen, benötigte Prozesse wie Dienste zu beenden. Für reine Java - Anwendungen sollte dies wenig hilfreich sein, da das Java - Heap-Limit teilweise besteht, um zu vermeiden, dass eine App das System bis zu diesem Punkt belasten kann.
In einer niedrigeren Version können Sie die Debug-API verwenden, um grundlegende Informationen zur Speichernutzung auf Kernel-Ebene abzurufen: Android.os.Debug.MemoryInfo
Beachten Sie, dass es ab 2.0 auch eine API gibt, ActivityManager.getProcessMemoryInfo
, um diese Informationen zu einem anderen Prozess abzurufen: ActivityManager.getProcessMemoryInfo (int []
Dies gibt eine einfache MemoryInfo-Struktur mit all diesen Daten zurück:
/** The proportional set size for dalvik. */
public int dalvikPss;
/** The private dirty pages used by dalvik. */
public int dalvikPrivateDirty;
/** The shared dirty pages used by dalvik. */
public int dalvikSharedDirty;
/** The proportional set size for the native heap. */
public int nativePss;
/** The private dirty pages used by the native heap. */
public int nativePrivateDirty;
/** The shared dirty pages used by the native heap. */
public int nativeSharedDirty;
/** The proportional set size for everything else. */
public int otherPss;
/** The private dirty pages used by everything else. */
public int otherPrivateDirty;
/** The shared dirty pages used by everything else. */
public int otherSharedDirty;
Aber was ist der Unterschied zwischen Pss
, PrivateDirty
und SharedDirty
... nun beginnt der Spaß.
Viel Speicher in Android (und Linux-Systemen im Allgemeinen) wird tatsächlich von mehreren Prozessen gemeinsam genutzt. Es ist also nicht klar, wie viel Speicher ein Prozess belegt. Fügen Sie zusätzlich das Auslagern auf die Festplatte hinzu (geschweige denn das Auslagern, das wir auf Android nicht verwenden), und es ist noch weniger klar.
Wenn Sie also den gesamten physischen RAM, der tatsächlich jedem Prozess zugeordnet ist, verwenden und alle Prozesse addieren, erhalten Sie wahrscheinlich eine Zahl, die viel größer ist als der tatsächliche Gesamtspeicher.
Die Zahl Pss
ist eine vom Kernel berechnete Metrik, die die gemeinsame Nutzung des Speichers berücksichtigt. Grundsätzlich wird jede Seite von RAM in einem Prozess durch ein Verhältnis der Anzahl anderer Prozesse skaliert, die diese Seite ebenfalls verwenden. Auf diese Weise können Sie (theoretisch) die PSS über alle Prozesse hinweg addieren, um die Gesamtmenge RAM zu sehen, die sie verwenden, und PSS zwischen Prozessen vergleichen, um eine ungefähre Vorstellung von ihrem relativen Gewicht zu erhalten.
Die andere interessante Metrik ist PrivateDirty
, die im Grunde genommen die Menge an RAM innerhalb des Prozesses ist, die nicht auf die Festplatte ausgelagert werden kann (sie wird nicht von denselben Daten auf der Festplatte gesichert) und nicht gemeinsam genutzt mit anderen Prozessen. Eine andere Möglichkeit, dies zu betrachten, ist das RAM, das dem System zur Verfügung steht, wenn dieser Prozess beendet ist (und wahrscheinlich schnell in Caches und andere Verwendungen davon zusammengefasst wird).
Das sind so ziemlich die SDK-APIs dafür. Mit Ihrem Gerät können Sie als Entwickler jedoch noch mehr tun.
Mit adb
erhalten Sie viele Informationen über die Speichernutzung eines laufenden Systems. Ein gebräuchlicher Befehl ist adb Shell dumpsys meminfo
, der eine Reihe von Informationen über die Speichernutzung jedes Java - Prozesses ausgibt und die obigen Informationen sowie eine Vielzahl anderer Dinge enthält. Sie können auch den Namen oder die PID eines einzelnen Prozesses eingeben, um zu sehen, wie zum Beispiel adb Shell dumpsys meminfo system
den Systemprozess angibt:
** MEMINFO in pid 890 [system] ** Native dalvik other gesamt Größe: 10940 7047 nv 17987 Zugeteilt: 8943 5516 nv 14459 Frei: 336 1531 N/A 1867 (Pss): 4585 9282 11916 25783 (Gemeinsam verschmutzt): 2184 3596 916 6696 (Privat verschmutzt) : 4504 5956 7456 17916 Objekte Aufrufe: 149 ViewRoots: 4 App-Kontexte: 13 Aktivitäten: 0 Assets: 4 AssetManager: 4 Lokale Ordner: 141 Proxy-Ordner: 158 Todesempfänger: 49 OpenSSL-Sockets: 0 SQL Heap: 205 dbFiles : 0 NumPagers: 0 inactivePageKB: 0 ActivePageKB: 0
Der obere Abschnitt ist der Hauptabschnitt, in dem size
die Gesamtgröße im Adressraum eines bestimmten Heaps ist, allocated
die KB der tatsächlichen Zuweisungen, die Heap für möglich hält, free
ist Verbleibende KB, die der Heap für zusätzliche Zuweisungen frei hat, und pss
und priv dirty
sind die gleichen, die zuvor für die mit jedem der Heaps verbundenen Seiten beschrieben wurden.
Wenn Sie nur die Speichernutzung für alle Prozesse anzeigen möchten, können Sie den Befehl adb Shell procrank
verwenden. Die Ausgabe auf demselben System sieht folgendermaßen aus:
PID Vss Rss Pss Uss cmdline 890 84456K 48668K 25850K 21284K system_server 1231 50748K 39088K 17587K 13792K com.Android.launcher2 947 34488K 28528K 10834K 9 .wallpaper 987 26964K 26956K 8751K 7308K com.google.process.gapps 954 24300K 24296K 6249K 4824K com.Android.phone 948 23020K 23016K 5864K 4748K com.Android.inputmethod. lateinamerikanisch 888 25728 K 25724 K 5774 K 3668 K zygote 977 24100 K 24096 K 5667 K 4340 K Android.process.acore ... 59 336 K 332 K 99 K 92 K/system/bin/installd 60 396K 392K 93K 84K /system/bin/keystore 51 280K 276K 74K 68K /system/bin/servicemanager 54 256K 252K 69K 64K/system/bin/debuggerd
Hier sind die Spalten Vss
und Rss
im Grunde genommen Rauschen (dies sind der direkte Adressraum und die RAM - Nutzung eines Prozesses, wenn Sie die RAM - Nutzung über addieren Prozesse erhalten Sie eine lächerlich große Anzahl).
Pss
ist wie wir zuvor gesehen haben, und Uss
ist Priv Dirty
.
Interessante Anmerkung: Pss
und Uss
unterscheiden sich geringfügig (oder mehr als geringfügig) von dem, was wir in meminfo
gesehen haben. Warum das? Nun, procrank verwendet einen anderen Kernel-Mechanismus, um seine Daten zu sammeln als meminfo
, und sie liefern leicht unterschiedliche Ergebnisse. Warum das? Ehrlich gesagt habe ich keine Ahnung. Ich glaube, procrank
mag der genauere sein ... aber wirklich, hier bleibt nur der Punkt: "Nehmen Sie alle Speicherinformationen, die Sie mit einem Salzkorn erhalten, oft ein sehr großes Korn."
Schließlich gibt es den Befehl adb Shell cat /proc/meminfo
, der eine Zusammenfassung der gesamten Speichernutzung des Systems gibt. Hier gibt es viele Daten, nur die ersten Zahlen, die es zu diskutieren gilt (und die übrigen, die nur von wenigen verstanden werden, und meine Fragen an diese wenigen Personen führen häufig zu widersprüchlichen Erklärungen):
MemTotal: 395144 kB MemFree: 184936 kB Puffer: 880 kB Cached: 84104 kB SwapCached: 0 kB
MemTotal
ist die Gesamtmenge an Speicher, die dem Kernel und dem Benutzerspeicherplatz zur Verfügung steht (häufig weniger als der tatsächliche physische RAM des Geräts, da ein Teil davon RAM für das Radio benötigt wird, DMA Puffer usw.).
MemFree
ist die Menge an RAM, die überhaupt nicht verwendet wird. Die Zahl, die Sie hier sehen, ist sehr hoch. In der Regel sind dies auf einem Android System nur wenige MB, da wir versuchen, den verfügbaren Speicher zu nutzen, um die Prozesse am Laufen zu halten
Cached
ist das RAM, das für Dateisystem-Caches und andere solche Dinge verwendet wird. Typische Systeme müssen etwa 20 MB groß sein, um zu vermeiden, dass sie in einen schlechten Paging-Status geraten. Der Killer Android für nicht genügend Arbeitsspeicher ist auf ein bestimmtes System abgestimmt, um sicherzustellen, dass Hintergrundprozesse abgebrochen werden, bevor der zwischengespeicherte RAM von ihnen zu stark beansprucht wird, um zu einem solchen Paging zu führen.
Ja, Sie können Speicherinformationen programmgesteuert abrufen und entscheiden, ob speicherintensive Arbeit ausgeführt werden soll.
Erhalten Sie VM Heap-Größe, indem Sie Folgendes anrufen:
Runtime.getRuntime().totalMemory();
Erhalten Sie zugewiesenen VM Speicher durch Aufruf von:
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
Erhalten Sie VM Heap-Größenlimit, indem Sie Folgendes anrufen:
Runtime.getRuntime().maxMemory()
Holen Sie sich systemeigen zugewiesenen Speicher durch Aufruf von:
Debug.getNativeHeapAllocatedSize();
Ich habe eine App erstellt, um das Verhalten von OutOfMemoryError herauszufinden und die Speicherauslastung zu überwachen.
https://play.google.com/store/apps/details?id=net.coocood.oomresearch
Den Quellcode erhalten Sie unter https://github.com/coocood/oom-research
Dies ist eine Arbeit in Arbeit, aber das verstehe ich nicht:
ActivityManager activityManager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
Log.i(TAG, " memoryInfo.availMem " + memoryInfo.availMem + "\n" );
Log.i(TAG, " memoryInfo.lowMemory " + memoryInfo.lowMemory + "\n" );
Log.i(TAG, " memoryInfo.threshold " + memoryInfo.threshold + "\n" );
List<RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
Map<Integer, String> pidMap = new TreeMap<Integer, String>();
for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses)
{
pidMap.put(runningAppProcessInfo.pid, runningAppProcessInfo.processName);
}
Collection<Integer> keys = pidMap.keySet();
for(int key : keys)
{
int pids[] = new int[1];
pids[0] = key;
Android.os.Debug.MemoryInfo[] memoryInfoArray = activityManager.getProcessMemoryInfo(pids);
for(Android.os.Debug.MemoryInfo pidMemoryInfo: memoryInfoArray)
{
Log.i(TAG, String.format("** MEMINFO in pid %d [%s] **\n",pids[0],pidMap.get(pids[0])));
Log.i(TAG, " pidMemoryInfo.getTotalPrivateDirty(): " + pidMemoryInfo.getTotalPrivateDirty() + "\n");
Log.i(TAG, " pidMemoryInfo.getTotalPss(): " + pidMemoryInfo.getTotalPss() + "\n");
Log.i(TAG, " pidMemoryInfo.getTotalSharedDirty(): " + pidMemoryInfo.getTotalSharedDirty() + "\n");
}
}
Warum wird die PID nicht in activityManager.getProcessMemoryInfo () dem Ergebnis zugeordnet? Natürlich möchten Sie die resultierenden Daten aussagekräftig machen. Warum hat es Google so schwierig gemacht, die Ergebnisse zu korrelieren? Das aktuelle System funktioniert nicht einmal gut, wenn ich die gesamte Speicherbelegung verarbeiten möchte, da das zurückgegebene Ergebnis ein Array von Android.os.Debug.MemoryInfo-Objekten ist, aber keines dieser Objekte sagt Ihnen tatsächlich, mit welchen Pids sie verbunden sind. Wenn Sie einfach ein Array aller Pids übergeben, haben Sie keine Möglichkeit, die Ergebnisse zu verstehen. Wenn ich die Verwendung verstehe, ist es sinnlos, mehr als eine PID gleichzeitig zu übergeben. Wenn dies der Fall ist, warum sollte es so sein, dass activityManager.getProcessMemoryInfo () nur ein int-Array verwendet?
Hackbod's ist eine der besten Antworten auf Stack Overflow. Es wirft Licht auf ein sehr dunkles Motiv. Es hat mir sehr geholfen.
Eine weitere wirklich hilfreiche Ressource ist dieses Video, das Sie gesehen haben müssen: Google I/O 2011: Speicherverwaltung für Android Apps
UPDATE:
Process Stats, ein Dienst, um herauszufinden, wie Ihre App Speicher verwaltet, der im Blogbeitrag Prozessstatistik: Verstehen, wie Ihre App RAMverwendet, _ von Dianne Hackborn:
Android Studio 0.8.10+ hat ein unglaublich nützliches Tool namens Memory Monitor eingeführt.
Was ist gut für:
- Anzeigen des verfügbaren und verwendeten Speichers in einem Diagramm und der Garbage Collection Ereignisse im Laufe der Zeit.
- Schnelles Testen, ob die Langsamkeit der App möglicherweise __ ist. im Zusammenhang mit übermäßigen Müllsammelereignissen.
- Schnell testen Ob die App abstürzt, hängt möglicherweise davon ab, dass nicht genügend Speicherplatz zur Verfügung steht.
Abbildung 1. Erzwingen eines GC-Ereignisses (Garbage Collection) auf dem Android Memory Monitor
Sie können viele Informationen über den Echtzeitverbrauch Ihrer App RAM erhalten, wenn Sie sie verwenden.
1) Ich vermute nicht, zumindest nicht von Java.
2)
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
MemoryInfo mi = new MemoryInfo();
activityManager.getMemoryInfo(mi);
Log.i("memory free", "" + mi.availMem);
Wir haben herausgefunden, dass alle gängigen Möglichkeiten, den Gesamtspeicher des aktuellen Prozesses zu erhalten, einige Probleme haben.
Runtime.getRuntime().totalMemory()
: Gibt nur den JVM-Speicher zurückActivityManager.getMemoryInfo()
, Process.getFreeMemory()
und alles andere basierend auf /proc/meminfo
- gibt Speicherinformationen über alle kombinierten Prozesse zurück (z. B. Android_util_Process.cpp )Debug.getNativeHeapAllocatedSize()
- verwendet mallinfo()
, die nur Informationen über die von malloc()
durchgeführten Speicherzuordnungen und zugehörige Funktionen zurückgibt (siehe Android_os_Debug.cpp )Debug.getMemoryInfo()
- erledigt den Job, aber es ist zu langsam. Es dauert ungefähr 200ms für Nexus 6 für einen einzelnen Anruf. Der Performance-Overhead macht diese Funktion für uns unbrauchbar, da wir sie regelmäßig nennen, und jeder Aufruf ist ziemlich auffällig (siehe Android_os_Debug.cpp ).ActivityManager.getProcessMemoryInfo(int[])
- ruft intern Debug.getMemoryInfo()
auf (siehe ActivityManagerService.Java )Schließlich haben wir folgenden Code verwendet:
const long pageSize = 4 * 1024; //`sysconf(_SC_PAGESIZE)`
string stats = File.ReadAllText("/proc/self/statm");
var statsArr = stats.Split(new [] {' ', '\t', '\n'}, 3);
if( statsArr.Length < 2 )
throw new Exception("Parsing error of /proc/self/statm: " + stats);
return long.Parse(statsArr[1]) * pageSize;
Es gibt VmRSS Metrik zurück. Weitere Informationen dazu finden Sie hier: Eins , Zwei und Drei .
P.S. Mir ist aufgefallen, dass dem Thema immer noch ein tatsächlicher und einfacher Code-Ausschnitt fehlt, wie schätzen die private Speicherauslastung des Prozesses _ ist, wenn die Leistung keine kritische Anforderung ist:
Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(memInfo);
long res = memInfo.getTotalPrivateDirty();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KitKat)
res += memInfo.getTotalPrivateClean();
return res * 1024L;
In Android Studio 3.0 wurde der Android-Profiler eingeführt, um zu verstehen, wie Ihre App Ressourcen für CPU, Arbeitsspeicher, Netzwerk und Batterie verwendet.
https://developer.Android.com/studio/profile/Android-profiler
Es gibt oben eine Menge Antworten, die Ihnen definitiv helfen werden, aber (nach 2 Tagen Erstattung und Recherche zu AdB-Speicher-Tools) Ich denke, ich kann mit meiner Meinung helfen auch.
Wie sagt Hackbod: Wenn Sie also alle physischen RAM tatsächlich jedem Prozess zugeordnet haben und alle Prozesse addieren, Sie würden wahrscheinlich eine Zahl haben, die viel größer ist als der tatsächliche Gesamtspeicher. Sie können also auf keinen Fall die genaue Größe des Speichers pro Prozess ermitteln.
Aber Sie können sich dem durch eine Logik nähern .. und ich werde sagen, wie ..
Es gibt einige APIs wie
Android.os.Debug.MemoryInfo
UndActivityManager.getMemoryInfo()
, über die Sie möglicherweise bereits gelesen und verwendet wurden, aber ich werde auf andere Weise darüber sprechen
Zunächst müssen Sie ein Root-Benutzer sein, damit es funktioniert. Rufen Sie die Konsole mit Root-Rechten auf, indem Sie su
in process ausführen und dessen output and input stream
Abrufen. Übergeben Sie dann id\n
(enter) in ouputstream und schreiben Sie es, um die Ausgabe zu verarbeiten. If erhält einen Eingabestream mit uid=0
, you sind root-Benutzer.
Nun ist hier die Logik, die Sie im obigen Prozess verwenden werden
Wenn Sie ouputstream von process übergeben Sie Ihren Befehl (procrank, dumpsys meminfo etc ...) mit \n
Anstelle von id erhalten und dessen inputstream
abrufen und lesen, speichern Sie das Stream in Bytes [], Zeichen [] usw. Verwenden Sie raw Daten..und Sie sind fertig !!!!!
genehmigung :
<uses-permission Android:name="Android.permission.FACTORY_TEST"/>
Überprüfen Sie, ob Sie Root-Benutzer sind:
// su command to get root access
Process process = Runtime.getRuntime().exec("su");
DataOutputStream dataOutputStream =
new DataOutputStream(process.getOutputStream());
DataInputStream dataInputStream =
new DataInputStream(process.getInputStream());
if (dataInputStream != null && dataOutputStream != null) {
// write id to console with enter
dataOutputStream.writeBytes("id\n");
dataOutputStream.flush();
String Uid = dataInputStream.readLine();
// read output and check if uid is there
if (Uid.contains("uid=0")) {
// you are root user
}
}
Führen Sie Ihren Befehl mit su
aus
Process process = Runtime.getRuntime().exec("su");
DataOutputStream dataOutputStream =
new DataOutputStream(process.getOutputStream());
if (dataOutputStream != null) {
// adb command
dataOutputStream.writeBytes("procrank\n");
dataOutputStream.flush();
BufferedInputStream bufferedInputStream =
new BufferedInputStream(process.getInputStream());
// this is important as it takes times to return to next line so wait
// else you with get empty bytes in buffered stream
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// read buffered stream into byte,char etc.
byte[] bff = new byte[bufferedInputStream.available()];
bufferedInputStream.read(bff);
bufferedInputStream.close();
}
}
Sie erhalten Rohdaten in einer einzelnen Zeichenfolge von der Konsole anstatt in einigen Fällen von einer API, deren Speicherung komplex ist, da Sie sie manuell trennen müssen.
Dies ist nur ein Versuch, bitte schlagen Sie mir vor, wenn ich etwas verpasst habe