在項目中,Activity多重跳轉一直是開發中最多見的問題,網上的解決方案不少,可是要怎麼解決纔是最佳的每每纔是頭疼的問題,我如今要講的是如何真正的解決這個問題而不留一絲Bug,先介紹幾種已有的方案以及優缺點android
這裏不講 AOP 的集成,如需瞭解請左拐百度,這裏只講優點和劣勢git
textView.setOnClickListener(new OnClickListener() {
@EnableFastOnClick
@Override
public void onClick(View v) {
}
});
複製代碼
優勢:對 View 點擊事件的方法進行註解,看起來比較簡潔github
缺點:每一處 View 點擊事件都要進行註解,開發成本較高,容易出現遺漏數組
<activity
android:name=".ui.activity.XXXActivity"
android:launchMode="singleTop" />
複製代碼
爲 Activity 文件中設置 singleTop,這裏複習一下 singleTop 啓動模式微信
singleTop:單一頂部模式,若是任務棧的棧頂存在這個要開啓的 Activity,不會從新的建立 Activity,而是複用已經存在的 Activity。保證棧頂若是存在,不會重複建立ide
優勢:直接在清單文件中設置 Activity 的啓動模式,簡單粗暴工具
缺點:每新增 Activity 都要設置啓動模式,而且只能指定singleTop,開發成本較高,容易出現遺漏測試
首先,咱們須要先造一個雙擊判斷工具類優化
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,結果只有第一個成功跳轉,而第二個卻沒有跳轉
上一個解決方案還殘留着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;
}
}
複製代碼
以上代碼已通過嚴格測試,沒有任何問題,再總結一下
優勢:天衣無縫
缺點:不存在的