本文主要介绍AspectJ入门,实现Android简单的AOP编程,完成线程切换和Log日志的输出。
另外AspectJ还可以实现方法的执行时间,日志的收集记录,登陆校验等功能。入门之后这些都不难。

Gradle配置

  • 配置Project Gradle
    • 引入aspectjtools
      在Project Gradle中配置classpath
   dependencies {        classpath 'com.android.tools.build:gradle:3.2.0-alpha17'        classpath 'org.aspectj:aspectjtools:1.9.1‘    }

AspectJ Marven库

  • 配置Moudle gradle
    • 添加依赖
dependencies {    //AspectJ    implementation 'org.aspectj:aspectjrt:1.9.1'    //rxandroid,用于切换线程    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'}
    • 调用AspectJ编译器
import org.aspectj.tools.ajc.Mainproject.android.applicationVariants.all {    JavaCompile compile = it.javaCompile    compile.doLast {        String[] args = [                //java版本                "-1.8",                //aspect处理的源文件                "-inpath", compile.destinationDir.toString(),                //aspect的输出目录                "-d", compile.destinationDir.toString(),                //aspect编译器的classpath                "-aspectpath", compile.classpath.asPath,                //java程序类的查找路径                "-classpath",compile.classpath.asPath,                //覆盖引导类的位置,                "-bootclasspath",project.android.bootClasspath.join(File.separator)        ]        //调用执行aspectJ编译器        new Main().runMain(args,false)    }}

代码

定义注解

  • 切换到主线程的注解MThread
package com.example.hi.aspectjdemo.anotations;import .../** * 切换到主线程 */@Retention(RetentionPolicy.CLASS)@Target(ElementType.METHOD)public @interface MThread {}
  • 切换到子线程的注解SThread
package com.example.hi.aspectjdemo.anotations;import .../** * 切换到子线程 */@Retention(RetentionPolicy.CLASS)@Target(ElementType.METHOD)public @interface SThread {}
  • 打印方法的参数与返回值的注解LogPR
package com.example.hi.aspectjdemo.anotations;import .../** * 打印方法中的参数与返回值 */@Retention(RetentionPolicy.CLASS)@Target(ElementType.METHOD)public @interface LogPR {}

注解的调用

  • 切换到主线程的方法的实现
package com.example.hi.aspectjdemo.anotations;import ...//类上添加注解@Aspect,以便AspectJ识别@Aspectpublic class MThreadImpl {    //切点方法 注意Pointcut的写法: 执行被MThread注解的方法,方法的返回值为void,*表示方法名任意,(..)表示参数任意    @Pointcut("execution(@com.example.hi.aspectjdemo.anotations.MThread void *(..))")    public void mainThreadMethod() {//方法的名称自定义    }    //Around为包裹方法,另外还有Before及After,参数为自己前面定义的方法名    @Around("mainThreadMethod()")    public void execute(final ProceedingJoinPoint joinPoint) {//方法的名称可自定义,返回值与切点方法的返回值应一致        Observable.create(new ObservableOnSubscribe() {            @Override            public void subscribe(ObservableEmitter emitter) throws Exception {                try {                    joinPoint.proceed();//执行方法                } catch (Throwable throwable) {                    throwable.printStackTrace();                }            }        }).subscribeOn(AndroidSchedulers.mainThread()).subscribe();    }}  
  • 切换到子线程的实现
package com.example.hi.aspectjdemo.anotations;import ... @Aspectpublic class SThreadImpl {    @Pointcut("execution(@com.example.hi.aspectjdemo.anotations.SThread void *(..))")    public void subThreadMethod(){    }    @Around("subThreadMethod()")    public void execute(final ProceedingJoinPoint joinPoint){        Observable.create(new ObservableOnSubscribe() {            @Override            public void subscribe(ObservableEmitter emitter) throws Exception {                try {                    joinPoint.proceed();                } catch (Throwable throwable) {                    throwable.printStackTrace();                }            }        }).subscribeOn(Schedulers.newThread()).subscribe();    }}  
  • 打印返回值的实现
package com.example.hi.aspectjdemo.anotations;import...@Aspectpublic class LogPRImpl {    @Pointcut("execution(@com.example.hi.aspectjdemo.anotations.LogPR * *(..))")    public void logMethod() {    }    @Around("logMethod()")    public Object execute(ProceedingJoinPoint joinPoint) {        Object result=null;        Object[] args = joinPoint.getArgs();        String methodName = joinPoint.getSignature().getName();        Object target = joinPoint.getTarget();        String arg = "";        for (int i = 0; i < args.length; i++) {            if (i == 0)                arg = args[i].toString();            else                arg = arg + "," + args[i].toString();        }        Log.i(methodName, "args: "+arg);        try {            result= joinPoint.proceed();            if (result != null)                Log.i("result:", "result=" + result.toString());            else                Log.i("result","没有返回值");        } catch (Throwable throwable) {            throwable.printStackTrace();        }        return result;    }}

注解的引用

写一个Acitivity,在其中调用一个方法mSleep,要求切换到子线程,线程睡眠一段时间,睡眠结束后,执行showToast切换到主线程,弹出Toast。调用另一个方法getResult,传递2个参数,打印出2个参数和执行的结果

package com.example.hi.aspectjdemo;import ...public class AspectJDemoActivity extends AppCompatActivity {    private static final String TAG = "AspectJDemoActivity";    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mSleep();        getResult(1,2);    }    @SThread    private void mSleep() {        Log.i(TAG, "mSleep: " + Thread.currentThread().getName());        try {            Thread.sleep(3000);            Log.i(TAG, "mSleep--> finish: " + Thread.currentThread().getName());        } catch (InterruptedException e) {            Log.i(TAG, "线程结束: "+e.getMessage());        }        showToast();    }    @MThread    private void showToast() {        Log.i(TAG, "showToast: " + Thread.currentThread().getName());        Toast.makeText(this, "来自主线程的消息,子线程睡眠结束", Toast.LENGTH_SHORT).show();    }    @LogPR    private int getResult(int i, int j) {        Log.i(TAG, "getResult: " + Thread.currentThread().getName());        return i + j;    }}

结果:


线程切换.PNG
参数打印.png

如上图所示,我们实现了切面编程,实现了一个注解实现子、主线程切换和参数及执行结果的打印

几个坑

  • 定义Pointcut及执行方法
    如果你真实的方法有返回值,在定义Pointcut的返回值时一定不要void,@Around注解的执行方法也不能返回void,否则不能正确的生成需要的class文件,导致执行结果与预期不符合。
    例如前面例子中 LogPRImplexecute 方法,如果返回void,那么就无法正确执行了!
  • 关于内存泄漏
    由于涉及到线程的切换,所以当Activity结束时,子线程可能会导致内存泄漏,所以要想办法拿到线程,当Activity销毁时,不需要的线程也要结束。
  • 关于切换线程执行方法的返回值
    目前还没有找到优雅的方法。

简单处理Activity销毁时使子线程结束

定义一个DisposableHandler,把*Impl中的Disposable存储起来,当Activity结束时,调用Disposable.dispose();

package com.example.hi.aspectjdemo;import ...public class DisposableHandler {    static HashMap> map=new HashMap<>();    public static void addNewDisposable(Activity activity,Disposable  disposable){        Set disposables = map.get(activity);        if(disposables==null){            disposables=new HashSet<>();            map.put(activity,disposables);        }        disposables.add(disposable);    }    public static void dispose(Activity activity){        Set disposables = map.get(activity);        if(disposables==null){            return;        }        Iterator iterator = disposables.iterator();        while (iterator.hasNext()){            Disposable next = iterator.next();            if(!next.isDisposed()){                next.dispose();            }        }    }}

在Impl中调用addNewDisposable

    ...    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)    @Around("subThreadMethod()")    public void execute(final ProceedingJoinPoint joinPoint) {        Object target = joinPoint.getTarget();//目标类,如果时Activity,则获取到原始方法所在的Activity实例        Disposable disposable = Observable.create(new ObservableOnSubscribe() {            @Override            public void subscribe(ObservableEmitter emitter) throws Exception {                try {                    joinPoint.proceed();                } catch (Throwable throwable) {                    throwable.printStackTrace();                }            }        }).subscribeOn(Schedulers.newThread()).subscribe();        if (target instanceof Activity) {            //保存Disposable             DisposableHandler.addNewDisposable((Activity) target, disposable);        }    }    ...  

当Activity销毁时

    ...    @Override    protected void onDestroy() {        super.onDestroy();        DisposableHandler.dispose(this);    }    ...

新手入门,难免有疏漏错误,欢迎指正!

更多相关文章

  1. Android(安卓)DataBinding使用详解(一)
  2. android 【点击输入框调出输入法前的】输入框获取焦点和输入法的
  3. 使用 SQLiteDatabase 操作 SQLite 数据库
  4. Android(安卓)studio 中与本地 html 页面交互
  5. Android(安卓)开发者必知必会的权限管理知识
  6. 真机上使用Hierarchy Viewer
  7. Android(安卓)实现音乐播放器【源码+注释】— MediaPlayer
  8. Android(安卓)studio 命令gradlew assembleRelease打包时,出现 Un
  9. android关闭应用程序

随机推荐

  1. Android的交叉编译工具
  2. Android中使用Handler机制更新UI的三种解
  3. android中的数据库操作
  4. Android进程 Handler Message Looper
  5. 关于 Android(安卓)安全的六个问题
  6. Android通过WebView与JS交互的全面方式
  7. (最全最详)Android简述
  8. 学习Android前,需掌握java基础
  9. Android亚平台是否有价值
  10. [Google Android] Google Cloud Messagin