當Activity跳轉偶遇單身多年的老漢

本文章已受權鴻洋微信公衆號轉載:當Activity跳轉偶遇單身多年的老漢

問題介紹

在項目中,Activity多重跳轉一直是開發中最多見的問題,網上的解決方案不少,可是要怎麼解決纔是最佳的每每纔是頭疼的問題,我如今要講的是如何真正的解決這個問題而不留一絲Bug,先介紹幾種已有的方案以及優缺點android

AOP 面向切面

這裏不講 AOP 的集成,如需瞭解請左拐百度,這裏只講優點和劣勢git

textView.setOnClickListener(new OnClickListener() {

    @EnableFastOnClick
    @Override
    public void onClick(View v) {
		
    }
});
複製代碼
  • 優勢:對 View 點擊事件的方法進行註解,看起來比較簡潔github

  • 缺點:每一處 View 點擊事件都要進行註解,開發成本較高,容易出現遺漏數組

Activity 啓動模式

<activity
    android:name=".ui.activity.XXXActivity"
    android:launchMode="singleTop" />
複製代碼

爲 Activity 文件中設置 singleTop,這裏複習一下 singleTop 啓動模式微信

singleTop:單一頂部模式,若是任務棧的棧頂存在這個要開啓的 Activity,不會從新的建立 Activity,而是複用已經存在的 Activity。保證棧頂若是存在,不會重複建立ide

  • 優勢:直接在清單文件中設置 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 方法ui

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;
    }
}
複製代碼

以上代碼已通過嚴格測試,沒有任何問題,再總結一下

  • 優勢:天衣無縫

  • 缺點:不存在的

此解決方案已經加入啃得香豪華套餐:AndroidProject,歡迎各位提 issue,歡迎 star

Android技術討論Q羣:78797078

相關文章
相關標籤/搜索