I've read How do I discover memory usage of my application in Android? and a bunch of other answers, but can't quite nail this down...
I have an Activity that will load a file from external storage into memory and do some parsing/manipulation/etc in-memory. Before I load it I want to guess whether or not doing so will cause an OutOfMemoryException and crash the Activity (I understand that exact answers aren't possible, an estimate is better than nothing.)
From the above-linked answer, I came up with:
ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(ACTIVITY_SERVICE);
MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
int pid [] = {android.os.Process.myPid()};
android.os.Debug.MemoryInfo[] mi = activityManager.getProcessMemoryInfo(pid);
// calculate total_bytes_used using mi...
long available_bytes = activityManager.getMemoryClass()*1024*1024 - total_bytes_used;
So, the questions:
1) am I crazy?
2) how to total the values from the MemoryInfo object to estimate the heap usage of the activity/task? (The above link gives an overview of pss/private-dirty/shared-dirty, but not enough info to guess how to do the total.)
3) does Debug always exist or only when debugging?
4) is there a smarter way?
Answers like these: Two questions about max heap sizes and available memory in android seem to imply that there isn't a better way than this?
I know that using less memory is a good thing, and I am. I'm interested to know how to code defensively, here. Seems weird to just wait for an exception to know that you're out of memory.
Thanks!
you may refer to this link.. A complete reserach on the same problem you are facing.
OOMRESEACH
Related
How can I check if the device has low storage on Android 8 Oreo. I saw in the Android Documentation that the Intent.ACTION_DEVICE_STORAGE_LOW is deprecated in API 26.
This constant was deprecated in API level 26.
if your app targets O or above, this broadcast will no longer be delivered to any BroadcastReceiver defined in your manifest. Instead, apps are strongly encouraged to use the improved getCacheDir() behavior so the system can automatically free up storage when needed.
- Android Documentation
They are encouraging me use getCacheDir() instead.
But I don't understand much of it, as getCacheDir() seems to return the system cache directory path as a FILE object, which can only be used to clear cache or some such.
But I need to check whether the device is running low on device storage. I hope someone will help me in this
See Android's (AndroidX work) StorageNotLowTracker implementation for an example of how to receive system broadcasts when storage becomes low or OK.
Note that this is the implementation used by AndroidX work when using a 'storage not low constraint'. It uses deprecated intent filter broadcast actions, but it still works today.
I have created a similar implementation (not shared in this answer) that can be registered, unregistered and has two callbacks: on storage low and on storage OK.
See StorageNotLowTrackerTest for an example of how to test this.
Old answer kept for reference below
As correctly stated in the question, the API 26 Intent.ACTION_DEVICE_STORAGE_LOW is deprecated and Context#getCacheDir() is advised to be used instead to free up space from you application's cache.
There are multiple problems with this (enumerated below), but first: note that it is good practice to keep cache 'reasonably small' (e.g. 1 MB), I quote:
getCacheDir()
Returns a File representing an internal directory for your app's temporary cache files. Be sure to delete each file once it is no longer needed and implement a reasonable size limit for the amount of memory you use at any given time, such as 1MB.
Caution: If the system runs low on storage, it may delete your cache files without warning.
(source)
So, there are three problems here:
We should clear the cache, but it is probably already reasonably small (e.g. 1 MB), so clearing it will probably not free enough space for the free storage to become OK again (similar to the also deprecated Intent.ACTION_DEVICE_STORAGE_OK that previously could be used for this)
As quoted, the cache quite possibly has already been cleared by the system, because the storage is low and the system may clear your application's cache if it so decides. Therefore, clearing it yourself possibly does not free up any storage.
The documentation does not specify at all how to actually detect if the device is low on storage.
So, clearing the cache doesn't seem to help, so I won't go into the details of how to do that.
However, as per this answer, we could assume that at 10% free storage the system enters the low storage state that we want to detect. This number is Android's default, but there's little preventing a device manufacturer (or ROM developer) from changing it, according to the linked answer.
At this point, to me, this 10% is a magic number and I'd like to know if I can determine this threshold programmatically. If you know how, please edit my answer, post an answer yourself or comment on my answer.
To do this using getCacheDir(), you could use the following:
Java, from a Context (e.g. Activity):
File cacheDir = getCacheDir();
if (cacheDir.getUsableSpace() * 100 / cacheDir.getTotalSpace() <= 10) { // Alternatively, use cacheDir.getFreeSpace()
// Handle storage low state
} else {
// Handle storage ok state
}
Kotlin, from a Context (e.g. Activity):
if (cacheDir.usableSpace * 100 / cacheDir.totalSpace <= 10) { // Alternatively, use cacheDir.freeSpace
// Handle storage low state
} else {
// Handle storage ok state
}
Now, whether to use the usable space or free space, that's not entirely clear to me. The difference is described here.
Diving into the Android source I found a system service, that I cannot access in my code, that checks for low storage: DeviceStorageMonitorService. It gets its lowBytes variable from StorageManager#getStorageLowBytes, which I cannot access either. If that would be possible in some non-hacky way, that would be a way to get the low storage bytes threshold. There you see the source uses getUsableSpace(), so that's why I chose that instead of getFreeSpace() too for my code snippets.
After digging into the code of android excatlty the class that release the Low storage notification called DeviceStorageMonitorService
Here's what i found, Some phones use the sys_storage_threshold_percentage and some use sys_storage_threshold_max_bytes so to test the storage you should get the real value from the Settings.Secure using both keys and then compare between sys_storage_threshold_percentage * Total memory size of data system folder and sys_storage_threshold_max_bytes and then take the small value and compare it to the available storage space of data system folder, here's the code of how to do it
private void checkForLowStorage() {
long mFreeMem = getDeviceCurrentStorage();
float deviceLowStorageThreshold = getDeviceLowStorageThreshold();
if (mFreeMem <= deviceLowStorageThreshold) {
Toast.makeText(this, R.string.low_storage_error_message, Toast.LENGTH_LONG).show();
// Handle storage low state
} else {
// Handle storage ok state
}
}
private long getDeviceCurrentStorage() {
long mFreeMem = 0;
try {
StatFs mDataFileStats = new StatFs("/data");
mDataFileStats.restat("/data");
mFreeMem = (long) mDataFileStats.getAvailableBlocksLong() *
mDataFileStats.getBlockSizeLong();
} catch (IllegalArgumentException e) {
// use the old value of mFreeMem
}
return mFreeMem;
}
private long getDeviceLowStorageThreshold() {
long value = Settings.Secure.getInt(
getContentResolver(),
"sys_storage_threshold_percentage",
10);
StatFs mDataFileStats = new StatFs("/data");
long mTotalMemory = ((long) mDataFileStats.getBlockCountLong() *
mDataFileStats.getBlockSizeLong()) / 100L;
value *= mTotalMemory;
long maxValue = Settings.Secure.getInt(
getContentResolver(),
"sys_storage_threshold_max_bytes",
500*1024*1024);
return Math.min(value, maxValue);
}
I'm still testing it, Dunno if it's not going to work on some devices
We have a problem with our app. Over time we have noticed that the totalPss value gets very large (depending on the device, it will be 300-700Mb):
int pid = android.os.Process.myPid();
int pids[] = new int[1];
pids[0] = pid;
android.os.Debug.MemoryInfo[] memoryInfoArray = activityManager.getProcessMemoryInfo(pids);
if (memoryInfoArray.length > 0)
{
android.os.Debug.MemoryInfo thisProcessMemoryInfo = memoryInfoArray[0];
Log.d("totalPss", thisProcessMemoryInfo.getTotalPss()+"");
}
Here is a graph showing results from a typical run:
But, at the same time, the totalMemory value never gets very large (40-50Mb at max, but falls to 10Mb after GC).
Log.d("totalMem", Runtime.getRuntime().totalMemory()+"");
Here is a graph showing that from the same run as above (please ignore the units, it is actually in bytes):
getMemoryClass for this device indicates that we have 192Mb available for the app:
Log.d("getMemoryClass", activityManager.getMemoryClass()+"");
Our memory usage pattern is to make a number of large allocations over time which are frequently released. After a long time of this, a large allocation will fail which typically causes the app to fail.
It seems like we are probably having fragmentation, does this seem correct? Can we resolve this by using the largeHeap attribute (my intuition is that it would not)? Are there any tools to help diagnose this more certainly?
The phenomenon: First do allocation some big memory blocks in the Java side until we catche OutOfMemoryError, then free them all. Now, weird things happen: load even a small picture(e.g. width:200, height:200) by BitmapFactory.decodeXXX(decodeResource, decodeFile, ...) will throw an OutOfMemoryError! But its OK to alloc any pure Java big Object(e.g. new byte[2*1024*1024]) now!
Verifying: I wrote some simple codes to verify the problem that can download here, press "Alloc" button many times and you will got an OOF Error, then press "Free All", now the environment is set up. Now you can press "LoadBitmap" and you will see its not work on most of Android 2.x phone.(But in the emulator its just OK, odd)
Digging deeper: I try to dig into some dalvik code to find out why, and find a possible bug in function externalAllocPossible in HeapSource.c which called by dvmTrackExternalAllocation who print the "xxx-byte external allocation too large for this process" messages in LogCat.
In externalAllocPossible it simply wrote:
if (currentHeapSize + hs->externalBytesAllocated + n <=
heap->absoluteMaxSize)
{
return true;
}
return false;
Which means once if the native Bitmap allocation size plus the currentHeapSize(NOT the actually allocated size as shown below, in this case, it's keeping the max size of the heap we bumped up but then freed them all) exceeds the limits, native Bitmap allocation will always fail, but the currentHeapSize in Java seems NOT decrease even when 91.3% Java objects' memory have been freed(set to null and trigger GC)!
Is there anybody else met this problem too?
I think this is correct. Its forcing the entire app (Java+native) take no more than a certain amount of memory from the OS. To do this it has to use the current heap size, because that amount of memory is still allocated to the app (it is not returned to the OS when freed by GC, only returned to the application's memory pool).
At any rate, 2.x is long dead so they're not going to fix it there. They did change how bitmaps store their memory in 3.x and 4.x. Your best bet is to allocate all the bitmaps you use first, then allocate those large structures. Or better yet- throw those large structures into a fixed size LRUCache, and don't use the grow until out of memory idea, instead load new data only when needed.
The class Bitmap has the recycle() method, described as:
Free the native object associated with this bitmap...
The reason behind this method is that there are two heaps: the Java heap and the heap used by native code. The GC only sees the Java heap sizes; for GC, a bitmap may look as a small object because it's size on the Java heap is small, despite the fact that it references a large memory block in the native heap.
I wrote Android application and it have some strange problems with Out Of Memory exception, which appears randomly. Yes, I know that problems with OOM exception usually because of images, and I used all what I can to avoid this problem.
The only way I could think to find the place where spend the memory is putting the logs with information about memory everywhere. But I really confused about which values do I need.
I used next values:
Runtime.getRuntime().maxMemory()
Runtime.getRuntime().freeMemory()
Runtime.getRuntime().totalMemory()
Debug.getNativeHeapAllocatedSize()
Debug.getNativeHeapFreeSize()
Debug.getNativeHeapSize()
And before OOM exception I have next values:
maxMemory: 57344K
freeMemory: 9581K
totalMemory: 22407K
NativeHeapAllocatedSize: 34853K
NativeHeapFreeSize: 302K
NativeHeapSize: 40060K
lowMemory false
In this question Android Bitmap Limit - Preventing java.lang.OutOfMemory I see that used compeering ((reqsize + Debug.getNativeHeapAllocatedSize() + heapPad) >= Runtime.getRuntime().maxMemory()) ant in this blog some strange information:
Debug.getNativeHeapFreeSize(); The free size is the amount of memory
from the heap that is not being used, because of fragmentation or
provision.
Also I can not understand haw can be OOM exception if (totalMemory: 22407K) much less than (maxMemory: 57344K).
Please help me understand how use this values.
The callback onLowMemory() of my Activity kept on being called and so to investigate the issue, I wrote the following lines of code in the main game loop:
ActivityManager activityManager = (ActivityManager) Activity.getActivity().getSystemService(Activity.ACTIVITY_SERVICE);
MemoryInfo mi = new MemoryInfo();
activityManager.getMemoryInfo(mi);
Log.v("Tag", "Testing mem: " + mi.availMem);
The logs were very interesting: the availMem kept on decreasing. Now to make sure objects were being destroyed properly I I took hprof memory dumps. The objects that were being destroyed, as expected, are not showing in those dumps.
Also, availMem is supposed to be memory available to the whole system and not just my app, which makes it even weirder.
Can anybody help me understand whats going on here. Any help would be much appreciated. Thanks!