Android Crash governance

Crash knows:

Crash refers to an unexpected exit caused by an unhandled exception or signal, which causes the Android application to crash. When the app crashes, Android will kill the app's progress and display a dialog box to inform the user that his app has stopped due to an unknown accident. Of course, most of the customized systems of domestic manufacturers now cancel the dialog box for notifying application termination. They think the dialog box provided by the system is meaningless. If developers want to pop up a dialog box to inform them when their application crashes, they can also customize it through the API provided by Android system.

The general Android Crash is mainly caused by the developers' irregular code writing. For example, some common exceptions are not caught and handled. In android application development, NullPointerException (null pointer exception) is the most common. A small initialization, irregular data obtained by the network, parsing errors, etc. may lead to null pointer exception. Secondly, index out of bounds exception (array corner out of bounds exception). Because ListView is often used in android applications, there are many crashes caused by such exceptions.

There are other situations that lead to Crash, such as Out of Memory (commonly known as OOM). Memory overflow is a big topic. We will not introduce it here. As long as we know that this Crash is caused by developers' irregular code writing, the memory used exceeds the maximum threshold of the memory applied by the application. In short, the memory is not enough, and the mobile phone is throwing away the pot.

As we all know, there are a variety of mobile phone manufacturers in China, resulting in a variety of android models, which are seriously fragmented. Sometimes the same application will appear Crash on some specific models. This kind of problem is also the most difficult one. It's hard to reproduce without the same model. If the underlying code is different, you have to read the underlying code. If you find a problem, you can't modify it. You have to find a way to bypass it.

In short, there are many reasons that can lead to Crash, and an excellent application should try to reduce Crash, or even zero Crash (this is a good wish ~ just a wish), so how to reduce the Crash rate of application is particularly important.

 

Crash detection:

Generally, problems encountered in the development process can be viewed through logcat, such as:

From logcat, you can easily see that the runtime exception is caused when the ActivityThread is running, and the reason for the exception is that ArrayIndexOutOfBoundsException option, that is, the array corner is out of bounds, which occurs in line 60 of the code CrashActivity class. So we can easily detect Crash.

In most cases, developers can not guarantee that there is no crash after the application is officially launched, so how to detect it at this time? If your app is published on the Google App store, congratulations. When your app crashes too many times, Android Vitals will remind you through the Play management center that there is an indicator called Crash rates in Android Vitals. It believes that it is abnormal when at least 1.09% of the working hours break down or at least 0.18% of the working hours break down twice or more every day.

Of course, if your app is not published to Google App store, don't worry. We can capture it through CrashHandler when applying Crash, then save and upload the log information, and then we can analyze it through the log. The CrashHandler principle is implemented by the uncautexception method in the thread.uncautexceptionhandler interface. When an uncaught exception occurs in an application, the method will be called back. We can make a big deal out of it.

Next, we will talk about how to implement CrashHandler through the code. First, we need to create a CrashHandler class and let it inherit the thread.uncaugh-exceptionhandler interface, and then finish the logic of saving exception information to sd card and uploading to server.

