歡迎光臨
每天分享高質量文章

當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;
}
}

此解決方案已經加入啃得香豪華套餐:

https://github.com/getActivity/AndroidProject

歡迎各位提 issue,歡迎 star。

贊(0)

分享創造快樂