Android中CrashHandler的实现
前言
相信大家在调试android应用程序包的时候经常会遇到“应用程序没有响应”这种情况,也就是当我们的应用程序出现crash情况,这种提示方式会给用户带来很不好的体验。而且,通常crash导致的退出,因为没有走正常的退出流程,会导致数据没保存、线程没有杀死、缓存没有清空等问题。这个时候就需要一个全局CrashHandler。
这个时候做过项目同学就知道有优秀的第三方SDK,譬如国内有:Umeng统计,腾讯的Bugly,国外有:fabric的Crashlytics 等吧,都不一一细说了,使用起来都很方便,接入也很简单。接下来讲讲他们的基本实现方式吧。
CrashHandler的作用
CrashHandler
: 崩溃处理器,捕获Crash信息并作出相应的处理
- 测试使用:应用在日常的开发中,我们经常需要去Logcat测试我们的App,但由于很多原因,Android Monitor会闪屏或者Crash信息丢失。 这个时候就需要一个
CrashHandler
来将Crash写入到本地方便我们随时随地查看。 - 上线使用:应用的崩溃率是用户衡量筛选应用的重要标准,那么应用上线以后 我们无法向用户借手机来分析崩溃原因。为了减低崩溃率,这个时候需要
CrashHandler
来帮我们将崩溃信息返回给后台,以便及时修复。
下面我们就手把手写一个实用、本地化、轻量级的CrashHandler吧
CrashHandler的实现
- 实现
Thread.UncaughtExceptionHandler
接口,并重写uncaughtException
方法,此时你的CrashHandler就具备了接收处理异常的能力了。 - 调用
Thread.setDefaultUncaughtExceptionHandler(CrashHandler)
,来使用我们自定义的CrashHandler
来取代系统默认的CrashHandler
- 结合单例模式
- 总体三步: 捕获异常、信息数据获取、数据写入和上传
CrashHandler实现代码如下:
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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 | public class CrashHandler implements UncaughtExceptionHandler { private static final String TAG = CrashHandler.class.getSimpleName(); private Context mContext; private String mPath; private static final String sFileName = "crash"; private static final String sFileNameSuffix = ".trace"; private DateFormat mDateFormat; private ExecutorService mExecutor = Executors.newSingleThreadExecutor(); //private ExecutorService mExecutor = Executors.newCachedThreadPool(); private static CrashHandler mInstance; private UncaughtExceptionHandler mUncaughtExceptionHandler; private ConcurrentHashMap<String, Object> mInfoMap = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, String> mPackageInfoMap = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, String> mDeviceInfoMap = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, String> mSysInfoMap = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, String> mSecureInfoMap = new ConcurrentHashMap<>(); private String mMemInfo; private String mThrowableInfo; /** 异常信息 */ private static final String sThrowableInfoString = "throwable_info"; /** 应用包信息 */ private static final String sPackageInfoMap = "package_info_map"; /** 设备数据信息 */ private static final String sBuildInfoMap = "build_info_map"; /** 系统常规配置信息 */ private static final String sSystemInfoMap = "system_info_map"; /** 手机安全配置信息 */ private static final String sSecureInfoMap = "secure_info_map"; /** 内存情况信息 */ private static final String sMemoryInfoString = "memory_info_map"; private CrashUploader mCrashUploader; public static CrashHandler getInstance() { if (mInstance == null) { synchronized (CrashHandler.class) { if (mInstance == null) { mInstance = new CrashHandler(); } } } return mInstance; } public void init(Context context, CrashUploader crashUploader) { mContext = context.getApplicationContext(); mCrashUploader = crashUploader; mUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this); mDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()); File crashDir = mContext.getExternalFilesDir(sFileName); if (crashDir != null){ if (!crashDir.exists()) { crashDir.mkdirs(); } mPath = crashDir.getAbsolutePath(); } } public void deleteCrashFile(){ clearInfo(); deleteDirectory(mPath); } /** * 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用uncaughtException方法 * * @param t 出现未捕获异常的线程 * @param e 未捕获的异常,有了这个ex,我们就可以得到异常信息 */ @Override public void uncaughtException(Thread t, Throwable e) { if (!handleException(e) && mUncaughtExceptionHandler != null) { //如果用户没有处理则让系统默认的异常处理器来处理 mUncaughtExceptionHandler.uncaughtException(t, e); }else { killProcess(); } } /** * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成. * * @param ex * @return true:如果处理了该异常信息;否则返回false. */ private boolean handleException(Throwable ex) { if (ex == null) { return false; } mExecutor.execute(new Runnable() { @Override public void run() { Looper.prepare(); Toast.makeText(mContext, "Sorry, The program is abnormal and will exit soon.", Toast.LENGTH_SHORT).show(); Looper.loop(); } }); collectInfo(ex); saveCrashInfoToFile(); uploadCrashMessage(); return true; } /** * 退出应用 */ private void killProcess() { MLog.e(TAG, "killProcess"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Intent startMain = new Intent(Intent.ACTION_MAIN); startMain.addCategory(Intent.CATEGORY_HOME); startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(startMain); //Process.killProcess(Process.myPid()); System.exit(1); } /** * 收集设备参数信息 * * @param ex */ private void collectInfo(Throwable ex){ clearInfo(); collectThrowableInfo(ex); collectPackageInfo(); collectBuildInfo(); collectSystemInfo(); collectSecureInfo(); collectMemInfo(); mInfoMap.put(sThrowableInfoString, mThrowableInfo); mInfoMap.put(sPackageInfoMap, mPackageInfoMap); mInfoMap.put(sBuildInfoMap, mDeviceInfoMap); mInfoMap.put(sSystemInfoMap, mSysInfoMap); mInfoMap.put(sSecureInfoMap, mSecureInfoMap); mInfoMap.put(sMemoryInfoString, mMemInfo); } private void clearInfo(){ mMemInfo = ""; mPackageInfoMap.clear(); mDeviceInfoMap.clear(); mSysInfoMap.clear(); mSecureInfoMap.clear(); mInfoMap.clear(); mThrowableInfo = ""; } /** * 保存日志文件 */ private void saveCrashInfoToFile(){ StringBuffer mStringBuffer = infoMapToStrBuffer(mPackageInfoMap); mStringBuffer.append(infoMapToStrBuffer(mDeviceInfoMap)); mStringBuffer.append(infoMapToStrBuffer(mSysInfoMap)); mStringBuffer.append(infoMapToStrBuffer(mSecureInfoMap)); mStringBuffer.append(mMemInfo); mStringBuffer.append(mThrowableInfo); String mTime = mDateFormat.format(new Date()); if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { try { String filePath = addSlash(mPath) + mTime + sFileName + sFileNameSuffix; FileOutputStream mFileOutputStream = new FileOutputStream(filePath); mFileOutputStream.write(mStringBuffer.toString().getBytes()); mFileOutputStream.close(); MLog.d(TAG, filePath); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }catch (Exception e) { e.printStackTrace(); } } } /** * 上传崩溃信息 */ private void uploadCrashMessage(){ new Thread(new Runnable() { @Override public void run() { //http 上传服务端 //deleteCrashFile(); //成功 Looper.prepare(); mCrashUploader.uploadSuccess(); Looper.loop(); //失败 Looper.prepare(); mCrashUploader.uploadError(""); Looper.loop(); } }).start(); } /** * 获取捕获异常的信息 * * @param ex */ private void collectThrowableInfo(Throwable ex) { Writer mWriter = new StringWriter(); PrintWriter mPrintWriter = new PrintWriter(mWriter); ex.printStackTrace(mPrintWriter); ex.printStackTrace(); Throwable throwable = ex.getCause(); while (throwable != null) { throwable.printStackTrace(mPrintWriter); mPrintWriter.append("\r\n"); throwable = throwable.getCause(); } mPrintWriter.close(); mThrowableInfo = mWriter.toString(); } /** * 获取APP进程内存信息 */ private void collectMemInfo() { BufferedReader bufferedReader = null; StringBuffer stringBuffer = new StringBuffer(); ArrayList<String> commandLine = new ArrayList<>(); commandLine.add("dumpsys"); commandLine.add("meminfo"); commandLine.add(Integer.toString(Process.myPid())); try { java.lang.Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()])); bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 4 * 1024); while (true) { String line = bufferedReader.readLine(); if (line == null) { break; } stringBuffer.append(line); stringBuffer.append("\n"); } } catch (IOException e) { e.printStackTrace(); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } mMemInfo = stringBuffer.toString(); } /** * 获取系统安全设置信息 */ private void collectSecureInfo() { Field[] fields = Settings.Secure.class.getFields(); for (Field field : fields) { if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class && field.getName().startsWith("WIFI_AP")) { try { String value = Settings.Secure.getString(mContext.getContentResolver(), (String) field.get(null)); if (value != null) { mSecureInfoMap.put(field.getName(), value); } } catch (IllegalAccessException e) { e.printStackTrace(); } } } } /** * 获取系统常规设定属性 */ private void collectSystemInfo() { Field[] fields = Settings.System.class.getFields(); for (Field field : fields) { if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class) { try { String value = Settings.System.getString(mContext.getContentResolver(), (String) field.get(null)); if (value != null) { mSysInfoMap.put(field.getName(), value); } } catch (IllegalAccessException e) { e.printStackTrace(); } } } } /** * 从系统属性中提取设备硬件和版本信息 * * 迭代Build的字段key-value 此处的信息主要是为了在服务器端手机各种版本手机报错的原因 * */ private void collectBuildInfo() { Field[] mFields = Build.class.getDeclaredFields(); for (Field field : mFields) { try { field.setAccessible(true); mDeviceInfoMap.put(field.getName(), field.get("").toString()); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } /** * 获取应用包参数信息 */ private void collectPackageInfo() { try { PackageManager mPackageManager = mContext.getPackageManager(); PackageInfo mPackageInfo = mPackageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES); if (mPackageInfo != null) { mPackageInfoMap.put("PackageName", TextUtils.isEmpty(mPackageInfo.packageName) ? "unknown" : mPackageInfo.packageName); mPackageInfoMap.put("VersionName", TextUtils.isEmpty(mPackageInfo.versionName) ? "unknown" : mPackageInfo.versionName); mPackageInfoMap.put("VersionCode", mPackageInfo.versionCode + ""); } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } /** * 将HashMap遍历转换成StringBuffer */ @NonNull public static StringBuffer infoMapToStrBuffer(ConcurrentHashMap<String, String> infoMap) { StringBuffer mStringBuffer = new StringBuffer(); for (Map.Entry<String, String> entry : infoMap.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); mStringBuffer.append(key + "=" + value + "\r\n"); } return mStringBuffer; } private String addSlash(String pPath) { if (!TextUtils.isEmpty(pPath)) { if (!pPath.endsWith(File.separator)) { pPath = pPath + File.separator; } }else { pPath = File.separator; } return pPath; } private boolean deleteDirectory(String pDir){ File dirFile = new File(addSlash(pDir)); if (!dirFile.exists() || !dirFile.isDirectory()) { return false; } boolean delFlag = true; File[] files = dirFile.listFiles(); for (File file : files) { if (file.isFile()) { delFlag = deleteFile(file.getAbsolutePath()); if (!delFlag) { break; } } else { delFlag = deleteDirectory(file.getAbsolutePath()); if (!delFlag) { break; } } } if (!delFlag) { return false; } if (dirFile.delete()) { return true; } else { return false; } } private boolean deleteFile(String pFileName){ try { File file = new File(pFileName); if (file.isFile() && file.exists()) { file.delete(); return true; } else { return false; } } catch (Exception e) { return false; } } /** * 崩溃信息上传接口回调 */ public interface CrashUploader { void uploadSuccess(); void uploadError(String error); } } |
在Application中使用CrashHandler:
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 | @Override public void onCreate() { super.onCreate(); registerCrashHandler(); MLog.setShowLog(BuildConfig.LOG_DEBUG); initUmeng(); //监听APP进入后台或切回前台 addAppForegroundListener(); } private void registerCrashHandler(){ CrashHandler.getInstance().init(getApplicationContext(), new CrashHandler.CrashUploader() { @Override public void uploadSuccess() { Toast.makeText(getContext(), "uploadSuccess",Toast.LENGTH_SHORT).show(); CrashHandler.getInstance().deleteCrashFile(); } @Override public void uploadError(String error) { } }); } |
结果查看/sdrard/Android/data/com.aoaoyi.poker/files/crash/,里的文件内容如下:

附加
1. 需要在AndroidManifest.xml加入权限声明,Android6.0以上实现动态权限申请。
1 2 3 | <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> |
2. System.exit(0)和Process.killProcess(Process.myPid())
相同点
1、APP进程都会被直接杀掉
我们通过adb shell ps可以查看当前手机所有运行的进程状态,
在执行完这两种方式之后,APP进程都会消失。
2、生命周期都不会被调用
onPause()、onStop()和onDestory(),也包括onSaveInstanceState,这些生命周期方法都不会被调用。
3、App被杀死,然后都安排会立即重启进程,并重启之前状态对应的Activity、Service、ContentProvider等。
我们通过adb shell ps可以查看当前手机所有运行的进程状态,
在执行完这两种方式之后,APP进程都会消失。
2、生命周期都不会被调用
onPause()、onStop()和onDestory(),也包括onSaveInstanceState,这些生命周期方法都不会被调用。
3、App被杀死,然后都安排会立即重启进程,并重启之前状态对应的Activity、Service、ContentProvider等。
不同点
1、影响范围不同
System.exit(0)
只会影响当前的程序;
Process.killProcess(Process.myPid())
会杀掉所有PID一样的进程,比如那些拥有相同UID的应用,统统都会被杀掉。
2、方式不一样
System.exit(0)
是停止程序的虚拟机;
Process.killProcess(Process.myPid())
是通过PID去杀死进程。
System.exit(0)
只会影响当前的程序;
Process.killProcess(Process.myPid())
会杀掉所有PID一样的进程,比如那些拥有相同UID的应用,统统都会被杀掉。
2、方式不一样
System.exit(0)
是停止程序的虚拟机;
Process.killProcess(Process.myPid())
是通过PID去杀死进程。
3. System.exit(0)和System.exit(1)
看官方的方法注释,如下:
Use 0 to signal success to the calling process and 1 to signal failure.1
0:代表成功的信号(正常退出);
1:代表失败的信号(异常退出),常用于捕获到异常执行。
Use 0 to signal success to the calling process and 1 to signal failure.1
0:代表成功的信号(正常退出);
1:代表失败的信号(异常退出),常用于捕获到异常执行。
从上面可以看出,这两种方式都比较暴力,不推荐使用,尤其是现在的手机配置都很好的情况下。而且进程都被杀掉了,相应的Service也会被干掉.
真要用的话,也得注意:如果程序有多个Activity,最好在主Activity中的OnBackPressed()中使用,如果在其它Activity中使用的话,APP会直接退出,退出后会重启应用,这样对于用户体验不是很好。
真要用的话,也得注意:如果程序有多个Activity,最好在主Activity中的OnBackPressed()中使用,如果在其它Activity中使用的话,APP会直接退出,退出后会重启应用,这样对于用户体验不是很好。
4. 稍微接近完美的退出方案
1 2 3 4 5 | Intent startMain = new Intent(Intent.ACTION_MAIN); startMain.addCategory(Intent.CATEGORY_HOME); startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(startMain); System.exit(1); |
先回到系统Home界面,在进行异常退出。
发表评论
要发表评论,您必须先登录。