As we all know, the versions and devices used to install the Android system vary widely. A program running well on the simulator may crash when it is installed on a certain mobile phone, individual developers cannot buy all devices for debugging one by one. Therefore, after the program is released, if a crash occurs, the developer should promptly obtain the information that causes the crash on the device, this is of great help to fix bugs in the next version. So today we will introduce how to collect related device parameter information and specific exception information when the program crashes, and send the information to the server for developers to analyze and debug the program.
1. Restart Activity
Normally, if the Android app encounters an unhandled exception, a dialog box similar to the following will appear, and then force the app to exit:
If you want to change this default behavior, such as displaying a custom dialog box when an unhandled exception occurs or restarting the application, you can use the following steps to redefine the android global exception handling event.
1. Implement the thread. uncaughtexceptionhandler Interface
Generally, you can derive the application class and implement the thread. uncaughtexceptionhandler method:
public class GNavigatorApplication extends Application implements Thread.UncaughtExceptionHandler { ... }
2. Define the thread. uncaughtexceptionhandler Method
For example, restarting an activity
public void uncaughtException(Thread thread, Throwable ex) { Intent intent = new Intent(this, GNavigatorActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); }
3. Reset the default exception processing function. For example, you can reset the Exception Processing Method in the oncreate method.
@Override public void onCreate() { ... Thread.setDefaultUncaughtExceptionHandler(this); }
In this way, the application will be automatically restarted when an unhandled exception occurs in the application, without the force close dialog box.
2. Obtain the exception information and upload it to the server.
We first set up a crash project and project structure
In mainactivity. Java code, the code is written as follows:
package com.scott.crash; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity { private String s; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); System.out.println(s.equals("any string")); } }
We intentionally created a potential runtime exception here. When we run the program, the following interface will appear:
When an exception is not captured by the software, the default force close dialog box is displayed.
Of course, we do not want users to see this phenomenon. It is a blow to the user's mind, and it is useless to fix our bugs. What we need is that the software has a global exception catcher. When an exception is not found, capture the exception and record the exception information, upload the file to the server for public publishing.
Next we will implement this mechanism, but first we will understand the following two classes: Android. App. Application and Java. Lang. thread. uncaughtexceptionhandler.
Application: used to manage the global status of an application. When the application starts, the application will first create and then start the corresponding activity and service according to the situation (intent. In this example, no exception processor is registered in the Custom enhanced application.
Thread. uncaughtexceptionhandler: The exception processor is not captured by the thread. It is used to handle uncaptured exceptions. If an uncaptured exception occurs in the program, the force close dialog box is displayed by default. We need to implement this interface and register it as a program without capturing exception handling by default. In this way, some personalized exception handling operations can be performed when no exceptions are captured.
The crashhandler. Java we saw in the project structure just now implements thread. uncaughtexceptionhandler, so that we can use it to handle major members who do not capture exceptions. The Code is as follows:
Package COM. scott. crash; import Java. io. file; import Java. io. fileoutputstream; import Java. io. printwriter; import Java. io. stringwriter; import Java. io. writer; import Java. lang. thread. uncaughtexceptionhandler; import Java. lang. reflect. field; import Java. text. dateformat; import Java. text. simpledateformat; import Java. util. date; import Java. util. hashmap; import Java. util. map; import android. content. CO Ntext; import android. content. PM. packageinfo; import android. content. PM. packagemanager; import android. content. PM. packagemanager. namenotfoundexception; import android. OS. build; import android. OS. environment; import android. OS. logoff; import android. util. log; import android. widget. toast;/*** uncaughtexception processing class. When an uncaught exception occurs in a program, this class is used to take over the program and send an error report. ** @ author user **/public class crashhandle R implements uncaughtexceptionhandler {public static final string tag = "crashhandler"; // The default uncaughtexception class private thread. uncaughtexceptionhandler mdefaulthandler; // crashhandler instance Private Static crashhandler instance = new crashhandler (); // The context object private context mcontext of the program; // Private map used to store device information and exception information <string, string> Infos = new hashmap <string, string> (); // used to format the date, as part of the log file name priv Ate dateformat formatter = new simpledateformat ("yyyy-mm-dd-hh-mm-SS");/** ensure that there is only one crashhandler instance */private crashhandler () {}/** get the crashhandler instance, Singleton mode */public static crashhandler getinstance () {return instance ;} /*** initialize ** @ Param context */Public void Init (context) {mcontext = context; // obtain the default uncaughtexception processor mdefaulthandler = thread. getdefaultuncaughtexceptionhandler (); // Set the crashhandler as the default processor thread of the program. setdefaultuncaughtexceptionhandler (this);}/*** when uncaughtexception occurs, it will be transferred to this function for processing */@ override public void uncaughtexception (thread, throwable ex) {If (! Handleexception (Ex) & mdefaulthandler! = NULL) {// if the user does not process the mdefaulthandler, the system's default exception processor is used to process the mdefaulthandler. uncaughtexception (thread, ex);} else {try {thread. sleep (3000);} catch (interruptedexception e) {log. E (TAG, "error:", e) ;}// exit the android program. OS. process. killprocess (Android. OS. process. mypid (); system. exit (1) ;}/ *** custom error handling, collecting error information, and sending error reports are all completed here. ** @ Param ex * @ return true: If the exception information is processed, false is returned. */private Boolean handleexception (throwable Ex) {If (EX = NULL) {return false;} // use toast to display exception information new thread () {@ override public void run () {Looper. prepare (); toast. maketext (mcontext, "Sorry, the program encountered an exception and is about to exit. ", toast. length_long ). show (); logoff. loop ();}}. start (); // collect device parameter information collectdeviceinfo (mcontext); // Save the log file savecrashinfo2file (Ex); return true ;} /*** collect device parameter information * @ Param CTX */Public void collectdeviceinfo (context CTX) {try {packagema Nager PM = CTX. getpackagemanager (); packageinfo Pi = PM. getpackageinfo (CTX. getpackagename (), packagemanager. get_activities); If (Pi! = NULL) {string versionname = pi. versionname = NULL? "Null": pi. versionname; string versioncode = pi. versioncode + ""; Infos. put ("versionname", versionname); Infos. put ("versioncode", versioncode) ;}} catch (namenotfoundexception e) {log. E (TAG, "an error occured when collect package Info", e);} field [] fields = build. class. getdeclaredfields (); For (field: fields) {try {field. setaccessible (true); Infos. put (field. getname (), field. get (null). Tostring (); log. D (TAG, field. getname () + ":" + field. get (null);} catch (exception e) {log. E (TAG, "an error occured when collect crash info", e );}}} /*** Save the error message to the file ** @ Param ex * @ return returns the file name, which is convenient for transferring the file to the server */private string savecrashinfo2file (throwable ex) {stringbuffer sb = new stringbuffer (); For (map. entry <string, string> entry: Infos. entryset () {string key = entry. getkey (); Str Ing value = entry. getvalue (); sb. append (Key + "=" + value + "\ n");} writer = new stringwriter (); printwriter = new printwriter (writer); Ex. printstacktrace (printwriter); throwable cause = ex. getcause (); While (cause! = NULL) {cause. printstacktrace (printwriter); cause = cause. getcause ();} printwriter. close (); string result = writer. tostring (); sb. append (result); try {long timestamp = system. currenttimemillis (); string time = formatter. format (new date (); string filename = "crash-" + time + "-" + timestamp + ". log "; if (environment. getexternalstoragestate (). equals (environment. media_mounted) {string Path = "/ Sdcard/crash/"; file dir = new file (PATH); If (! Dir. exists () {dir. mkdirs ();} fileoutputstream Fos = new fileoutputstream (path + filename); FOS. write (sb. tostring (). getbytes (); FOS. close () ;}return filename;} catch (exception e) {log. E (TAG, "an error occured while writing file... ", e) ;}return null ;}}
When collecting exception information, friends can also use properties, because properties has a very convenient method of properties. store (outputstream out, string comments) is used to import the key values in the properties instance to the output stream. However, during use, the exception information in the generated file is printed on the same row, it seems very difficult, so I replaced it with map to store this information, and then made some operations when generating the file.
After the crashhandler is completed, we need to run it in an application environment. Therefore, we inherit Android. App. Application and add our own code. The crashapplication. Java code is as follows:
package com.scott.crash; import android.app.Application; public class CrashApplication extends Application { @Override public void onCreate() { super.onCreate(); CrashHandler crashHandler = CrashHandler.getInstance(); crashHandler.init(getApplicationContext()); } }
Finally, in order for our crashapplication to replace Android. App. Application to take effect in our code, we need to modify androidmanifest. xml:
<application android:name=".CrashApplication" ...> </application>
Because in the crashhandler above, the device parameters and specific exception information must be saved to the sdcard after an exception occurs, we need to add the Read and Write sdcard permission to androidmanifest. xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
After completing the above steps, let's run the project:
As you can see, there will not be a forced close dialog box, instead we have a better prompt.
Next, let's take a look at the sdcard file:
Open the log file in a text editor to view a piece of log information:
CPU_ABI=armeabi CPU_ABI2=unknown ID=FRF91 MANUFACTURER=unknown BRAND=generic TYPE=eng ...... Caused by: java.lang.NullPointerException at com.scott.crash.MainActivity.onCreate(MainActivity.java:13) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627) ... 11 more
This information is of great help to developers. Therefore, we need to upload this log file to the server. For more information about File Upload technology, see Introduction to using HTTP service in Android.
Before using the HTTP service, we need to determine whether the network is smooth. We can use the following method to determine whether the Network is available:
/*** Network availability ** @ Param context * @ return */public static Boolean isnetworkavailable (context) {connectivitymanager Mgr = (connectivitymanager) context. getsystemservice (context. connectivity_service); networkinfo [] info = Mgr. getallnetworkinfo (); If (info! = NULL) {for (INT I = 0; I <info. length; I ++) {If (info [I]. getstate () = networkinfo. state. connected) {return true ;}} return false ;}
References:
Android app sets global exception to handle events
Handle crash exceptions in Android