AOP:面向切面編程

轉載自https://www.jianshu.com/p/b96a68ba50db

 


 

AOP:面向切面編程(Aspect-Oriented Programming)。如果說,OOP如果是把問題劃分到單個模塊的話,那麼AOP就是把涉及到衆多模塊的某一類問題進行統一管理。 Aspect介紹篇:Android中的AOP編程 這裏通過幾個小例子,講解在Android開發中,如何運用AOP的方式,進行全局切片管理,達到簡潔優雅,一勞永逸的效果。

1、SingleClickAspect,防止View被連續點擊出發多次事件

在使用aop之前,可以這樣寫了單獨寫個Click類(不優雅)或者RxBinding(不簡潔):


 
  1. RxView.clicks(mButton)

  2. .throttleFirst(1, TimeUnit.SECONDS)

  3. .subscribe(new Action1<Void>() {

  4.  
  5. @Override

  6. public void call(Void v) {

  7. dosomething();

  8. }

  9. });

  10.  

現在,只需要一個註解,就可以輕鬆解決一切問題:


 
  1. @Aspect

  2. public class SingleClickAspect {

  3. static int TIME_TAG = R.id.click_time;

  4. public static final int MIN_CLICK_DELAY_TIME = 600;//間隔時間600ms

  5.  
  6. @Pointcut("execution(@com.app.annotation.aspect.SingleClick * *(..))")//根據SingleClick註解找到方法切入點

  7. public void methodAnnotated() {

  8. }

  9.  
  10. @Around("methodAnnotated()")//在連接點進行方法替換

  11. public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

  12. View view = null;

  13. for (Object arg : joinPoint.getArgs())

  14. if (arg instanceof View) view = (View) arg;

  15. if (view != null) {

  16. Object tag = view.getTag(TIME_TAG);

  17. long lastClickTime = ((tag != null) ? (long) tag : 0);

  18. LogUtils.showLog("SingleClickAspect", "lastClickTime:" + lastClickTime);

  19. long currentTime = Calendar.getInstance().getTimeInMillis();

  20. if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {//過濾掉600毫秒內的連續點擊

  21. view.setTag(TIME_TAG, currentTime);

  22. LogUtils.showLog("SingleClickAspect", "currentTime:" + currentTime);

  23. joinPoint.proceed();//執行原方法

  24. }

  25. }

  26. }

  27. }

使用方法:標註在onClick上


 
  1. @SingleClick

  2. public void onClick(View view) {

  3. String comment = mViewBinding.btComment.getText().toString();

  4. if (TextUtils.isEmpty(comment))

  5. Snackbar.make(mViewBinding.fab, "評論不能爲空!", Snackbar.LENGTH_LONG).show();

  6. else mPresenter.createComment(comment, mArticle, SpUtil.getUser());

  7. }

或者任何參數內有view可以做爲參照系(view可以不是onClick的view,僅僅作爲時間tag依附對象作爲參照)的方法上,例如TRouter的頁面跳轉,防止連續快速點擊重複跳頁現象:


 
  1. public class RouterHelper {

  2.  
  3. @SingleClick // 防止連續點擊

  4. public static void go(String actionName, HashMap data, View view) {

  5. TRouter.go(actionName, data, view);

  6. }

  7. }

2、CheckLoginAspect 攔截未登錄用戶的權限

不使用aop的情況,需要在每個方法體內判斷用戶登錄狀態,然後處理,現在,只需要一個註解輕鬆解決:


 
  1.  
  2. /**

  3. * Created by baixiaokang

  4. * 通過CheckLogin註解檢查用戶是否登陸註解,通過aop切片的方式在編譯期間織入源代碼中

  5. * 功能:檢查用戶是否登陸,未登錄則提示登錄,不會執行下面的邏輯

  6. */

  7. @Aspect

  8. public class CheckLoginAspect {

  9.  
  10. @Pointcut("execution(@com.app.annotation.aspect.CheckLogin * *(..))")//方法切入點

  11. public void methodAnnotated() {

  12. }

  13.  
  14. @Around("methodAnnotated()")//在連接點進行方法替換

  15. public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

  16. if (null == SpUtil.getUser()) {

  17. Snackbar.make(App.getAppContext().getCurActivity().getWindow().getDecorView(), "請先登錄!", Snackbar.LENGTH_LONG)

  18. .setAction("登錄", new View.OnClickListener() {

  19. @Override

  20. public void onClick(View view) {

  21. TRouter.go(C.LOGIN);

  22. }

  23. }).show();

  24. return;

  25. }

  26. joinPoint.proceed();//執行原方法

  27. }

  28. }

