
前言
由RuntimeException的使用延申和滿(mǎn)腦子騷操作的Android開(kāi)發(fā)人員陰差陽(yáng)錯(cuò)的被全局異常捕獲拘留直到Application認(rèn)領(lǐng)才知道大水沖了龍王廟的Android開(kāi)發(fā)人員跟全局異常捕獲原來(lái)是遠(yuǎn)方親戚
這方法我已經(jīng)用上癮了

代碼POU析
實(shí)際上從上圖看,表面上是看不出來(lái)什么的,但是其內(nèi)容與處理方式已經(jīng)是天翻地覆的變化了,至于怎么天翻地覆,我們來(lái)看看代碼就知道了
當(dāng)然我只貼出部分核心代碼,上圖的進(jìn)入主界面的按鈕事件監(jiān)聽(tīng)在這里
事先聲明一下,第一個(gè)界面是 MainActivity ,第二個(gè)界面是 SignInActivity 第三個(gè)界面則是登陸后的 HomeActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* 設(shè)置點(diǎn)擊事件 */
findViewById(R.id.start_to_home).setOnClickListener(new View.OnClickListener() {
/**
* 你會(huì)看到很神奇的一幕,當(dāng) {@link com.var.intercept_android.data.Storage} 中的
* {@link com.var.intercept_android.data.Storage#isSign} 為false的時(shí)候.跳轉(zhuǎn)到
* {@link HomeActivity} 的代碼并沒(méi)有執(zhí)行
*/
@Override
public void onClick(View v) {
/* 首先檢查用戶(hù)是否登陸 */
Sign.isSign();
/* 然后跳轉(zhuǎn)到HomeActivity */
startActivity(new Intent(MainActivity.this, HomeActivity.class));
}
});
}
}
然而你看到的是,我只是在上邊調(diào)用了 Sign 類(lèi)的 isSign() 方法,至于 isSign() 怎么實(shí)現(xiàn)的,我們等會(huì)在看
其實(shí)看到這里,你已經(jīng)發(fā)現(xiàn)有些不對(duì)勁了,是吧?正常情況下 isSign() 執(zhí)行完是要執(zhí)行跳轉(zhuǎn)到主界面的,然而并沒(méi)有執(zhí)行跳轉(zhuǎn)到主界面的那句代碼,這是為什么呢?我們?cè)賮?lái)看看 isSing() 方法里邊用了什么 騷操作
public class Sign {
/**
* 檢查是否登陸
*/
public static void isSign() {
/* 如果未登錄 */
if (!Storage.isSign) {
/* 拋出未登錄異常 */
throw new NotSignException();
}
}
}
對(duì)的,你沒(méi)看錯(cuò) isSign() 方法拋了個(gè)名為 NotSignException 的自定義異常,那我們?cè)賮?lái)看看 NotSignException 是怎么寫(xiě)的
public class NotSignException extends RuntimeException {
/**
* 構(gòu)造方法,一個(gè)就夠了
*/
public NotSignException() {
super("用戶(hù)未登錄");
}
}
居然是繼承的 RuntimeException ,想不到吧,這樣的程序居然沒(méi)崩潰?
原理POU析
靈感來(lái)源
事實(shí)上,這個(gè)騷操作是這樣來(lái)的,最開(kāi)始,我準(zhǔn)備做一個(gè)檢查用戶(hù)是否登陸的功能,但是呢,用返回值為布爾類(lèi)型的方法來(lái)做驗(yàn)證感覺(jué)太繁瑣,并且吧,if和else寫(xiě)多了也并不怎么美觀,所以尋思著怎么讓代碼執(zhí)行到某一句的時(shí)候不繼續(xù)執(zhí)行了
實(shí)現(xiàn)原理
眾所周知,Java中的異常機(jī)制,只要發(fā)生異常,那從發(fā)生異那句代碼開(kāi)始到整個(gè)方法結(jié)束的代碼都不會(huì)執(zhí)行了,所以我就想到了使用異常的方式來(lái)簡(jiǎn)化代碼的騷操作,不過(guò),這個(gè)異常也是要有講究的,他不能在編譯的時(shí)候就直接被編譯器檢查出來(lái)了,所以要隱藏起來(lái),那就得使用Error或者RuntimeException,不過(guò)Error是不可捕獲的異常,那么就只能使用RuntimeException及其子類(lèi)
后遺癥處理
既然拋出了異常,那在Java中不處理的就會(huì)拋給虛擬機(jī),那虛擬機(jī)就會(huì)停掉,所以需要寫(xiě)一個(gè)異常捕獲
延申問(wèn)題
由于使用環(huán)境是在Android上,因?yàn)锳ndroid平臺(tái)的特殊機(jī)制,所以通常的異常捕獲是行不通的,那怎么處理呢?
Android的應(yīng)用程序里邊有一個(gè)叫 Looper 的好東西,我們可以通過(guò)直接接管 Looper 然后捕獲 Looper.loop(); 的異常來(lái)捕獲主線(xiàn)程的異常信息,然后其他的異常信息則是通過(guò)以往的全局異常捕獲來(lái)實(shí)現(xiàn),我們來(lái)看看全局異常捕獲是怎么實(shí)現(xiàn)的
public class GlobalCrashCapture implements Thread.UncaughtExceptionHandler {
/* 靜態(tài)實(shí)例 */
private static GlobalCrashCapture instance;
/* 應(yīng)用程序的上下文參數(shù) */
private Application context;
/* looper狀態(tài) */
private boolean running = true;
/**
* 構(gòu)造方法
*/
private GlobalCrashCapture() {
}
/**
* 初始化
*
* @param context 上下文參數(shù)
*/
public void init(Application context) {
this.context = context;
this.looperException();
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 獲取當(dāng)前對(duì)象的靜態(tài)實(shí)例
*
* @return 返回 {@link GlobalCrashCapture} 對(duì)象
*/
public static GlobalCrashCapture instance() {
if (GlobalCrashCapture.instance == null) {
GlobalCrashCapture.instance = new GlobalCrashCapture();
}
return GlobalCrashCapture.instance;
}
/**
* 重點(diǎn)在這里,正常情況下主線(xiàn)程的異常就被捕獲也會(huì)導(dǎo)致虛擬機(jī)停止,為了不讓虛擬機(jī)停止(簡(jiǎn)單來(lái)說(shuō)是不讓APP崩潰)
* 最好的方法就是接管Android的Looper,然后捕獲異常
* <p>
* 接管Android的Looper并捕獲異常
*/
private void looperException() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
while (running) {
try {
Looper.loop();
} catch (Throwable e) {
/* 如果是自己定義的異常 */
if (e instanceof NotSignException) {
/* 那就自己去處理吧 */
handleNotSignException();
} else {
/* 如果不是自己拋出的異常,那就用全局異常捕獲去處理 */
handleException(e);
}
}
}
}
});
}
@Override
public void uncaughtException(Thread t, Throwable e) {
this.handleException(e);
}
/**
* 默認(rèn)異常信息處理
*
* @param ex 異常信息
*/
private void handleException(final Throwable ex) {
ex.printStackTrace();
new Thread() {
@Override
public void run() {
/* 彈個(gè)Toast告訴你程序出問(wèn)題了 */
Looper.prepare();
if (BuildConfig.DEBUG) {
Toast.makeText(context, "哦豁,程序問(wèn)題咯: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG).show();
}
Looper.loop();
/* 等三秒 */
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/* 然后結(jié)束應(yīng)用程序 */
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
}.start();
}
/**
* 未登錄異常處理
*/
private void handleNotSignException() {
/* 其實(shí)你可以定義一個(gè)接口,然后使用回調(diào)的方式處理,此處為了簡(jiǎn)單明了的說(shuō)明問(wèn)題,不做詳細(xì)解釋 */
context.startActivity(new Intent(context, SignInActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
}
首先是保證全局異常捕獲的單例,然后是一貫的實(shí)現(xiàn) Thread.UncaughtExceptionHandler 接口來(lái)捕獲異常,重點(diǎn)在 looperException() 方法, looperException() 方法通過(guò)接管 Looper 來(lái)實(shí)現(xiàn)異常捕獲,并將捕獲到的異常進(jìn)行處理,然后將捕獲到的 NotSignException 異常信息交由 handleNotSignException() 方法處理,從而達(dá)到異常不拋給虛擬機(jī)又實(shí)現(xiàn)了異常信息收集和代碼執(zhí)行截?cái)嗟墓δ?是不是很爽?
最重要的一點(diǎn)是,千萬(wàn)要記得在你的Application的onCeate()方法中初始化全局異常捕獲,否則你的應(yīng)用程序點(diǎn)哪那崩,加上這句就是點(diǎn)哪都不會(huì)崩
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
/* 初始化全局異常捕獲,這個(gè)很重要,不加這句你的應(yīng)用就是點(diǎn)哪哪崩 */
GlobalCrashCapture.instance().init(this);
}
}

上邊的代碼,只要 Storage 中的 isSign 為 false , 你在任何地方調(diào)用 Sign.isSign() 方法,其后邊的代碼都不會(huì)執(zhí)行,用在 登陸驗(yàn)證,輸入檢查,接口請(qǐng)求,Bean對(duì)象非空驗(yàn)證等 地方簡(jiǎn)直就是爽歪歪,當(dāng)然適用場(chǎng)景不止這些,更多的還有待小伙伴們?nèi)ヌ剿?/p>
干貨分享
上邊的源碼我已經(jīng)上傳Github,有需要的小伙伴可以自己拉下來(lái)研究研究,注釋到都還是有的,只不過(guò)這只是寫(xiě)個(gè)Demo而已,有些地方代碼沒(méi)寫(xiě)很詳細(xì),也就簡(jiǎn)單的表述一下我要實(shí)現(xiàn)的功能而已
源碼地址: https://github.com/scvax/Intercept_Android
如果有疑問(wèn),可以在本文下方留言,在我的能力范圍內(nèi)我還是很樂(lè)意解答的,我上簡(jiǎn)書(shū)很勤的哦~~
