Android AOP基础

AOP概述
  1. AOP,即面向切面编程,是一种编程思想,强调的是在‘某一层面’上编写程序的方式,而这‘某一层面’就被称为切面。

  2. 比如打印log,作为调试的一种手段,一般会渗透到项目中的许多地方,那么打印log就可看成是一种切
    面,而AOP会指导我们怎样编写打印log会更好。

  3. AOP的主要目标是尽可能地对切面代码进行解耦。

Android代码注入

AOP采用代码注入技术来实现高度的代码解耦,而在Android中常见的代码注入主要分为以下几种:

  1. 编译时注入:在编译代码阶段直接对class文件进行代码注入,属于静态代码注入,它的特点是对性能的影响较小,而缺陷仅能注入参与编译的代码。AspectJ支持编译时注入,注意在Android中是无法注入系统API类,如android.jar下的类。但可以注入support包。

  2. 加载时注入:在Android的虚拟机加载dex文件时进行代码注入,特点是对性能的影响较小,缺陷是门槛较高,比如热更新等。

  3. 运行时注入:在程序运行阶段进行注入,相比前面的静态注入,运行时进行注入会更加的灵活,但是对性能的运行较大,比如Java的动态代理。

AspectJ
  1. AspectJ是AOP这种理论来指导实践的一种工具,更切确的说是一种编程语言,它也有自己的语法规则、编译工具等。

  2. 对于打印log这一切面进行编程,AspectJ的做法是通过代码注入技术来解耦,此种做法让人耳目一新。通过预先标记目标代码,在编译代码阶段注入切面代码。对于Java项目来说即在编译阶段往字节码注入代码,注入的技术支撑是hook。

  3. AspectJ与Java是两种不同语言,不能直接交流。目前较好的解决方案是:AspectJ通过java注解进行语法规则的映射,并借助gradle插件完成编译工作。

AspectJ核心
  1. Joint point:在AspectJ中,允许注入代码的地方被称为Joint point(连接点)。Joint point主要分为以下几种:
    这里写图片描述
    显然Joint point就是程序中关键的地方,如方法的调用与执行、字段的读写、异常和静态代码块等。虽Joint point类型是有限的,但对于一般的应用场景是足够了。重点关注execution与call的区别:

    • execution:这个Joint point是位于执行的方法体中
    • call:这个Joint point是位于该方法被调用的地方
    • handler:这个Joint point是位于异常throw的地方
  2. Pointcut:切入点,其作用是挑选出需要注入的目标Joint point。简单理解就是告诉AspectJ要在哪些Joint point执行代码注入。除了上图中包括的直接Joint point类型,Pointcut还提供了以下过滤Joint point的辅助工具:

    • within():可以筛选出指定包或类下的Joint point
    • withincode():可以筛选出指定构造方法或普通方法的execution类型的Joint point
    • cflow():可以筛选出目标call类型Joint point中包括的所有Joint point,包括目标call这个Joint point,注意括号中的参数必须是Pointcut
    • cflowbelow():与cflow()唯一区别就是不包括目标call这个Joint point
      总之,Pointcut借助于逻辑操作符(&&,||,!)可以组合成一个功能复杂的Pointcut
  3. Advice:作用决定代码注入的时机以及执行代码注入。比如对某个方法进行注入,到底是在方法执行之前或之后,甚至于替换这个方法。分为以下几种:

    • before:在目标方法执行之前注入
    • around:取得目标方法执行的控制权,可以替换目标方法
    • after:在目标方法执行之后注入
  4. Aspect:指的是Pointcut与Advice的组合,其代表的就是往目标切面注入代码。

  5. Weaving:代码织入,对于Android应用需要反编译代码才能看到AspectJ所注入的代码。

  6. 小结:上述AspectJ中的涉及的核心概念可总结为如下关系图:

这里写图片描述

