Android应用Crash应对 (1)------Crash信息本地存储

        前段时间在编写一个图片上传应用的时候,最后发布第一版的时候还是会出现各种各样的程序崩溃现象,由于测试与调试的不同步,所以就需要记录程序所有的异常信息用于优化改进;这个时候就遇到一个问题,如何捕获所有的异常信息;这个就是我们今天要讨论的内容了。

        今天主要介绍以下几点内容:

  • logcat未主动捕获日志信息演示
  • 未捕获异常Android处理机制对于的API
  • 结合前两点保存logcat异常日志信息到本地

 

1,logcat未主动捕获日志信息演示

当我们在一个按钮的点击事件中添加如下代码:

  int a=0;
  int b=1;
  int c=b/a;

logcat输入如下日志信息并且应用给出提示弹窗或者异常退出:

 

如果我把上面的代码改为:

        int a=0;
        int b=1;
        try {
            int c = b / a;
        }
        catch (ArithmeticException e){
            e.printStackTrace();
            Log.e(TAG, "catchExceptionTest1: 主动捕获异常" );
        }

点击按钮,logcat输入日志信息:

 

        所以我们就可以得出一个结论,系统是帮助我们捕获了这些异常信息,只是没有保存这些信息并且处理的方式可能不是我们想要的。

 

2,未捕获异常Android处理机制对于的API

      Android内部所有未主动捕获的异常都会走UncaughtExceptionHandler的uncaughtException方法  。为了体现这句话,我们直接看一下效果:

(1)第一步:新建一个未捕获异常处理类实现UncaughtExceptionHandler

package com.hfut.operationcrashhandler;

import android.os.Process;
import android.util.Log;

/**
 * author:why
 * created on: 2018/12/22 22:55
 * description:
 */
public class MyCrashHandler implements Thread.UncaughtExceptionHandler {
    private static final String TAG = "MyCrashHandler";

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Log.e(TAG, "uncaughtException: 默认异常捕获处理");
        Process.killProcess(Process.myPid());//杀死当前进程
    }

}

(2)第二步:设置默认未捕获异常处理器;在测试的Activity的onCreate中添加如下代码:

 Thread.setDefaultUncaughtExceptionHandler(new MyCrashHandler());

(3)第三步:点击未主动捕获异常时的按钮,logcat日志如下:

(4)第四步:点击主动捕获异常时的按钮,logcat日志如下:

上面几步的操作证明了一下几个观点:

  • 未主动捕获的异常系统会在UncaughtExceptionHandler的uncaughtException方法中处理
  • 主动捕获的异常不会在UncaughtExceptionHandler的uncaughtException方法中处理
  • 在无法主动获取全部异常信息时(Crash时);我们可以通过自定义一个异常处理器实现UncaughtExceptionHandler来记录异常信息和处理异常Crash来分别实现收集Crash信息以及优化用户体验的目的(下一部分介绍)

 

3,结合前两点保存logcat异常日志信息到本地

    3.1 分析 

        上面我们得出很多结论,现在我就想实现一个目的:保存未捕获异常logcat窗口输出的异常信息到本地。我们首先分析一下:

(1)首先保存的信息除了上面说到的还需要设备以及应用的相关信息,所以自定义异常处理器就需要传入当前应用的上下文对象用于获取的这些当中的部分信息

(2)因为我们通过Thread的setDefaultUncaughtExceptionHandler方法设置的异常处理器是一个静态全局变量,所以它是针对进程的,所以应该在Application初始化的时候就该完成


    // null unless explicitly set
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
         defaultUncaughtExceptionHandler = eh;
     }

(3)因为(2)的原因,我们的异常处理器应该被设计成单例模式

下面就来看看具体的实现,本部分代码很多参考《Android开发艺术探索》;想更多详细内容可参考原书。

 

3.2 实现

MyCrashHandler1.java代码:

package com.hfut.operationcrashhandler;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import android.util.Log;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * author:why
 * created on: 2018/12/23 9:39
 * description: crashInfo manage class
 */
public class MyCrashHandler1 implements Thread.UncaughtExceptionHandler {

    private static final String TAG = "MyCrashHandler1";
    private static final MyCrashHandler1 singleInstance = new MyCrashHandler1();
    private final String crashStorageDir = "/mnt/sdcard/crashInfo/";
    private final String crashStorageFile = "crash.txt";
    private Context mContext;
    private Thread.UncaughtExceptionHandler mUncaughtExceptionHandler;

    private MyCrashHandler1() {

    }

    /**
     * 获取单例
     *
     * @return
     */
    public static MyCrashHandler1 getSingleInstance() {
        return singleInstance;
    }

