I recently read this article on Managing Your App's Memory,I strongly suggest to read it if you are an AndroidDev and never did.
There are lots of good practices and one thing I never happen to know about is the onTrimMemory(int level) method called by the system on every Activity/Fragment to notify events on which memory should or could be released.
Here is a quote from that article:
Notice that your app receives the onTrimMemory() callback with
TRIM_MEMORY_UI_HIDDEN only when all the UI components of your app
process become hidden from the user. This is distinct from the
onStop() callback, which is called when an Activity instance becomes
hidden, which occurs even when the user moves to another activity in
your app. So although you should implement onStop() to release
activity resources such as a network connection or to unregister
broadcast receivers, you usually should not release your UI resources
until you receive onTrimMemory(TRIM_MEMORY_UI_HIDDEN). This ensures
that if the user navigates back from another activity in your app,
your UI resources are still available to resume the activity quickly.
I am really interested in implementing a good memory management in my application so I am looking forward to implement the onTrimMemory() in the right way.
I only have a few questions about it:
is onTrimMemory(TRIM_MEMORY_UI_HIDDEN) called right after the onStop()?
what "release your UI resources" means in that context? just for instance clean the Bitmap cache, or actually remove and destroy every View in the View tree? i usually destroy the Views in the onDestroy() or onDestroyView() methods, i am now wondering if i'm doing it right.
is there a Twin/correspondent callback to onTrimMemory(TRIM_MEMORY_UI_HIDDEN)? like onCreate-onDestroy, onStart-onStop, onCreateView-onDestroyView. I'm asking to understand where and how i should restore the UI state after an Activity/Fragment as been brought in foreground after onTrimMemory(TRIM_MEMORY_UI_HIDDEN) has been called.
Sample Implementation
public class AppContext extends Application {
//This my introduce OutOfMemoryException if you don't handle register and removal quiet well, better to replace it with weak reference
private static List<IMemoryInfo> memInfoList = new ArrayList<AppContext.IMemoryInfo>();
public static abstract interface IMemoryInfo {
public void goodTimeToReleaseMemory();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
//don't compare with == as intermediate stages also can be reported, always better to check >= or <=
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
try {
// Activity at the front will get earliest than activity at the
// back
for (int i = memInfoList.size() - 1; i >= 0; i--) {
try {
memInfoList.get(i).goodTimeToReleaseMemory();
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
*
* @param implementor
* interested listening in memory events
*/
public static void registerMemoryListener(IMemoryInfo implementor) {
memInfoList.add(implementor);
}
public static void unregisterMemoryListener(IMemoryInfo implementor) {
memInfoList.remove(implementor);
}
}
public class ActivityParent extends Activity implements AppContext.IMemoryInfo {
protected ActivityParent child;
@Override
protected void onStop() {
super.onStop();
try {
if (child != null)
AppContext.unregisterMemoryListener(child);
} catch (Exception e) {
}
}
}
public class ActivityChild extends ActivityParent {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
child = this;
}
/---move following onResume() in parent as following eg:
/*
*@Override
* protected void onResume() {
* super.onResume();
* if(null != child){
* AppContext.registerMemoryListener(this);
* }
* }
*/
@Override
protected void onResume() {
super.onResume();
AppContext.registerMemoryListener(this);
}
@Override
public void goodTimeToReleaseMemory() {
super.goodTimeToReleaseMemory();
//remove your Cache etc here
}
//--NO Need because parent implementation will be called first, just for the sake of clarity
@Override
protected void onStop() {
super.onStop();
try {
if (null != child)
AppContext.unregisterMemoryListener(child);
} catch (Exception e) {
}
}
More Info:
When your app is running:
TRIM_MEMORY_RUNNING_MODERATE
The device is beginning to run low on memory. Your app is running and not killable.
TRIM_MEMORY_RUNNING_LOW
The device is running much lower on memory. Your app is running and not killable, but please release unused resources to improve system performance (which directly impacts your app's performance).
TRIM_MEMORY_RUNNING_CRITICAL
The device is running extremely low on memory. Your app is not yet considered a killable process, but the system will begin killing background processes if apps do not release resources, so you should release non-critical resources now to prevent performance degradation.
When your app's visibility changes:
TRIM_MEMORY_UI_HIDDEN
Your app's UI is no longer visible, so this is a good time to release large resources that are used only by your UI.
When your app's process resides in the background LRU list:
TRIM_MEMORY_BACKGROUND
The system is running low on memory and your process is near the beginning of the LRU
list. Although your app process is not at a high risk of being killed, the system may already be killing processes in the LRU
list, so you should release resources that are easy to recover so your process will remain in the list and resume quickly when the user returns to your app.
TRIM_MEMORY_MODERATE
The system is running low on memory and your process is near the middle of the LRU list. If the system becomes further constrained for memory, there's a chance your process will be killed.
TRIM_MEMORY_COMPLETE
The system is running low on memory and your process is one of the first to be killed if the system does not recover memory now. You should release absolutely everything that's not critical to resuming your app state.
To support API levels lower than 14, you can use the onLowMemory()
method as a fallback that's roughly equivalent to the TRIM_MEMORY_COMPLETE
level.
http://developer.android.com/reference/android/content/ComponentCallbacks2.html
I was forcing the problem that onTrimMemory() was never called when the display turned off.
Thus I tried a workaround using ActivityLifecycleCallbacks:
I used a simple counter:
onActivityStarted(){
i++;
}
onActivityStopped(){
i--;
if(i==0) // no more open activities, thus screen probably turned off
}
It worked for me but im not sure if it is a safe way. RFC
UPDATE: Starting a camera intent, the application closed, too so it is not exactly behaving as desired.
Used this code instead. works just fine:
private void registerBroadcastReceiver() {
final IntentFilter theFilter = new IntentFilter();
theFilter.addAction(Intent.ACTION_SCREEN_OFF);
BroadcastReceiver screenOnOffReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String strAction = intent.getAction();
if (strAction.equals(Intent.ACTION_SCREEN_OFF)) {
// do sth
}
}
};
getApplicationContext()
.registerReceiver(screenOnOffReceiver, theFilter);
}