30

I know the best way to prevent system crashes is catching all possible exception in different methods. So I use try catch blocks every where in my code. However as you know sometimes you forget to test some scenarios which cause some unhanded exceptions and a user gets "Unfortunately App stopped working..." message. This is bad for any application. Unfortunately the people who will use my app are not native English, so they will not understand crash message too.

So I want to know is it possible to catch all possible exception globally ( with just one try catch block in some main classes not all classes and methods!!!) and reload application automatically and without any weird messages? Or at least is it possible to change the crash message?

Thanks.

Taher
  • 1,185
  • 3
  • 18
  • 36

4 Answers4

41

In your onCreate

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread paramThread, Throwable paramThrowable) {
                //Catch your exception
                // Without System.exit() this will not work.
                System.exit(2);
            }
        });
Broak
  • 4,161
  • 4
  • 31
  • 54
  • I have 3 or 4 activity and some fragments. I use this code in all of them? Also this code causes exit, I need to reload application. – Taher Aug 26 '15 at 14:23
  • 2
    You can use it on all the activities, the parent activity to the fragments will handle those exceptions. You will need to exit really, reloading is not possible without awkward hacks, i.e. having a separate app service running to start the app again etc.. – Broak Aug 26 '15 at 14:32
  • 3
    Why you use 2 as argument of System.exit? Is there any difference between non-zero arguments? I know they are for quitting with error. – Taher Aug 26 '15 at 14:36
  • 1
    It does not really mean anything other than that an error occurred anything other than 0 = error – Broak Aug 26 '15 at 14:42
  • 1
    Thanks, I used Toast before System.exit but it does not show the Toast. Is it possible that System.exit prevent from showing that? – Taher Aug 26 '15 at 14:43
  • 4
    I should imagine it's because the app has crashed. There isn't much you can do on that thread. Simply show the toast in a new thread – Broak Aug 26 '15 at 14:46
25

So I want to know is it possible to catch all possible exception globally ... and reload application automatically

DO NOT DO THIS. If your app enters a state where even just launching it causes a crash, you will create an endless loop of crashing and relaunching that the user cannot get out of.

The error message is not weird; it is the system message, and it is translated in all supported languages on the device.

The only thing you should be doing is using some kind of crash reporting library to report crashes back to you without the user having to do anything, so that you can fix your app and submit an update. You can set a custom UncaughtExceptionHandler in your Application's onCreate(), but I would limit this to logging data and perhaps preparing to send that to you for debugging purposes, and then forward the call back to the default UncaughtExceptionHandler. This is exactly what a crash reporting library does.

I know the best way to prevent system crashes is catching all possible exception in different methods. So I use try catch blocks every where in my code.

No, the best way to to write good code and fix bugs before releasing. It is considered bad practice to catch all forms of exceptions indiscriminately. You should be using try-catch blocks only where either

  1. A method call can throw a checked exception, in which case the compiler forces you to surround with try-catch or rethrow the exception; or
  2. You want to handle certain unchecked (runtime) exceptions. An example would be parsing user input with something like Integer.parseInt(), catching the NumberFormatException and showing message to the user that their input is invalid.

However as you know sometimes you forget to test some scenarios

Then improve your coding and your testing practices. Do not use this as an excuse for taking rash actions.

Unfortunately the people who will use my app are not native English, so they will not understand crash message too.

Which crash message? If you mean the system's crash message, that should be in whatever language they set their device to. If you mean your crashmessage (e.g. in logcat), they shouldn't have to. They should send you the crash message, you should fix the app and distribute an update. Don't expect an end user to do any legwork in determining what causes your app to crash. Better to use some kind of crash reporting library.

Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • 17
    Please bare in mind he asked how TO do it, not why he SHOULDN'T do it – Broak Aug 26 '15 at 17:59
  • 20
    @xBroak When the answer has significant ramifications for the user, the user's device, and other apps on the same device, I think it's irresponsible not to mention them. – Karakuri Aug 26 '15 at 21:30
  • 3
    Certainly! But it should be prefixed with the answer to his question. – Broak Aug 26 '15 at 22:34
  • Years after you've written it, your work of art, built with best practices and 100% test coverage will encounter some obscure network error that didn't even exist when you wrote it... and crash silently. My ugly hack, with its global error handler will show a toast "TCP protocol error #4906771", which I'll paste into Google. – smirkingman Jun 22 '23 at 10:15
