Android 异常捕获

摘要:
未捕获的异常处理首先由线程控制,然后由线程的ThreadGroup对象控制,最后由未捕获的默认异常处理程序控制(=Null){//如果用户不处理它,则让系统的默认异常处理器处理mDefaultHandler.uncautException;}否则{try{thread.sleep;}catch{Log.e;}//退出程序android.os.Process.killProcess;System.exit;}}PrivatebookeanhandleException{if{returnfalse;}//使用Toast显示异常信息newThread(){@Overridepublicvoid run(){Looper.prepare();Toast.makeText.show();Looper.lop();}}。start()//此处可以执行其他操作,例如获取设备信息、执行异常注销请求、将错误日志保存到本地或发送到服务器returntrue;}}此外,因为我们需要捕获全局异常,所以我们需要在Application中启动,所以我们也需要实现Application。

  在用户使用APP时,如果APP毫无征兆的突然退出程序,又没有任何提示信息。我想这是一种最差劲的用户体验了吧,如果是我估计干脆就直接卸载APP了。因此,作为Android开发者对于这种情况的发生一定要有处理才行。否则,对于大多数最求完美的程序员而言自己也不能原谅自己。其实捕获全局异常,还可以做一个登出处理,比如用户在登陆APP后,服务端会通过seesion判断用户是否在线,如果在APP异常退出后没有注销掉服务端的在线记录,那就会造成用户实际上已经离线而在服务端的记录为在线的错误。如果用户再登陆时就可能登陆不上了。

  既然全局异常的捕获如此重要,那么我们该怎么来实现这样重要的功能呢?

  最重要的便是继承UncaughtExceptionHandler接口。未捕获到的异常处理首先由线程控制,然后由线程的 ThreadGroup 对象控制,最后由未捕获到的默认异常处理程序控制。如果线程不设置明确的未捕获到的异常处理程序,并且该线程的线程组(包括父线程组)未特别指定其 uncaughtException 方法,则将调用默认处理程序的 uncaughtException方法。通过设置未捕获到的默认异常处理程序,应用程序可以为那些已经接受系统提供的任何“默认”行为的线程改变未捕获到的异常处理方式(如记录到某一特定设备或文件)。

  具体实现捕获工具类如下:

package com.example.crash;

import java.lang.Thread.UncaughtExceptionHandler;

import android.content.Context;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

public class CrashHandler implements UncaughtExceptionHandler {
    
    public static final String TAG = "CrashHandler";  
    
    //系统默认的UncaughtException处理类   
    private Thread.UncaughtExceptionHandler mDefaultHandler;  
    //CrashHandler实例  
    private static CrashHandler INSTANCE = new CrashHandler();  
    //程序的Context对象  
    private Context mContext;  
  
    /** 保证只有一个CrashHandler实例 */  
    private CrashHandler() {  
    }  
  
    /** 获取CrashHandler实例 ,单例模式 */  
    public static CrashHandler getInstance() {  
        return INSTANCE;  
    }  
  
    /** 
     * 初始化 
     *  
     * @param context 
     */  
    public void init(Context context) {  
        mContext = context;  
        //获取系统默认的UncaughtException处理器  
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();  
        //设置该CrashHandler为程序的默认处理器  
        Thread.setDefaultUncaughtExceptionHandler(this);  
    } 

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) { 
            //如果用户没有处理则让系统默认的异常处理器来处理  
            mDefaultHandler.uncaughtException(thread, ex);  
        } else {  
            try {  
                Thread.sleep(3000);  
            } catch (InterruptedException e) {  
                Log.e(TAG, "error : ", e);  
            }  
            //退出程序  
            android.os.Process.killProcess(android.os.Process.myPid());  
            System.exit(1);  
        }
        
    }

    private boolean handleException(Throwable ex) {
        if (ex == null) {  
            return false;  
        }  
        //使用Toast来显示异常信息  
        new Thread() {  
            @Override  
            public void run() {  
                Looper.prepare();  
                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();  
                Looper.loop();  
            }  
        }.start();  
        
        //在此可执行其它操作,如获取设备信息、执行异常登出请求、保存错误日志到本地或发送至服务端
        
        return true;
    }

}

此外,因为需要捕获全局的异常,因此我们需要在Application中启动,所以我们还需要实现Application。

具体实现如下:

package com.example.crash;

import android.app.Application;

public class MyApplication extends Application{
    
    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        CrashHandler crashHandler = CrashHandler.getInstance();  
        crashHandler.init(getApplicationContext());
    }

}

从代码中可以看出,我们在启动Application时,就获取了CrashHandler实例并初始化。

如果想要使用这个MyApplication还必须要在配置文件中设置,否则会启动Android默认的Application,设置如下:

<application
        android:allowBackup="true"
        android:name="com.example.crash.MyApplication"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
..................................

最重要的是android:name="com.example.crash.MyApplication"这句代码,其表示使用我们自己的Application。

OK,准备工作做好了,接下来就是模拟一个异常了。我们模拟一个未捕获的空指针异常。

场景为在第一个Activity跳转到第二个Activity时,第二个Activity出现异常。

因为布局很简单就不贴代码了。

第一个Activity:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn=(Button) findViewById(R.id.btn);
        btn.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                Intent inent=new Intent(MainActivity.this,ToActivity.class);
                startActivity(inent);
                
            }
        });
    }


}

第二个Activity:

public class ToActivity extends Activity {
    
    private Button btn2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.toactivity_main);
        //btn2 = (Button) findViewById(R.id.btn2);
        btn2.setText("保存");
    }

}

