作者:碼字員小D
有點復雜,雖然知道原理,但是其實不好從哪開始寫了。。。。。。
首先這是個需要在全部app運行狀態中都需要存在的對象,所以需要在application里初始化這個類,并且這個類實例~~~慢著!發現這里代碼有疑問,application中只在oncreate方法里面初始化
public class CrashApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// CrashHandler crashHandler = CrashHandler.getInstance();
CrashHandler crashHandler = new CrashHandler();
crashHandler.init(getApplicationContext());
}
}
在application里面并沒有持有這個UncaughtExceptionHandler的實例(CrashHandler實現了UncaughtExceptionHandler接口類);僅僅在這里做初始化的工作,可讓UncaughtExceptionHandler這個類從1開始就攔截app運行時候的任何uncaught異常。
寫CrashHandler類可以用單例寫,也能夠直接new對象。其實個人覺得這里單例也沒太必要,直接new1個對象,這個對象反正是要被set到Thread類里去的
/**
* Sets the default uncaught exception handler. This handler is invoked in
* case any Thread dies due to an unhandled exception.
*
* @param handler
* The handler to set or null.
*/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {
Thread.defaultUncaughtHandler = handler;
}
上面的Thread.defaultUncaughtHandler就是系統的1個靜態的實例。現在明白了其實在外面用static保持1個單例對象也沒多大必要的緣由了吧,系統里為我們保持了1個單例對象。
好了用單例初始化CrashHandler和直接new的兩種方法都寫出來吧
/** 保證只有1個CrashHandler實例 */
public CrashHandler() {
}
//CrashHandler實例
private static CrashHandler INSTANCE = new CrashHandler();
/** 獲得CrashHandler實例 ,單例模式 */
public static CrashHandler getInstance() {
return INSTANCE;
}
下面我們需要討論的是如何實現攔截異常。其實在android系統中有異常就會報錯,閃退,application not response ANR。我們要做的是要分擔下系統為我們默許對異常處理的任務而已。系統默許處理異常不友好,半天不響應,返回鍵也沒用,最后會彈出個窗來要用戶自己決定是不是結束或等待。
看android里Thread的源代碼可以看到,還有個
public class ThreadGroup implements Thread.UncaughtExceptionHandler{
實現了Thread里面UncaughtExceptionHandler的接口,這個類里有系統線程組。。。好了這系統代碼也不太好看
//獲得系統默許的UncaughtException處理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
我們需要獲得系統的默許的defaultUncaughtHandler。不過疑惑的是這個defaultUncaughtHandler是誰初始化或設置過去的呢,在Thread類里沒有初始化進程。應當是系統給初始化設置過去的,暫時沒找到具體代碼實現。。。有知道的教教我唄!
然后呢,我們需要把自己寫的UncaughtException類設置到系統變量defaultUncaughtHandler中去。
//設置該CrashHandler為程序的默許處理器
Thread.setDefaultUncaughtExceptionHandler(this);
Ok,這里其實有點亂了。理理,首先我們需要獲得1個系統默許的異常處理對象defaultUncaughtHandler,然后我們要把系統默許的異常處理對象設置成我們自己寫的1個實現UncaughtException接口的對象。為何要這么做呢?
首先系統默許的異常處理對象defaultUncaughtHandler保持了1個系統的異常處理對象,獲得到這個系統處理對象可以通過調用uncaughtException(thread, ex);方法實現系統默許的異常處理。
而我們自己要把自己寫的1個實現UncaughtException接口的對象設置成系統默許的異常處理對象是為了讓系統檢測到未catch的異常是會調用我們自己實現的uncaughtException(thread, ex)方法。然后在此方法里面實現處理邏輯。
理理就知道了,我們需要在實現了UncaughtException接口的CrashHandler類的uncaughtException方法里面實現處理邏輯。并且完成了自己的處理邏輯以后要履行系統原理默許對未捕獲異常的處理。
PS:為何1定要自己處理完后讓系統默許的異常處理對象再處理1遍呢,由于系統默許有對異常處理有默許的處理方案,比如runtimeexcption運行時異常,nullexception空指針異常怎樣處理。其實都是卡死半天,然后彈窗讓用戶決定怎樣處理,等待還是結束。這個彈窗的兩個選項,用戶選擇了等待,說明這個卡死狀態是正常了,或許是網絡要求慢但是確切是邏輯沒錯致使的。用戶選擇了結束,說明了這個確切是異常要立馬結束掉。選擇結束也就是用戶選擇了系統默許的異常處理方式,即調用了類似的defaultUncaughtHandler.uncaughtException(thread, ex)方法處理。
/**
* 當UncaughtException產生時會轉入該函數來處理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
//如果用戶沒有處理則讓系統默許的異常處理器來處理
mDefaultHandler.uncaughtException(thread, ex);
// mDefaultHandler.uncaughtException(null, ex);
// android.os.Process.killProcess(android.os.Process.myPid());
// System.exit(0);
} else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Log.e(TAG, "error : ", e);
}
//退出程序
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
}
這里回調接口方法的handleException(ex)是我們對異常的自定義處理。先不斟酌。
其實這里摹擬了系統的默許異常處理方式,即當沒有默許異常處理的時候,會線程睡眠3秒然后退出利用。等價于卡死3秒然后彈窗提示用戶選擇。
自己處理完對未捕獲異常處理后交由系統默許異常處理對象處理,默許就退出利用了。
但是呢,這里我發現如果是mDefaultHandler.uncaughtException(thread, ex);處理的話第1次是正常退出利用的,當第2次點擊利用圖標的時候并沒有履行到我們自己處理異常方法里。第3次點擊利用圖標時候直接彈出我們的異常處理方式。這是為何呢?我的觀點是系統有緩存異常機制,任務1樣的毛病1樣的處理方式,就不再會履行我們自己寫的異常處理方式里了,直接退出。這是我的觀點。求反駁批評。
所以我讓mDefaultHandler.uncaughtException(null, ex);中的1個參數都傳null則系統不會緩存記錄異常,由于是null的,沒異常啊~我這算是在調戲系統么。所以每次都能正常履行我們的處理方法。
固然你也能夠在系統處理異常方式里面寫上我們自己的異常處理方式。但是如果最后不退出利用的話是會卡死的,,,,,,其實這里個人更推薦自己寫android.os.Process.killProcess(android.os.Process.myPid());由于,如果傳1個null給系統默許異常處理睬報null指針異常的~~
我們再來聊聊自己怎樣處理異常
/**
* 自定義毛病處理,搜集毛病信息 發送毛病報告等操作均在此完成.
*
* @param ex
* @return true:如果處理了該異常信息;否則返回false.
*/
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();
TestService.getInstance().sendError("error:made by byl");
Looper.loop();
}
}.start();
//搜集裝備參數信息
collectDeviceInfo(mContext);
//保存日志文件
saveCrashInfo2File(ex);
return false;
}
//搜集裝備參數信息 collectDeviceInfo(mContext);
//保存日志文件 saveCrashInfo2File(ex);
這里有兩個方法。保存出錯日志。我覺得類似umeng這類毛病分析的系統肯定也是這樣做處理的,要不然可以知道甚么手機出甚么毛病的統計1清2楚的。
這里關鍵在TestService這個后臺服務。
順便說下這里的Looper.prepare和Looper.loop方法.網上的說法Toast要想在子線中顯示,就必須在當前線程中存在1個消息隊列(Looper)。 具體Handler的操作,你看下Toast源碼就知道了。好吧~ ~相信很多面試官也不清楚!
很討厭面試的時候很多人問handler啊,Looper對象啊,message消息隊列啊,然后我會很籠統的說android里面的Looper是1個輪詢的類,handler每次send1個消息的時候會把消息放到消息隊列里面去,然后looper去輪詢這個消息隊列去1個1個的處理消息。其實我能理解的也就是這1層,具體怎樣輪詢,怎樣處理消息的其實代碼具體怎樣實現我也不知道啊
public class TestService extends Service
TestService是1個服務了,在第1個Activity啟動的時候就
Intent intent = new Intent(this, TestService.class);
startService(intent);
這個后臺服務默默地等待著系統出現未捕獲的異常,然后履行自己的方法
Intent intent = new Intent(this, SendErrorActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("msg", message);
startActivity(intent);
stopSelf();
然后這個SendErrorActivity呢可以實現彈窗提示的功能,具體邏輯要看業務需求。
再說下這個SendErrorActivity的具體實現吧
manifest里面配置
<activity
android:name="com.example.testerror.SendErrorActivity"
android:screenOrientation="portrait"
android:theme="@style/bklistDialog" >
</activity>
style里面配置bklistDialog
<style name="bklistDialog" parent="@android:style/Theme.Dialog">
<item name="android:windowFrame">@android:color/transparent</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<!--控制灰度的值,當為1時,界面除我們的dialog內容是高亮顯示的,dialog之外的區域是黑色的,完全看不到其他內容,系統的默許值是0.5,而已根據自己的需要調劑-->
<item name="android:backgroundDimAmount">0.3</item>
<item name="android:windowAnimationStyle">@null</item>
</style>
layout文件
<?xml version="1.0" encoding="utf⑻"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="#ffffff"
android:gravity="center"
android:orientation="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="10dp"
android:text="提示"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:text="是不是發送毛病信息?"
android:textColor="#A19D94"
android:textSize="16sp" />
<TextView
android:id="@+id/info_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:textColor="#A19D94"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="@+id/ok"
android:layout_width="fill_parent"
android:layout_height="45dp"
android:layout_weight="1"
android:gravity="center"
android:padding="10dp"
android:text="確 定"
android:textSize="18sp" />
<Button
android:id="@+id/cancel"
android:layout_width="fill_parent"
android:layout_height="45dp"
android:layout_weight="1"
android:gravity="center"
android:padding="10dp"
android:text="取消"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
SendErrorActivity里面的oncreate方法具體實現
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loginoutinfo);
//全屏顯示
getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
ok=(Button) findViewById(R.id.ok);
cancel=(Button) findViewById(R.id.cancel);
error_msg=getIntent().getStringExtra("msg");
ok.setOnClickListener(this);
cancel.setOnClickListener(this);
}
OK,花了兩個小時寫下了這個東西研究了下還是很值得的。純手敲啊,麻煩高人賜教啊!謝了
上一篇 對于csdn我是有感情的