使用方法:


 
  1. public class AdvisePresenter extends AdviseContract.Presenter {

  2.  
  3. @CheckLogin

  4. public void createMessage(String msg) {

  5. _User user = SpUtil.getUser();

  6. ApiFactory.createMessage(

  7. new Message(ApiUtil.getPointer(

  8. new _User(C.ADMIN_ID)), msg,

  9. ApiUtil.getPointer(user),

  10. user.objectId))

  11. .subscribe(

  12. res -> mView.sendSuc(),

  13. e -> mView.showMsg("消息發送失敗!"));

  14. }

  15.  
  16. @CheckLogin

  17. public void initAdapterPresenter(AdapterPresenter mAdapterPresenter) {

  18. mAdapterPresenter

  19. .setRepository(ApiFactory::getMessageList)

  20. .setParam(C.INCLUDE, C.CREATER)

  21. .setParam(C.UID, SpUtil.getUser().objectId)

  22. .fetch();

  23. }

  24. }

從此只需要專注主要邏輯即可。

3、MemoryCacheAspect內存緩存切片

根據參數key緩存方法返回值,使我們純淨的Presenter(無參構造和無內部狀態)達到全局緩存的單例複用效果,同樣適用於其他需要緩存結果的方法:


 
  1. /**

  2. * Created by baixiaokang on 16/10/24.

  3. * 根據MemoryCache註解自動添加緩存代理代碼,通過aop切片的方式在編譯期間織入源代碼中

  4. * 功能:緩存某方法的返回值,下次執行該方法時,直接從緩存裏獲取。

  5. */

  6. @Aspect

  7. public class MemoryCacheAspect {

  8.  
  9. @Pointcut("execution(@com.app.annotation.aspect.MemoryCache * *(..))")//方法切入點

  10. public void methodAnnotated() {

  11. }

  12.  
  13. @Around("methodAnnotated()")//在連接點進行方法替換

  14. public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

  15. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

  16. String methodName = methodSignature.getName();

  17. MemoryCacheManager mMemoryCacheManager = MemoryCacheManager.getInstance();

  18. StringBuilder keyBuilder = new StringBuilder();

  19. keyBuilder.append(methodName);

  20. for (Object obj : joinPoint.getArgs()) {

  21. if (obj instanceof String) keyBuilder.append((String) obj);

  22. else if (obj instanceof Class) keyBuilder.append(((Class) obj).getSimpleName());

  23. }

  24. String key = keyBuilder.toString();

  25. Object result = mMemoryCacheManager.get(key);//key規則 : 方法名+參數1+參數2+...

  26. LogUtils.showLog("MemoryCache", "key:" + key + "--->" + (result != null ? "not null" : "null"));

  27. if (result != null) return result;//緩存已有,直接返回

  28. result = joinPoint.proceed();//執行原方法

  29. if (result instanceof List && result != null && ((List) result).size() > 0 //列表不爲空

  30. || result instanceof String && !TextUtils.isEmpty((String) result)//字符不爲空

  31. || result instanceof Object && result != null)//對象不爲空

  32. mMemoryCacheManager.add(key, result);//存入緩存

  33. LogUtils.showLog("MemoryCache", "key:" + key + "--->" + "save");

  34. return result;

  35. }

  36. }

看看Apt生成的Factory:


 
  1. /**

  2. * @ 實例化工廠 此類由apt自動生成 */

  3. public final class InstanceFactory {

  4. /**

  5. * @此方法由apt自動生成 */

  6. @MemoryCache

  7. public static Object create(Class mClass) throws IllegalAccessException, InstantiationException {

  8. switch (mClass.getSimpleName()) {

  9. case "AdvisePresenter": return new AdvisePresenter();

  10. case "ArticlePresenter": return new ArticlePresenter();

  11. case "HomePresenter": return new HomePresenter();

  12. case "LoginPresenter": return new LoginPresenter();

  13. case "UserPresenter": return new UserPresenter();

  14. default: return mClass.newInstance();

  15. }

  16. }

  17. }

從此Presenter就是全局單例的可複用狀態。

4、TimeLogAspect 自動打印方法的耗時

