原创内容,转载请注明原文网址:http://homeqin.cn/a/wenzhangboke/jishutiandi/Android/2019/0622/552.html
朋友们都晓得,当今安置Android体系的手机版本和建筑千差万别,在模拟器上运转良好的程序安置到某款手机上说未必就出现溃散的现象,开发者个人不行能购买所有建筑逐一调试,所以在程序公布出去以后,要是出现了溃散现象,开发者应该实时获取在该建筑上招致溃散的信息,这对于下一个版本的bug修复赞助极大,所以本日就来先容一下若何在程序溃散的环境下网络关联的建筑参数信息和具体的非常信息,并发送这些信息到服无器供开发者阐发和调试程序。
我们先确立一个crash名目,名目布局如图:
在MainActivity.java代码中,代码是如许写的:
[java] view plaincopy
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"));
}
}
我们在这里存心制作了一个潜伏的运转期非常,当我们运转程序时就会出现以下界面:
遇到软件没有捕捉的非常以后,体系会弹出这个默认的强迫关闭对话框。
我们固然不有望用户看到这种现象,的确是对用户心灵上的袭击,并且对我们的bug的修复也是毫无赞助的。我们必要的是软件有一个全局的非常捕捉器,当出现一个我们没有发现的非常时,捕捉这个非常,并且将非常信息记录下来,上传到服无器公示发这阐发出现非常的具体缘故。
接下来我们就来实现这一机制,但是开始我们照旧来打听以下两个类:android.app.Application和java.lang.Thread.UncaughtExceptionHandler。
Application:用来经管运用程序的全局状况。在运用程序启动时Application会开始建立,而后才会凭据环境(Intent)来启动响应的Activity和Service。本示例中将在自定义增强版的Application中注册未捕捉非常处分器。
Thread.UncaughtExceptionHandler:线程未捕捉非常处分器,用来处分未捕捉非常。要是程序出现了未捕捉非常,默认会弹出体系中强迫关闭对话框。我们必要实现此接口,并注册为程序中默认未捕捉非常处分。如许当未捕捉非常产生时,就可以做少许性格化的非常处分操纵。
朋友们适才在名目的布局图中看到的CrashHandler.java实现了Thread.UncaughtExceptionHandler,使我们用来处分未捕捉非常的主要成员,代码如下:
[java] view plaincopy
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.Context;
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.Looper;
import android.util.Log;
import android.widget.Toast;
/**
* UncaughtException处分类,当程序产生Uncaught非常的时候,有该类来回收程序,并记录发送错误汇报.
*
* @author user
*
*/
public class CrashHandler implements UncaughtExceptionHandler {
public static final String TAG = "CrashHandler";
//体系默认的UncaughtException处分类
private Thread.UncaughtExceptionHandler mDefaultHandler;
//CrashHandler实例
private static CrashHandler INSTANCE = new CrashHandler();
//程序的Context工具
private Context mContext;
//用来存储建筑信息和非常信息
private Map<String, String> infos = new HashMap<String, String>();
//用于花样化日期,作为日记文件名的一片面
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-妹妹-ss");
/** 包管惟有一个CrashHandler实例 */
private CrashHandler() {
}
/** 获取CrashHandler实例 ,单例模式 */
public static CrashHandler getInstance() {
return INSTANCE;
}
/**
* 初始化
*
* @param context
*/
public void init(Context context) {
mContext = context;
//获取体系默认的UncaughtException处分器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//配置该CrashHandler为程序的默认处分器
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 当UncaughtException产生时会转入该函数来处分
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
//要是用户没有处分则让体系默认的非常处分器来处分
mDefaultHandler.uncaughtException(thread, ex);
} else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Log.e(TAG, "error : ", e);
}
//退出程序
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
}
/**
* 自定义错误处分,网络错误信息 发送错误汇报等操纵均在此实现.
*
* @param ex
* @return true:要是处分了该非常信息;否则回笼false.
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
//应用Toast来显示非常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "很抱歉,程序出现非常,即将退出.", Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
//网络建筑参数信息
collectDeviceInfo(mContext);
//留存日记文件
saveCrashInfo2File(ex);
return true;
}
/**
* 网络建筑参数信息
* @param ctx
*/
public void collectDeviceInfo(Context ctx) {
try {
PackageManager 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 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);
}
}
}
/**
* 留存错误信息到文件中
*
* @param ex
* @return 回笼文件称号,便于将文件传送到服无器
*/
private String saveCrashInfo2File(Throwable ex) {
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : infos.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key + "=" + value + "\n");
}
Writer writer = new StringWriter();
PrintWriter 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;
}
}
在网络非常信息时,朋友们也能够应用Properties,由于Properties有一个很方便的要领properties.store(OutputStream out, String co妹妹ents),用来将Properties实例中的键值对外输到输出流中,但是在应用的过程中发现生成的文件中非常信息打印在统一行,看起来极为费事,所以换成Map来存放这些信息,而后生成文件时稍加了些操纵。
实现这个CrashHandler后,我们必要在一个Application环境中让其运转,为此,我们秉承android.app.Application,增加本人的代码,CrashApplication.java代码如下:
[java] view plaincopy
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());
}
}
末了,为了让我们的CrashApplication取代android.app.Application的职位,在我们的代码中见效,我们必要点窜AndroidManifest.xml:
[html] view plaincopy
<application android:name=".CrashApplication" ...>
</application>
由于我们上头的CrashHandler中,遇到非常后要留存建筑参数和具体非常信息到SDCARD,所以我们必要在AndroidManifest.xml中加入读写SDCARD权限:
[html] view plaincopy
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
搞定了上边的步骤以后,我们来运转一下这个名目:
看以看到,并不会有强迫关闭的对话框出现了,取而代之的是我们对照有好的提醒信息。
而后看一下SDCARD生成的文件:
用文本编纂器翻开日记文件,看一段日记信息:
[java] view plaincopy
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
这些信息对于开发者来说赞助极大,所以我们必要将此日记文件上传到服无器,相关文件上传的技术,请参照Android中应用HTTP服无关联先容。
但是在应用HTTP服无以前,必要确定网络通顺,我们可以应用下面的体例校验网络是否可用:
[java] view plaincopy
/**
* 网络是否可用
*
* @param context
* @return
*/
public static boolean isNetworkAvailable(Context 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;
}
顶
上篇:上一篇:Android WebView获取cookie
下篇:下一篇:Android最佳底部导航栏,开源框架