轉載自https://www.jianshu.com/p/b96a68ba50db
AOP:面向切面編程(Aspect-Oriented Programming)。如果說,OOP如果是把問題劃分到單個模塊的話,那麼AOP就是把涉及到衆多模塊的某一類問題進行統一管理。 Aspect介紹篇:Android中的AOP編程 這裏通過幾個小例子,講解在Android開發中,如何運用AOP的方式,進行全局切片管理,達到簡潔優雅,一勞永逸的效果。
1、SingleClickAspect,防止View被連續點擊出發多次事件
在使用aop之前,可以這樣寫了單獨寫個Click類(不優雅)或者RxBinding(不簡潔):
-
RxView.clicks(mButton)
-
.throttleFirst(1, TimeUnit.SECONDS)
-
.subscribe(new Action1<Void>() {
-
@Override
-
public void call(Void v) {
-
dosomething();
-
}
-
});
現在,只需要一個註解,就可以輕鬆解決一切問題:
-
@Aspect
-
public class SingleClickAspect {
-
static int TIME_TAG = R.id.click_time;
-
public static final int MIN_CLICK_DELAY_TIME = 600;//間隔時間600ms
-
@Pointcut("execution(@com.app.annotation.aspect.SingleClick * *(..))")//根據SingleClick註解找到方法切入點
-
public void methodAnnotated() {
-
}
-
@Around("methodAnnotated()")//在連接點進行方法替換
-
public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
-
View view = null;
-
for (Object arg : joinPoint.getArgs())
-
if (arg instanceof View) view = (View) arg;
-
if (view != null) {
-
Object tag = view.getTag(TIME_TAG);
-
long lastClickTime = ((tag != null) ? (long) tag : 0);
-
LogUtils.showLog("SingleClickAspect", "lastClickTime:" + lastClickTime);
-
long currentTime = Calendar.getInstance().getTimeInMillis();
-
if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {//過濾掉600毫秒內的連續點擊
-
view.setTag(TIME_TAG, currentTime);
-
LogUtils.showLog("SingleClickAspect", "currentTime:" + currentTime);
-
joinPoint.proceed();//執行原方法
-
}
-
}
-
}
-
}
使用方法:標註在onClick上
-
@SingleClick
-
public void onClick(View view) {
-
String comment = mViewBinding.btComment.getText().toString();
-
if (TextUtils.isEmpty(comment))
-
Snackbar.make(mViewBinding.fab, "評論不能爲空!", Snackbar.LENGTH_LONG).show();
-
else mPresenter.createComment(comment, mArticle, SpUtil.getUser());
-
}
或者任何參數內有view可以做爲參照系(view可以不是onClick的view,僅僅作爲時間tag依附對象作爲參照)的方法上,例如TRouter的頁面跳轉,防止連續快速點擊重複跳頁現象:
-
public class RouterHelper {
-
@SingleClick // 防止連續點擊
-
public static void go(String actionName, HashMap data, View view) {
-
TRouter.go(actionName, data, view);
-
}
-
}
2、CheckLoginAspect 攔截未登錄用戶的權限
不使用aop的情況,需要在每個方法體內判斷用戶登錄狀態,然後處理,現在,只需要一個註解輕鬆解決:
-
/**
-
* Created by baixiaokang
-
* 通過CheckLogin註解檢查用戶是否登陸註解,通過aop切片的方式在編譯期間織入源代碼中
-
* 功能:檢查用戶是否登陸,未登錄則提示登錄,不會執行下面的邏輯
-
*/
-
@Aspect
-
public class CheckLoginAspect {
-
@Pointcut("execution(@com.app.annotation.aspect.CheckLogin * *(..))")//方法切入點
-
public void methodAnnotated() {
-
}
-
@Around("methodAnnotated()")//在連接點進行方法替換
-
public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
-
if (null == SpUtil.getUser()) {
-
Snackbar.make(App.getAppContext().getCurActivity().getWindow().getDecorView(), "請先登錄!", Snackbar.LENGTH_LONG)
-
.setAction("登錄", new View.OnClickListener() {
-
@Override
-
public void onClick(View view) {
-
TRouter.go(C.LOGIN);
-
}
-
}).show();
-
return;
-
}
-
joinPoint.proceed();//執行原方法
-
}
-
}
使用方法:
-
public class AdvisePresenter extends AdviseContract.Presenter {
-
@CheckLogin
-
public void createMessage(String msg) {
-
_User user = SpUtil.getUser();
-
ApiFactory.createMessage(
-
new Message(ApiUtil.getPointer(
-
new _User(C.ADMIN_ID)), msg,
-
ApiUtil.getPointer(user),
-
user.objectId))
-
.subscribe(
-
res -> mView.sendSuc(),
-
e -> mView.showMsg("消息發送失敗!"));
-
}
-
@CheckLogin
-
public void initAdapterPresenter(AdapterPresenter mAdapterPresenter) {
-
mAdapterPresenter
-
.setRepository(ApiFactory::getMessageList)
-
.setParam(C.INCLUDE, C.CREATER)
-
.setParam(C.UID, SpUtil.getUser().objectId)
-
.fetch();
-
}
-
}
從此只需要專注主要邏輯即可。
3、MemoryCacheAspect內存緩存切片
根據參數key緩存方法返回值,使我們純淨的Presenter(無參構造和無內部狀態)達到全局緩存的單例複用效果,同樣適用於其他需要緩存結果的方法:
-
/**
-
* Created by baixiaokang on 16/10/24.
-
* 根據MemoryCache註解自動添加緩存代理代碼,通過aop切片的方式在編譯期間織入源代碼中
-
* 功能:緩存某方法的返回值,下次執行該方法時,直接從緩存裏獲取。
-
*/
-
@Aspect
-
public class MemoryCacheAspect {
-
@Pointcut("execution(@com.app.annotation.aspect.MemoryCache * *(..))")//方法切入點
-
public void methodAnnotated() {
-
}
-
@Around("methodAnnotated()")//在連接點進行方法替換
-
public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
-
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
-
String methodName = methodSignature.getName();
-
MemoryCacheManager mMemoryCacheManager = MemoryCacheManager.getInstance();
-
StringBuilder keyBuilder = new StringBuilder();
-
keyBuilder.append(methodName);
-
for (Object obj : joinPoint.getArgs()) {
-
if (obj instanceof String) keyBuilder.append((String) obj);
-
else if (obj instanceof Class) keyBuilder.append(((Class) obj).getSimpleName());
-
}
-
String key = keyBuilder.toString();
-
Object result = mMemoryCacheManager.get(key);//key規則 : 方法名+參數1+參數2+...
-
LogUtils.showLog("MemoryCache", "key:" + key + "--->" + (result != null ? "not null" : "null"));
-
if (result != null) return result;//緩存已有,直接返回
-
result = joinPoint.proceed();//執行原方法
-
if (result instanceof List && result != null && ((List) result).size() > 0 //列表不爲空
-
|| result instanceof String && !TextUtils.isEmpty((String) result)//字符不爲空
-
|| result instanceof Object && result != null)//對象不爲空
-
mMemoryCacheManager.add(key, result);//存入緩存
-
LogUtils.showLog("MemoryCache", "key:" + key + "--->" + "save");
-
return result;
-
}
-
}
看看Apt生成的Factory:
-
/**
-
* @ 實例化工廠 此類由apt自動生成 */
-
public final class InstanceFactory {
-
/**
-
* @此方法由apt自動生成 */
-
@MemoryCache
-
public static Object create(Class mClass) throws IllegalAccessException, InstantiationException {
-
switch (mClass.getSimpleName()) {
-
case "AdvisePresenter": return new AdvisePresenter();
-
case "ArticlePresenter": return new ArticlePresenter();
-
case "HomePresenter": return new HomePresenter();
-
case "LoginPresenter": return new LoginPresenter();
-
case "UserPresenter": return new UserPresenter();
-
default: return mClass.newInstance();
-
}
-
}
-
}
從此Presenter就是全局單例的可複用狀態。
4、TimeLogAspect 自動打印方法的耗時
經常遇到需要log一個耗時操作究竟執行了多長時間,無aop時,需要每個方法體內添加代碼,現在,只需要一個註解就可以一勞永逸:
-
/**
-
* 根據註解TimeLog自動添加打印方法耗代碼,通過aop切片的方式在編譯期間織入源代碼中
-
* 功能:自動打印方法的耗時
-
*/
-
@Aspect
-
public class TimeLogAspect {
-
@Pointcut("execution(@com.app.annotation.aspect.TimeLog * *(..))")//方法切入點
-
public void methodAnnotated() {
-
}
-
@Pointcut("execution(@com.app.annotation.aspect.TimeLog *.new(..))")//構造器切入點
-
public void constructorAnnotated() {
-
}
-
@Around("methodAnnotated() || constructorAnnotated()")//在連接點進行方法替換
-
public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
-
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
-
LogUtils.showLog("TimeLog getDeclaringClass", methodSignature.getMethod().getDeclaringClass().getCanonicalName());
-
String className = methodSignature.getDeclaringType().getSimpleName();
-
String methodName = methodSignature.getName();
-
long startTime = System.nanoTime();
-
Object result = joinPoint.proceed();//執行原方法
-
StringBuilder keyBuilder = new StringBuilder();
-
keyBuilder.append(methodName + ":");
-
for (Object obj : joinPoint.getArgs()) {
-
if (obj instanceof String) keyBuilder.append((String) obj);
-
else if (obj instanceof Class) keyBuilder.append(((Class) obj).getSimpleName());
-
}
-
String key = keyBuilder.toString();
-
LogUtils.showLog("TimeLog", (className + "." + key + joinPoint.getArgs().toString() + " --->:" + "[" + (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) + "ms]"));// 打印時間差
-
return result;
-
}
-
}
使用方法:
-
@TimeLog
-
public void onCreate() {
-
super.onCreate();
-
mApp = this;
-
SpUtil.init(this);
-
store = new Stack<>();
-
registerActivityLifecycleCallbacks(new SwitchBackgroundCallbacks());
-
}
從此方法耗時打印一個註解搞定!
5、SysPermissionAspect運行時權限申請
-
/**
-
* 申請系統權限切片,根據註解值申請所需運行權限
-
*/
-
@Aspect
-
public class SysPermissionAspect {
-
@Around("execution(@com.app.annotation.aspect.Permission * *(..)) && @annotation(permission)")
-
public void aroundJoinPoint(ProceedingJoinPoint joinPoint, Permission permission) throws Throwable {
-
AppCompatActivity ac = (AppCompatActivity) App.getAppContext().getCurActivity();
-
new AlertDialog.Builder(ac)
-
.setTitle("提示")
-
.setMessage("爲了應用可以正常使用,請您點擊確認申請權限。")
-
.setNegativeButton("取消", null)
-
.setPositiveButton("允許", new DialogInterface.OnClickListener() {
-
@Override
-
public void onClick(DialogInterface dialog, int which) {
-
MPermissionUtils.requestPermissionsResult(ac, 1, permission.value()
-
, new MPermissionUtils.OnPermissionListener() {
-
@Override
-
public void onPermissionGranted() {
-
try {
-
joinPoint.proceed();//獲得權限,執行原方法
-
} catch (Throwable e) {
-
e.printStackTrace();
-
}
-
}
-
@Override
-
public void onPermissionDenied() {
-
MPermissionUtils.showTipsDialog(ac);
-
}
-
});
-
}
-
})
-
.create()
-
.show();
-
}
-
}
使用方法:
-
@Permission(Manifest.permission.CAMERA)
-
public void takePhoto() {
-
startActivityForResult(
-
new Intent(MediaStore.ACTION_IMAGE_CAPTURE)
-
.putExtra(MediaStore.EXTRA_OUTPUT,
-
Uri.fromFile(new File(getExternalCacheDir()+ "user_photo.png"))),
-
C.IMAGE_REQUEST_CODE);
-
}
動態權限申請一步搞定。
除了這些簡單的示例,AOP還可以實現動態權限申請和其他用戶權限管理,包括功能性切片和邏輯性切片,使日常開發更加簡潔優雅,只需要關注重點業務邏輯,把其他的小事,都交給切片來自動處理吧。
更多AOP的實際應用,請關注項目T-MVP
或者加羣來搞基:
QQ羣:AndroidMVP 555343041
更新日誌:
2017/1/31:AOP新增SysPermissionAspect支持動態申請系統權限切片,輕鬆適配6.0+
2017/1/27:AOP新增DbRealmAspect支持Realm數據庫,數據庫突破你想像的簡單(年夜特供)
2017/1/8: 使用Apt封裝Retrofit生成ApiFactory替換掉所有的Repository,狂刪代碼
2017/1/7: 使用DataBinding替換掉所有的ButterKnife,狂刪代碼
2017/1/6: 使用DataBinding替換掉所有的ViewHolder,狂刪代碼,從此邁向新時代
2016/12/30:使用Apt生成全局路由TRouter,更優雅的頁面跳轉,支持傳遞參數和共享view轉場動畫
2016/12/29:去掉BaseMultiVH新增VHClassSelector支持更完美的多ViewHolder
2016/12/28:使用Apt生成全局的ApiFactory替代所有的Model
2016/12/27:增加了BaseMultiVH擴展支持多類型的ViewHolder
2016/12/26:抽離CoreAdapterPresenter優化TRecyclerView
安卓AOP實戰:面向切片編程
Android實用技巧之:用好泛型,少寫代碼
安卓AOP實戰:APT打造極簡路由
全局路由TRouter,更優雅的頁面跳轉
安卓AOP實戰:Javassist強擼EventBus
加入OkBus,實現註解傳遞事件
安卓AOP三劍客:APT,AspectJ,Javassist
1、去掉所有反射>2、新增apt初始化工廠,替換掉了dagger2。>3、新增aop切片,處理緩存和日誌