Android | 使用 AspectJ 限制按鈕快速點選
前言
在Android開發中,限制按鈕快速點選(按鈕防抖)是一個常見的需求;
在這篇文章裡,我將介紹一種使用AspectJ的方法,基於註解處理器 & 執行時註解反射的原理。如果能幫上忙,請務必點贊加關注,這真的對我非常重要。
目錄
1. 定義需求
在開始講解之前,我們先 定義需求,具體描述如下:
- 限制快速點選需求 示意圖:
2. 常規處理方法
目前比較常見的限制快速點選的處理方法有以下兩種,具體如下:
2.1 封裝代理類
封裝一個代理類
處理點選事件,代理類通過判斷點選間隔決定是否攔截點選事件,具體程式碼如下:
// 代理類
public abstract class FastClickListener implements View.OnClickListener {
private long mLastClickTime;
private long interval = 1000L;
public FastClickListener() {
}
public FastClickListener(long interval) {
this.interval = interval;
}
@Override
public void onClick(View v) {
long currentTime = System.currentTimeMillis();
if (currentTime - mLastClickTime > interval) {
// 經過了足夠長的時間,允許點選
onClick();
mLastClickTime = nowTime;
}
}
protected abstract void onClick();
}
在需要限制快速點選的地方使用該代理類,具體如下:
tv.setOnClickListener(new FastClickListener() {
@Override
protected void onClick() {
// 處理點選邏輯
}
});
2.2 RxAndroid 過濾表示式
使用RxJava
的過濾表示式throttleFirst
也可以限制快速點選,具體如下:
RxView.clicks(view)
.throttleFirst(1, TimeUnit.SECONDS)
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
// 處理點選邏輯
}
});
2.3 小結
代理類
和RxAndroid過濾表示式
這兩種處理方法都存在兩個缺點:
- 1. 侵入核心業務邏輯,需要將程式碼替換到需要限制點選的地方;
- 2. 修改工作量大,每一個增加限制點選的地方都要修改程式碼。
我們需要一種方案能夠規避這兩個缺點 —— AspectJ
。 AspectJ
是一個流行的Java
AOP(aspect-oriented programming)
程式設計擴充套件框架,若還不瞭解,請務必檢視文章:《Android | 一文帶你全面瞭解 AspectJ 框架》
3. 詳細步驟
在下面的內容裡,我們將使用AspectJ
框架,把限制快速點選的邏輯作為核心關注點
從業務邏輯中抽離出來,單獨維護。具體步驟如下:
步驟1:新增AspectJ
依賴
- 1.依賴滬江的
AspectJX
Gradle外掛 —— 在專案build.gradle
中新增外掛依賴:
// 專案級build.gradle
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'
}
如果外掛下載速度過慢,可以直接依賴外掛 jar檔案,將外掛下載到專案根目錄(如/plugins),然後在專案build.gradle
中新增外掛依賴:
// 專案級build.gradle
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath fileTree(dir:'plugins', include:['*.jar'])
}
- 2.應用外掛 —— 在
App Module
的build.gradle
中應用外掛:
// App Module的build.gradle
apply plugin: 'android-aspectjx'
...
- 3.依賴AspectJ框架 —— 在包含
AspectJ
程式碼的Module
的build.gradle
檔案中新增依賴:
// Module級build.gradle
dependencies {
...
api 'org.aspectj:aspectjrt:1.8.9'
...
}
步驟2:實現判斷快速點選的工具類
- 我們先實現一個判斷
View
是否快速點選的工具類; - 實現原理是使用
View
的tag
屬性儲存最近一次的點選時間,每次點選時判斷當前時間距離儲存的時間是否已經經過了足夠長的時間; - 為了避免呼叫
View#setTag(int key,Object tag)
時傳入的key
與其他地方傳入的key
衝突而造成覆蓋,務必使用在資原始檔中定義的 id,資原始檔中的 id 能夠有效保證全域性唯一性,具體如下:
// ids.xml
<resources>
<item type="id" name="view_click_time" />
</resources>
public class FastClickCheckUtil {
/**
* 判斷是否屬於快速點選
*
* @param view 點選的View
* @param interval 快速點選的閾值
* @return true:快速點選
*/
public static boolean isFastClick(@NonNull View view, long interval) {
int key = R.id.view_click_time;
// 最近的點選時間
long currentClickTime = System.currentTimeMillis();
if(null == view.getTag(key)){
// 1\. 第一次點選
// 儲存最近點選時間
view.setTag(key, currentClickTime);
return false;
}
// 2\. 非第一次點選
// 上次點選時間
long lastClickTime = (long) view.getTag(key);
if(currentClickTime - lastClickTime < interval){
// 未超過時間間隔,視為快速點選
return true;
}else{
// 儲存最近點選時間
view.setTag(key, currentClickTime);
return false;
}
}
}
步驟3:定義Aspect
切面
使用@Aspect註解
定義一個切面
,使用該註解修飾的類會被AspectJ編譯器
識別為切面類:
@Aspect
public class FastClickCheckerAspect {
// 隨後填充
}
步驟4:定義PointCut
切入點
使用@Pointcut註解
定義一個切入點
,編譯期AspectJ編譯器
將搜尋所有匹配的JoinPoint
,執行織入:
@Aspect
public class FastClickAspect {
// 定義一個切入點:View.OnClickListener#onClick()方法
@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")
public void methodViewOnClick() {
}
// 隨後填充 Advice
}
步驟5:定義Advice
增強
增強的方式有很多種,在這裡我們使用@Around註解
定義環繞增強
,它將包裝PointCut
,在PointCut
前後增加橫切邏輯,具體如下:
@Aspect
public class FastClickAspect {
// 定義切入點:View.OnClickListener#onClick()方法
@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")
public void methodViewOnClick() {}
// 定義環繞增強,包裝methodViewOnClick()切入點
@Around("methodViewOnClick()")
public void aroundViewOnClick(ProceedingJoinPoint joinPoint) throws Throwable {
// 取出目標物件
View target = (View) joinPoint.getArgs()[0];
// 根據點選間隔是否超過2000,判斷是否為快速點選
if (!FastClickCheckUtil.isFastClick(target, 2000)) {
joinPoint.proceed();
}
}
}
步驟6:實現View.OnClickListener
在這一步我們為View
設定OnClickListener
,可以看到我們並沒有新增限制快速點選的相關程式碼,增強的邏輯對原有邏輯沒有侵入,具體程式碼如下:
// 原始碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.text).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("AspectJ","click");
}
});
}
}
編譯程式碼,隨後反編譯AspectJ編譯器
執行織入後的.class檔案
。還不瞭解如何查詢編譯後的.class檔案
,請務必檢視文章:《Android | 一文帶你全面瞭解 AspectJ 框架》
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(2131361820);
findViewById(2131165349).setOnClickListener(new View.OnClickListener() {
private static final JoinPoint.StaticPart ajc$tjp_0;
// View.OnClickListener#onClick()
public void onClick(View v) {
View view = v;
// 重構JoinPoint,執行環繞增強,也執行@Around修飾的方法
JoinPoint joinPoint = Factory.makeJP(ajc$tjp_0, this, this, view);
onClick_aroundBody1$advice(this, view, joinPoint, FastClickAspect.aspectOf(), (ProceedingJoinPoint)joinPoint);
}
static {
ajc$preClinit();
}
private static void ajc$preClinit() {
Factory factory = new Factory("MainActivity.java", null.class);
ajc$tjp_0 = factory.makeSJP("method-execution", (Signature)factory.makeMethodSig("1", "onClick", "com.have.a.good.time.aspectj.MainActivity$1", "android.view.View", "v", "", "void"), 25);
}
// 原來在View.OnClickListener#onClick()中的程式碼,相當於核心業務邏輯
private static final void onClick_aroundBody0(null ajc$this, View v, JoinPoint param1JoinPoint) {
Log.i("AspectJ", "click");
}
// @Around方法中的程式碼,即原始碼中的aroundViewOnClick(),相當於Advice
private static final void onClick_aroundBody1$advice(null ajc$this, View v, JoinPoint thisJoinPoint, FastClickAspect ajc$aspectInstance, ProceedingJoinPoint joinPoint) {
View target = (View)joinPoint.getArgs()[0];
if (!FastClickCheckUtil.isFastClick(target, 2000)) {
// 非快速點選,執行點選邏輯
ProceedingJoinPoint proceedingJoinPoint = joinPoint;
onClick_aroundBody0(ajc$this, v, (JoinPoint)proceedingJoinPoint);
null;
}
}
});
}
}
小結
到這裡,我們就講解完使用AspectJ框架
限制按鈕快速點選的詳細,總結如下:
- 使用
@Aspect註解
描述一個切面
,使用該註解修飾的類會被AspectJ編譯器
識別為切面類; - 使用
@Pointcut註解
定義一個切入點
,編譯期AspectJ編譯器
將搜尋所有匹配的JoinPoint
,執行織入; - 使用
@Around註解
定義一個增強
,增強會被織入匹配的JoinPoint
4. 演進
現在,我們迴歸文章開頭定義的需求,總共有4點。其中前兩點使用目前的方案中已經能夠實現,現在我們關注後面兩點,即允許定製時間間隔與覆蓋儘可能多的點選場景。
- 需求迴歸 示意圖:
4.1 定製時間間隔
在實際專案不同場景中的按鈕,往往需要限制不同的點選時間間隔,因此我們需要有一種簡便的方式用於定製不同場景的時間間隔,或者對於一些不需要限制快速點選的地方,有辦法跳過快速點選判斷,具體方法如下:
- 定義註解
/**
* 在需要定製時間間隔地方新增@FastClick註解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FastClick {
long interval() default FastClickAspect.FAST_CLICK_INTERVAL_GLOBAL;
}
- 修改切面類的
Advice
@Aspect
public class SingleClickAspect {
public static final long FAST_CLICK_INTERVAL_GLOBAL = 1000L;
@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")
public void methodViewOnClick() {}
@Around("methodViewOnClick()")
public void aroundViewOnClick(ProceedingJoinPoint joinPoint) throws Throwable {
// 取出JoinPoint的簽名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 取出JoinPoint的方法
Method method = methodSignature.getMethod();
// 1\. 全域性統一的時間間隔
long interval = FAST_CLICK_INTERVAL_GLOBAL;
if (method.isAnnotationPresent(FastClick.class)) {
// 2\. 如果方法使用了@FastClick修飾,取出定製的時間間隔
FastClick singleClick = method.getAnnotation(FastClick.class);
interval = singleClick.interval();
}
// 取出目標物件
View target = (View) joinPoint.getArgs()[0];
// 3\. 根據點選間隔是否超過interval,判斷是否為快速點選
if (!FastClickCheckUtil.isFastClick(target, interval)) {
joinPoint.proceed();
}
}
}
- 使用註解
findViewById(R.id.text).setOnClickListener(new View.OnClickListener() {
@FastClick(interval = 5000L)
@Override
public void onClick(View v) {
Log.i("AspectJ","click");
}
});
4.2 完整場景覆蓋
ButterKnife @OnClick android:onClick OK RecyclerView / ListView Java Lambda NO Kotlin Lambda OK DataBinding OK
Editting...
作者:Android進階架構
連結:https://www.jianshu.com/p/597894717331
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
相關文章
- Android 點選按鈕跳轉Android
- Android處理按鈕重複點選Android
- Android優雅地處理按鈕重複點選Android
- Android開發 如何使用選擇器(selector) 來實現點選按鈕變色Android
- JavaScript 點選按鈕返回底部JavaScript
- 防止頁面按鈕多次點選
- 快速搭建直播平臺,點選按鈕(Button)後改變顏色
- jQuery點選按鈕刪除div元素jQuery
- JavaScript點選按鈕彈出層效果JavaScript
- JavaScript點選按鈕返回底部詳解JavaScript
- android短視訊開發,點選分享按鈕生成分享連結Android
- 單選多選按鈕
- uniapp點選按鈕提交textarea值為undifineAPP
- VC 點陣圖按鈕CBitmapButton的使用
- 異形按鈕的點選區域處理
- 點選一個按鈕使其樣式發生變化,再點選另一個按鈕發生同樣變化,但上一個按鈕樣式復原
- 我的前端元件—-多個按鈕快速點選只執行最後一次。前端元件
- 點選按鈕自動複製剪貼簿功能
- 點選大中小按鈕設定文章字型大小
- ASPxGridView中Command列自定義按鈕點選事件概要View事件
- 刪除按鈕點選後的虛線輪廓
- 基於js實現點選按鈕回到頂部JS
- 對於防止按鈕重複點選的嘗試
- 如何使用 Bootstrap class 向按鈕新增下拉選單boot
- 是否應該在未選中行時禁用刪除按鈕,還是應該在點選按鈕時提示選擇資料?
- js點選按鈕劃出選單容器第一版JS
- radio 單選按鈕 選中多個
- HTML input radio單選按鈕HTML
- HTML input radio 單選按鈕HTML
- 使用 Busy Dialog 動畫阻止 SAP UI5 應用按鈕短時間內快速被點選試讀版動畫UI
- 點選同一按鈕顯示隱藏切換
- VBA 控制元件學習筆記(按鈕點選事件)控制元件筆記事件
- 手機直播原始碼,點選按鈕,立即回到頂部原始碼
- 直播軟體搭建,點選按鈕自動回到頂部
- vue-button設定按鈕是否可點選狀態Vue
- 仿抖音點贊按鈕
- 使用SVG實現的一個Android播放/暫停按鈕SVGAndroid
- Android Material Design控制元件使用(二)——FloatButton TextInputEditText TextInputLayout 按鈕AndroidMaterial Design控制元件