我们写程序的时候都希望能写出一个没有任何Bug的程序,期望在任何情况下都不会发生程序崩溃。不过理想是丰满的,现实是骨感的。没有一个程序员能保证自己写的程序绝对不会出现异常崩溃。特别是针对用户数达到几十万几百万的程序,当你用户数达到一定数量级后,就算你的程序出现个别异常崩溃情况也不用惊讶。此时及时收集用户的日志成了解决问题的关键。看了网上大部分是采用日志收集的第三方jar包来完成的,还有一种是自定义一个自己的CrashHandler实现UncaughtExceptionHandler接口来捕获闪退信息然后上传到自己的服务器。这样的实现方式总觉得如果用户误删了闪退的日志文件那么就会导致无法及时上报闪退日志,也就无从分析隐藏的Bug了,于是采用另一种实现思路,即:在程序发生异常时提醒用户发生了什么样的异常,同时把本次捕获的Exception的字段写入到自己定义的log文件中,然后上报异常字段到自己的异常服务器上,从手机端或者后台都可以看到发生的异常堆栈。日志记录系统不借助与任何第三方jar包。说干就干,
项目目录如下:
关键代码如下:
1、定义IExceptionHandler接口
/** *异常处理类 * */ public interface IExceptionHandler { /** * 异常处理类. * @param t * @param db */ public String handlerException(Throwable t, SQLiteDatabase db); /** * 异常处理类. * @param t */ public String handlerException(Throwable t); /** * 异常处理类. * @param t */ public void sendException(Throwable t); /** * 给用户提示 * @param errorMsg */ public void showTipMessgae(String errorMsg); }
2、定义ExceptionHandler实现自IExceptionHandler
/**
* 异常处理类
*/
public class ExceptionHandler implements IExceptionHandler{
private Context context;
private ExceptionSender exceptionSender;
public ExceptionHandler(Context context)
{
this.context = context;
exceptionSender = new ExceptionSender(context);
}
@Override
public String handlerException(Throwable t) {
return handlerException(t, null);
}
@Override
public String handlerException(Throwable exception, SQLiteDatabase db) {
String message = "";
if (exception instanceof ConnectException)
{
message = "网络连接有问题!";
}
else if (exception instanceof SocketTimeoutException)
{
message = "连接超时!";
}
else if (exception instanceof NullPointerException)
{
message = "程序出错,空指针错误!";
}
else if (exception instanceof IllegalArgumentException)
{
message = "程序出错,错误的请求参数!";
}
else if (exception instanceof ArithmeticException)
{
message = "程序出错,数值计算出错!";
}
else if (exception instanceof NetworkOnMainThreadException)
{
message = "程序出错,网络请求放在主线程中运行!";
}
else if (exception instanceof IllegalStateException)
{
message = "程序出错,网络请求要放在主线程中运行!";
}
else if (exception instanceof IndexOutOfBoundsException)
{
message = "数组越界异常!";
}
else if (exception instanceof IOException)
{
message = "程序出错,IO错误!";
}
else
{
message = exception.getMessage();
}
showTipMessgae(message);
exceptionSender.send(exception);
return message;
}
@Override
public void sendException(Throwable t) {
exceptionSender.send(t);
}
@Override
public void showTipMessgae(final String msg)
{
if (context != null)
{
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
}
3、在AndroidManifest.xml里面配置自己的Application:IApplication,并在里面初始化app日志文件目录
public class IApplication extends Application{
/**
* 应用根目录
*/
private File rootFile;
/**
* log目录
*/
private File logFile;
private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
private static IApplication INSTANCE = null;
public static IApplication getInstance()
{
return INSTANCE;
}
private FileLog fileLog;
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
initDataRoot();
File log = new File(logFile, df.format(new Date()) + ".log");
PrintWriter pw = null;
try {
pw = new PrintWriter(new FileOutputStream(log, true), true);
} catch (FileNotFoundException e1) {
}
fileLog = new FileLog(pw);
fileLog.setLevel(FileLog.LEVEL_INFO);//设置日志系统的等级
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
}
/**
* 初始化log目录
*/
private void initDataRoot() {
String state = android.os.Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
rootFile =new File(Environment.getExternalStorageDirectory(),"11");
if (!rootFile.exists()) {
rootFile.mkdirs();
}
}else {
rootFile = getDatabasePath("11");
}
logFile = new File(rootFile, "logs");
if (!logFile.exists()) {
logFile.mkdirs();
}
}
public FileLog getFileLog() {
return fileLog;
}
}
4、采用HTTP方式将实时catch的异常发送到异常服务器,并将异常的堆栈信息写入到SDcard中
/** * 异常发送类 */ public class ExceptionSender { private Context context; private String url="你的收集异常信息的服务器的地址"; //版本名称 private String app_name; //版本号 private String app_version; //设备名称 private String device_name; //操作系统 private String os_name; //操作系统版本号 private String os_version; private ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); private FileLog fileLog; public ExceptionSender(Context context) { init(context); this.context = context; this.fileLog = IApplication.getInstance().getFileLog(); } private void init(Context context) { PackageInfo packInfo; try { packInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); if (packInfo != null) { app_name = packInfo.packageName; app_version = packInfo.versionName; } device_name = android.os.Build.MODEL; os_name = "Android"; os_version = android.os.Build.VERSION.RELEASE; } catch (PackageManager.NameNotFoundException e) { } } /** * 发送错误信息到错误收集中心. * * @param ex */ public void send(final Throwable ex) { fileLog.e(ex.getClass().getSimpleName(), ex); cachedThreadPool.execute(new Runnable() { @Override public void run() { try { DefaultHttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(url); ByteArrayOutputStream out = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(out); ex.printStackTrace(ps); ps.flush(); String err_msg = new String(out.toByteArray(), "UTF-8"); Map params = new HashMap(); List parameters = new ArrayList(); addParameter(params, parameters, "class_name", ex.getClass().getSimpleName()); addParameter(params, parameters, "app_name", app_name); addParameter(params, parameters, "app_version", app_version); addParameter(params, parameters, "device_name", device_name); addParameter(params, parameters, "os_name", os_name); addParameter(params, parameters, "os_version", os_version); parameters.add(new BasicNameValuePair("err_msg", err_msg)); parameters.add(new BasicNameValuePair("stack_msg", "")); addParameter(params, parameters, "error_time", Long.toString(System.currentTimeMillis())); httpPost.setEntity(new UrlEncodedFormEntity(parameters, HTTP.UTF_8)); httpClient.execute(httpPost); } catch (Throwable e) { } } }); } private void addParameter(Map params, List parameters, String key, String value) { value = value == null ? "" : value; params.put(key, value); parameters.add(new BasicNameValuePair(key, value)); } }
4、使用方式:在程序里面采用try catch捕获可能会出现的异常的代码块(界面给出提示,程序不闪退),另一种是没有进行try catch应用程序直接闪退
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private IExceptionHandler exceptionHandler; private Button btn1, btn2; private Student student; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); exceptionHandler = new ExceptionHandler(getApplicationContext()); btn1 = (Button)findViewById(R.id.button1); btn1.setOnClickListener(this); btn2 = (Button)findViewById(R.id.button2); btn2.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.button1: try{//捕获了异常,异常的堆栈信息写入了SDcard中同时也上报到了后台 int m = 1/0; System.out.print(m); }catch (Exception e){ exceptionHandler..handlerException(e); } break; case R.id.button2://没有捕获异常,应用程序直接退了,异常的堆栈信息写入了SDcard中同时也上报到了后台 System.out.print(student.getId()); break; } } }
这样友好的提示也给了用户,异常信息也及时上报了。但是该方式也有自己的缺点:大量的try catch会导致代码的效率不高。
如果觉得《Android记录日志方式 关于Android中处理崩溃异常和记录日志的另一种实现思路》对你有帮助,请点赞、收藏,并留下你的观点哦!