/**
 * Capture and handle when an application crashes unexpectedly
 * Created by ledding on 2020/4/14.
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    private static final String TAG = "CrashHandler";
    private static final boolean DEBUG = true;

    private static final String PATH = Environment.getExternalStorageDirectory() + "/Crash/log";

    private static CrashHandler INSTANCE = new CrashHandler();
    private Context mContext;
    private Thread.UncaughtExceptionHandler mDefaultExceptionHandler;

    private CrashHandler(){

    }

    public static CrashHandler getInstance(){
        return INSTANCE;
    }

    public void init(Context context){
        this.mContext = context;
        //Get the current default ExceptionHandler and save it in the global object
        mDefaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        //Replace the default object with the current object
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * This method is called back when an application has an uncaught exception
     * @param t
     * @param e
     */
    @Override
        public void uncaughtException(Thread t, Throwable e) {
        //Save trace information to sd card
        dumpToSDCard(t,e);
        //TODO can be uploaded to the server or other time
        e.printStackTrace();
        if (mDefaultExceptionHandler!=null){
            mDefaultExceptionHandler.uncaughtException(t,e);
        }else {
            //Actively kill the process
            Process.killProcess(Process.myPid());
        }

    }

    /**
     * dump trace Information to sd card
     * @param t
     * @param e
     */
    private void dumpToSDCard(final Thread t,final Throwable e){
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            Log.i(TAG, "no sdcard skip dump");
            return;
        }

        //Determine whether the folder path exists
        File file = new File(PATH);
        if (!file.exists()){
            file.mkdirs();
        }

        //Name the current time as the file name
        Date nowDate = new Date(System.currentTimeMillis());
        String time = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.CHINA).format(nowDate);
        File logFile = new File(PATH,time+".trace");
        //Note that the actual saved address may be different from the address obtained by Environment.getExternalStorageDirectory()
        //You can view the actual location through adb naming
        Log.i(TAG, logFile.getAbsolutePath());
        try {
            //Write mobile phone information and exception log information
            PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(logFile)));
            if (pw.checkError()) {
                pw.println(time);
                dumpPhoneInfo(pw);
                pw.println();
                e.printStackTrace();
            }
            pw.close();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }

    /**
     * Save phone information
     * @param pw
     */
    private void dumpPhoneInfo(PrintWriter pw){
        PackageManager pm = mContext.getPackageManager();
        PackageInfo pi = null;

        try {
            pi = pm.getPackageInfo(mContext.getPackageName(),PackageManager.GET_ACTIVITIES);
            if (pi != null){
                pw.print("APP Version:");
                pw.print(pi.versionName);
                pw.print('_');
                pw.print(pi.versionCode);

                //android version number
                pw.print("OS Version: ");
                pw.print(Build.VERSION.RELEASE);
                pw.print("_");
                pw.println(Build.VERSION.SDK_INT);

                //Mobile phone manufacturer
                pw.print("Vendor: ");
                pw.println(Build.MANUFACTURER);

                //Mobile phone model
                pw.print("Model: ");
                pw.println(Build.MODEL);

                //cpu architecture
                pw.print("CPU ABI: ");
                pw.println(Build.CPU_ABI);
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * Compress folders to prepare for upload. Save traffic.
     * @param src
     * @param dest
     * @throws IOException
     */
    private void zip(String src, String dest) throws IOException {
        ZipOutputStream out = null;
        File outFile = new File(dest);
        File fileOrDirectory = new File(src);
        out = new ZipOutputStream(new FileOutputStream(outFile));
        if (fileOrDirectory.isFile()) {
            zipFileOrDirectory(out, fileOrDirectory, "");
        }else {
            File[] entries = fileOrDirectory.listFiles();
            for (int i = 0; i < entries.length; i++) {
                zipFileOrDirectory(out, entries[i], "");
            }
        }
        if(null != out){
            out.close();
        }
    }

    private static void zipFileOrDirectory(ZipOutputStream out,File fileOrDirectory, String curPath) throws IOException {
        FileInputStream in = null;
        if (!fileOrDirectory.isDirectory()){
            byte[] buffer = new byte[4096];
            int bytes_read;
            in = new FileInputStream(fileOrDirectory);
            ZipEntry entry = new ZipEntry(curPath + fileOrDirectory.getName());
            out.putNextEntry(entry);
            while ((bytes_read = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytes_read);
            }
            out.closeEntry();
        }else{
            File[] entries = fileOrDirectory.listFiles();
            for (int i = 0; i < entries.length; i++) {
                zipFileOrDirectory(out, entries[i], curPath + fileOrDirectory.getName() + "/");
            }
        }
        if (null != in){
            in.close();
        }
    }
}

Next, create a MyApplication that inherits from the Application, and initialize the CrashHandler in onCreate. In this way, the life cycle of CrashHandler changes with the life cycle of Application.

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //Catch exception
        CrashHandler.getInstance().init(getApplicationContext());
    }
}

Finally, add the uncaught exception code manually, and then run it.

        int[] numbs = new int[5];
        numbs[5] = 0;

Normally, there should be a trace file in sd card of mobile phone at this time. The file path is Environment.getExternalStorageDirectory()+ "/ crash / log", it should be noted that the path obtained through Environment.getExternalStorageDirectory() is not necessarily the real path of the mobile phone, so we can search the file name directly in the mobile file manager (the file here is saved in time).

Next, you can directly open the trace file. When you connect to the adb, you can also find and export it to the computer through the adb shell command.

You can see the time, APP version, os version, mobile phone model, error log and other information written in the log. We can easily detect the problem. Of course, if the user crashes while using the app, you can't let the user provide you with the log files in his mobile phone, but you can upload the log to the server in uncaughtException method, so that you can analyze and solve the problem.

 

Crash prevention:

In terms of user experience, if your application crashes, it is a bad experience. You can detect and repair it in time. Therefore, reasonable prevention of Crash becomes the top priority.

  1. Avoid NullPointException, try to judge empty objects that may be empty, and form the habit of using @ NonNull annotation.
  2. Avoid IndexOutOfBoundsException, generally encapsulate BaseAdapter, unify data for Adapter management, and try to use thread safe container collection.
  3. If it's system level Crash caused by Android fragmentation, we can't prevent it in advance. We can only prevent some previously encountered problems in our own accumulation, based on our own experience, or through network accumulation.
  4. To avoid OutOfMemoryError, there are many reasons for memory leakage. For example, the singleton mode refers to the Context of an Activity, and the non static internal class holds the reference of the external class by default. Once the life cycle is longer than the external class life cycle, the external class is difficult to be killed, and the Bitmap processing does not recycle in time, etc. are all problems we must pay attention to. How to predict There are a lot of anti memory leaks on the Internet, and I will sort them out later.

 

Summary:

Crash is a topic that can't be avoided in Android performance optimization. We often see that an app crashes again on the hot microblog search, and the reputation of an app often plummets because of several crashes. Therefore, crash governance is of great importance. This paper is just a brief summary. There is a long way to go for crash governance.

Tags: Android Mobile network Google

Posted on Wed, 22 Apr 2020 22:13:59 -0700 by bruceleejr