Android AspectJ实践
  1. 目前AspectJ还未提供标准的gradle插件,考虑到接入成本,此处选用开源插件gradle_plugin_android_aspectjx。具体配置如下:在工程目录build.gradle的配置aspectjx插件依赖,如下代码

    dependencies {
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.10'
    }
  2. 在app目录build.gradle的配置如下代码

    apply plugin: 'android-aspectjx'
    aspectjx {
       excludeJarFilter '.jar'
    }
    dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'org.aspectj:aspectjrt:1.8.+'
    }
  3. 编写Pointcut,预先标记目标代码。Pointcut指明在何处注入代码,如下例子

    @Aspect
    public class DemoAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(DemoAspect.class);
    
    @Pointcut("execution(* com.example.dson.app.ui.activity.MainActivity.onCreate(..))")
    public void injectCode() {
    }
    
    @Before("injectCode()")
    public void execInjectCode(JoinPoint joinPoint) {
        logger.debug("joinPoint:" + joinPoint);
        logger.debug("getStaticPart:" + joinPoint.getStaticPart().getKind());
        logger.debug("getKind:" + joinPoint.getKind());
        logger.debug("getTarget:" + joinPoint.getTarget());
        logger.debug("getThis:" + joinPoint.getThis());
    }
    }
    public class MainActivity extends BaseActivity {
    
    private static final String TAG = MainActivity.class.getSimpleName();
    
    private ListView mListView;
    private final List<ActivityEntry> mEntries = new ArrayList<>();
    @Inject
    ActivityEntryDao mDao;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        PackageManager pm = getPackageManager();
        try {
            PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), PackageManager
                    .GET_ACTIVITIES);
            ActivityInfo[] activityInfo = packageInfo.activities;
            int len = activityInfo.length;
            for (int i = 0; i < len; i++) {
                ActivityEntry entry = new ActivityEntry();
                entry.setActivityClass(Class.forName(activityInfo[i].name));
                String name = entry.getActivityClass().getSimpleName();
                if (name.equals(TAG)) {
                    continue;
                }
                entry.setName(name);
                entry.setInfo(activityInfo[i]);
                mEntries.add(entry);
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Collections.sort(mEntries);
        mDao.saveAll(mEntries);
        mListView = (ListView) findViewById(R.id.activity_list);
        mListView.setAdapter(new ActivityAdapter());
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Intent intent = new Intent(MainActivity.this, mEntries.get(position)
                        .getActivityClass());
                startActivity(intent);
            }
        });
    }
    
    @Override
    public String getActivityTag() {
        return TAG;
    }
    
    private class ActivityAdapter extends BaseAdapter {
    
        @Override
        public int getCount() {
            return mEntries.size();
        }
    
        @Override
        public Object getItem(int position) {
            return mEntries.get(position);
        }
    
        @Override
        public long getItemId(int position) {
            return 0;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            Holder holder;
            if (convertView == null) {
                holder = new Holder();
                convertView = LayoutInflater.from(MainActivity.this).inflate(android.R.layout
                        .simple_list_item_1, null);
                holder.textView = (TextView) convertView.findViewById(android.R.id.text1);
                convertView.setTag(holder);
            } else {
                holder = (Holder) convertView.getTag();
            }
            ActivityEntry entry = (ActivityEntry) getItem(position);
            holder.textView.setText(entry.getName());
            return convertView;
        }
    }
    
    private static class Holder {
        TextView textView;
    }
    }
    
    

    运行结果如下:
    这里写图片描述
    这里写图片描述

  4. aspect编译与代码混淆的关系:结论是aspect编译阶段是在代码混淆之前执行的,因为gradle task是以依赖链的顺序执行的,aspect注入代码生成新包作为混淆的输入文件。比如如下构建过程的log:

#开始执行构建
:app:preBuild UP-TO-DATE

 #此处省略的其它log

:app:transformClassesWithAspectTransformForDebug
aspect start..........
excludeJar:::D:\code\MyApplication\app\libs\pinyin4j-2.5.0.jar
excludeJar:::C:\Users\tscyd\.gradle\caches\modules-2\files-2.1\com.github.tony19\logback-android-classic\1.1.1-4\f20b1158fcca0aef6043886ef8605556a09058ca\logback-android-classic-1.1.1-4.jar
excludeJar:::C:\Users\tscyd\.android\build-cache\a88d9c5e67accbd9b1823c970eac763ceaaa5d66\output\jars\classes.jar
excludeJar:::C:\Users\tscyd\.android\build-cache\b192e9d007b5d7be93e942b19d27dbb92e2180b7\output\jars\classes.jar

#此处省略的其它log