具体运行效果如下:

Android 异常捕获第1张

值得注意的是如果在捕获到异常时Toast信息没有弹出,则可能是context为空导致的。

大家如果在使用时出现了什么异常可以将异常以文本的形式保存在本地,因为我们捕获了全局异常因此在日志中几乎不会有什么异常信息,所以将异常以文本的形式保存在本地也方便开发人员查看。

在捕获到全局异常时,可以执行其它操作,如获取设备信息、执行异常登出请求、保存错误日志到本地或发送至服务端等。

如:

/** 
     * 收集设备参数信息 
     * @param ctx 
     */  
    public void collectDeviceInfo(Context ctx) {  
        try {  
            PackageManager pm = ctx.getPackageManager();  
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);  
            if (pi != null) {  
                String versionName = pi.versionName == null ? "null" : pi.versionName;  
                String versionCode = pi.versionCode + "";  
                infos.put("versionName", versionName);  
                infos.put("versionCode", versionCode);  
            }  
        } catch (NameNotFoundException e) {  
            Log.e(TAG, "an error occured when collect package info", e);  
        }  
        Field[] fields = Build.class.getDeclaredFields();  
        for (Field field : fields) {  
            try {  
                field.setAccessible(true);  
                infos.put(field.getName(), field.get(null).toString());  
                Log.d(TAG, field.getName() + " : " + field.get(null));  
            } catch (Exception e) {  
                Log.e(TAG, "an error occured when collect crash info", e);  
            }  
        }  
    }
/** 
     * 保存错误信息到文件中 
     *  
     * @param ex 
     * @return  返回文件名称,便于将文件传送到服务器 
     */  
    private String saveCrashInfo2File(Throwable ex) {  
          
        StringBuffer sb = new StringBuffer();  
        for (Map.Entry<String, String> entry : infos.entrySet()) {  
            String key = entry.getKey();  
            String value = entry.getValue();  
            sb.append(key + "=" + value + "
");  
        }  
          
        Writer writer = new StringWriter();  
        PrintWriter printWriter = new PrintWriter(writer);  
        ex.printStackTrace(printWriter);  
        Throwable cause = ex.getCause();  
        while (cause != null) {  
            cause.printStackTrace(printWriter);  
            cause = cause.getCause();  
        }  
        printWriter.close();  
        String result = writer.toString();  
        sb.append(result);  
        try {  
            long timestamp = System.currentTimeMillis();  
            String time = formatter.format(new Date());  
            String fileName = "crash-" + time + "-" + timestamp + ".log";  
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {  
                String path = "/sdcard/crash/";  
                File dir = new File(path);  
                if (!dir.exists()) {  
                    dir.mkdirs();  
                }  
                FileOutputStream fos = new FileOutputStream(path + fileName);  
                fos.write(sb.toString().getBytes());  
                fos.close();  
            }  
            return fileName;  
        } catch (Exception e) {  
            Log.e(TAG, "an error occured while writing file...", e);  
        }  
        return null;  
    }

感谢博文:http://blog.csdn.net/liuhe688/article/details/6584143提供的帮助。

本实例是基于http://blog.csdn.net/liuhe688/article/details/6584143的实例完成。

附上DEMO:http://download.csdn.net/detail/af74776/8381245

另外,APP的版本更新也有一篇不错的博文:http://blog.csdn.net/jj120522/article/details/7948554。

免责声明:文章转载自《Android 异常捕获》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇gVim的 设置vim 高级使用技巧第二篇下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android通过反射获取资源ID

通过反射获取布局文件: protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); int id = this.getResources().getIdentifier("layout_test", "l...

Android读取JSON格式数据

Android读取JSON格式数据 1. 何为JSON? JSON,全称为JavaScript Object Notation,意为JavaScript对象表示法。 JSON 是轻量级的文本数据交换格式 JSON 独立于语言 JSON 具有自我描写叙述性,更易理解 相比 XML 的不同之处: 没有结束标签 更短 读写的速度更快 使用数组 不...

Android Studio添加原生库并自动构建

[时间:2017-09] [状态:Open] [关键词:Android,Android Studio,gradle,native,c,c++,cmake,原生开发,ndk-build] 0 引言 最近在工作中遇到了升级Android Studio 2.3.3稳定版之后,无法编译jar包的问题。之后寻找AS文档-探索 Android Studio发现。可以通...

Oracle 异常处理汇总

Oracle 异常处理汇总 1、plsql无法连接 安装oracle,中间录入密码,用户是:sys,pass: 录入的密码。 连接数据库,建议创建新的用户,最好别直接用sys 安装完毕,则需要配置Net Configration Assistant,才能通过pl/sql连接 确保服务监听服务已经启动 2、遇到ORACLE错误12514 解决方案: 1)...

如何在不同的语言/平台中获取Android ID

如何在不同的语言/平台中获取Android ID# 最近开发工作中需要使用到AndroidID,在Unity和native code中也需要使用,java获取很方便,Unity中也不难,最难的是在native code中获取。 获取android ID需要有一个上下文实例,也就是Context实例,看下面的java获取方式: 在java中获取## Andr...

转载 Android之网络与通信

2.三种网络接口简述2.1标准Java接口java.net.*提供与联网有关的类,包括流和数据包套接字、Internet协议、常见HTTP处理。使用java.net.*包连接网络代码:Java代码 收藏代码try{ //定义地址 URL url=newURL("http://www.google.com"); //打开连接 HttpURLConn...