North

Code&Rock n'Roll


  • Home

  • Archives
North

安卓AOP实战:Javassist强撸EventBus

Posted on 2016-11-25

####前言

EventBus到了3.0版本,使用Apt注解处理器来在编译期通过读取@Subscribe()注解并解析生成java类来保存订阅者关于的信息,比在之前使用反射来获得这些订阅者的信息速度要快。但是事件触发依然是使用method.invoke来调用。纵观EventBus的源码,还是有大量使用反射的地方。

然而,尽管反射非常强大,但也不能随意大量使用。如果一个功能可以不用反射完成,那么最好就不用。由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,使用反射相对来说不安全 --代码有功能上的错误,降低可移植性。反射代码破坏了类的封装性抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。而且使用反射的性能较低。

反射这个异教徒,并不属于OOP的范畴,简直就是代码界的IS,强行蹂躏对象,破坏原有结构,对面向对象的世界观进行残(危)酷(言)肢(耸)解(听)。好了,进入正题:

我眼中EventBus的缺点:
一,不够简洁:需要手写大量register和unregister的垃圾代码,这对我来说是致命的缺点
二、不够高效:虽然用了Apt和METHOD_CACHE来优化了反射,单仍然不够完美理想化
三、不够专注:事件传递的框架,就不需要关心线程切换什么的吧,你这么牛逼RxJava知道吗

好了,话不多说,开车。

先看效果:

OkBus

对,没错,触发事件一句代码:

null);```
1
2
3
4
接收事件一句代码
```@Bus(tag = EventTags.JUMP_TO_MAIN)

支持粘连事件(先触发后注册)

OkBus.getInstance().onStickyEvent(EventTags.FLASH_INIT_UI, null);

什么?注册和反注册?这些代码全部都是插件自动生成,不用再手动写啦!

Javassist在编译期间通过插件,已经帮你自动生成注册、反注册、事件分发的代码并且帮你加上啦!如果没有这样的方法,会自动生成并加上,如果本来就有,就更简单了,直接在方法里面追加就可以了。

来看build/intermediates/transforms下的源代码
transforms下的源代码

Activity(Fragment)有onCreate(onActivityCreated),onDestroy可以自动加,想在别的地方用怎么办?

请看Presenter中用法:

Presenter中用法

即使没有那样的时机让插件自动插入,那只要吱个声(加个注解),插件也会自己自动帮你加。

添加后的代码:

自动插入后的代码

需要在哪个方法里注册就加上@BusRegister注解,反注册就加@BusUnRegister,现在你可以在任何地方使用了。

那要是忘加注解了怎么办?

别急,插件依然会在编译期间提醒你:

非正常使用会报错

所有一切都是编译期间所做的,零反射零代理。实现的方式,不过一类,一插件,一注解,百余行代码而已。

##1、OkBus类

######OkBus属性:

主要就是基本信息的存储和单例的实现

OkBus属性

######OkBus register方法:
注册的时候放入回调列表,如果是粘连事件,注册时就去已分发的粘连事件库存去拿参数并触发回调。

OkBus  register方法

######OkBus反注册和触发事件方法:
反注册就直接remove回调,触发时分普通事件和粘连事件,粘连事件就存库里备用

OkBus反注册和触发事件方法
OK,以上就是全部代码。这里直接用回调实现事件通知,没有什么复杂的逻辑,现在考虑的情况也比较简单

##2、插件代码
剩下的都是插件代码:

######BusInfo类:
存储事件的相关的信息,便于生成代码逻辑:

BusInfo类

######transform操作
然后在transform中,根据源代码创建BusInfo,处理BusInfo、利用BusHelper操作源代码进行方法和代码的插入,

在transform中,根据源代码创建BusInfo,BusHelper处理BusInfo

######BusHelper生成代码
BusHelper里面有一些模版代码:

模版代码

处理BusInfo:

处理BusInfo

获取初始化OkBus方法的代码
 获取初始化OkBus方法的代码

生成event事件分发的逻辑代码
生成event事件分发的逻辑代码

生成取消事件注册的代码
生成取消事件注册的代码

Ok,以上便是全部代码,百十行而已(Log占了一大部分),Gradle插件虽然是使用用groovy,我真的完全没看过任何博客学过groovy,因为groovy完全兼容java,闭着眼睛当java写好像并没有什么不对(不要喷我,,,)。

