I'm currently serving a kiosk application on a dedicated tablet.
Most of them works well, but in some uncertain cases, an OOM crashed the app.
I use the below library to revive my app with the code inside my Application class.
com.jakewharton:process-phoenix:2.0.0
this.uncaughtException = Thread.UncaughtExceptionHandler { _, e ->
ErrorLogger.saveError(e)
try {
System.gc()
ProcessPhoenix.triggerRebirth(this)
} catch (e: java.lang.Exception) {
ErrorController.showError(e)
} finally {
Runtime.getRuntime().exit(0)
}
}
Thread.setDefaultUncaughtExceptionHandler(uncaughtException)
The app revives but after it, severe lags occur.
When the app starts fresh within a rebooted tablet, it takes the app about three seconds after launch to load everything from the server and internal database. But, after when it launches after OOM, it takes about five minutes to load, as if it's suffering from low memory. AlarmManagers stop working after an ANR of the AlarmManager.
Is there a way to clean the memory or a more fresh way to restart my app?
Yeah- fix your OOM crash. Use less memory. Any kind of library like the one above is a bad idea- especially since you're doing it on any uncaught exception and not just OOM, meaning you could be in any kind of bad state. Just blindly trying to restart your app on any exception is a horrible idea.
In addition, there's no way to fix the slow down. The lag is because it now needs to free and reallocate almost every object in your app. That's going to be slow. Just don't do this.
Related
My Android app currently uses a custom UncaughtExceptionHandler that aims to capture any crash, and schedules an app restart for several seconds in the future with AlarmManager before manually calling Process.killProcess(Process.myPid()) to avoid Android's Force Close popup as in my app's use case, the user will not be able to interact with the device to tap "ok" on the FC dialog and restart the app.
Now, I'd like to integrate with Firebase Crash reports, but I fear wrong behaviors, so here are my questions:
How should I make my code so my custom UncaughtExceptionHandler passes the exception to Firebase Crash Report before killing it's process? Would calling Thread.getDefaultUncaughtExceptionHandler() give me the Firebase Crash report UncaughtExceptionHandler so I can just call uncaughtException(...) on it?
May Process.killProcess(Process.myPid()) prevent Firebase Crash reporting library to do it's reporting work? Would Firebase have started it's crash report in a separated process before it's uncaughtException(...) returns? Does Firebase own UncaughtExceptionHandler calls back to Android default's UncaughtExceptionHandler, showing the FC dialog?
May Process.killProcess(Process.myPid()) kill Firebase Crash Reporting process in addition to the default process?
How can my custom Application class detect if it is instantiated in Firebase Crash Reporting process? Treating both processes the same way would probably lead to inconsistent states.
Thanks to anyone that tries to help me!
If you kill the process in your exception handler, you will not be able to receive crashes. It will interfere with the ability to persist the crash for either immediate or delayed transmission. It will possibly interfere with any library that has registered uncaught exception handlers that behave well.
In fact, Process.killProcess(Process.myPid()) is very much an anti-pattern for Android development. Android apps should not be concerned at all with the overall lifecycle if the process that hosts the app. The fact that Android manages the process for you is an optimization designed for the benefit of the users.
I strongly recommend, for uncaught exceptions in your app, to simply let the app die as it normally would. Masking the proper effect of the crash is like sweeping dirt under a rug. You might be resolving a short term problem, but what really needs to happen is the normal logging and handling of the error so you can fix it.
Don't depend on the fact that Firebase Crash Reporting transmits exceptions in another process. That other process will be removed in the full non-beta release.
The best situation for your Application subclass is to not depend at all which process it's operating. In fact, the Android team at Google does not recommend use of Application subclasses at all since it only leads to trouble for multi-process apps. If you must use an Application subclass, it should expect to run within multiple processes.
After some testing, I finally found a way to both ensure my app restarts properly after an UncaughtException.
I attempted three different approaches, but only the first, which is my original code, with just a little tweak to pass the uncaught Throwable to `FirebaseCrash, and ensure it is considered as a FATAL error.
The code that works:
final UncaughtExceptionHandler crashShield = new UncaughtExceptionHandler() {
private static final int RESTART_APP_REQUEST = 2;
#Override
public void uncaughtException(Thread thread, Throwable ex) {
if (BuildConfig.DEBUG) ex.printStackTrace();
reportFatalCrash(ex);
restartApp(MyApp.this, 5000L);
}
private void reportFatalCrash(Throwable exception) {
FirebaseApp firebaseApp = FirebaseApp.getInstance();
if (firebaseApp != null) {
try {
FirebaseCrash.getInstance(firebaseApp)
.zzg(exception); // Reports the exception as fatal.
} catch (com.google.firebase.crash.internal.zzb zzb) {
Timber.wtf(zzb, "Internal firebase crash reporting error");
} catch (Throwable t) {
Timber.wtf(t, "Unknown error during firebase crash reporting");
}
} else Timber.wtf("no FirebaseApp!!");
}
/**
* Schedules an app restart with {#link AlarmManager} and exit the process.
* #param restartDelay in milliseconds. Min 3s to let the user got in settings force
* close the app manually if needed.
*/
private void restartApp(Context context, #IntRange(from = 3000) long restartDelay) {
Intent restartReceiver = new Intent(context, StartReceiver_.class)
.setAction(StartReceiver.ACTION_RESTART_AFTER_CRASH);
PendingIntent restartApp = PendingIntent.getBroadcast(
context,
RESTART_APP_REQUEST,
restartReceiver,
PendingIntent.FLAG_ONE_SHOT
);
final long now = SystemClock.elapsedRealtime();
// Line below schedules an app restart 5s from now.
mAlarmManager.set(ELAPSED_REALTIME_WAKEUP, now + restartDelay, restartApp);
Timber.i("just requested app restart, killing process");
System.exit(2);
}
};
Thread.setDefaultUncaughtExceptionHandler(crashShield);
Explanation of why and unsuccessful attempts
It's weird that the hypothetically named reportFatal(Throwable ex) method from FirebaseCrash class has it's name proguarded while being still (and thankfully) public, giving it the following signature: zzg(Throwable ex).
This method should stay public, but not being obfuscated IMHO.
To ensure my app works properly with multi-process introduced by Firebase Crash Report library, I had to move code away from the application class (which was a great thing) and put it in lazily loaded singletons instead, following Doug Stevenson's advice, and it is now multi-process ready.
You can see that nowhere in my code, I called/delegated to the default UncaughtExceptionHandler, which would be Firebase Crash Reporting one here. I didn't do so because it always calls the default one, which is Android's one, which has the following issue:
All code written after the line where I pass the exception to Android's default UncaughtExceptionHandler will never be executed, because the call is blocking, and process termination is the only thing that can happen after, except already running threads.
The only way to let the app die and restart is by killing the process programmatically with System.exit(int whatever) or Process.kill(Process.myPid()) after having scheduled a restart with AlarmManager in the very near future.
Given this, I started a new Thread before calling the default UncaughtExceptionHandler, which would kill the running process after Firebase Crash Reporting library would have got the exception but before the scheduled restart fires (requires magic numbers). It worked on the first time, removing the Force Close dialog when the background thread killed the process, and then, the AlarmManager waked up my app, letting it know that it crashed and has a chance to restart.
The problem is that the second time didn't worked for some obscure and absolutely undocumented reasons. The app would never restart even though the code that schedules a restart calling the AlarmManager was properly run.
Also, the Force Close popup would never show up. After seeing that whether Firebase Crash reporting was included (thus automatically enabled) or not didn't change anything about this behavior, it was tied to Android (I tested on a Kyocera KC-S701 running Android 4.4.2).
So I finally searched what Firebase own UncaughtExceptionHandler called to report the throwable and saw that I could call the code myself and manage myself how my app behaves on an uncaught Throwable.
How Firebase could improve such scenarios
Making the hypothetically named reportFatal(Throwable ex) method non name-obfuscated and documented, or letting us decide what happens after Firebase catches the Throwable in it's UncaughtExceptionHandler instead of delegating inflexibly to the dumb Android's default UncaughtExceptionHandler would help a lot.
It would allow developers which develop critical apps that run on Android to ensure their app keep running if the user is not able to it (think about medical monitoring apps, monitoring apps, etc).
It would also allow developers to launch a custom activity to ask users to explain how it occurred, with the ability to post screenshots, etc.
BTW, my app is meant to monitor humans well-being in critical situations, hence cannot tolerate to stop running. All exceptions must be recovered to ensure user safety.
I am using a very unstable library on Android that crashes sporadically. I start it using the startActivity() in my code.
The unstable part of the code is doing a lot of video processing and uploading the result to a server. I do not really mind if the activity crashes, but I need to signal the server that it did.
The crash comes from a memory leak (no time to solve it yet). Is there a way I can catch the error a display a more friendly/funny message instead?
try {
context.startActivity(intent);
} catch (ApplicationCrashedException e) {
server.notifyServerOfCrash();
toast("I really disliked your face...");
}
Edit: Here is the Error:
java.lang.OutOfMemoryError
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at java.nio.MemoryBlock.allocate(MemoryBlock.java:125)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:72)
at io.cine.android.streaming.FFmpegMuxer.writeSampleData(FFmpegMuxer.java:151)
at io.cine.android.streaming.AndroidEncoder.drainEncoder(AndroidEncoder.java:128)
at io.cine.android.streaming.TextureMovieEncoder.handleFrameAvailable(TextureMovieEncoder.java:264)
at io.cine.android.streaming.TextureMovieEncoder$EncoderHandler.handleMessage(TextureMovieEncoder.java:409)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at io.cine.android.streaming.TextureMovieEncoder.run(TextureMovieEncoder.java:219)
at java.lang.Thread.run(Thread.java:841)
For some reason once the BroadcastActivity runs out of memory, the Activity gets killed and comes back to the previous one. It then displays the 'app' has stopped working Dialog. If I press OK, it kills the entire application and comes back to the home screen.
Ideally I would just have it come back to the previous activity notifying the server silently. (Not killing the application)
Is there a way I can catch the error a display a more friendly/funny message instead?
If the app is leaking so much memory that it crashes, you cannot easily catch this.
Why you can't always catch this
When you start running out of memory, exceptions are thrown from any part of your app's code that tries to allocate more memory than is available. So it isn't just your library that will throw the exceptions.
How you could detect this
Catch the OutOfMemoryError and make an effort to tell user/server
Your results will vary, but you could wrap the Thread's run() method with a try catch pair and try to catch the out of memory error. You will still be out of memory and any thing you do in the catch code might fail because of it. Still it might work if the process frequently allocates large chunks of memory which would mean there is still a little bit left.
Example:
public void run() {
try {
//video process code
} catch (OutOfMemoryError error) {
//we are still out of memory so these operations might fail
tellServerWeFailed();
triggeruserDialog();
}
}
onTrimMemory() override
You could also try stopping your video tasks when onTrimMemory() is called. Your mileage may vary, I've never been able to get it to work consistently across different Android devices.
Error Reporting Framework
One solution that is pretty involved is to use a third party error reporting framework as they will often let you show your user custom crash messages.
Check out http://www.acra.ch/ and specifically the documentation on User Messages
Started Service
I do not really mind if the activity crashes, but I need to signal the server that it did.
Great! What you might try is to use a started service that android automatically relaunches after the crash. The details on how to tell your server are up to you. You might find that you will need to save some token or other information so that you can tell your server which session has ended so you might, for example, always keep that information saved in Preference entry and read it out after the crash.
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(LOG_TAG, "Received start id " + startId + ": " + intent);
if (intent == null) {
Log.w(LOG_TAG, "Service was stopped and automatically restarted by the system. ");
//Tell your server about the crash.
stopSelf();
}
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
See http://developer.android.com/reference/android/app/Service.html#ServiceLifecycle
More about running out of memory on Android
To maintain a functional multi-tasking environment, Android sets a hard limit on the heap size for each app. The exact heap size limit varies between devices based on how much RAM the device has available overall. If your app has reached the heap capacity and tries to allocate more memory, it will receive an OutOfMemoryError.
In some cases, you might want to query the system to determine exactly how much heap space you have available on the current device—for example, to determine how much data is safe to keep in a cache. You can query the system for this figure by calling getMemoryClass(). This returns an integer indicating the number of megabytes available for your app's heap. This is discussed further below, under Check how much memory you should use. (source)
Some key points:
Your entire app shares the system-assigned memory heap.
Once you've leaked memory, it is gone until the application is restarted.
You won't be able to segregate memory leaked in one activity by leaving that activity.
If you are looking for the type of Exception to catch, just repeat the steps you took to make your app crash and take a look at the logcat. You should see a message like "SomeTypeOfException thrown" catch that.
Edit: otherwise use the generic Exception
I have an annoying problem:
I'm fetching a lot of GeoJSON data from a server. This works, even with 16MB heap and even if I start and stop the app several times. The memory consumption stays constant and never exceeds. But there is a case where I exceed the 16MB heap. I'd like to describe it shortly:
The app is used and "quit" by home button, so the app resides in the background and is not destroyed yet. When the app is resumed, my "controller" which is a part of my app, checks for new GeoJSON data. If there is a GeoJSON data update, the app downloads and processes it and here the problem begins. If the app was already started before and is resumed from background the heap size of 16MB is not enough for the following code (if and only if the app is resumed from background instead of a fresh start):
private synchronized String readUrlData(HttpURLConnection urlConnection) throws IOException {
Log.i(TAG, "Start reading data from URL");
urlConnection.setReadTimeout(1000 * 45); //45 sec
Reader reader = new InputStreamReader(urlConnection.getInputStream());
StringBuilder sb = new StringBuilder(1024 * 16);
char[] chars = new char[1024 * 16]; //16k
int len;
while((len = reader.read(chars)) >= 0) {
sb.append(chars, 0, len);
}
reader.close();
Log.i(TAG, "Finished reading data from URL");
return sb.toString();
}
I get OutOfMemory either in append() or in toString(). Obviously the app takes little to much memory for this when it's somehow used before. I already tried to find a more resource friendly way for the code above but there is no solution. Again, if the app is started from new, there are never problems. And I'm absolutely sure that I don't have any memory leaks because
I checked this part and more with MAT and there was never more than 1.6MB occupied (this is the GeoJSON data).
I performed this use case several times consecutively with 24MB heap size.
If there would be a memory leak, it would have been crashed after the 3rd ot 4th time with 24MB heap, but it ran without problems.
I know how to avoid the crash. I could show an AlertDialog to the user which tells him that there is new GeoJSON data available and he needs to restart the app. But there is a catch. If the application is "terminated" by Activity's finish(), the application still remains in memory so when it restarts, the crash comes again because the memory is never deallocated (at least I can't rely on it in most cases). I already figured out that System.exit(0); instead of finish would free all memory because it kills the whole app, so no crash occures after restart with the new GeoJSON data. But I know this is no good solution. I already tried System.gc() on important parts but this doesn't work either. Any ideas how to deal with this problem? Probably I need something like restarting the app with deallocing all used memory.
Another solution could be to redesign the code above but I don't think that it's possible to get more MBs out of this.
If I don't find a reasonable solution for this, I will use System.exit(0) when heap is 16MB (I think there is a way to check that) to restart the app.
here are some ideas :
as soon as you know there is anything to read , set the old data to null so that it will be GC-ed .
use a service that does this task , that will run on a different process (thus having at least 16 mb just for this task) . once it handles the data , move it in some way to whatever component that you need , while null-ing the old data before.
decode the data as you get it , instead of getting all of it and then decode it .
compress the data so that the while decoding it , it will decompress it on the way .
use an alternative to json , such as google's protocol buffers or your own customized data type .
most of the devices out there have more than 16 mb in their heap memory . you could simply set the min sdk version to 8 or 10 since most devices that have more memory than this also have a higher API .
Thanks for your answers. At the end I decided to let System.exit(0) in this one special case because the data is hardly ever updated and even if, it must be coincidence that it happens exactly when someone has the app running in background.
I created an application that creates a background service. When I close the application the service is running and when I go back to get it back to "bind" (bindService) to it for communications.
The problem is that when I close the application and re-enter, it significantly increases the memory in use. Spend 20Mb -> 24Mb, if I go out and come 24Mb -> 28Mb, 28Mb -> 30Mb ... Thus breaking up the application. I have no bitmaps. That if enough use static arrays but I've tested and are not the source of the problem.
When I close the application completely (including service) call System.exit(0) and clean all the memory, but of course, I can call when I leave the service running as it closes. I tried to call the Garbage Collector (System.gc()) and if I notice that memory decreases to close, but when you open the application again same memory increases.
I would greatly appreciate the help, Bye!
Considering information we have, I suspect the problem is an Activity leak.
Can you please check 2 things:
Check carefully do you use Activity Context (reference to Activity) that can be stored? Particularly in Service.
Try to make an experiment - select "Do not keep activities" in Developer setting - will the memory be decreasing after each Activity launch?
I have an app which let users to pick an image from sd card and then app process the image. I am downsizing images to 1/5 of avialable vm memory and i do call recycle() for every bitmap in onDestroy() call and i still get out of memory error if i close and open my app multiple times.
There are various memory leak scenarios in Android. One way to track them down is to use the Traceview tool http://developer.android.com/guide/developing/debugging/debugging-tracing.html.
For more info on common Android memory leak problems see http://android-developers.blogspot.co.uk/2009/01/avoiding-memory-leaks.html
Note that when you finish the last Activity of the app the Java process of your app may (in most cases will) be alive meaning all static stuff is still alive when you "start" the app again. Do you store any heavy objects in static fields?
Also note that according to the Activity life-cycle the onDestroy() is not guaranteed to be called. However I don't think this is related, because when you (versus the OS) close the Activity (either by pressing 'Back' button or by calling finish() from the code) then OS always calls onDestroy().
In general, without seeing the code it is difficult to say what happens.