    public void init(Context context) {
        mUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        mContext = context;
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        saveCrashInfo2SD(e);
    }

    /**
     * 保存crash信息到SDcard
     *
     * @param e
     */
    private void saveCrashInfo2SD(Throwable e) {
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {// No sdcard
            Log.e(TAG, "saveCrashInfo2SD: sdcard unmounted,can not save crash info");
            return;
        }
        File file = new File(crashStorageDir);
        if (!file.exists()) {
            file.mkdirs();
        }
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
        try {
            //time = time.replace("-", "_").replace(":", "_").replace(" ", "_");
            File file1 = new File(crashStorageDir + time + crashStorageFile);
            if (!file1.exists()) {
                file1.createNewFile();
            }
            PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(file1)));
            writer.print(time);
            saveDeviceInfo(writer);
            writer.println();//换行
            e.printStackTrace(writer);
            writer.close();//关闭输出流
            Process.killProcess(Process.myPid());//关闭应用进程
        } catch (Exception e1) {
            Log.e(TAG, "saveCrashInfo2SD: save crash exception");
        }

    }

    private void saveDeviceInfo(PrintWriter writer) throws Exception{
        PackageManager packageManager=mContext.getPackageManager();
        PackageInfo packageInfo=packageManager.getPackageInfo(mContext.getPackageName(),
                PackageManager.GET_ACTIVITIES);
        writer.print("APP Version: ");
        writer.print(packageInfo.versionName+"_");
        writer.println(packageInfo.versionCode);

        //Android OS
        writer.print("OS Version: ");
        writer.print(Build.VERSION.RELEASE+"_");
        writer.println(Build.VERSION.SDK_INT);

        //phone maker
        writer.print("Vendor: ");
        writer.println(Build.MANUFACTURER);

        //phone type
        writer.print("Model: ");
        writer.println(Build.MODEL);

        //cpu 架构
        writer.print("CPU ABI: ");
        writer.println(Build.CPU_ABI);
    }
}

MyApplication.java代码:

package com.hfut.operationcrashhandler;

import android.app.Application;

/**
 * author:why
 * created on: 2018/12/23 11:46
 * description:
 */
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        MyCrashHandler1.getSingleInstance().init(this);//初始化异常捕获
    }
}

MainActivity.java测试代码:

package com.hfut.operationcrashhandler;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

/**
 * @author why
 * @date 2018-12-22 22:41:20
 */
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //Thread.setDefaultUncaughtExceptionHandler(new MyCrashHandler());
    }


    //测试默认异常捕获
    public void catchExceptionTest(View view){
        int a=0;
        int b=1;
        int c=b/a;
    }

    //测试主动异常捕获
    public void catchExceptionTest1(View view){
        int a=0;
        int b=1;
        try {
            int c = b / a;
        }
        catch (ArithmeticException e){
            e.printStackTrace();
            Log.e(TAG, "catchExceptionTest1: 主动捕获异常" );
        }
    }
}

测试结果

在点击第一个按钮之后,找到对应的Crash信息保存目录“/mnt/sdcard/crashInfo”找到日志文件打开:

2018-12-22 22:56:31APP Version: 1.0_1
OS Version: 5.1_22
Vendor: unknown
Model: Google Nexus 7 - 5.1.0 - API 22 - 800x1280
CPU ABI: x86

java.lang.IllegalStateException: Could not execute method for android:onClick
	at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:389)
	at android.view.View.performClick(View.java:4780)
	at android.view.View$PerformClick.run(View.java:19866)
	at android.os.Handler.handleCallback(Handler.java:739)
	at android.os.Handler.dispatchMessage(Handler.java:95)
	at android.os.Looper.loop(Looper.java:135)
	at android.app.ActivityThread.main(ActivityThread.java:5254)
	at java.lang.reflect.Method.invoke(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:372)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Caused by: java.lang.reflect.InvocationTargetException
	at java.lang.reflect.Method.invoke(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:372)
	at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:384)
	... 10 more
Caused by: java.lang.ArithmeticException: divide by zero
	at com.hfut.operationcrashhandler.MainActivity.catchExceptionTest(MainActivity.java:27)
	... 13 more

可见异常信息已经全都完整的保留下来了,后续可上传至服务器用于产品优化。好了到这里关于应用crash第一部分就介绍完成了。这里在测试的时候需要注意一下:

(1)自定义的MyApplication需要在配置文件中的Application标签下面添加一下android:name属性,如下:

 

(2)因为涉及到读写SD卡权限,6.0及以上请添加权限申请部分代码,可参考Android权限介绍

注:欢迎扫码关注

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值