Javassist实现逻辑插入的部分,说高深点叫操作修改字节码,说简单点就是字符串拼接,插入代码或者方法。相比ASM,真的太适合java开发者来使用了。

以上代码在T-MVP可以看到,TMVP本来想做成库,现在已经彻底沦为实验室,欢迎各位客官前来把玩。

OK,车已到站,下车请刷卡

North

安卓AOP三剑客:APT,AspectJ,Javassist

Posted on 2016-11-25

AOP:面向切面编程(Aspect-Oriented Programming)。如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。

Android AOP就是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。本文仅做知识介绍,相关详细内容不做过多描述,全部代码在项目T-MVP。

话不多说,先上图:

APT,AspectJ,Javassist对应的编译时期

AOP在Java后台,已经被各路大神研发出各种框架风生水起,例如SSH、SpringMVC等等殿堂级框架。在Android端,近年来也是异军突起。

#APT

代表框架:Dagger2, ButterKnife, EventBus3 、DBFlow、AndroidAnnotation

注解处理器 Java5 中叫APT(Annotation Processing Tool),在Java6开始,规范化为 Pluggable Annotation Processing。Apt应该是这其中我们最常见到的了,难度也最低。定义编译期的注解,再通过继承Proccesor实现代码生成逻辑,实现了编译期生成代码的逻辑。

使用姿势 :
1、建立一个java的Module,写一个继承AbstractProcessor的类

AbstractProcessor

2、在工具类里处理我们自定义的注解、生成代码:

Processor

3、在Gradle中添加 dependencies annotationProcessor project(‘:apt’)
低版本需要使用第三方插件 apply plugin: ‘com.neenbedankt.android-apt’
然后apt project(‘:apt’)

生成的源代码在build/generated/source/apt下可以看到
apt生成代码的路径
难点:
就apt本身来说没有任何难点可言,难点一在于设计模式和解耦思想的灵活应用,二在与代码生成的繁琐,你可以手动字符串拼接,当然有更高级的玩法用squareup的javapoet,用建造者的模式构建出任何你想要的源代码。
想详细了解可以看官网或这篇博客:
Android 利用 APT 技术在编译期生成代码

优点:
它的强大之处无需多言,看代表框架的源码,你可以学到很多新姿势。总的一句话:它可以做任何你不想做的繁杂的工作,它可以帮你写任何你不想重复代码。懒人福利,老司机必备神技,可以提高车速,让你以任何姿势漂移。它可以生成任何源代码供你在任何地方使用,就像剑客的剑,快疾如风,无所不及。

#AspectJ

代表框架: Hugo(Jake Wharton)

AspectJ支持编译期和加载时代码注入,在开始之前,我们先看看需要了解的词汇:
Advice(通知): 典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。

Joint point(连接点): 程序中可能作为代码注入目标的特定的点和入口。

Pointcut(切入点): 告诉代码注入工具,在何处注入一段特定代码的表达式。

Aspect(切面): Pointcut 和 Advice 的组合看做切面。例如,在本例中通过定义一个 pointcut 和给定恰当的advice,添加一个了内存缓存的切面。

Weaving(织入): 注入代码(advices)到目标位置(joint points)的过程。

下面这张图简要总结了一下上述这些概念。

AOP概念图

使用姿势:
1、建立一个android lib Module,定义一个切片,处理自定义注解,和添加切片逻辑

AspectJ

2、自定义一个gradle插件,使用 AspectJ 的编译器(ajc,一个java编译器的扩展),对所有受 aspect 影响的类进行织入,在 gradle 的编译 task 中增加额外配置,使之能正确编译运行。

AspectjPlugin

3、在grade中apply plugin:com.app.plugin.AspectjPlugin

生成的class文件在build/intermediates/classes下可以看到

Aspectj编织后的文件路径

难点:
AspectJ语法比较多,但是掌握几个简单常用的,就能实现绝大多数切片,完全兼容Java(纯Java语言开发,然后使用AspectJ注解,简称@AspectJ。)想详细了解可以看官网或这篇博客:
深入理解AndroidAOP

