Android——Exception异常的正确打开方式

背景介绍

我们每天都需要与各种个样的异常打交到,但是我们对异常了解吗?对其处理方式正确吗?了解的话就算了,不了解的可以看看下面的内容。

打开Exception

Exception的分类

先来看看下面这张图:
Exception的分类
从图中可以看出:
1. Error(错误)和Exception(异常)都继承自Throwable类,我们重点关注Exception;
2. 异常类分为检查异常(直接继承自Exception,除RuntimeException)和可不检查异常(Error和继承自RuntimeException的);

throw和throws的区别

  • throw通常在代码片段中用于直接抛出异常。
public void test(){
    throw new ClassNotFoundException();
}
  • throws用在方法签名上,可抛出多种异常
public void test() throws IOException, ArrayIndexOutOfBoundsException{
    do something...
}

自定义异常

只需要继承Exception就可以了。

public class CustomException extends Exception{

    public CustomException(String exceptionInfo){
        super(exceptionInfo);
    }
}

//使用CustomExeption
public void test(){
    throw new CustomException("抛出了自定义异常");
}

try-catch-finally

我们都很熟悉使用try-catch-finally去捕获异常,但现在值得思考一下我们之前写的try-catch-finally是否正确了。
Code Clean 指出,try-catch-finally代码块在程序中定义了一个范围,我们应该让它的语意更明确,所以不应该把大段的代码放在其中,而应该抽离出来。来看看下面这个例子。

//这个方法中只定义了try-catch,而真正的操作放到deletePagerAndAllReference()中进行
public void delete(){
    try{
        deletePagerAndAllReference();
    } catch (Exception e){
        Log.e(e);
    }
}

public void deletePagerAndAllReference() throws Exception{
    //do delete
}

需要注意,catch中的return语句会在finally执行完成后才会被执行。

关于方法返回null的讨论

Code Clean 中,鲍勃大叔严厉批评了return null 这种骇人听闻的做法,这让程序中充满了类似obj != null 的判断。他建议在可能返回null的地方使用抛出异常,或者直接返回一种特例情况。例如下面这样:

List<Employee> employees = getEmployees();
if(employees != null){
    // doSomething...
}

由于getEmployees()可能返回null值,所以我们不得不每次调用的时候都去检查是否为null,但如果做如下更改:

public List<Employee> getEmployees(){
    if(..there are no employees..){
        return Collections.emptyList();
        //没有数据返回一个空的List,调用时就不必去检查它是否为空了
    }
}

或者像下面这样:

public List<Employee> getEmployees(){
    if(..there are no employees..){
        throw new NullPointerException("嘿!List<Employee>不能为null,仔细检查下吧!");
        //没有数据返回一个空的List,直接抛出异常,让调用者们知道,这个地方存在错误,不该让List<Employee>为null的。
    }
}

Android中处理未捕获异常,并上报异常信息

在我们的应用中,可能存在一些我们没有捕获的异常,对于这些异常,我们可以把它保存下来,然后进行分析。来看看怎么做。
首先我们需要implements Thread.UncaughtExceptionHandler 实现自己的异常处理类,然后调用* Thread.setDefaultUncaughtExceptionHandler()* 方法把我们的异常处理器设置到系统中,这样有为捕获的异常出现时,就能被我们自己处理了。
当然,有一个重要的方法需要重写uncaughtException() 。下面看看完整例子。

public class CrashHandler implements UncaughtExceptionHandler {

  private static final CrashHandler mInstance = new CrashHandler();
  private UncaughtExceptionHandler mDefualtCrashHandler;
  private Context mContext;


  /**
   * 防止被重复创建
   */
  private CrashHandler() {}


  public static CrashHandler getInstance() {
    return mInstance;
  }

  public void init(Context context) {
    mContext = context.getApplicationContext(); // 确保获得的是系统级的Context
    mDefualtCrashHandler = Thread.getDefaultUncaughtExceptionHandler(); // 获取系统默认的异常处理器
    Thread.setDefaultUncaughtExceptionHandler(this); // 把当前实例设置为系统默认异常处理器
  }


  /**
   * 这个方法是我们重写的重点,当系统出现未捕获异常时,就会调用这个方法
   * 
   * @param t 出现未捕获异常的线程
   * @param e 未捕获的异常
   */
  @Override
  public void uncaughtException(Thread t, Throwable e) {
    try {
      saveExceptionToFile(e);
    } catch (Exception ex) {
      ex.printStackTrace();
    }

    if (mDefualtCrashHandler != null) {
      //如果系统有默认异常处理就使用它处理
      mDefualtCrashHandler.uncaughtException(t, e);
    } else {
      //否则我们自行结束程序
      android.os.Process.killProcess(Process.myPid());
    }



  }

  private void saveExceptionToFile(Throwable e) throws IOException{
    if (FileUtils.ExistSDCard()){
      long currentTime = System.currentTimeMillis();
      String crashTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(currentTime);
      File file = new File(FileUtils.getAppCrashDir()+"crash" + crashTime + ".txt");
      file.createNewFile();
      try{
        PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
        pw.println(crashTime);
        printPhoneInfo(pw);
        pw.println();
        e.printStackTrace(pw); //输出错误信息
        pw.close();
      } catch (Exception ex){
        ex.printStackTrace();
      }
    }
  }

  /**
   * 输出手机信息
   */
  private void printPhoneInfo(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);
  }
}

看看怎么使用。

public class IceApplication extends MultiDexApplication {
  private static Context context;
  @Override
  public void onCreate() {
    super.onCreate();
    context = this;
    //初始化异常处理类,这样我们的异常类就生效了
    CrashHandler.getInstance().init(context);
  }

  public static Context getAppContext(){
    return context;
  }
}

总结

现在我们对异常有了一定的了解,从现在开始,在编程过程中要开始注意对异常的处理艺术了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值