当用户发生了crash,开发者却无法得知程序为何crash,即便开发人员想去解决这个crash,但是由于无法知道用户当时crash信息,所以往往无能为力。android 提供了获取crash信息的方法。话不多说,直接上代码。
package com.example.wmk.utils; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import com.lidroid.xutils.util.LogUtils; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.PrintWriter; /** * Created by 老王 on 2016/12/2. */ public class CrashHandler implements Thread.UncaughtExceptionHandler { /** * CrashHandler */ private static CrashHandler CRASHHANDLER = null; private Thread.UncaughtExceptionHandler mDefaultUncaughtExceptionHandler; private Context mContext; private static final String FILE_NAME = "crash"; private static final String FILE_NAME_TXT = ".txt"; /** * Get Instance * * @return CrashHandler */ public static CrashHandler getInstance() { if (CRASHHANDLER == null) { CRASHHANDLER = new CrashHandler(); } return CRASHHANDLER; } private CrashHandler() { } public void init(Context context) { mDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this); mContext = context.getApplicationContext(); } @Override public void uncaughtException(Thread thread, Throwable ex) { //导出异常信息到SD卡中 dumpExceptionToSDCard(ex); //这里可以上传异常信息到服务器,便于开发人员分析日志从而解决bug uploadExceptionToServer(); ex.printStackTrace(); //如果系统提供了默认的异常处理器,则交给系统去结束程序,否则就由自己结束自己 if (mDefaultUncaughtExceptionHandler != null) { mDefaultUncaughtExceptionHandler.uncaughtException(thread, ex); } else { ActivityManager.getInstance().appExceptionExit(); } } private void dumpExceptionToSDCard(Throwable ex) { String time = TimeUtils.formatDate("yyyy-MM-dd HH:mm:ss"); //创建crash文件 File file = ProjectUtils.createFile(ProjectUtils.WMK_LOG + FILE_NAME + time + FILE_NAME_TXT); try { PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file))); pw.println(time); dumpPhoneInfo(pw); pw.println(); ex.printStackTrace(pw); pw.close(); } catch (Exception e) { LogUtils.e("dump crash info failed"); } } private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException { PackageManager pm = mContext.getPackageManager(); PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES); pw.print("App Version: "); pw.print(pi.versionName); pw.print("_"); pw.println(pi.versionCode); //Android版本号 pw.print("OS Version: "); pw.print(Build.VERSION.RELEASE); pw.print("_"); pw.println(Build.VERSION.SDK_INT); //手机制造商 pw.print("Vendor: "); pw.println(Build.MANUFACTURER); //手机型号 pw.print("Model: "); pw.println(Build.MODEL); //CPU架构 pw.print("CPU ABI:"); pw.println(Build.CPU_ABI); } private void uploadExceptionToServer() { //TODO Upload Exception Message To your Web Server } }
首先,封装了一个日期工具类,用于显示日期,代码如下:
package com.example.wmk.utils; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; /** * Created by 老王 on 2016/12/2. */ public class TimeUtils { /** * 格式化当前日期 * * @param rule 格式规则 * @return 格式化后的日期 */ public static String formatDate(String rule) { return new SimpleDateFormat(rule, Locale.getDefault()).format(new Date()); } }
其次,用到了项目工具类中的创建文件的方法。
package com.example.wmk.utils; import android.os.Environment; import java.io.File; /** * Created by 老王 on 2016/12/1. */ public class ProjectUtils { /** * 根目录 */ public static File ROOT_DIRECTORY = Environment.getExternalStorageDirectory(); /** * 项目路径 */ public static final String PROJECT_PATH = ROOT_DIRECTORY.getAbsolutePath() + "/WMK/"; /** * 数据库 */ public static final String WMK_DB = PROJECT_PATH + "DB/"; /** * 图片 */ public static final String WMK_IMG = PROJECT_PATH + "IMG/"; /** * Log */ public static final String WMK_LOG = PROJECT_PATH + "LOG/"; /** * sdCard * * @return If there is a SD card to return true, otherwise false */ public static boolean existsSdCard() { return android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); } /** * 创建文件 * * @param s * @return If you create a file successfully returned to the current file, otherwise * Null */ public static File createFile(String s) { return new File(s); } /** * 创建文件夹 * * @param s * @return If you create a folder successfully returned to the current folder, otherwise * Null */ public static File createFolder(String s) { File file = new File(s); if (!file.exists()) { file.mkdirs(); } return file; } /** * 初始化 * * @return If true is returned to the initial success, otherwise false */ public static boolean init() { boolean result = false; if (existsSdCard()) { result = true; result &= createFolder(PROJECT_PATH) != null; result &= createFolder(WMK_DB) != null; result &= createFolder(WMK_IMG) != null; result &= createFolder(WMK_LOG) != null; } return result; } }
最后,在application中,捕获crash信息,代码如下:
package com.example.wmk; import android.app.Activity; import android.app.Application; import android.os.Build; import android.os.Bundle; import com.example.wmk.utils.ActivityManager; import com.example.wmk.utils.CrashHandler; import com.example.wmk.utils.ProjectUtils; import com.lidroid.xutils.util.LogUtils; /** * Created by 老王 on 2016/12/1. */ public class WMKApplication extends Application { ActivityManager mActivityManager; @Override public void onCreate() { super.onCreate(); //init ActivityManager mActivityManager = ActivityManager.getInstance(); // 注册activity监听器 registerActivityListener(); if (ProjectUtils.init()) { //在这里为应用设置异常处理,然后程序才能获取未处理的异常 CrashHandler crashHandler = CrashHandler.getInstance(); crashHandler.init(this); } else { mActivityManager.appExceptionExit(); } } private void registerActivityListener() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /** * 监听到 Activity创建事件 将该 Activity 加入list */ mActivityManager.pushActivity(activity); /** * 栈顶元素名称 */ LogUtils.d("TopActivityName:" + mActivityManager.getTopActivityName()); } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { if (null == mActivityManager.getActivitys() && mActivityManager.getActivitys().isEmpty()) { return; } if (mActivityManager.getActivitys().contains(activity)) { /** * 监听到 Activity销毁事件 将该Activity 从list中移除 */ mActivityManager.popActivity(activity); } } }); } } }
下面在activity中抛出一个异常,布局文件如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".activity.MainActivity"> <Button android:id="@+id/btn_crash" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="crash" /> </RelativeLayout>
activity界面代码如下:
package com.example.wmk.activity; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import com.example.wmk.R; import com.lidroid.xutils.ViewUtils; import com.lidroid.xutils.view.annotation.ViewInject; public class MainActivity extends Activity { @ViewInject(R.id.btn_crash) private Button btnCrash = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewUtils.inject(this); initOnclickListener(); } private void initOnclickListener() { View.OnClickListener onClickListener = new View.OnClickListener() { @Override public void onClick(View v) { if (v == btnCrash) { throw new RuntimeException("自定义异常"); } } }; btnCrash.setOnClickListener(onClickListener); } }
点击自定义异常按钮,抛出crash信息,在SDCard中,会产生一个crash文件,如图:
最后可以把用户的crash信息上传服务器。