Android应用捕获全局异常自定义处理
[2016-06-30]最新的全局异常处理DRCrashHandler已经集成在DR_support_lib库中
具体请看: https://coding.net/u/wrcold520/p/DR_support_lib/git/tree/master
[2016-06-28] 1 增加log4j的支持
[2016-06-28] 2 增加全局异常处理(可自定义程序崩溃提示消息,自定义发送错误报告到服务器)
[2016-06-28] 3 增加两种应用退出方法:① appExit,结束掉所有Acitivity的生命周期,正常退出;② appKill,结束掉所有Acitivity的生命周期,杀掉程序进程后退出。
[2016-06-29] 4 增加透明状态栏和导航栏(默认开启,蓝色背景)
在Android程序中,我们通常会在可能发生错误的地方加上try {} catch (Exception e) {}语句来捕获异常,但是,我们无法保证程序就一定不会出错,比如,你玩Android游戏的过程中经常会碰到黑屏或者直接退出的情况,那么这些异常就应该是没有被捕获到(一般获取到都会提示用户错误,并提示用户是否将错误上传至服务器),那么现在我们来看下如何捕获全局异常,然后自己处理。
1、新建DRCrashHandler.java(这是一个抽象类,我放在了DR_supprot_lib库中,以便以后再写应用程序的时候直接使用,里面用到了一些变量是DRConstants类中的,这是一个常量类,用来定义常用的常量的)
默认Toast弹窗提示用户(消息可以自定义)
package cn.dr.lib.app; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Field; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import org.apache.log4j.Logger; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.os.Looper; import android.widget.Toast; import cn.dr.lib.common.DRConstants; import cn.dr.lib.utils.DateUtil; import cn.dr.lib.utils.FileUtil; /** * DarkRanger的全局异常处理类 * * @author DarkRanger * */ public abstract class DRCrashHandler implements UncaughtExceptionHandler { /** log4j **/ private static final Logger log = Logger.getLogger(DRCrashHandler.class); /** 系统默认的UncaughtException处理类 **/ private Thread.UncaughtExceptionHandler mDefaultHandler; /** 程序context **/ private DRApplication mContext; /** 存储设备信息和异常信息 **/ private Map<String, String> mInfos = new HashMap<String, String>(); /** 程序出错提示信息 **/ private String mDRTipMsg = "抱歉,程序异常,3s后退出!"; /** 设置crash文件位置 **/ private String mDRCrashFilePath = DRConstants.CRASH_FILE_PATH; /** 生成的log文件 **/ private File logFile; /** 生成的crash文件 **/ private File crashFile; /** * 初始化 * * @param context */ public void init(DRApplication context) { log.info("DRCrashHandler is Ready For Application! "); // 1、上下文 mContext = context; // 2、获取系统默认的UncaughtException处理器 mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); // 3、初始化参数 initParams(); // 4、设置当前CrashHandler为默认处理异常类 Thread.setDefaultUncaughtExceptionHandler(this); } /** * 3.1 初始化参数 <br/> * <br/> * * {@link #setTipMsg(String)} setTipMsg("this is crash tip msg!!!"); <br/> * {@link #setCrashFilePath(String)} * setCrashFilePath(Constants.CRASH_FILE_PATH); <br/> * <br/> * * 如果想使用自己的CrashHandler,则复写initParams()方,然后设置参数<br/> * * <code> * public class MyCrashHandler extends DRCrashHandler {<br/> * private static final Logger log = Logger.getLogger(MyCrashHandler.class);<br/> * * @Override<br/> * public void initParams() {<br/> * log.trace("MyCrashHandler: initParams()");<br/> * * setDRTipMsg("MyCrashHandler tip msg!!!");<br/> * setDRCrashFilePath(Constants.CRASH_FILE_PATH);<br/> * }<br/> * }<br/> * </code> */ public abstract void initParams(); @Override public void uncaughtException(Thread thread, Throwable ex) { log.info("DRCrashHandler dispatcher uncaughtException! "); if (mDefaultHandler != null && !handlerException(ex)) { mDefaultHandler.uncaughtException(thread, ex); } else { // 程序休眠3s后退出 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } ((DRApplication) mContext.getApplicationContext()).appExit(); } } /** * 5、处理异常<br> * <br> * * 5.1 收集设备参数信息<br> * 5.2 弹出窗口提示信息<br> * 5.3 保存log和crash到文件<br> * 5.4 发送log和crash到服务器<br> * * @param ex * @return 是否处理了异常 */ protected boolean handlerException(Throwable ex) { log.info("DRCrashHandler is handling Exception! "); if (ex == null) { return false; } else { // 5.1 收集设备参数信息 collectDeviceInfo(mContext); // 5.2 弹出窗口提示信息 new Thread(new Runnable() { public void run() { log.info("DRCrashHandler is ready send crash-info to device!"); Looper.prepare(); Toast.makeText(mContext, getDRTipMsg(), Toast.LENGTH_SHORT).show(); Looper.loop(); } }).start(); // 5.3 保存log和crash到文件 saveLogAndCrash(ex); // 5.4 发送log和crash到服务器 sendLogAndCrash(); return true; } } /** * 5.1 收集设备信息 * * @param ctx */ protected void collectDeviceInfo(Context ctx) { log.info("DRCrashHandler is collecting DeviceInfo! "); 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 + ""; mInfos.put("versionName", versionName); mInfos.put("versionCode", versionCode); } } catch (NameNotFoundException e) { log.error("An error occured when collect package info, Error: " + e); } Field[] fields = Build.class.getDeclaredFields(); for (Field field : fields) { try { field.setAccessible(true); mInfos.put(field.getName(), field.get(null).toString()); } catch (Exception e) { log.error("An error occured when collect crash info, Error: " + e); } } } /** * 5.3 保存log和crash到文件 * * @param ex */ protected void saveLogAndCrash(Throwable ex) { log.info("DRCrashHandler is saving Log! "); StringBuffer sb = new StringBuffer(); sb.append("[DateTime: " + DateUtil.date2String(new Date()) + "]\n"); sb.append("[DeviceInfo: ]\n"); // 遍历infos for (Map.Entry<String, String> entry : mInfos.entrySet()) { String key = entry.getKey().toLowerCase(Locale.getDefault()); String value = entry.getValue(); sb.append(" " + key + ": " + value + "\n"); } // 将错误手机到writer中 Writer writer = new StringWriter(); PrintWriter pw = new PrintWriter(writer); ex.printStackTrace(pw); Throwable cause = ex.getCause(); while (cause != null) { cause.printStackTrace(pw); cause = cause.getCause(); } pw.close(); String result = writer.toString(); sb.append("[Excetpion: ]\n"); sb.append(result); // 将异常写入日志文件 log.error(result); // 5.3.1 记录异常到特定文件中 saveToCrashFile(sb.toString()); } /** * 5.3.1写入文本 * * @param crashText */ protected void saveToCrashFile(String crashText) { log.info("DRCrashHandler is writing crash-info to CrashFile(" + this.mDRCrashFilePath + ")! "); crashFile = new File(mDRCrashFilePath); // 创建文件(自己写的操作文件相关的工具类) FileUtil.createFileAndFolder(crashFile); // 追加文本(自己写的操作文件相关的工具类) FileUtil.appendToFile(crashFile, crashText); } /** * 5.4 发送log和crash到服务器 */ protected void sendLogAndCrash() { logFile = new File(mContext.getDrLogHelper().getLog4jFilePath()); crashFile = new File(getDRCrashFilePath()); // 5.4.1 sendToServer(logFile, crashFile); } /** * 5.4.1 将错误报告发送到服务器 * * @param crashFile * @param logFile */ protected abstract void sendToServer(File logFile, File crashFile); public String getDRTipMsg() { return mDRTipMsg; } /** * 设置程序崩溃提示信息 * * @param mDRTipMsg */ public void setDRTipMsg(String mDRTipMsg) { this.mDRTipMsg = mDRTipMsg; } public String getDRCrashFilePath() { return mDRCrashFilePath; } /** * 设置记录崩溃信息的文件位置 * * @param mDRCrashFilePath */ public void setDRCrashFilePath(String mDRCrashFilePath) { this.mDRCrashFilePath = mDRCrashFilePath; } }
2、新建DRApplication.java(抽象类,里面用到了Log4j,上一篇有讲到如果在Android中集成Log4j,这里在Application中使用DRLogHelper来初始化log4j)
package cn.dr.lib.app; import java.io.File; import java.util.LinkedList; import java.util.List; import org.apache.log4j.Logger; import android.app.Activity; import android.app.Application; import cn.dr.lib.log.DRLogHelper; import cn.dr.lib.ui.DRAcitivity; /** * DarkRanger的Application * * @author DarkRanger * */ public abstract class DRApplication extends Application { // 当前类的log private static Logger log; // Activity列表 private List<DRAcitivity> mActivityList = new LinkedList<DRAcitivity>(); // drLog实例 public DRLogHelper mDRLogHelper = DRLogHelper.getInstance(); // 全局异常处理类的实例 public DRCrashHandler mDRCrashHandler = new DRCrashHandler() { @Override public void initParams() { } @Override public void sendToServer(File logFile, File crashFile) { } }; @Override public void onCreate() { super.onCreate(); // 1、初始化DRLog参数 initDRLogHelperParam(); // 2、初始化DRLog initDRLogHelper(); // 3、初始化全局异常处理类 initCrashHandler(); } /** * 1、初始化DRLog参数,如: <br/> * * {@link DRApplication#getDrlog()} DRLog drLog = getDrlog(); <br/> * {@link DRLogHelper#setLog4jFilePath(String)} * drLog.setLog4jFilePath(Constants.LOG4J_FILE_PATH); <br/> * {@link DRLogHelper#setType(cn.dr.lib.log.DRLogHelper.LogType)} * drLog.setType(LogType.TYPE_LOG4J); <br/> * * 只有在子类中完成initDRLogParam参数设置以后才能使用log */ protected abstract void initDRLogHelperParam(); /** * 2、初始化DRLog */ private void initDRLogHelper() { getDrLogHelper().init(); log = Logger.getLogger(DRApplication.class); } /** * 3、初始化CrashHandler */ private void initCrashHandler() { log.trace("DRApplication: initCrashHandler()"); // 3.1 setHandler(); // 3.2 getDRCrashHandler().init(this); } /** * * 3.1 调用以下方法为mCrashHandler设置实例<br/> * <br/> * * {@link #setCrashHandler(DRCrashHandler)} */ public abstract void setHandler(); /** * 获取DRLog单例 * * @return */ public DRLogHelper getDrLogHelper() { return mDRLogHelper; } public DRCrashHandler getDRCrashHandler() { return mDRCrashHandler; } public void setDRCrashHandler(DRCrashHandler mCrashHandler) { this.mDRCrashHandler = mCrashHandler; } /** * 添加activity到App的mActivityList * * @param activity */ public void addActivity(DRAcitivity activity) { this.mActivityList.add(activity); } /** * 遍历mActivityList,结束每一个activity的声明周期 */ private void finishActivityList() { for (Activity activity : mActivityList) { activity.finish(); } } @Override public void onTerminate() { log.trace("DRApplication: onTerminate()"); super.onTerminate(); appExit(); } /** * 完全退出程序 */ public void appExit() { log.trace("DRApplication: appExit()"); finishActivityList(); // 正常退出 System.exit(0); } /** * 出现异常杀掉进程 */ public void appKill() { log.trace("DRApplication: appKill()"); finishActivityList(); android.os.Process.killProcess(android.os.Process.myPid()); System.exit(1); } }
3、
首先要说明的是:前面两个类我已经集成在DR_supprot_lib库中,在新的应用程序中只需要继承这两个类,分类初始化一些参数即可。
写个App程序测试:
3.1 新建MyCrashHandler.java继承DRCrashHandler,复写两个抽象方法initParams()和sendToServer()
package cn.darkranger.test; import java.io.File; import org.apache.log4j.Logger; import android.os.Environment; import cn.dr.lib.app.DRCrashHandler; public class MyCrashHandler extends DRCrashHandler { private static final Logger log = Logger.getLogger(MyCrashHandler.class); String crashFilePath = Environment.getExternalStorageDirectory() + File.separator + "MyApp" + File.separator + "Crash.txt"; @Override public void initParams() { // 设置程序崩溃提示信息 setDRTipMsg("this is my msg - -"); // 设置程序崩溃的文件位置 setDRCrashFilePath(crashFilePath); } @Override protected void sendToServer(File logFile, File crashFile) { log.info("logFile: " + logFile + "; crashFile: " + "\naction:sendToServer - -"); // 这里要写上传错误信息到服务器的代码,暂时不写,看需求,自己实现 } }
3.2 新建MyApplication.java继承DRApplication,复写两个抽象方法initDRLogHelperParam()和setHandler()
package cn.darkranger.test; import java.io.File; import org.apache.log4j.Logger; import android.os.Environment; import cn.dr.lib.app.DRCrashHandler; public class MyCrashHandler extends DRCrashHandler { private static final Logger log = Logger.getLogger(MyCrashHandler.class); String crashFilePath = Environment.getExternalStorageDirectory() + File.separator + "MyApp" + File.separator + "Crash.txt"; @Override public void initParams() { // 设置程序崩溃提示信息 setDRTipMsg("this is my msg - -"); // 设置程序崩溃的文件位置 setDRCrashFilePath(crashFilePath); } @Override protected void sendToServer(File logFile, File crashFile) { log.info("logFile: " + logFile + "; crashFile: " + "\naction:sendToServer - -"); // 这里要写上传错误信息到服务器的代码,暂时不写,看需求,自己实现 } }
3.3 新建MainActivity,这里暂时不继承DRActivity,继承AppCompatActivity,两个testview,点击第一个,调用DRApplication的退出,点击第二个,抛出异常,我们自己捕获。
package cn.darkranger.test; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView;public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView exitTv = (TextView) findViewById(R.id.id_exit); exitTv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ((MyApplication) getApplication()).appExit(); } }); TextView testCrashTv = (TextView) findViewById(R.id.id_testCrash); testCrashTv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { throw new IllegalArgumentException("this is an IllegalArgumentException! "); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
3.4 更改AndroidManifest.xml(添加读写文件的权限,设置application为:cn.darkranger.test.MyApplication)
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.darkranger.test" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="21" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:name="cn.darkranger.test.MyApplication" android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
测试:
运行,点击第一个按钮,退出程序,查看log文件:
[2016-06-30 22:19:09][Class: cn.dr.lib.log.DRLogHelper.initLog4j(DRLogHelper.java:89)]
[Level: INFO ] - Msg: Log4j is Ready For DRApplication!
[2016-06-30 22:19:09][Class: cn.dr.lib.app.DRApplication.initCrashHandler(DRApplication.java:87)]
[Level: TRACE] - Msg: DRApplication: initCrashHandler()
[2016-06-30 22:19:09][Class: cn.dr.lib.app.DRCrashHandler.init(DRCrashHandler.java:65)]
[Level: INFO ] - Msg: DRCrashHandler is Ready For Application!
[2016-06-30 22:19:12][Class: cn.dr.lib.app.DRApplication.appExit(DRApplication.java:152)]
[Level: TRACE] - Msg: DRApplication: appExit()
[2016-06-30 22:19:58][Class: cn.dr.lib.log.DRLogHelper.initLog4j(DRLogHelper.java:89)]
[Level: INFO ] - Msg: Log4j is Ready For DRApplication!
[2016-06-30 22:19:58][Class: cn.dr.lib.app.DRApplication.initCrashHandler(DRApplication.java:87)]
[Level: TRACE] - Msg: DRApplication: initCrashHandler()
[2016-06-30 22:19:58][Class: cn.dr.lib.app.DRCrashHandler.init(DRCrashHandler.java:65)]
[Level: INFO ] - Msg: DRCrashHandler is Ready For Application!
[2016-06-30 22:20:02][Class: cn.dr.lib.app.DRApplication.appExit(DRApplication.java:152)]
[Level: TRACE] - Msg: DRApplication: appExit()
运行,点击第二个按钮,抛出异常,查看log文件和crash文件:
Log.txt
[2016-06-30 22:19:09][Class: cn.dr.lib.log.DRLogHelper.initLog4j(DRLogHelper.java:89)]
[Level: INFO ] - Msg: Log4j is Ready For DRApplication!
[2016-06-30 22:19:09][Class: cn.dr.lib.app.DRApplication.initCrashHandler(DRApplication.java:87)]
[Level: TRACE] - Msg: DRApplication: initCrashHandler()
[2016-06-30 22:19:09][Class: cn.dr.lib.app.DRCrashHandler.init(DRCrashHandler.java:65)]
[Level: INFO ] - Msg: DRCrashHandler is Ready For Application!
[2016-06-30 22:19:12][Class: cn.dr.lib.app.DRApplication.appExit(DRApplication.java:152)]
[Level: TRACE] - Msg: DRApplication: appExit()
[2016-06-30 22:19:58][Class: cn.dr.lib.log.DRLogHelper.initLog4j(DRLogHelper.java:89)]
[Level: INFO ] - Msg: Log4j is Ready For DRApplication!
[2016-06-30 22:19:58][Class: cn.dr.lib.app.DRApplication.initCrashHandler(DRApplication.java:87)]
[Level: TRACE] - Msg: DRApplication: initCrashHandler()
[2016-06-30 22:19:58][Class: cn.dr.lib.app.DRCrashHandler.init(DRCrashHandler.java:65)]
[Level: INFO ] - Msg: DRCrashHandler is Ready For Application!
[2016-06-30 22:20:02][Class: cn.dr.lib.app.DRApplication.appExit(DRApplication.java:152)]
[Level: TRACE] - Msg: DRApplication: appExit()
[2016-06-30 22:20:48][Class: cn.dr.lib.log.DRLogHelper.initLog4j(DRLogHelper.java:89)]
[Level: INFO ] - Msg: Log4j is Ready For DRApplication!
[2016-06-30 22:20:48][Class: cn.dr.lib.app.DRApplication.initCrashHandler(DRApplication.java:87)]
[Level: TRACE] - Msg: DRApplication: initCrashHandler()
[2016-06-30 22:20:48][Class: cn.dr.lib.app.DRCrashHandler.init(DRCrashHandler.java:65)]
[Level: INFO ] - Msg: DRCrashHandler is Ready For Application!
[2016-06-30 22:20:50][Class: cn.dr.lib.app.DRCrashHandler.uncaughtException(DRCrashHandler.java:109)]
[Level: INFO ] - Msg: DRCrashHandler dispatcher uncaughtException!
[2016-06-30 22:20:50][Class: cn.dr.lib.app.DRCrashHandler.handlerException(DRCrashHandler.java:139)]
[Level: INFO ] - Msg: DRCrashHandler is handling Exception!
[2016-06-30 22:20:50][Class: cn.dr.lib.app.DRCrashHandler.collectDeviceInfo(DRCrashHandler.java:175)]
[Level: INFO ] - Msg: DRCrashHandler is collecting DeviceInfo!
[2016-06-30 22:20:50][Class: cn.dr.lib.app.DRCrashHandler.saveLogAndCrash(DRCrashHandler.java:206)]
[Level: INFO ] - Msg: DRCrashHandler is saving Log!
[2016-06-30 22:20:50][Class: cn.dr.lib.app.DRCrashHandler.run(DRCrashHandler.java:151)]
[Level: INFO ] - Msg: DRCrashHandler is ready send crash-info to device!
[2016-06-30 22:20:50][Class: cn.dr.lib.app.DRCrashHandler.saveLogAndCrash(DRCrashHandler.java:233)]
[Level: ERROR] - Msg: java.lang.IllegalArgumentException: this is an IllegalArgumentException!
at cn.darkranger.test.MainActivity$2.onClick(MainActivity.java:33)
at android.view.View.performClick(View.java:4792)
at android.view.View$PerformClick.run(View.java:19936)
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:5595)
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:964)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759)
[2016-06-30 22:20:50][Class: cn.dr.lib.app.DRCrashHandler.saveToCrashFile(DRCrashHandler.java:246)]
[Level: INFO ] - Msg: DRCrashHandler is writing crash-info to CrashFile(/storage/emulated/0/MyApp/Crash.txt)!
[2016-06-30 22:20:50][Class: cn.dr.lib.utils.FileUtil.createFileAndFolder(FileUtil.java:444)]
[Level: INFO ] - Msg: File[/storage/emulated/0/MyApp/Crash.txt] was created successfully!
[2016-06-30 22:20:50][Class: cn.darkranger.test.MyCrashHandler.sendToServer(MyCrashHandler.java:26)]
[Level: INFO ] - Msg: logFile: /storage/emulated/0/MyApp/Log.txt; crashFile:
action:sendToServer - -
[2016-06-30 22:20:53][Class: cn.dr.lib.app.DRApplication.appExit(DRApplication.java:152)]
[Level: TRACE] - Msg: DRApplication: appExit()
Crash.txt
[DateTime: 2016-06-30 22:20:50]
[DeviceInfo: ]
supported_64_bit_abis: [Ljava.lang.String;@16d078ef
versioncode: 1
board: mozart
bootloader: unknown
type: user
matchers: [Ljava.lang.String;@3d5b7685
id: HUAWEIM2-801W
time: 1437332391000
brand: HUAWEI
tag: Build
serial: TJF4C15804003585
hardware: hi3635
supported_abis: [Ljava.lang.String;@33767ffc
no_hota: false
cpu_abi: arm64-v8a
radio: unknown
is_debuggable: false
replacements: [Ljava.lang.String;@2dec2cda
manufacturer: HUAWEI
supported_32_bit_abis: [Ljava.lang.String;@3271c1ce
tags: ota-rel-keys,release-keys
cpu_abi2:
unknown: unknown
user: huawei
fingerprint: HUAWEI/M2/HWMozart:5.1.1/HUAWEIM2-801W/C233B009:user/release-keys
host: WUH1000005635
product: M2
versionname: 1.0
display: M2-801WV100R001C233B009
hide_product_info: false
model: HUAWEI M2-801W
device: HWMozart
[Excetpion: ]
java.lang.IllegalArgumentException: this is an IllegalArgumentException!
at cn.darkranger.test.MainActivity$2.onClick(MainActivity.java:33)
at android.view.View.performClick(View.java:4792)
at android.view.View$PerformClick.run(View.java:19936)
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:5595)
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:964)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759)