directoryInput:::D:\code\MyApplication\app\build\intermediates\classes\debug
aspect do work..........
aspect jar merging..........
aspect done...................
:app:transformClassesWithAspectTransformForDebug spend 1732ms
:app:processDebugJavaRes NO-SOURCE
:app:processDebugJavaRes spend 1ms
:app:transformResourcesWithMergeJavaResForDebug
:app:transformResourcesWithMergeJavaResForDebug spend 52ms
:app:transformClassesAndResourcesWithProguardForDebug

ProGuard, version 5.3.2
Reading input...
Reading program jar [D:\code\MyApplication\app\build\intermediates\transforms\mergeJavaRes\debug\jars\2\1f\main.jar] (filtered)
Reading program jar [D:\code\MyApplication\app\build\intermediates\transforms\AspectTransform\debug\jars\1\10\007a1ac9907af9957c6690d04b022d90eac5914e.jar] (filtered)
Reading program jar [D:\code\MyApplication\app\build\intermediates\transforms\AspectTransform\debug\jars\1\10\063b4994867b45bf033b64ccc6f46e3c5b4a4fde.jar] (filtered)
Reading program jar [D:\code\MyApplication\app\build\intermediates\transforms\AspectTransform\debug\jars\1\10\0c89586b3ffc5096ea8d8623ce3eeef124d37923.jar] (filtered)
Reading program jar [D:\code\MyApplication\app\build\intermediates\transforms\AspectTransform\debug\jars\1\1f\aspected.jar] (filtered)
Reading program jar [D:\code\MyApplication\app\build\intermediates\transforms\AspectTransform\debug\jars\1\2\505132f1bd3fb37ea5a4e2660b961781369b6547.jar] (filtered)
Reading library jar [C:\sdk\platforms\android-25\android.jar]
Reading library jar [C:\sdk\platforms\android-25\optional\org.apache.http.legacy.jar]

#此处省略的其它log

Shrinking...
Printing usage to [D:\code\MyApplication\app\build\outputs\mapping\debug\usage.txt]...
Removing unused program classes and class elements...
  Original number of program classes: 3011
  Final number of program classes:    2548

Obfuscating...
Printing mapping to [D:\code\MyApplication\app\build\outputs\mapping\debug\mapping.txt]...

Writing output...
Preparing output jar [D:\code\MyApplication\app\build\intermediates\transforms\proguard\debug\jars\3\1f\main.jar]

#此处省略的其它log

:app:transformClassesWithDexForDebug spend 882ms
:app:transformClassesWithShrinkResForDebug
Removed unused resources: Binary resource data reduced from 514KB to 492KB: Removed 4%
:app:transformClassesWithShrinkResForDebug spend 167ms
:app:mergeDebugJniLibFolders
:app:mergeDebugJniLibFolders spend 5ms
:app:transformNativeLibsWithMergeJniLibsForDebug
:app:transformNativeLibsWithMergeJniLibsForDebug spend 9ms
:app:transformNativeLibsWithStripDebugSymbolForDebug
:app:transformNativeLibsWithStripDebugSymbolForDebug spend 3ms
:app:validateSigningDebug
:app:validateSigningDebug spend 0ms
:app:packageDebug
:app:packageDebug spend 330ms
:app:assembleDebug
:app:assembleDebug spend 0ms

BUILD SUCCESSFUL in 11s
43 actionable tasks: 43 executed
Task spend time:
   1239ms   :app:mergeDebugResources
    471ms   :app:processDebugResources
   2373ms   :app:compileDebugJavaWithJavac
   1732ms   :app:transformClassesWithAspectTransformForDebug
     52ms   :app:transformResourcesWithMergeJavaResForDebug
   3078ms   :app:transformClassesAndResourcesWithProguardForDebug
    882ms   :app:transformClassesWithDexForDebug
    167ms   :app:transformClassesWithShrinkResForDebug
    330ms   :app:packageDebug
AspectJ实践总结
  1. 优点:AOP编程的神兵利器,可极大地提高编程的效率。
  2. 缺点:语法比较多,相关资料不多;方法数增加;延长编译时间。
  3. 局限:
    • 无法植入系统框架层代码,如view包等,对用户实现代码或依赖库则没有限制。
    • 多个工程项目依赖下,源码项目工程植入困难。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值