6

There you go:

    Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread paramThread, Throwable paramThrowable) {

        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(getActivity(),"Your message", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();
        try
        {
            Thread.sleep(4000); // Let the Toast display before app will get shutdown
        }
        catch (InterruptedException e) {    }
        System.exit(2);
    }
});
Hamed Ghadirian
  • 6,159
  • 7
  • 48
  • 67
0

I found this useful when debugging crashes in my app. It doesn't rely on any existing context or intent; it sets itself up from scratch.

In android.manifest, add an ErrorReportActivity:

<activity
    android:name=".MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ch.calvert.gauges.ErrorReportActivity"
    android:theme="@style/Theme.AppCompat.Dialog">
</activity>

Create ErrorHandler.kt:

import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.TextView
import java.io.PrintWriter
import java.io.StringWriter

private var oldHandler: Thread.UncaughtExceptionHandler? = null

class MyUncaughtExceptionHandler(context: Context) : Thread.UncaughtExceptionHandler {
    private val context: Context
    private val defaultHandler: Thread.UncaughtExceptionHandler?

    init {
        this.context = context.applicationContext
        defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
    }

    override fun uncaughtException(thread: Thread, exception: Throwable) {
        try {
            val sw = StringWriter()
            exception.printStackTrace(PrintWriter(sw))
            val intent = Intent(context, ErrorReportActivity::class.java)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            intent.putExtra("error_message", "Woops. An unexpected error occurred")
            intent.putExtra("stack_trace", sw.toString())

            // Start the new activity directly without using startActivity()
            if (context is Activity) {
                context.startActivity(intent)
            } else {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                context.startActivity(intent)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            System.exit(1)
        }
    }
}

class ErrorReportActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handleUncaughtException()
    }

    private fun handleUncaughtException() {
        val error: String = intent.getStringExtra("error_message")!!
        val stackTrace:String = intent.getStringExtra("stack_trace")!!

        showDialogWithStackTrace(error, stackTrace)
    }

    private fun showDialogWithStackTrace(error: String, stackTrace: String) {
        val dialog = Dialog(this)
        dialog.setCancelable(true)

        val dialogLayout = LinearLayout(this)
        dialogLayout.orientation = LinearLayout.VERTICAL
        dialogLayout.setBackgroundColor(Color.WHITE)
        dialog.setContentView(dialogLayout)
        val errorTextView = createTextView(error,10f,Color.RED)
        val stackTraceTextView = createTextView(stackTrace, 8f, Color.BLACK)

        dialogLayout.addView(errorTextView)
        dialogLayout.addView(stackTraceTextView)

        dialog.show()
    }

    private fun createTextView(text: String, textSize: Float, textColor: Int): TextView {
        val textView = TextView(this)
        textView.text = text
        textView.textSize = textSize
        textView.setTextColor(textColor)
        textView.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
            LinearLayout.LayoutParams.WRAP_CONTENT)
        return textView
    }
}

In your main activity, instantiate the handler:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
Thread.setDefaultUncaughtExceptionHandler(MyUncaughtExceptionHandler(applicationContext));
    
        setContentView(R.layout.activity_main)
    }

Way later in your application:

override fun onDraw(canvas: Canvas) {
    throw Exception("SNAFU")
    ...

and you'll see this:

SNAFU screenshot

Please spare me the lecture that this is sacrilege. Some might find it useful and you're welcome to ignore it.

Oh, and to add to my sins, ChatGPT was a great help >;-).

smirkingman
  • 6,167
  • 4
  • 34
  • 47