0

Recently I was investigating a crash in an Android app. I found the cause and fixed it but I've been wondering since then how I could have handled it ("handled" in the sense of trapping it to avoid an "unhandled" exception.)

The crash was occurring when I was starting up a new Activity. But it was not while executing any of the code visible to me in my source code. Instead it crashed after I exited the last event handler in my code, but before the View was displayed.

I had several event handlers in my Activity, such as onCreate(), onPause() and onAttachedToWindow(). In the latter I did a . . .

this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);

The program had originally been built against API 8 but when I switched to API 18 that's when it started blowing up. Based on another SO question I commented-out that line and the problem went away.

When it was crashing, the monitor showed . . .

W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x4136a960) E/AndroidRuntime: FATAL EXCEPTION: main java.lang.IllegalArgumentException: Window type can not be changed after the window is added. at android.os.Parcel.readException(Parcel.java:1429) at android.os.Parcel.readException(Parcel.java:1379) at android.view.IWindowSession$Stub$Proxy.relayout(IWindowSession.java:860) at android.view.ViewRootImpl.relayoutWindow(ViewRootImpl.java:4755) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1661) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1236) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5160) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:791) at android.view.Choreographer.doCallbacks(Choreographer.java:591) at android.view.Choreographer.doFrame(Choreographer.java:561) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:777) at android.os.Handler.handleCallback(Handler.java:725) at android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:176) at android.app.ActivityThread.main(ActivityThread.java:5365) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869) at dalvik.system.NativeStart.main(Native Method) I/Process: Sending signal. PID: 23536 SIG: 9 Disconnected from the target VM, address: 'localhost:8600', transport: 'socket'

My startActvity() was already wrapped in a try/catch, but it didn't land there . . .

  Intent svc = new Intent(ctx, RegisterActivity.class);
  svc.putExtra("Projectors2Register", params);
  try {
      ctx.startActivity(svc);
  }
  catch (Exception e) {
      Log.e("ShowButtons(normal)Reg", "Exception" + e);
  }

So where can I put a handler to catch these kinds of crashes in the future?

...N.B., By "these kinds" of crashes I don't mean specifically "TYPE_KEYGUARD"; I mean crashes that happen outside my code when the system is setting up or displaying a screen/view that was called earlier for inside my code.

In other words, I don't want the user to get "Unfortunately your app has stopped" errors without going through an error handler I write. I want to be able to log the details of every crash, what the user was doing, and to gracefully close connections and tell the user what happened.

Deepzz
  • 4,573
  • 1
  • 28
  • 52
user316117
  • 7,971
  • 20
  • 83
  • 158
  • Was the `setType` line wrapped in a try-catch block of it's own? – jlynch630 Jun 24 '17 at 00:05
  • 2
    This is an exception that probably should not be "handled" since it points to a programming error. Hiding it in some way just complicates the diagnosis to fix it. – Henry Jun 24 '17 at 04:45
  • _Was the setType line wrapped in a try-catch block of it's own? _ Yes. As I said in the OP the crash happens after leaving my code - all the event handlers exit normally and the actual crash occurs later while Android is trying to put up the View. – user316117 Jun 24 '17 at 16:59
  • @Henry _This is an exception that probably should not be "handled" since it points to a programming error. Hiding it in some way just complicates the diagnosis to fix it._ I don't agree. Handling it allows the programmer to log information about the crash and what the user was doing, to aid in diagnosis. Otherwise the user just gets "_unfortunately your app has stopped_" and then how do you diagnosis that? – user316117 Jun 24 '17 at 17:21
  • 1
    @user316117 The user should not get it at all. The code should not be released with such a bug in it. 'Handling' an exception consists of more than just logging it: you either have to recover at runtime, in the case og genuine runtime errors. or fix the coding problem that caused it, as in this case. – user207421 Jul 02 '17 at 07:58

6 Answers6

3

The try catch that you implemented will not be able to catch the exception raised in the target activity as the new activity will be started on another thread stack.

you should call

this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);

before calling setContentView()

setContentView(R.layout.yourlayoutfile);

That will prevent the exception that you are facing.

datalost
  • 3,765
  • 1
  • 25
  • 32
  • Thank you, but my **QUESTION** was not about how to **prevent** the crash. it was how to handle it. I don't want the user getting an _"unfortunately app has stopped"_ message without my being able to log information about the crash when it occurs. – user316117 Jun 24 '17 at 17:03
  • try { this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD); } catch (Exception e) { Log.e("ShowButtons(normal)Reg", "Exception" + e); } – datalost Jun 25 '17 at 07:35
  • My _getWindow*().setType()_... is already wrapped in a try/catch. I've added a clarification to my question. The TYPE_KEYGUARD is just an **example**. Android has a whole category of errors where the crash happens outside the programmer's code, producing _"unfortunately your app has stopped"_ messages. I don't want "unhandled" exceptions. Think of this like wrapping the whole app in a try/catch. – user316117 Jun 26 '17 at 15:47
2

If you simply want to obtain crash data (logs, stack traces etc.) from your users then any of the answers to this question will do:

How do I obtain crash-data from my Android application?

The most popular crash reporting tools can be found here and you can see that Crashlytics, for instance, is currently #2 on the list.

Moreover, you probably already automatically obtain crash reports that have been manually reported by the user or from users who have handsets that have opted in to share usage and diagnostics data.

