本文实例讲述了android实现将应用崩溃信息发送给开发者并重启应用的方法。分享给大家供大家参考,具体如下:
在开发过程中,虽然经过测试,但在发布后,在广大用户各种各样的运行环境和操作下,可能会发生一些异想不到的错误导致程序崩溃。将这些错误信息收集起来并反馈给开发者,对于开发者改进优化程序是相当重要的。好了,下面就来实现这种功能吧。
(更正时间:2012年2月9日18时42分07秒)
由于为历史帖原因,以下做法比较浪费,但抓取异常的效果是一样的。
1.对于ui线程(即android中的主线程)抛出的未捕获异常,将这些异常信息存储起来然后关闭到整个应用程序。并再次启动程序,则进入崩溃信息反馈界面让用户将出错信息以email的形式发送给开发者。
2.对于非ui线程抛出的异常,则立即唤醒崩溃信息反馈界面提示用户将出错信息发送email。
效果图如下:
过程了解了,则需要了解的几个知识点如下:
1.拦截uncaughtexception
application.oncreate()是整个android应用的入口方法。在该方法中执行如下代码即可拦截uncaughtexception:
1
2
3
|
uehandler = new uehandler( this );
// 设置异常处理实例
thread.setdefaultuncaughtexceptionhandler(uehandler);
|
2.抓取导致程序崩溃的异常信息
uehandler是thread.uncaughtexceptionhandler的实现类,在其public void uncaughtexception(thread thread, throwable ex)的实现中可以获取崩溃信息,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// fetch excpetion info
string info = null ;
bytearrayoutputstream baos = null ;
printstream printstream = null ;
try {
baos = new bytearrayoutputstream();
printstream = new printstream(baos);
ex.printstacktrace(printstream);
byte [] data = baos.tobytearray();
info = new string(data);
data = null ;
} catch (exception e) {
e.printstacktrace();
} finally {
try {
if (printstream != null ) {
printstream.close();
}
if (baos != null ) {
baos.close();
}
} catch (exception e) {
e.printstacktrace();
}
}
|
3.程序抛异常后,要关闭整个应用
悲催的程序员,唉,以下三种方式都无效了,咋办啊!!!
3.1 android.os.process.killprocess(android.os.process.mypid());
3.2 activitymanager am = (activitymanager) getsystemservice(activity_service);
am.restartpackage("lab.sodino.errorreport");
3.3 system.exit(0)
好吧,*告诉我们:自己动手丰衣足食。
softapplication中声明一个变量need2exit,其值为true标识当前的程序需要完整退出;为false时该干嘛干嘛去。该变量在应用的启动activity.oncreate()处赋值为false。
在捕获了崩溃信息后,调用softapplication.setneed2exit(true)标识程序需要退出,并finish()掉acterrorreport,这时acterrorreport退栈,抛错的actoccurerror占据手机屏幕,根据activity的生命周期其要调用onstart(),则我们在onstart()处读取need2exit的状态,若为true,则也关闭到当前的activity,则退出了整个应用了。此方法可以解决一次性退出已开启了多个activity的application。详细代码请阅读下面的示例源码。
好了,代码如下:
lab.sodino.errorreport.softapplication.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package lab.sodino.errorreport;
import java.io.file;
import android.app.application;
/**
* @author sodino e-mail:sodinoopen@hotmail.com
* @version time:2011-6-9 下午11:49:56
*/
public class softapplication extends application {
/** "/data/data/<app_package>/files/error.log" */
public static final string path_error_log = file.separator + "data" + file.separator + "data"
+ file.separator + "lab.sodino.errorreport" + file.separator + "files" + file.separator
+ "error.log" ;
/** 标识是否需要退出。为true时表示当前的activity要执行finish()。 */
private boolean need2exit;
/** 异常处理类。 */
private uehandler uehandler;
public void oncreate() {
need2exit = false ;
uehandler = new uehandler( this );
// 设置异常处理实例
thread.setdefaultuncaughtexceptionhandler(uehandler);
}
public void setneed2exit( boolean bool) {
need2exit = bool;
}
public boolean need2exit() {
return need2exit;
}
}
|
lab.sodino.errorreport.actoccurerror.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
package lab.sodino.errorreport;
import java.io.file;
import java.io.fileinputstream;
import android.app.activity;
import android.content.intent;
import android.os.bundle;
import android.util.log;
import android.view.view;
import android.widget.button;
public class actoccurerror extends activity {
private softapplication softapplication;
/** called when the activity is first created. */
@override
public void oncreate(bundle savedinstancestate) {
super .oncreate(savedinstancestate);
setcontentview(r.layout.main);
softapplication = (softapplication) getapplication();
// 一开始进入程序恢复为"need2exit=false"。
softapplication.setneed2exit( false );
log.d( "android_lab" , "actoccurerror.oncreate()" );
button btnmain = (button) findviewbyid(r.id.btnthrowmain);
btnmain.setonclicklistener( new button.onclicklistener() {
public void onclick(view v) {
log.d( "android_lab" , "thread.main.run()" );
int i = 0 ;
i = 100 / i;
}
});
button btnchild = (button) findviewbyid(r.id.btnthrowchild);
btnchild.setonclicklistener( new button.onclicklistener() {
public void onclick(view v) {
new thread() {
public void run() {
log.d( "android_lab" , "thread.child.run()" );
int i = 0 ;
i = 100 / i;
}
}.start();
}
});
// 处理记录于error.log中的异常
string errorcontent = geterrorlog();
if (errorcontent != null ) {
intent intent = new intent( this , acterrorreport. class );
intent.setflags(intent.flag_activity_new_task);
intent.putextra( "error" , errorcontent);
intent.putextra( "by" , "error.log" );
startactivity(intent);
}
}
public void onstart() {
super .onstart();
if (softapplication.need2exit()) {
log.d( "android_lab" , "actoccurerror.finish()" );
actoccurerror. this .finish();
} else {
// do normal things
}
}
/**
* 读取是否有未处理的报错信息。<br/>
* 每次读取后都会将error.log清空。<br/>
*
* @return 返回未处理的报错信息或null。
*/
private string geterrorlog() {
file fileerrorlog = new file(softapplication.path_error_log);
string content = null ;
fileinputstream fis = null ;
try {
if (fileerrorlog.exists()) {
byte [] data = new byte [( int ) fileerrorlog.length()];
fis = new fileinputstream(fileerrorlog);
fis.read(data);
content = new string(data);
data = null ;
}
} catch (exception e) {
e.printstacktrace();
} finally {
try {
if (fis != null ) {
fis.close();
}
if (fileerrorlog.exists()) {
fileerrorlog.delete();
}
} catch (exception e) {
e.printstacktrace();
}
}
return content;
}
}
|
lab.sodino.errorreport.acterrorreport.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
package lab.sodino.errorreport;
import android.app.activity;
import android.content.intent;
import android.os.bundle;
import android.util.log;
import android.view.view;
import android.widget.button;
import android.widget.edittext;
import android.widget.textview;
/**
* @author sodino e-mail:sodinoopen@hotmail.com
* @version time:2011-6-12 下午01:34:17
*/
public class acterrorreport extends activity {
private softapplication softapplication;
private string info;
/** 标识来处。 */
private string by;
private button btnreport;
private button btncancel;
private btnlistener btnlistener;
public void oncreate(bundle savedinstancestate) {
super .oncreate(savedinstancestate);
setcontentview(r.layout.report);
softapplication = (softapplication) getapplication();
by = getintent().getstringextra( "by" );
info = getintent().getstringextra( "error" );
textview txthint = (textview) findviewbyid(r.id.txterrorhint);
txthint.settext(geterrorhint(by));
edittext editerror = (edittext) findviewbyid(r.id.editerrorcontent);
editerror.settext(info);
btnlistener = new btnlistener();
btnreport = (button) findviewbyid(r.id.btnreport);
btncancel = (button) findviewbyid(r.id.btncancel);
btnreport.setonclicklistener(btnlistener);
btncancel.setonclicklistener(btnlistener);
}
private string geterrorhint(string by) {
string hint = "" ;
string append = "" ;
if ( "uehandler" .equals(by)) {
append = " when the app running" ;
} else if ( "error.log" .equals(by)) {
append = " when last time the app running" ;
}
hint = string.format(getresources().getstring(r.string.errorhint), append, 1 );
return hint;
}
public void onstart() {
super .onstart();
if (softapplication.need2exit()) {
// 上一个退栈的activity有执行“退出”的操作。
log.d( "android_lab" , "acterrorreport.finish()" );
acterrorreport. this .finish();
} else {
// go ahead normally
}
}
class btnlistener implements button.onclicklistener {
@override
public void onclick(view v) {
if (v == btnreport) {
// 需要 android.permission.send权限
intent mailintent = new intent(intent.action_send);
mailintent.settype( "plain/text" );
string[] arrreceiver = { "sodinoopen@hotmail.com" };
string mailsubject = "app error info[" + getpackagename() + "]" ;
string mailbody = info;
mailintent.putextra(intent.extra_email, arrreceiver);
mailintent.putextra(intent.extra_subject, mailsubject);
mailintent.putextra(intent.extra_text, mailbody);
startactivity(intent.createchooser(mailintent, "mail sending..." ));
acterrorreport. this .finish();
} else if (v == btncancel) {
acterrorreport. this .finish();
}
}
}
public void finish() {
super .finish();
if ( "error.log" .equals(by)) {
// do nothing
} else if ( "uehandler" .equals(by)) {
// 1.
// android.os.process.killprocess(android.os.process.mypid());
// 2.
// activitymanager am = (activitymanager)
// getsystemservice(activity_service);
// am.restartpackage("lab.sodino.errorreport");
// 3.
// system.exit(0);
// 1.2.3.都失效了,google你让悲催的程序员情何以堪啊。
softapplication.setneed2exit( true );
// ////////////////////////////////////////////////////
// // 另一个替换方案是直接返回“home”
// intent i = new intent(intent.action_main);
// // 如果是服务里调用,必须加入newtask标识
// i.setflags(intent.flag_activity_new_task);
// i.addcategory(intent.category_home);
// startactivity(i);
// ////////////////////////////////////////////////////
}
}
}
|
lab.sodino.errorreport.uehandler.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
package lab.sodino.uncaughtexception;
import java.io.bytearrayoutputstream;
import java.io.file;
import java.io.fileoutputstream;
import java.io.printstream;
import android.content.intent;
import android.util.log;
/**
* @author sodino e-mail:sodinoopen@hotmail.com
* @version time:2011-6-9 下午11:50:43
*/
public class uehandler implements thread.uncaughtexceptionhandler {
private softapplication softapp;
private file fileerrorlog;
public uehandler(softapplication app) {
softapp = app;
fileerrorlog = new file(softapplication.path_error_log);
}
@override
public void uncaughtexception(thread thread, throwable ex) {
// fetch excpetion info
string info = null ;
bytearrayoutputstream baos = null ;
printstream printstream = null ;
try {
baos = new bytearrayoutputstream();
printstream = new printstream(baos);
ex.printstacktrace(printstream);
byte [] data = baos.tobytearray();
info = new string(data);
data = null ;
} catch (exception e) {
e.printstacktrace();
} finally {
try {
if (printstream != null ) {
printstream.close();
}
if (baos != null ) {
baos.close();
}
} catch (exception e) {
e.printstacktrace();
}
}
// print
long threadid = thread.getid();
log.d( "android_lab" , "thread.getname()=" + thread.getname() + " id=" + threadid + " state=" + thread.getstate());
log.d( "android_lab" , "error[" + info + "]" );
if (threadid != 1 ) {
// 此处示例跳转到汇报异常界面。
intent intent = new intent(softapp, acterrorreport. class );
intent.setflags(intent.flag_activity_new_task);
intent.putextra( "error" , info);
intent.putextra( "by" , "uehandler" );
softapp.startactivity(intent);
} else {
// 此处示例发生异常后,重新启动应用
intent intent = new intent(softapp, actoccurerror. class );
// 如果<span style="background-color: rgb(255, 255, 255); ">没有new_task标识且</span>是ui线程抛的异常则界面卡死直到anr
intent.setflags(intent.flag_activity_new_task);
softapp.startactivity(intent);
// write 2 /data/data/<app_package>/files/error.log
write2errorlog(fileerrorlog, info);
// kill app progress
android.os.process.killprocess(android.os.process.mypid());
}
}
private void write2errorlog(file file, string content) {
fileoutputstream fos = null ;
try {
if (file.exists()) {
// 清空之前的记录
file.delete();
} else {
file.getparentfile().mkdirs();
}
file.createnewfile();
fos = new fileoutputstream(file);
fos.write(content.getbytes());
} catch (exception e) {
e.printstacktrace();
} finally {
try {
if (fos != null ) {
fos.close();
}
} catch (exception e) {
e.printstacktrace();
}
}
}
}
|
/res/layout/main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?xml version= "1.0" encoding= "utf-8" ?>
<linearlayout xmlns:android= "http://schemas.android.com/apk/res/android"
android:orientation= "vertical"
android:layout_width= "fill_parent"
android:layout_height= "fill_parent"
>
<textview
android:layout_width= "fill_parent"
android:layout_height= "wrap_content"
android:text= "@string/hello"
/>
<button android:layout_width= "fill_parent"
android:layout_height= "wrap_content"
android:text= "throws exception by main thread"
android:id= "@+id/btnthrowmain"
></button>
<button android:layout_width= "fill_parent"
android:layout_height= "wrap_content"
android:text= "throws exception by child thread"
android:id= "@+id/btnthrowchild"
></button>
</linearlayout>
|
/res/layout/report.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?xml version= "1.0" encoding= "utf-8" ?>
<linearlayout xmlns:android= "http://schemas.android.com/apk/res/android"
android:orientation= "vertical" android:layout_width= "fill_parent"
android:layout_height= "fill_parent" >
<textview android:layout_width= "fill_parent"
android:layout_height= "wrap_content"
android:text= "@string/errorhint"
android:id= "@+id/txterrorhint" />
<edittext android:layout_width= "fill_parent"
android:layout_height= "wrap_content" android:id= "@+id/editerrorcontent"
android:editable= "false" android:layout_weight= "1" ></edittext>
<linearlayout android:layout_width= "fill_parent"
android:layout_height= "wrap_content" android:background= "#96cdcd"
android:gravity= "center" android:orientation= "horizontal" >
<button android:layout_width= "fill_parent"
android:layout_height= "wrap_content" android:text= "report"
android:id= "@+id/btnreport" android:layout_weight= "1" ></button>
<button android:layout_width= "fill_parent"
android:layout_height= "wrap_content" android:text= "cancel"
android:id= "@+id/btncancel" android:layout_weight= "1" ></button>
</linearlayout>
</linearlayout>
|
用到的string.xml资源为:
重要的一点是要在androidmanifest.xml中对<application>节点设置android:name=".softapplication"
希望本文所述对大家android程序设计有所帮助。