經常遇到需要log一個耗時操作究竟執行了多長時間,無aop時,需要每個方法體內添加代碼,現在,只需要一個註解就可以一勞永逸:


 
  1. /**

  2. * 根據註解TimeLog自動添加打印方法耗代碼,通過aop切片的方式在編譯期間織入源代碼中

  3. * 功能:自動打印方法的耗時

  4. */

  5. @Aspect

  6. public class TimeLogAspect {

  7.  
  8. @Pointcut("execution(@com.app.annotation.aspect.TimeLog * *(..))")//方法切入點

  9. public void methodAnnotated() {

  10. }

  11.  
  12. @Pointcut("execution(@com.app.annotation.aspect.TimeLog *.new(..))")//構造器切入點

  13. public void constructorAnnotated() {

  14. }

  15.  
  16. @Around("methodAnnotated() || constructorAnnotated()")//在連接點進行方法替換

  17. public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

  18. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

  19. LogUtils.showLog("TimeLog getDeclaringClass", methodSignature.getMethod().getDeclaringClass().getCanonicalName());

  20. String className = methodSignature.getDeclaringType().getSimpleName();

  21. String methodName = methodSignature.getName();

  22. long startTime = System.nanoTime();

  23. Object result = joinPoint.proceed();//執行原方法

  24. StringBuilder keyBuilder = new StringBuilder();

  25. keyBuilder.append(methodName + ":");

  26. for (Object obj : joinPoint.getArgs()) {

  27. if (obj instanceof String) keyBuilder.append((String) obj);

  28. else if (obj instanceof Class) keyBuilder.append(((Class) obj).getSimpleName());

  29. }

  30. String key = keyBuilder.toString();

  31. LogUtils.showLog("TimeLog", (className + "." + key + joinPoint.getArgs().toString() + " --->:" + "[" + (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) + "ms]"));// 打印時間差

  32. return result;

  33. }

  34. }

使用方法:


 
  1. @TimeLog

  2. public void onCreate() {

  3. super.onCreate();

  4. mApp = this;

  5. SpUtil.init(this);

  6. store = new Stack<>();

  7. registerActivityLifecycleCallbacks(new SwitchBackgroundCallbacks());

  8. }

從此方法耗時打印一個註解搞定!

5、SysPermissionAspect運行時權限申請


 
  1. /**

  2. * 申請系統權限切片,根據註解值申請所需運行權限

  3. */

  4. @Aspect

  5. public class SysPermissionAspect {

  6.  
  7. @Around("execution(@com.app.annotation.aspect.Permission * *(..)) && @annotation(permission)")

  8. public void aroundJoinPoint(ProceedingJoinPoint joinPoint, Permission permission) throws Throwable {

  9. AppCompatActivity ac = (AppCompatActivity) App.getAppContext().getCurActivity();

  10. new AlertDialog.Builder(ac)

  11. .setTitle("提示")

  12. .setMessage("爲了應用可以正常使用,請您點擊確認申請權限。")

  13. .setNegativeButton("取消", null)

  14. .setPositiveButton("允許", new DialogInterface.OnClickListener() {

  15. @Override

  16. public void onClick(DialogInterface dialog, int which) {

  17. MPermissionUtils.requestPermissionsResult(ac, 1, permission.value()

  18. , new MPermissionUtils.OnPermissionListener() {

  19. @Override

  20. public void onPermissionGranted() {

  21. try {

  22. joinPoint.proceed();//獲得權限,執行原方法

  23. } catch (Throwable e) {

  24. e.printStackTrace();

  25. }

  26. }

  27.  
  28. @Override

  29. public void onPermissionDenied() {

  30. MPermissionUtils.showTipsDialog(ac);

  31. }

  32. });

  33. }

  34. })

  35. .create()

  36. .show();

  37. }

  38. }

使用方法:


 
  1. @Permission(Manifest.permission.CAMERA)

  2. public void takePhoto() {

  3. startActivityForResult(

  4. new Intent(MediaStore.ACTION_IMAGE_CAPTURE)

  5. .putExtra(MediaStore.EXTRA_OUTPUT,

  6. Uri.fromFile(new File(getExternalCacheDir()+ "user_photo.png"))),

  7. C.IMAGE_REQUEST_CODE);

  8. }

  9.  

動態權限申請一步搞定。

除了這些簡單的示例,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切片,處理緩存和日誌

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章