优点:
AspectJ除了hook之外,AspectJ还可以为目标类添加变量,接口。另外,AspectJ也有抽象,继承等各种更高级的玩法。它能够在编译期间直接修改源代码生成class,强大的团战切入功能,指哪打哪,鞭辟入里。有了此神器,编程亦如庖丁解牛,游刃而有余。

#Javassist

代表框架:热修复框架HotFix 、Savior(InstantRun)等

Javassist作用是在编译器间修改class文件,与之相似的ASM(热修复框架女娲)也有这个功能,可以让我们直接修改编译后的class二进制代码,首先我们得知道什么时候编译完成,并且我们要赶在class文件被转化为dex文件之前去修改。在Transfrom这个api出来之前,想要在项目被打包成dex之前对class进行操作,必须自定义一个Task,然后插入到predex或者dex之前,在自定义的Task中可以使用javassist或者asm对class进行操作。而Transform则更为方便,Transfrom会有他自己的执行时机,不需要我们插入到某个Task前面。Tranfrom一经注册便会自动添加到Task执行序列中,并且正好是项目被打包成dex之前。

使用姿势
1、定义一个buildSrc module添加自定义Plugin
自定义Plugin

2、自定义Transform

自定义Transform

3、在Transform里处理Task,通过inputs拿到一些东西,处理完毕之后就输出outputs,而下一个Task的inputs则是上一个Task的outputs。

处理Task

4、使用Javassist操作字节码,添加新的逻辑或者修改原有逻辑
Javassist操作字节码

5、在grade中apply plugin:com.app.plugin.MyPlugin

修改后的class文件在build/intermediates/transforms/MyTrans下可以看到

Javassist修改后的文件路径

难点:
相比ASM,Javassist对java极度友好的api更容易快速上手,难点在思想的应用,小到切片逻辑的控制,如本例中的性能log打印日志,大到宏观的热修复,插件化中对preDex的操作修改,剑客精神到了这一层级,已经是上帝视角,无所不能。

优点:
由于Javassist可以直接操作修改编译后的字节码,直接绕过了java编译器,所以可以做很多突破限制的事情,例如,跨dex引用,解决热修复中CLASS_ISPREVERIFIED的问题。

想详细了解可以看官网或这篇博客:
Android热补丁动态修复技术

基于Instant Run思想的HotFix方案实现

#AOP
AOP技术常用在以下方面:
1、日志记录:业务埋点
2、持久化
3、性能监控:性能日志
4、数据校验:方法的参数校验
5、缓存:内存缓存和持久缓存
6、权限检查:业务权限(如登陆,或用户等级)、系统权限(如拍照定位)
7、异常处理

利用AOP技术将这些功能代码从业务逻辑代码中划分出来,通过对这些行为的分离,可以将它们独立到非业务逻辑。无论是日后新增,或是修改,都手到擒来易如反掌。

例如新的tmvp的demo中 apt用于生成实例化工厂,替换掉(对于小项目来说)繁杂冗余的Dagger2,实现了初始化功能的aop ;aspectj 的切片主要用在缓存和日志,用注解实现方法级别的内存缓存和方法耗时日志;Javassist 这里只是做了个示例,也是通过注解实现方法耗时日志的自动打印功能,当然这些都只是AOP的九牛一毛,AOP还可以做很多事,弥补OOP的不足,把所有跨对象的横切面关注点的功能都可以提取出来用AOP去实现 ,好处显而易见,将来要改的地方永远只有一处,而不是像OOP那样牵扯很多模块很多代码很多类。

当然还有更多未知的可能,需要等各位大侠来研究开发,让Aop在Android上的应用更加广泛。

PS:刚发现一个歪果仁的框架 http://6thsolution.github.io/EasyMVP 基于Clean Architecture 用了apt、aspectj、javassisit 不多说赶紧看源代码学习去了

North

T-MVP:泛型深度解耦下的MVP大瘦身

Posted on 2016-11-25

关键词 MVP+Retrofit+Rxjava+MaterialDesign+LeanCloud+NodeJS
简书:http://www.jianshu.com/p/b49958e1889d
weibo:http://weibo.com/1917320262
Github:https://github.com/north2016/T-MVP
QQ群:AndroidMVP 555343041

####导语
删繁就简三秋树,领异标新二月花

