問題介紹
在專案中,Activity多重跳轉一直是開發中最常見的問題,網上的解決方案很多,但是要怎麼解決才是最佳的往往才是頭疼的問題,我現在要講的是如何真正的解決這個問題而不留一絲Bug,先介紹幾種已有的方案以及優缺點。
AOP 面向切麵
這裡不講 AOP 的整合,如需瞭解請左拐百度,這裡只講優勢和劣勢。
textView.setOnClickListener(new OnClickListener() {
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 {
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 {
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 {
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 {
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;
}
}