It's nice that you want to prevent your user from having a bad experience, but I wonder if there is some misunderstanding about the types of exceptions that can and should be caught and those that don't.

With methods that throw checked exceptions you have no choice but to surround the call with a try/catch block:

File file = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_DOWNLOADS), filename);
try {
    file.createNewFile();
} catch (IOException e) {
    //recover from the exception here if possible
}

In addition there are calls that you would have a reasonable suspicion might fail. For instance, parsing a DateTime from an untrusted source:

String text = editText.getText().toString();
try {
    dateTime = dateTimeParser.parse(text);
}
catch (IllegalArgumentException e) {
    //try and recover here
}

However, if you carefully inspect the documentation for a call to the Android SDK and there is no talk of the type of exceptions you would need to handle there is no need to surround that code with try/catch blocks:

try { //don't surround this code with a try/catch block
    ctx.startActivity(svc);
}
catch (Exception e) {
    Log.e("ShowButtons(normal)Reg", "Exception" + e);
}

For this type of code, if you have set up your activity correctly you have no reason to suspect the call will fail and even if it does, you do not have a viable way to recover.

Furthermore, trying to recover from such errors (for instance, by implementing an UncaughExceptionHandler with some kind of UI) may do more harm than good. What assumptions about the state of the app can you make in the case it has crashed?

How would you handle the error? Perhaps, by taking the user to the landing page for the app. This might have a worse reception than the "Unfortunately the app has stopped dialog".

A better solution is to try and find such errors before they occur by keeping your app up to date (targeting the correct API) and writing instrumented tests and executing them against as many devices as you can. If you make your app a Firebase app, you can setup testing against a number of devices in the cloud. If you cannot do such testing, using Crashlytics to obtain the stack traces and promptly fixing the errors may be better than trying to implement some custom, non-standard error recovery method.

David Rawson
  • 20,912
  • 7
  • 88
  • 124
2

FireCrasher is a good library that helps you recover your app on crash. LINK

Requirement: Min SDK version 14

You can do your logic when your app crashed.

public class App extends Application {
@Override
public void onCreate() {
    FireCrasher.install(this, new CrashListener() {

        @Override
        public void onCrash(Throwable throwable, final Activity activity) {

            // show your own message
            Toast.makeText(activity, throwable.getMessage(), Toast.LENGTH_SHORT).show();

            // start the recovering process
            recover(activity);

            //you need to add your crash reporting tool here
            //Ex: Crashlytics.logException(throwable);
        }
    });
    super.onCreate();
    }
  }
Hamid Reza
  • 624
  • 7
  • 23
1

Interesting question. I can see how you want a better UX in face of an unforeseen crash. I haven't tried this, but take a look at setUncaughtExceptionHandler in the Thread documentation.

setUncaughtExceptionHandler

void setUncaughtExceptionHandler (Thread.UncaughtExceptionHandler eh)

Set the handler invoked when this thread abruptly terminates due to an uncaught exception.

Community
  • 1
  • 1
Cheticamp
  • 61,413
  • 10
  • 78
  • 131
1

You can register your own handler if you don't want to use 3rdparty tools.

import android.app.Application;
import android.util.Log;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * Application class writing unexpected exceptions to a crash file before crashing.
 */
public class MyApplication extends Application {
    private static final String TAG = "ExceptionHandler";

    @Override
    public void onCreate() {
        super.onCreate();

        // Setup handler for uncaught exceptions.
        final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread thread, Throwable e) {
                try {
                    handleUncaughtException(e);
                    System.exit(1);
                } catch (Throwable e2) {
                    Log.e(TAG, "Exception in custom exception handler", e2);
                    defaultHandler.uncaughtException(thread, e);
                }
            }
        });
    }

    private void handleUncaughtException(Throwable e) throws IOException {
        Log.e(TAG, "Uncaught exception logged to local file", e);

        // Create a new unique file
        final DateFormat dateFormat =  new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US);
        String timestamp;
        File file = null;
        while (file == null || file.exists()) {
            timestamp = dateFormat.format(new Date());
            file = new File(getFilesDir(), "crashLog_" + timestamp + ".txt");
        }
        Log.i(TAG, "Trying to create log file " + file.getPath());
        file.createNewFile();

        // Write the stacktrace to the file
        FileWriter writer = null;
        try {
            writer = new FileWriter(file, true);
            for (StackTraceElement element : e.getStackTrace()) {
                writer.write(element.toString());
            }
        } finally {
            if (writer != null) writer.close();
        }

        // You can (and probably should) also display a dialog to notify the user
    }
}

Then register this Application class in your AndroidManifest.xml:

<application android:name="de.ioxp.arkmobile.MyApplication" >
CL.
  • 173,858
  • 17
  • 217
  • 259
PhilLab
  • 4,777
  • 1
  • 25
  • 77
1

https://github.com/Ereza/CustomActivityOnCrash

I'm not exactly sure if this is what you mean but I take it you want a way that a user can send you useful information in case this happens to them?

I did not write it but I use this library which shows a custom crash activity and gives them the option to view the stack trace so they can send it back to me and also to restart the app. Not overly graceful but better than "App has stopped." I haven't looked into customizing it but I imagine you could use it to solve your problem and find where the code needs to go to handle the exception.

drawinfinity
  • 315
  • 4
  • 21