本文章已授權(quán)鴻洋微信公眾號(hào)轉(zhuǎn)載:當(dāng)Activity跳轉(zhuǎn)偶遇單身多年的老漢
問題介紹
在項(xiàng)目中搂根,Activity多重跳轉(zhuǎn)一直是開發(fā)中最常見的問題渣淤,網(wǎng)上的解決方案很多,但是要怎么解決才是最佳的往往才是頭疼的問題古劲,我現(xiàn)在要講的是如何真正的解決這個(gè)問題而不留一絲Bug竹揍,先介紹幾種已有的方案以及優(yōu)缺點(diǎn)
AOP 面向切面
這里不講 AOP 的集成恼五,如需了解請(qǐng)左拐百度镰矿,這里只講優(yōu)勢(shì)和劣勢(shì)
textView.setOnClickListener(new OnClickListener() {
@EnableFastOnClick
@Override
public void onClick(View v) {
}
});
優(yōu)點(diǎn):對(duì) View 點(diǎn)擊事件的方法進(jìn)行注解,看起來比較簡(jiǎn)潔
缺點(diǎn):每一處 View 點(diǎn)擊事件都要進(jìn)行注解罢屈,開發(fā)成本較高嘀韧,容易出現(xiàn)遺漏
Activity 啟動(dòng)模式
<activity
android:name=".ui.activity.XXXActivity"
android:launchMode="singleTop" />
為 Activity 文件中設(shè)置 singleTop,這里復(fù)習(xí)一下 singleTop 啟動(dòng)模式
singleTop:?jiǎn)我豁敳磕J讲疲绻蝿?wù)棧的棧頂存在這個(gè)要開啟的 Activity锄贷,不會(huì)重新的創(chuàng)建 Activity,而是復(fù)用已經(jīng)存在的 Activity曼月。保證棧頂如果存在谊却,不會(huì)重復(fù)創(chuàng)建
優(yōu)點(diǎn):直接在清單文件中設(shè)置 Activity 的啟動(dòng)模式,簡(jiǎn)單粗暴
缺點(diǎn):每新增 Activity 都要設(shè)置啟動(dòng)模式哑芹,并且只能指定singleTop炎辨,開發(fā)成本較高,容易出現(xiàn)遺漏
startActivity 攔截
首先聪姿,我們需要先造一個(gè)雙擊判斷工具類
public final class DoubleClickHelper {
private static final long[] TIME_ARRAY = new long[2]; // 數(shù)組的長(zhǎng)度為2代表只記錄雙擊操作
/**
* 是否在短時(shí)間內(nèi)進(jìn)行了雙擊操作
*/
public static boolean isOnDoubleClick() {
// 默認(rèn)間隔時(shí)長(zhǎng)
return isOnDoubleClick(1500);
}
/**
* 是否在短時(shí)間內(nèi)進(jìn)行了雙擊操作
*/
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);
}
}
這樣寫其實(shí)存在一個(gè)漏洞碴萧,讓我們看 Activity 的跳轉(zhuǎn)方法
我想大家的第一眼感覺是和我一樣的乙嘀,這是神馬?我難道要重寫那么多個(gè)破喻?
遇到這種問題乒躺,一般菜鳥抱大腿的流程:
菜鳥:遇到不會(huì)的問題怎么辦?
老鳥:不會(huì)百度暗退酢!百度不會(huì)嗎曹货?
菜鳥:百度不行怎么辦咆繁?
老鳥:百度不行就換谷歌啊顶籽!
菜鳥:谷歌也不行怎么辦玩般?
老鳥:源碼是最好的老師!
這里只是講個(gè)段子礼饱,接下來讓我們通過查看源碼來解決這個(gè)問題坏为,先看 startActivity 的源碼
這里調(diào)用了同名不同參的方法,再看
原來 startActivity 最終還是要回調(diào) startActivityForResult
從這里看到 startActivityForResult 兩個(gè)方法镊绪,參數(shù)短的方法還是調(diào)用了參數(shù)長(zhǎng)的方法匀伏,這里我們只需要重寫那個(gè)參數(shù)長(zhǎng)的方法即可,那我們不能用剛剛那種方式了蝴韭,把 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);
}
}
其實(shí)這樣還存在一個(gè)問題够颠,如果這個(gè)界面需要多重跳轉(zhuǎn)怎么辦呢?這樣直接寫死 BaseActivity 是不是不利于擴(kuò)展榄鉴?
這個(gè)問題解決也很簡(jiǎn)單履磨,在 BaseActivity 預(yù)留一個(gè)方法,子類可以重寫這個(gè)方法來決定是否要檢查和判斷 Activity 多重跳轉(zhuǎn)的問題
public abstract class BaseActivity extends AppCompatActivity {
@Override
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (isCheckActivityJump() && DoubleClickHelper.isOnDoubleClick(500)) {
return;
}
// 查看源碼得知 startActivity 最終也會(huì)調(diào)用 startActivityForResult
super.startActivityForResult(intent, requestCode, options);
}
/**
* 是否檢查 Activity 跳轉(zhuǎn)頻率庆尘,避免重復(fù)跳轉(zhuǎn)
*/
protected boolean isCheckActivityJump() {
// 默認(rèn)需要檢查和判斷
return true;
}
}
其實(shí)就這兩句代碼剃诅,非常簡(jiǎn)單,接下來總結(jié)一下
優(yōu)點(diǎn):基類處理驶忌,一勞永逸矛辕,開發(fā)成本極低
缺點(diǎn):不能精準(zhǔn)的判斷跳轉(zhuǎn)的 Activity 是否是重復(fù)的,也就是說如果同時(shí)跳轉(zhuǎn)兩個(gè)不同的 Activity位岔,結(jié)果只有第一個(gè)成功跳轉(zhuǎn)如筛,而第二個(gè)卻沒有跳轉(zhuǎn)
startActivityForResult 攔截優(yōu)化
上一個(gè)解決方案還殘留著Bug,追求完美的我們?cè)跄苋菰S這種事情的發(fā)生抒抬,接下來讓我們來給這個(gè)問題畫上圓滿的句號(hào)
首先要想知道重復(fù)跳轉(zhuǎn)的 Activity 是不是同一個(gè)杨刨,我們可以通過 Intent 這個(gè)對(duì)象來進(jìn)行判斷,不過在此之前我們要先復(fù)習(xí)一下 Activity 的啟動(dòng)方式
-
顯式意圖啟動(dòng)
構(gòu)造方法:new Intent(Context packageContext, Class<?> cls)
對(duì)象方法:intent.setClass(Context packageContext, Class<?> cls)
-
隱式意圖啟動(dòng)
構(gòu)造方法:new Intent(String action)
對(duì)象方法:intent.setAction(String action)
這里已經(jīng)列出這兩種啟動(dòng)方式的使用了擦剑,我們可以利用顯式意圖和隱式意圖來分別創(chuàng)建一個(gè) Tag 標(biāo)記妖胀,用于判斷跳轉(zhuǎn)的 Activity 是否是重復(fù)的
// 標(biāo)記對(duì)象
String tag;
if (intent.getComponent() != null) { // 顯式跳轉(zhuǎn)
tag = intent.getComponent().getClassName();
}else if (intent.getAction() != null) { // 隱式跳轉(zhuǎn)
tag = intent.getAction();
}
除了判斷是否重復(fù)了之外芥颈,還需要再判斷跳轉(zhuǎn)時(shí)間間隔
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 最終也會(huì)調(diào)用 startActivityForResult
super.startActivityForResult(intent, requestCode, options);
}
}
private String mActivityJumpTag;
private long mActivityJumpTime;
/**
* 檢查當(dāng)前 Activity 是否重復(fù)跳轉(zhuǎn)了,不需要檢查則重寫此方法并返回 true 即可
*
* @param intent 用于跳轉(zhuǎn)的 Intent 對(duì)象
* @return 檢查通過返回true, 檢查不通過返回false
*/
protected boolean startActivitySelfCheck(Intent intent) {
// 默認(rèn)檢查通過
boolean result = true;
// 標(biāo)記對(duì)象
String tag;
if (intent.getComponent() != null) { // 顯式跳轉(zhuǎn)
tag = intent.getComponent().getClassName();
}else if (intent.getAction() != null) { // 隱式跳轉(zhuǎn)
tag = intent.getAction();
}else {
return result;
}
if (tag.equals(mActivityJumpTag) && mActivityJumpTime >= SystemClock.uptimeMillis() - 500) {
// 檢查不通過
result = false;
}
// 記錄啟動(dòng)標(biāo)記和時(shí)間
mActivityJumpTag = tag;
mActivityJumpTime = SystemClock.uptimeMillis();
return result;
}
}