传统MVP给人的第一感觉通常是接口和类的暴涨,将Activity中除V之外的繁杂操作搬到P之后依然臃肿不堪。MVP从来都不是救命稻草,只能锦上添花,不能雪中送炭。也许在权衡利弊之后,很多人会对MVP望而却步。 现在 我们换一个思路,取其精华,去其糟粕,完成T-MVP大变身。

#一、T-MVP简介


话不多说,先上图:
T-MVP架构图

下面,看看如何利用泛型把我们从繁杂重复的劳动中解救出来。

后台接口用的是LeanCloud的REST API,数据通过Node爬虫从网页上获取,先爬出十个页面的列表,然后每一个列表爬进去获取文章详情。爬虫代码见app.js。(建议替换成自己申请的appId和appKey)。

##1、页面分层结构

宗旨:纯粹界面操作交互,不需要MP参与的行为,尽量V自己做,保证MVP职责清晰,P只有干净简洁的协助VM的业务逻辑操作,M只处理数据操作。

登录契约类

相比谷歌官方Contract类,多了一个Model,用于将P中繁杂的数据操作分离解耦,让P层变得简洁明了,只处理M和V相关操作即可,登录Present的全部代码:

登录Present的全部代码

RxManage用于管理订阅者、观察者、以及事件。(详见代码)
例如:
发送事件: mRxManage.post(C.EVENT_LOGIN, user);
接收事件:mRxManage.on(C.EVENT_LOGIN, arg ->mView.initUserInfo((_User) arg));

LoginModel的全部代码:
LoginModel的全部代码

抽离出Model不仅各层更加分工明确便于Model的复用,而且大大简化了Presenter的代码量,让P层更简洁更专注。职能清爽,一目了然。

##2、列表页
例如一个列表页,用户关心的,只有列表的Item展示:

mTRecyclerView.setItemView(ItemView.class);

不需要在当前页面写网络请求,不需要写下拉刷新和分页加载更多的回调,不需要写任何Adapter,只需要这一句即可。因为用泛型在TRecyclerView里面写过抽象层的操作,将监听和数据的获取封装成通用模板,从此可以一劳永逸了。

比如,分页加载的封装操作:
TRecyclerView部分代码

对于有HeaderView的List也一样:

mTRecyclerView.setHeaderView(HeaderView.class).setItemView(ItemView.class);

ViewHolder也是相当简洁的写法:
ViewHolder

至此,项目可以永远告别OnRefresh ,onLoadMore ,Adapter。

##3、项目结构
利用泛型封装的一些Base类,总代码不超过1000行,也是T-MVP的核心:
T-MVP项目结构])X{FRT]K9SMSZ}CY.jpg

T-MVP利用泛型解藕和高度抽象封装之后,相较于传统MVC和MVP代码量大大精简,架构的代码量也是精简至极:

例如BasePresenter的全部代码:

BasePresenter的全部代码

#二、T-MVP的目标
//TODO

  • 1 整理Base类,吸纳意见之后,继续优化封装,将简洁进行到底。让MVP不再背负繁杂的名声。
  • 2 发布到github
  • 3 争取发布到jcenter

正在做:

  • 1 添加持久层数据存储realm

  • 2 recyclerview支持多种LayoutManager (GridLayoutManager StaggeredGridLayoutManager),支持多头部,多列表类型,多底部。

  • 3 添加fragment的MVP,同时也解决了一个页面需要多个P的问题

  • 4 尽量把反射去掉,免得有人抓住把柄说性能不好

  • 5 把框架部分隔离出来,单独做成Library

  • 6 用apt自动帮我们生成一些黑科技代码,达到事半功倍的效果

#三、进度

  • 1登录、注册
  • 2列表分类
  • 3文章详情 文章评论列表
  • 4用户中心 用户评论列表
  • 5更换头像
  • 6用户列表

项目截图:

7247fc46jw1f3p5r5tsynj20u01hcwl0.jpg

7247fc46jw1f3p5r8cjjhj20u01hc47t.jpg

7247fc46jw1f3p5r35jqnj20u01hcdt4.jpg

7247fc46jw1f3p5rceufqj20u01hcnaz.jpg

North

Hello World

Posted on 2016-11-25

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

north

north

山高路远

4 posts
© 2016 north
Powered by Hexo
Theme - NexT.Muse