當Activity跳轉偶遇單身多年的老漢
問題介紹

在專案中,Activity多重跳轉一直是開發中最常見的問題,網上的解決方案很多,但是要怎麼解決才是最佳的往往才是頭疼的問題,我現在要講的是如何真正的解決這個問題而不留一絲Bug,先介紹幾種已有的方案以及優缺點
AOP 面向切面
這裡不講 AOP 的整合,如需瞭解請左拐百度,這裡只講優勢和劣勢
textView.setOnClickListener(new OnClickListener() { @EnableFastOnClick @Override public void onClick(View v) { } });
-
優點:對 View 點選事件的方法進行註解,看起來比較簡潔
-
缺點:每一處 View 點選事件都要進行註解,開發成本較高,容易出現遺漏
Activity 啟動模式
<activity android:name=".ui.activity.XXXActivity" android:launchMode="singleTop" />
為 Activity 檔案中設定 singleTop,這裡複習一下 singleTop 啟動模式
singleTop:單一頂部模式,如果任務棧的棧頂存在這個要開啟的 Activity,不會重新的建立 Activity,而是複用已經存在的 Activity。保證棧頂如果存在,不會重複建立
-
優點:直接在清單檔案中設定 Activity 的啟動模式,簡單粗暴
-
缺點:每新增 Activity 都要設定啟動模式,並且只能指定singleTop,開發成本較高,容易出現遺漏
startActivity 攔截
首先,我們需要先造一個雙擊判斷工具類
public final class DoubleClickHelper { private static final long[] TIME_ARRAY = new long[2]; // 陣列的長度為2代表只記錄雙擊操作 /** * 是否在短時間內進行了雙擊操作 */ public static boolean isOnDoubleClick() { // 預設間隔時長 return isOnDoubleClick(1500); } /** * 是否在短時間內進行了雙擊操作 */ public static boolean isOnDoubleClick(int time) { System.arraycopy(TIME_ARRAY, 1, TIME_ARRAY, 0, TIME_ARRAY.length - 1); TIME_ARRAY[TIME_ARRAY.length - 1] = SystemClock.uptimeMillis(); return TIME_ARRAY[0] >= (SystemClock.uptimeMillis() - time); } }
重寫 Activity 的 startActivity 方法
public abstract class BaseActivity extends AppCompatActivity { @Override public void startActivity(Intent intent) { if (DoubleClickHelper.isOnDoubleClick(500)) { return; } super.startActivity(intent); } }
這樣寫其實存在一個漏洞,讓我們看 Activity 的跳轉方法

我想大家的第一眼感覺是和我一樣的,這是神馬?我難道要重寫那麼多個?
遇到這種問題,一般菜鳥抱大腿的流程:
-
菜鳥:遇到不會的問題怎麼辦?
-
老鳥:不會百度啊!百度不會嗎?
-
菜鳥:百度不行怎麼辦?
-
老鳥:百度不行就換谷歌啊!
-
菜鳥:谷歌也不行怎麼辦?
-
老鳥:原始碼是最好的老師!
這裡只是講個段子,接下來讓我們通過檢視原始碼來解決這個問題,先看 startActivity 的原始碼

這裡呼叫了同名不同參的方法,再看

原來 startActivity 最終還是要回調 startActivityForResult


從這裡看到 startActivityForResult 兩個方法,引數短的方法還是呼叫了引數長的方法,這裡我們只需要重寫那個引數長的方法即可,那我們不能用剛剛那種方式了,把 startActivity 換成 startActivityForResult
public abstract class BaseActivity extends AppCompatActivity { @Override public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { if (DoubleClickHelper.isOnDoubleClick(500)) { return; } super.startActivityForResult(intent, requestCode, options); } }
其實這樣還存在一個問題,如果這個介面需要多重跳轉怎麼辦呢?這樣直接寫死 BaseActivity 是不是不利於擴充套件?
這個問題解決也很簡單,在 BaseActivity 預留一個方法,子類可以重寫這個方法來決定是否要檢查和判斷 Activity 多重跳轉的問題
public abstract class BaseActivity extends AppCompatActivity { @Override public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { if (isCheckActivityJump() && DoubleClickHelper.isOnDoubleClick(500)) { return; } // 檢視原始碼得知 startActivity 最終也會呼叫 startActivityForResult super.startActivityForResult(intent, requestCode, options); } /** * 是否檢查 Activity 跳轉頻率,避免重複跳轉 */ protected boolean isCheckActivityJump() { // 預設需要檢查和判斷 return true; } }
其實就這兩句程式碼,非常簡單,接下來總結一下
-
優點:基類處理,一勞永逸,開發成本極低
-
缺點:不能精準的判斷跳轉的 Activity 是否是重複的,也就是說如果同時跳轉兩個不同的 Activity,結果只有第一個成功跳轉,而第二個卻沒有跳轉
startActivityForResult 攔截優化
上一個解決方案還殘留著Bug,追求完美的我們怎能容許這種事情的發生,接下來讓我們來給這個問題畫上圓滿的句號
首先要想知道重複跳轉的 Activity 是不是同一個,我們可以通過 Intent 這個物件來進行判斷,不過在此之前我們要先複習一下 Activity 的啟動方式
-
顯式意圖啟動
-
構造方法:new Intent(Context packageContext, Class<?> cls)
-
物件方法:intent.setClass(Context packageContext, Class<?> cls)
-
-
隱式意圖啟動
-
構造方法:new Intent(String action)
-
物件方法:intent.setAction(String action)
-
這裡已經列出這兩種啟動方式的使用了,我們可以利用顯式意圖和隱式意圖來分別建立一個 Tag 標記,用於判斷跳轉的 Activity 是否是重複的
// 標記物件 String tag; if (intent.getComponent() != null) { // 顯式跳轉 tag = intent.getComponent().getClassName(); }else if (intent.getAction() != null) { // 隱式跳轉 tag = intent.getAction(); }
除了判斷是否重複了之外,還需要再判斷跳轉時間間隔
if (tag.equals(mActivityJumpTag) && mActivityJumpTime >= SystemClock.uptimeMillis() - 500) { // 檢查不通過 result = false; }
完整程式碼如下
public abstract class BaseActivity extends AppCompatActivity { @Override public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { if (startActivitySelfCheck(intent)) { // 檢視原始碼得知 startActivity 最終也會呼叫 startActivityForResult super.startActivityForResult(intent, requestCode, options); } } private String mActivityJumpTag; private long mActivityJumpTime; /** * 檢查當前 Activity 是否重複跳轉了,不需要檢查則重寫此方法並返回 true 即可 * * @param intent用於跳轉的 Intent 物件 * @return檢查通過返回true, 檢查不通過返回false */ protected boolean startActivitySelfCheck(Intent intent) { // 預設檢查通過 boolean result = true; // 標記物件 String tag; if (intent.getComponent() != null) { // 顯式跳轉 tag = intent.getComponent().getClassName(); }else if (intent.getAction() != null) { // 隱式跳轉 tag = intent.getAction(); }else { return result; } if (tag.equals(mActivityJumpTag) && mActivityJumpTime >= SystemClock.uptimeMillis() - 500) { // 檢查不通過 result = false; } // 記錄啟動標記和時間 mActivityJumpTag = tag; mActivityJumpTime = SystemClock.uptimeMillis(); return result; } }
以上程式碼已經過嚴格測試,沒有任何問題,再總結一下
-
優點:完美無缺
-
缺點:不存在的