iOS 直播技术总结 [四] 图解 音视频数据采集
今天要和大家一起分析的是图片加载开源库Glide,目前图片加载比较流行的开源库有Picasso,Glide,Fresco等,我用过的只有Picasso和Glide,Picasso给我的唯一感觉就是代码精简,功能强大。而Glide 相对而言代码要比Picasso复杂得多。之前项目中一直用Picasso但是后来项目中需要支持Gif才切换到Glide.Glide 特点也很明显,就是它能够响应生命周期事件以及网络状态监听事件,会随着这两类事件启动,暂停,恢复网络请求。两者的相同点就是接口相近,所以从Picasso切换到Glide不会有任何的困难。
好了我们先来分析下源码:
首先是Glide对象的创建:
这里的设计思想比较值得借鉴: 首先使用单例+建造者模式来创建出Glide,而将GlideMode相当于一个插件从AndroidMenifest解析出来,每个插件可以往Glide里面添加配置以及对应的组件实例.
这里 applyOption 以及 registerComponents 分别将 GlideBuilder 以及glide.registry 丢进去,在两个方法中将当前插件的配置信息以及插件本身注册到Glide中。
public static Glide get(Context context) { |
实际的Glide是通过GlideBuilder的createGlide中创建的,这个套路在Picasso中也应用过,在这里主要判断加载图片所需的部件是否都进行了设置如果没有设置那么就赋给它默认的对象。
Glide中的主要可设置的组件有如下:
1. 线程池 |
这些会在涉及到的环节进行展开介绍。需要注意的是在解码格式的设置上,如果图片不支持透明度那么就使用565的格式,如果有透明度那么就使用8888的格式。这两种在保存的图片大小上会有影响。很显然8888的格式保存下来文件会相对大。但是效果会好很多。
Glide createGlide() { |
紧接着就是调用Glide 构造方法来创建对象。在Glide 构造方法中最主要的任务就是注册一系列内置的图片资源编解码器,创建ImageViewTargetFactory,新建RequestOptions,并为其设置图片编码格式。最后将全局中比较常用的Glide,Register,RequestOption,Engine存放到GlideContext 中供后续使用,这个也是比较常用的方式。注册器会在后面进行进一步介绍。这里先主要看下整个流程。
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) |
到目前为止,Glide对象创建结束,我们回顾下整个过程:
Glide创建出来后就进入了with阶段:
之前提到的Glide 图片加载过程中会响应Activity/Fragment生命周期 以及网络状态而启动,恢复,暂停图片加载请求都是在这个环节中体现的。这个环节中主要的两个对象是RequestManagerFragment 以及 ConnectivityMonitor。
好了我们接下来就来带着这两个问题进一步阅读源码:
大家可以发现Glide.java 中有很多with方法,为什么需要重载这么多个呢?它们之间的区别是什么呢?
使用context启动的请求将只会拥有应用等级的options,不会根据生命周期事件来自动开始或者停止图片的加载。通常而言,如果资源在一个子Fragment中的一个View中使用,那么load就应该使用子Fragment作为参数的with方法。如果资源应用在父Fragment中的View中,那么load就应该使用父Fragment作为参数的with方法。同样的道理如果资源在Activity中使用那么就应该使用Activity作为参数的with方法,使用Context作为参数的一般用于不在常用的fragment或者activity生命周期的资源,比如services或者notification中资源 |
我们以最常用的Activty的情况作为分析情景:
public static RequestManager with(Activity activity) { |
第一阶段我们先看下怎么获取到RequestManagerFragment,首先我们应该明白RequestManagerFragment和我们开发过程中常见的Fragment的区别是它是一个没有界面不可见的一个Fragment,但是它有Fragment所拥有的生命周期。我们可以借助这一点来实现前面提到的图片加载请求状态随着生命周期的改变而改变。
public RequestManager get(Activity activity) { |
那么对于Activity而言这个Fragment藏在哪里呢?它和Activty所拥有的一般Fragment一样都归到Activity FragmentManager中统一管理。我们看下这个过程:
RequestManager fragmentGet(Context context, android.app.FragmentManager fm) { |
从上面代码可以看出,这个阶段首先会先从FragmentManger 中获取,如果没获取到就会新建一个并添加到FragmentManager,获取完RequestManagerFragment后就从中获取RequestManager,下面是获取RequestManagerFragment的过程代码:
RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) { |
我们上面介绍了RequestManagerFragment的获取方式,那么RequestManagerFragment又是怎么将请求和生命周期给对应起来的呢?在回答这个问题之前我们先来看下RequestManagerFragment的结构,RequestManagerFragment中有两个重要对象ActivityFragmentLifecycle,RequestManager
前者是生命周期的触发者,需要响应生命周期的部件都需要监听它,而RequstManger负责请求的管理,它里面有个RequestTracker 用于实行请求的管理。RequstManaer是生命周期事件的响应者,RequestManagerFragment触发对应生命周期后通过ActivityFragmentLifecycle传出来,ActivityFragmentLifecycle再通知监听它的监听者。对应的监听者在不同的生命周期作出不同的响应。
我们来看下这部分的代码
ActivityFragmentLifecycle 是在RequestManagerFragment构造方法中创建的,如下所示:
public RequestManagerFragment() { |
RequestManager是通过setRequestManager进行注入的,这个可以看之前的代码介绍。
public void setRequestManager(RequestManager requestManager) { |
我们再看下RequestManagerFragment的生命周期中做了什么,和之前说的一样它在对应的生命周期中触发lifecycle事件,
|
ActivityFragmentLifecycle 很简单就是注册对应生命周期事件的观察者,一旦ActivtyFragmentLifeCyble被RequestManagerFragment 生命周期所触发,那么ActivtyFragmentLifeCycle也会将对应的事件通知到各个观察者。
@Override |
ActivtyFragmentLifeCycle 有两个重要的观察者:RequestManager 和 ConnectivityMonitor。所以这个阶段的整个过程如下图所示:
到此为止with阶段代码分析完毕,我们来简单回顾下:
这个阶段主要是完成请求随着生命周期的状态改变而改变状态的工作。主要涉及到四个重要对象一个是RequestManagerFragment 一个是 ActivityFragmentLifeCycle ,还有两个是RequestManger,以及ConnectivityMonitor。三者之间的关系以及如何实现请求随着生命周期的状态改变而改变状态这个在上面已经详细介绍过了。这个需要重点了解下。
接下来是as阶段:
load 有多种实现,现将这些实现都罗列如下:
/** |
上面最终都是调用如下方法:
/** |
从这里可以看出load阶段的工作就是创建一个RequestBuilder。
创建了一个RequestBuilder后就可以调用load开始加载了。我们来看下load阶段:
和as以及with阶段一样load也有各种重载,但是最终还是调用:
private RequestBuilder<TranscodeType> loadGeneric(Object model) { |
这里只是简单得设置了model并将isModelSet标识为设为true后返回。很简单吧,其实Picasso这个阶段也很简单,不信可以看下我之前写的博客。
Glide对象创建了,请求状态随生命周期改变而改变实现了,RequestBuilder创建出来了,model设置了,接下来就是最重要的图片加载流程了,也就是into阶段:
into阶段实际上就是开始加载图片数据,并将图片数据设置到Target上,我们这里以ImageView为Target来看下这个流程:
public Target<TranscodeType> into(ImageView view) {
//..........
if (!requestOptions.isTransformationSet() && view.getScaleType() != null) {
if (requestOptions.isLocked()) {
requestOptions = requestOptions.clone();
}
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions.optionalCenterCrop(context);
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions.optionalFitCenter(context);
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
return into(context.buildImageViewTarget(view, transcodeClass));
}
在这个阶段最重要的是最后一行代码into(context.buildImageViewTarget(view, transcodeClass))
public <X> Target<X> buildImageViewTarget(ImageView imageView, Class<X> transcodeClass) { |
这里使用Glide对象构造阶段创建的ImageViewTargetFactory来创建出我们需要的Target,具体需要创建什么Target需要根据transcodeClass来决定。
public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) { |
而transcodeClass 是在创建RequestBuilder的时候传入的,而RequestBuilder是在as方法中创建的,所以这里的resourceClass就是buildTarget所对应的transcodeClass。所以我们调用asBitmap 的时候这里传入的就是Bitmap.class,调用asGif的时候这里传入的就是GifDrawable.class,如果我们调用asDrawable的时候,这里传入的就是Drawable.class。
public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) { |
不管怎样buildTarget 返回的就两个对象要么是BitmapImageViewTarget要么是DrawableImageViewTarget
BitmapImageViewTarget –> ImageViewTarget
DrawableImageViewTarget –> ImageViewTarget
但是我们前面已经提到asDrawable 和asGif两个都是对应的DrawableImageViewTarget那么这两个会有什么区别呢?我们看下DrawableImageViewTarget,两种区别在于是否继承自Animatable,GifDrawable是继承自Animatable 所以在资源获取结束后会调用start方法让它动起来。
|
我们再继续顺着继承树看下去,
BitmapImageViewTarget –> ImageViewTarget
DrawableImageViewTarget –> ImageViewTarget
ViewTarget 中有几个重要的回调函数onLoadStarted,onLoadFailed ,onLoadCleared ,onResourceReady
分别在启动加载,加载失败,清除加载,资源获取完毕的情况下回调,主要用于设置占位图片,错误图片以及加载完的图片
|
这个继承树根真深,还得继续刨:
ViewTarget有两个重要的任务就是获取这个view的Size以及设置tag:
这里有个重要的对象SizeDeterminer,获取View的尺寸就是通过这个类来完成的。它首先会先通过getWidth()和getHeight()来获取View的宽高,如果有一个为0的话,它会检查View的LayoutParams从中获取宽高大小,如果还是没有那么就会监听OnPreDrawListener回调从而等到在绘制之前进行测量的时候回调。
public void getSize(SizeReadyCallback cb) { |
在测量结束的时候会调用checkCurrentDimens来检查当前的宽高
|
在checkCurrentDimens 会重新获取并检查View的宽高如果获取到了就通过notifyCbs来通知对应的监听者。
并将其从OnPreDrawListener监听队列中返回。
private void checkCurrentDimens() { |
所以整个继承树的完成工作如下:
DrawableImageViewTarget –> ImageViewTarget
将加载的图片设置到Target,如果是Gif启动播放Gif –> 设置占位图,错误图缩率图等 –> 获取图片尺寸,设置Tag –> 存储Request
有了上述的了解我们再继续返回看into的流程:
public <Y extends Target<TranscodeType>> Y into(Y target) { |
into方法中会首先检查当前target是否有请求正在进行如果有那么先清除原先的请求。调用buildRequest来创建该次请求,并调用setRequest将请求与target进行绑定(也就是为当前View打上request的标签,并将request保存下来)。然后通过ReqestManager对该请求进行track。
这里关注两点
private Request buildRequest(Target<TranscodeType> target) { |
public static <R> SingleRequest<R> obtain(RequestContext<?, R> requestContext, Target<R> target, |
了解了请求的创建过程后我们继续看下如果使用获取的请求进行加载图片:
我们看下RequestManager track方法:
void track(Target<?> target, Request request) { |
track中处理很简单,就是让target监听生命周期后,调用requestTracker.runRequest。还记得上面介绍Target的时候有提到生命周期对Target的影响了吧– 在Target为GifDrawble的时候Target会随着生命周期启动和停止播放动画。
Ok 我们继续看runRequest,上面已经知道request是一个SingleRequest,SingleRequest中启动请求是通过begin方法来完成的。
public void runRequest(Request request) { |
begin 方法中会先获取View的尺寸,然后通过onSizeReady传出,实际的加载也是在onSizeReady中进行的。
在开始加载之前先调用onLoadStarted显示占位图片。
public void begin() { |
看下onSizeReady,转了大半圈感觉终于走到了正道,之前都是各种做铺垫,在onSizeReady中会通过engine.load进行图片的加载。关于请求的信息都放在了requestContext中。
public void onSizeReady(int width, int height) { |
在看load代码之前我先给大家剧透下整个加载流程,大家可以结合下面的代码注释对细节进行查看:
整个流程如下:
可能大家都听说过两级缓存,内存缓存,磁盘缓存,但是Glide在这基础上添加了一层活跃资源缓存,那么什么是活跃资源呢?
活跃资源指的是那些不止一次被加载并没有进行过资源释放的图片,一旦被释放,那么该资源则会从近期活跃资源中删除并进入到内存缓存中,但是如果该资源再次从内存缓存中读取,则会重新添加到活跃资源中
public <Z, R> LoadStatus load(RequestContext<?, R> requestContext, int width, int height, |
我们先看下内存缓存部分:
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { |
在内存缓存获取到我们所需要的数据后会将其添加到Active Resource中.
private EngineResource<?> getEngineResourceFromCache(Key key) { |
接着我们再看下Active Resource 缓存中获取数据的过程:
Active Resource 的缓存数据存在activeResources中,使用弱引用来持有。在内存不足的时候这部分会被gc掉。
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) { |
介绍完内存缓存以及Active Resource缓存后我们看下磁盘缓存:
在磁盘缓存开始前会先在当前获取数据的队列中查看是否先前已经启动,如果先前已经启动的话就不重新创建了。这样可以达到复用数据的目的。最后的最后才会启动从磁盘中获取缓存数据的任务。
DecodeJob是一个Runnable的实现类,主要负责从磁盘加载数据,调用start方法后,这个线程就会run起来。
它的作用如下:
public void run() { |
我们第一次进入的时候runReason为INITIALIZE,这时候会先通过getNextStage来获取当前阶段的下一阶段。首先在看代码之前我们需要先明白Data和Resource的区别:
Resource:原始的图片(或gif)数据
Data:经过处理(旋转,缩放)后的数据
该方法的大致逻辑如下:
简单的来说,就是根据Resource—>Data—>source的顺序去解码加载数据,该阶段Stage的确定,影响着下一阶段DataFetcherGenerator相应子类的实例创建
private Stage getNextStage(Stage current) { |
通过getNextStage已经获取到适当的状态后紧接着就是通过getNextGenerateor生成获取数据的DataFetcherGenerator
private DataFetcherGenerator getNextGenerator() { |
DataFetcherGenerator使用已注册的ModelLoaders和Model来生成一系列的DataFetcher。有如下实现类
ResourceCacheGenerator:经过处理的资源数据缓存文件(采样转换等处理)
DataCacheGenerator:未经处理的资源数据缓存文件
SourceGenerator:源数据的生成器,包含了根据来源创建的ModelLoader和Model(文件路径,URL…)
下面我们一一来看下这些DataFetcherGenerator
####### ResourceCacheGenerator
public boolean startNext() { |
public boolean startNext() { |
public boolean startNext() { |
private void cacheData() { |
这里需要注意下,SourceGenerator可以根据磁盘缓存策略选择是直接返回还是先写到磁盘再从缓存文件中加载。
public void onDataReady(Object data) { |
看完上面的三个DataFetcher后我们看下在DecodeJob中怎么通过这些Fetcher进行获取数据,这就涉及到runGenerators这个方法:
如果获取成功则直接回调onDataFetcherReady,如果失败则通过reschedule重新调度
private void runGenerators() { |
看完上面代码密密麻麻的,但是最主要的代码就一句generator.startNext()也就是上面介绍DataFetcher的时候重点注解的那个方法,在那个方法中会调用对应的Fetcher来获取数据。后续的部分会着重介绍一个从网络上获取数据的Fetcher,这里先着重介绍流程。好了我们继续:
不论是哪种Fetcher,获取完数据后都会回调DecodeJob里面的onDataFetcherReady
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher, |
private void decodeFromRetrievedData() {
//对原始数据进行解码
Resource
if (resource != null) {
//通过回调进行返回
callback.onResourceReady(resource);
cleanup();
} else {
runGenerators();
}
}
private Resource
try {
if (data == null) {
return null;
}
Resource
return result;
} finally {
fetcher.cleanup();
}
}
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource) { |
getLoadPath的任务是从注册表中获取特定数据类型,转换类型的图像解码器:
<Data> LoadPath<Data, ?, TranscodeClass> getLoadPath(Class<Data> dataClass) { |
这里调用getLoadPath了从loadPathCache中获取对应数据类型的解码器,loadPathCache 是一个缓存,这个大家见怪不怪了:
public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath( |
下面是从注册表中获取符合要求的解码器列表的实现:
private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths( |
经过层层调用后我们就获取到了当前数据的解码器,拿到解码器后不用说要做的事情就是对数据进行解码了,我们看下这部分内容:
private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path) { |
我们接下来看下LoadPath的load方法,这里最关键的部分就是path.decode这个方法,它就是调用从注册表中获取到的解码器到decode方法对数据进行解码的。
public Resource<Transcode> load(Data data, RequestContext<?, Transcode> context, |
到这里为止整个数据获取,解码都完成了,那么我们接下来顺着原路返回,看下怎么将这些经过解码后的图片设置到对应的Target上,我们先回到Engine类:
它有一个叫做onEngineJobComplete的回调,是在上面加载数据,解码数据之后对调的。
public void onEngineJobComplete(Key key, EngineResource<?> resource) { |
这里处理很简单就是将加载后的数据添加到activeResources,下一次的时候就可以从activeResources中获取了。
还记得前面介绍Engine.load的时候如果从内存缓存以及Active Resource缓存中获取到数据后是怎么处理的吧。是的就是调用:
cb.onResourceReady(cached); |
cb 是啥,看代码可以看出SingleRequest,所以我们看下SingleRequest的onResourceReady
@Override |
上面终于看到target了。既然都讲到这了我就再回头看下吧,比如我们当前是一个GifDrawable,那么Target就是DrawableImageTarget
我们就再来看下它的onResourceReady
public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) { |
它调用了父类也就是ImageViewTarget的onResourceReady,在这里会调用传入的Transition对图像进行一次转换,然后调用setResource设置到对应的Target上。
public void onResourceReady(Z resource, Transition<? super Z> transition) { |
所以总的就是先将图像转换,设置到ImageView上,然后如果是gif就调用start方法开始播放。整个流程结束了,真他妈累。对了好像还忘记给大家介绍HttpUrlFetcher了。实在讲不动了就贴个标有注释的代码给大家吧。
public class HttpUrlFetcher implements DataFetcher<InputStream> { |
Dagger2 搞Android开发以及Java开发的同学估计即使没有使用过也应该听说过这个开源库吧。它是一个依赖注入库.源码地址如下所示:
Dagger2 github地址
在项目中引入Dagger2
project的build.gradle添加
dependencies { |
module的build.gradle添加
// 添加其他插件 |
在介绍如何使用Dagger2之前我们必须先搞清楚一件事–为甚么需要使用Dagger2,也就是Dagger2的功能,刚刚提到了Dagger2 是一个依赖注入框架,那么什么是依赖注入呢?为什么要使用依赖注入呢?
我们传统的开发过程中如果某个类中需要一个依赖可以通过在这个类中创建需要的依赖,这样的缺点显而易见,比方我们需要在一个已经开发成熟的项目中更换某个类的实现,再具体点,比如我们在
现有项目中使用的是Picasso作为图像处理框架,但是某天项目中需要显示gif,这时候我们可能考虑到Glide库能够支持Gif播放,所以我们想要将图像处理框架更换为Glide
如果原先没有设计好的话可能改动的代码就很庞大。而且极为容易出错。但是如果使用了依赖注入框架,那么这个问题就变得很简单了,只需要修改对应的Module就可以了,对代码不需要大幅的改动。(这里的前提条件是这两个库接口上是相似的)
那么什么是依赖注入呢?这里我就谈谈自己对依赖注入的理解:
依赖注入就是将使用依赖的代码部分与依赖生成的部分分开,这样做的好处就是:因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。并且可以通过注入这些依赖的Mock对象来进行模拟测试。从而使得对项目的测试更加方便。
有Dagger2就说明有Dagger的存在,关于这段历史大家如果感兴趣的话可以去网上了解下,这里就不展开介绍了,Dagger2的最大改进就是,它使用了Java注解处理器,完全去除了反射机制,在编译的时候检查并分析依赖关系。使得在效率上得到很大的提升。
下面是Dagger2的大致结构:
整个依赖注入体系分成三个部分:
了解了大体的结构后我们就需要了解下Dagger2中常用的一些注解,其实Dagger2的注解并不太多,但是需要注意的是这些注解的理解。
我们看到上图中依赖注入器左右两端各有一个@Inject注解也就是说@Inject既可以用在提供方也可以用在依赖需求方,用在依赖提供方的时候一般用来注解待注入对象的构造函数,用在依赖需求方的时候一般用来注解需要Dagger2进行依赖注入的成员变量。
@Module:
我们看到依赖提供方还有个@Module注解,它的作用是什么呢?我们知道提供方已经有了一个@Inject为什么还需要@Module呢?我们考虑一个情景我们现有项目中使用了第三方的类库,在不采用导入第三方类库源码或者源代码非开源的情况,如果用Inject要怎么处理,根本不可能使用Inject注解加入这些类中是吧,那这还怎么办呢?这时候就需要@Module出场了。Modules类是由一系列专门提供依赖的方法组成,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的依赖。
那么这两种方式有没优先级区分呢?
有的,它的查找规则如下:
提到@Module就不得不提到 @Provide,我们用这个注解来告诉Dagger2被这个注解的方法是被用来提供依赖的,具体提供哪种依赖对象是由返回值决定的,一般这类方法规定以provide作为开头,后面的可以随意。Module中@Provides方法可以是带输入参数的方法,其参数由Module集合中的其他@Provides方法提供,或者自动调用构造方法,也就是说如果找不到@Provides方法提供对应参数的对象,Dagger2就会自动调用带@Inject参数的构造方法生成相应对象。
下面是一个最基本的@Module的写法
|
如果待注入方需要依赖同个类的两种不同的对象的时候,那要怎么办,我们可能会想就写两个@Provides方法,而且这两个@Provides方法都是返回需要的类型,但是我们前面提到过Dagger2是靠返回值的类型来判断具体选择哪个@Provide方法来提供依赖的,现在有两个provide方法返回同一个类型,那就比较尴尬了,这种现象也有专门的叫法叫做注入迷失,为了解决这个问题这就需要使用@Named来进行区分了:
|
在待注入方也要使用@Named来标记到底使用的是哪个依赖,具体的待注入方以及注入会在下面进行介绍。
@Named("typeA") //添加标记@Name("typeA"),只获取对应的@Name("typeA")的依赖 @Inject |
上面的方式只能使用字符串作为区分标签,一般来说是够用的,但是如果你需要其他的方式作为区分标签可以使用Qualifier进行定义了:
@Qualifier //必须,表示IntNamed是用来做区分用途 |
用法和@Named注解类似就不展开介绍了。
Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。它注释的类必须是接口或抽象类。
既然它是注入器,必定由两个部分构成,一个是提供方,一个是需求方,提供方是由module引入,需求方是由inject方法引入。Component的职责就是在inject目标中有使用@Inject
注解的成员变量的时候顺着 Component 所管理的 Module中进行查找需要的依赖,但是如果不需要@ Module那么就不需要定义Component了,也就是说Component 是用于管理 @Module的,可以通过Component中的modules属性把Module加入Component,modules可以加入多个Module。
这样Component获取依赖时候会自动从多个Module中查找获取,需要注意的是Module间不能有重复方法,不然也会照成上面所提到的依赖迷失。
添加多个module有两种方法
1. @Component(modules={××××,×××}) |
假设ComponentA依赖ComponentB,B必须定义带返回值的方法来提供A缺少的依赖
ComponentA依赖ComponentB的代码如下
//定义ComponentB |
//定义ComponentA |
这样,当使用ComponentA注入Container时,如果找不到对应的依赖,就会到ComponentB中查找。但是,ComponentB必须显式把这些A找不到的依赖提供给A。怎么提供呢,只需要在ComponentB中添加方法即可,如下
@Component(modules={××××××××}) |
Component 当中定义的方法可以分成两类:
下面是一个最基本的Component 定义方法
//指明Component在哪些Module中查找依赖 |
@Component的注入
Component注入有两种方式:
public Container{ |
上面简单例子中,当调用DaggerFruitComponent.create()实际上等价于DaggerFruitComponent.builder().build()。在构建的过程中,默认使用Module无参构造器产生实例。
如果需要传入特定的Module实例,可以使用
DaggerFruitComponent.builder() |
如果Module只有有参构造器,则必须显式传入Module实例。
这里还留有一个问题等到讲 Component 依赖以及子Component的时候讲,那就是在Component依赖以及子Component的情况下怎么进行依赖注入。
在学Dagger2的时候最难理解的部分就是@Scope 以及 Component依赖,子Component.还有就是如何在项目中组织Component。
我们接下来先来看下@Scope的作用,在不使用@Scope 的时候我们的例子如下,我们注入到MainActivity后将这两个对象打印出来,
public class Apple {
}
|
|
@Inject |
打印出来的结果如下:
07-18 20:24:15.355 3059-3059/com.idealist.tbfungeek.mvpframework I/MainActivity: class -> MainActivity method -> onCreate() line -> 29 [ Message ] com.idealist.tbfungeek.mvpframework.Apple@3dd96071 |
接着我们再做个对比实验:
@Scope |
@Module |
@FruidScope |
@FruidScope |
结果如下:
07-18 20:27:36.364 5818-5818/? V/ActivityLifeCycleManager$1: class -> ActivityLifeCycleManager$1 method -> onActivityCreated() line -> 61 [ Message ] onCreate --> MainActivity |
发现了什么没?我们在没有用scope注解的时候两个对象实际是不同的两个对象,但是如果用scope注解标记后两个返回的是同一个对象。
最早看到Singleton注解的时候我第一反应就是只要用上这个注解就可以实现单例模式了,但是它并非我们通常以为的单例,Java中,单例通常保存在一个静态域中,这样的单例往往要等到虚拟机关闭时候,该单例所占用的资源才释放。但是,Dagger通过Singleton创建出来的单例并不保持在静态域上,而是保留在Component实例中。也就是这种单例只是针对对应的Component。如果要实现传统意义上的单例模式,那么就需要通过一定的方法保证对应的Component是全局单例的。
下面是来自网络上的一个很经典的例子,估计看过后大家一定会豁然开朗:
在实际开发中我们可能还需要一种局部单例的控件(这个应该是更常用),比如说我们有三个Activity,MainActivity,BActivity和CActivity,我们想让MainActivity和BActivity共享同一个实例,而让CActivity获取另外一个实例,这又该怎么实现呢?在Dagger2中,我们可以通过自定义Scope来实现局部单例。那就动手吧:
首先让我们先来定义一个局部作用域:
@Scope |
然后在我们的UserModule和ActivityComponent中应用该局部作用域:
@Module |
@UserScope |
大家注意,我的ActivityComponent作为一个注入器只可以向MainActivity和BActivity两个Activity中注入依赖,不可以向CActivity中注入依赖。最后,要让该局部作用域产生单例效果,需要我们在自定义的Appliation类中来初始化这个Component,如下:
public class MyApp extends Application { |
接下来我们在MainActivity和BActivity中注入依赖,MainActivity如下:
@Inject |
BActivity如下:
@Inject |
那么如果我还想在CActivity中使用User对象该怎么办呢?再来一个CUserModule和CActivityComponent呗!
CUserModule如下:
|
这里我没有再注明单例了哦!
CActivityComponent如下:
|
在CActivity中注入依赖:
@Inject |
大家看到,MainActivity和BActivity是同一个实例,而CActivity则是另外一个实例。
同时还需要注意一点就是:一个@Module和component中可以有多个scope对象。这些scope将一个component划分成多个不同的区域:
|
@FruidScope1 |
@FruidScope |
输出结果:
07-18 21:09:07.245 5676-5676/? I/MainActivity: class -> MainActivity method -> onCreate() line -> 36 [ Message ] com.idealist.tbfungeek.mvpframework.Apple@3dd96071 |
在使用scope的时候我们还需要注意如下两点:
编译器会检查 Component管理的Modules,若发现标注Component的自定义Scope注解与Modules中的标注创建类实例方法的注解不一样,就会报错。所以Component和Modules中的scope必须匹配。
如果两个Component间有依赖关系,那么它们不能使用相同的Scope。
如果一个Component的功能不能满足你的需求,我们需要对它进行拓展,这时候有两种方法
那么它们的不同之处在哪里呢?@Component 只能获取到依赖的 Component 所暴露出来的对象,而 @Subcomponent 则可以获取到父类所有的对象。
Subcomponent其功能效果优点类似component的dependencies。但是使用@Subcomponent不需要在父component中显式添加子component需要用到的对象,只需要添加返回子Component的方法即可,子Component能自动在父Component中查找缺失的依赖。
|
通过Subcomponent,子Component就好像同时拥有两种Scope,当注入的元素来自父Component的Module,则这些元素会缓存在父Component,当注入的元素来自子Component的Module,则这些元素会缓存在子Component中。
下面是较好的文章,如果看了该博客还是不大明白可以通过下面的文章来进一步阅读
提到EventBus同样是每个Android 开发所必须掌握的一个开源库,它是一个事件发布订阅系统,用法十分简单,但是能够在很大程度上解决模块间存在的耦合问题,当某个模块的某个事件产生的时候,对应的事件通过post方法将其发布到Eventbus上,再由EventBus对该事件进行分发,如果某个类需要响应某个事件,必须事先通过register方法进行注册将自己订阅到总线上,这样EventBus就会根据实际的情况将事件源post出来的事件分发到有处理指定事件类型能力的订阅者方法上。一旦订阅者订阅了对应的事件,订阅者将会接收到对应类型的事件,直到调用unregitser方法取消注册。事件订阅方法必须使用@Subscribe注释,并且必须是public方法。返回值必须是void,并且只能有一个参数,该参数的类型为事件对象类型,用法就这么简单,三言两语就搞定了。但是这篇博客的关注点不在于它的使用上,这篇博客想从源码角度来对EventBus原理进行分析看下上述提到的功能是如何实现的。
但是在分析源码之前还是先要熟悉下EventBus是如何使用的,毕竟我们熟悉源码的目的也是为了应用。
首先我们先看下下面两张对比图:
第一张是没有使用EventBus的项目结构图,可以看出整个结构几乎呈网状,这样的结构相对来说耦合度就相对高。
而第二张整个结构呈现的是星型结构,EventBus处于星型结构的核心位置,主要负责事件的接送与调度。事件的产生源和事件的消费者耦合度就大大得降低了,大家只和EventBus进行交互,事件源将事件分发到事件总线,订阅者不会相互交互而是监听事件总线分发的事件。所以订阅者和事件源避免了之间的强约束。
这篇博客介绍的是EventBus 3.xEventBus 3 相对于之前的版本引入了EventBusAnnotationProcessor,我们可以使用编译时注解的方式来使用Eventbus了。在EventBus 早期的版本中事件注册信息的获取采用的是反射机制,这样就会导致效率上的降低,在EventBus 3的版本上并行使用反射和编译时注解两种方式,我们可以根据自己的实际需求来选择采用哪种方式。这个在后面源代码分析的时候会进行介绍。
这里推荐大家使用编译时注解方式。我这边为了方便起见,也只介绍使用编译时注解的方式引入。
这里需要注意的是目前很多教程注解预编译所采用的是android-apt的方式,不过随着Android Gradle 插件 2.2 版本的发布,Android Studio推出了编译时预处理官方插件,所以Apt工具的作者也就宣布不再维护该工具了。目前使用的是annotationProcessor来取代android-apt方式。
如果大家使用的是还是android-apt方式的话,建议通过如下方式来切换到annotationProcess方式。
切换步骤:
首先要确保Android Gradle插件版本是2.2以上:
android-apt方式
dependencies { |
修改后annotationProcessor 方式
dependencies { |
也就是把原先的
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 去掉 |
android-apt方式
buildscript { |
dependencies {
compile ‘org.greenrobot:eventbus:3.0.0’
apt’org.greenrobot:eventbus-annotation-processor:3.0.1’
}
|
dependencies {
compile ‘org.greenrobot:eventbus:3.0.0’
annotationProcessor ‘org.greenrobot:eventbus-annotation-processor:3.0.1’
}
|
apt {
arguments {
eventBusIndex “org.greenrobot.eventbus.demo.MyEventBusIndex”
}
}
修改后annotationProcessor 方式 |
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ eventBusIndex : ‘org.greenrobot.eventbus.demo.MyEventBusIndex’ ]
}
}
}
|
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
|
@Subscribe(threadMode = ThreadMode.POSTING,sticky = false,priority = 1)
public void onMessageEvent(MessageEvent event) {
tv.setText(event.message);
}
|
public static EventBus getDefault() {
//使用单例的方法创建eventbus
//getDefault方法使用了double check(双重检查锁定模式),多了一层判断,故可以减少上锁开销。
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
在创建的时候我们会通过丢进去一个设置好各种属性后的Builder,然后在EventBus构造函数上从Builder上获取已经设置好的各种属性,这个在很多开源代码中都使用过这种方式,比如picasso Okhttp等,这种比较适合于有多个属性需要设置的情况。但是我们这里的重点在于EventBus有哪些属性,在下面代码中对一些部件进行了注释,还有一些没有注释的是比较重要的,需要在后面分析中重点提到的,我们接着往下看。 |
public EventBus() {
this(DEFAULT_BUILDER);
}
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
EventBus(EventBusBuilder builder) {
//这三个变量很重要后面会重点介绍
subscriptionsByEventType = new HashMap<>();
typesBySubscriber = new HashMap<>();
stickyEvents = new ConcurrentHashMap<>();
//三个事件分发器对应不同的threadMode
//主线程分发器
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
//后台线程分发器
backgroundPoster = new BackgroundPoster(this);
//异步线程分发器
asyncPoster = new AsyncPoster(this);
//这个是执行任务的线程池
executorService = builder.executorService;
//这个是我们上面提到的在使用EventBus编译时注解方式的时候会通过addIndex将编译时生成的Index注入。subscriberInfoIndexes就是用于存放这些Index的。
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
//这个类负责查找订阅者方法
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex);
//下面是和调试相关的开关:
//是否打印订阅异常
logSubscriberExceptions = builder.logSubscriberExceptions;
//是否打印没有订阅者的Log
logNoSubscriberMessages = builder.logNoSubscriberMessages;
//是否发送订阅者异常
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
//是否发送没有订阅者的事件
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
//是否抛出订阅异常
throwSubscriberException = builder.throwSubscriberException;
//eventInheritance 设置为true的时候会发送事件以及当前事件所实现的接口以及当前事件的父类事件。
eventInheritance = builder.eventInheritance;
}
|
public void register(Object subscriber) {
//获取订阅者的class对象
Class<?> subscriberClass = subscriber.getClass();
//在这个类中查找对应的订阅方法以及父类的订阅方法
List
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
//针对每个订阅方法调用subscribe进行订阅
subscribe(subscriber, subscriberMethod);
}
}
}
这里会使用SubscriberMethodFinder对作为参数传入对象的class进行查找,找到对应的订阅方法以及父类的订阅方法。我们先看下这部分代码: |
List
subscriberMethods = findUsingReflection(subscriberClass);
} else {
//使用编译期间获取到的Subscribe注释方法
//通过 findUsingInfo(Class<?> subscriberClass) 在apt中进行查找获取
subscriberMethods = findUsingInfo(subscriberClass);
}
//如果在当前类以及父类中没找到任何的订阅方法抛出异常
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
//如果有找到那么添加到缓存中以便后续查找使用,存储的方式为订阅类----->订阅方法
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
|
private List
/** 为FindState创建一个对象池,复用FindState对象,防止对象被多次new或者gc. */
FindState findState = prepareFindState();
//将订阅方法赋给FindState对象
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//查找当前订阅类的订阅方法
findUsingReflectionInSingleClass(findState);
//查找当前订阅类父类的订阅方法
findState.moveToSuperclass();
}
//将所有的订阅方法从findState中取出并返回
return getMethodsAndRelease(findState);
}
首先在查找的时候,每次查找会对应一个FindState,这里为了避免频繁创建FindState对象,使用了复用对象池的方法,每次使用先在对象池中查找,如果有之前用过的就直接使用,避免了重新创建一个对象。实在没有的情况再通过new的方式来创建,用完后并不是立即就释放不用,而是放到缓存中供下一次使用。 |
private FindState prepareFindState() {
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
FindState state = FIND_STATE_POOL[i];
if (state != null) {
FIND_STATE_POOL[i] = null;
return state;
}
}
}
return new FindState();
}
|
//对注册对类的方法进行遍历,必须是public 必须只有一个参数,必须使用@SubScribe注释,并且当前事件类型
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
//获取全部的方法
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
//获取方法的修饰符
int modifiers = method.getModifiers();
//在需要检查修饰符的情况下需要修饰符为public
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
//获取参数
Class>[] parameterTypes = method.getParameterTypes();
//如果参数为1个那么满足要求
if (parameterTypes.length == 1) {
//查看当前的注释释放包括Subscribe
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
//如果有Subscribe注释,那么parameterTypes[0]就是事件类型
Class> eventType = parameterTypes[0];
//通过上面的层层筛选,获取到通过上述筛选的方法,以及方法中作为参数的事件类型,将其作为参数传递到findState进行检查
//检查分两级,一般只需要一级检查,检查当前类中当前事件是否被某个方法已经注册处理,如果没有那么就添加到subscriberMethods中。
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
//获取注释中的线程模型,事件类型,方法,优先级,是否是sticky方法
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + “.” + method.getName();
throw new EventBusException(“@Subscribe method “ + methodName +
“must have exactly 1 parameter but has “ + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + “.” + method.getName();
throw new EventBusException(methodName +
“ is a illegal @Subscribe method: must be public, non-static, and non-abstract”);
}
}
}
首先会检查方法的修饰符是否是public,是否只有一个参数,是否目前已经有订阅方法订阅了该事件,订阅该事件的订阅方法是否是同一个方法。如果通过上述的检查,就会将这个订阅方法添加到FindState中的subscriberMethods。查找完当前类后会继续查找其父类。最后调用getMethodsAndRelease将FindState中存放的找到的订阅方法取出,然后将FindState添加到对象池中。 |
private List
//从池中取出一个不为空的FindState对象,避免了重新创建
FindState findState = prepareFindState();
//将当前的subscriberClass赋给FindState
findState.initForSubscriber(subscriberClass);
//如果事件对象不为空
while (findState.clazz != null) {
//从订阅者类到其父类,逐步获取订阅者信息
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
//如果存在订阅者信息,那么获取订阅者方法
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
//检查是否已经可以添加
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
//添加
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
//在当个类中使用反射来获取对应的订阅方法
findUsingReflectionInSingleClass(findState);
}
//移动到它的父类继续查找
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
上面的流程和之前介绍的用反射的流程一致,只不过在获取方式上存在差别: |
private SubscriberInfo getSubscriberInfo(FindState findState) {
//这部分很重要回头要认真看下有部分与EventBusAnnotationProcessor相关
if (subscriberInfoIndexes != null) {
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
}
return null;
}
getSubscriberInfo 中会遍历subscriberInfoIndexes 取出其中的SubscribeInfo,subscriberInfoIndexes还记得是怎么来的吧,就是我们之前通过addIndex将编译生成的类添加进来。 |
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ eventBusIndex : ‘org.greenrobot.eventbus.demo.MyEventBusIndex’ ]
}
}
}
否则就不会继续进行,eventBusIndex主要用于指定要生成哪个类。 |
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Messager messager = processingEnv.getMessager();
try {
//首先需要在build.gradle中配置生成的EventBusIndex类的名字,如果没有那么就会打印出错误消息
String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
if (index == null) {
messager.printMessage(Diagnostic.Kind.ERROR, “No option “ + OPTION_EVENT_BUS_INDEX +
“ passed to annotation processor”);
return false;
}
verbose = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE));
int lastPeriod = index.lastIndexOf(‘.’);
String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null;
round++;
if (verbose) {
messager.printMessage(Diagnostic.Kind.NOTE, “Processing round “ + round + “, new annotations: “ +
!annotations.isEmpty() + “, processingOver: “ + env.processingOver());
}
if (env.processingOver()) {
if (!annotations.isEmpty()) {
messager.printMessage(Diagnostic.Kind.ERROR,
“Unexpected processing state: annotations still available after processing over”);
return false;
}
}
//如果当前方法没有注解那么就返回false
if (annotations.isEmpty()) {
return false;
}
//收集订阅者
collectSubscribers(annotations, env, messager);
checkForSubscribersToSkip(messager, indexPackage);
//如果不为空那么就创建Index文件
if (!methodsByClass.isEmpty()) {
createInfoIndexFile(index);
} else {
messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
}
writerRoundDone = true;
} catch (RuntimeException e) {
// IntelliJ does not handle exceptions nicely, so log and print a message
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in EventBusAnnotationProcessor: " + e);
}
return true;
}
|
private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
for (TypeElement annotation : annotations) {
Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
//如果是可执行的元素
if (element instanceof ExecutableElement) {
ExecutableElement method = (ExecutableElement) element;
//检查方法的修饰符以及参数个数
if (checkHasNoErrors(method, messager)) {
TypeElement classElement = (TypeElement) method.getEnclosingElement();
//方法所处的类 ——- 方法
methodsByClass.putElement(classElement, method);
}
} else {
messager.printMessage(Diagnostic.Kind.ERROR, “@Subscribe is only valid for methods”, element);
}
}
}
}
紧接着我们看下checkHasNoErrors这个方法,这个方法主要检查方法的修饰符以及方法的参数个数。 |
//检测方法的修饰符,以及方法的参数个数
private boolean checkHasNoErrors(ExecutableElement element, Messager messager) {
if (element.getModifiers().contains(Modifier.STATIC)) {
messager.printMessage(Diagnostic.Kind.ERROR, “Subscriber method must not be static”, element);
return false;
}
if (!element.getModifiers().contains(Modifier.PUBLIC)) {
messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must be public", element);
return false;
}
List<? extends VariableElement> parameters = ((ExecutableElement) element).getParameters();
if (parameters.size() != 1) {
messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must have exactly 1 parameter", element);
return false;
}
return true;
}
最后我们看下如何创建Index文件: |
private void createInfoIndexFile(String index) {
BufferedWriter writer = null;
try {
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
int period = index.lastIndexOf(‘.’);
String myPackage = period > 0 ? index.substring(0, period) : null;
String clazz = index.substring(period + 1);
writer = new BufferedWriter(sourceFile.openWriter());
if (myPackage != null) {
writer.write(“package “ + myPackage + “;\n\n”);
}
writer.write(“import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n”);
writer.write(“import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n”);
writer.write(“import org.greenrobot.eventbus.meta.SubscriberInfo;\n”);
writer.write(“import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n”);
writer.write(“import org.greenrobot.eventbus.ThreadMode;\n\n”);
writer.write(“import java.util.HashMap;\n”);
writer.write(“import java.util.Map;\n\n”);
writer.write(“/** This class is generated by EventBus, do not edit. */\n”);
writer.write(“public class “ + clazz + “ implements SubscriberInfoIndex {\n”);
writer.write(“ private static final Map<Class>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
writer.write(" static {\n");
writer.write(" SUBSCRIBER_INDEX = new HashMap
writeIndexLines(writer, myPackage);
writer.write(“ }\n\n”);
writer.write(“ private static void putIndex(SubscriberInfo info) {\n”);
writer.write(“ SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n”);
writer.write(“ }\n\n”);
writer.write(“ @Override\n”);
writer.write(“ public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n”);
writer.write(“ SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n”);
writer.write(“ if (info != null) {\n”);
writer.write(“ return info;\n”);
writer.write(“ } else {\n”);
writer.write(“ return null;\n”);
writer.write(“ }\n”);
writer.write(“ }\n”);
writer.write(“}\n”);
} catch (IOException e) {
throw new RuntimeException(“Could not write source for “ + index, e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
//Silent
}
}
}
}
|
/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
putIndex(new SimpleSubscriberInfo(com.idealist.tbfungeek.core.mvp.view.core.BaseWebViewActivity.class, true,
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onProcessMainEvent", BaseMainEvent.class, ThreadMode.MAIN),
new SubscriberMethodInfo("onProcessAsyncEvent", BaseAsyncEvent.class, ThreadMode.ASYNC),
new SubscriberMethodInfo("onProcessBgEvent", BaseBackgroundEvent.class, ThreadMode.BACKGROUND),
new SubscriberMethodInfo("onProcessPostEvent", BasePostEvent.class),
}));
putIndex(new SimpleSubscriberInfo(com.idealist.tbfungeek.core.mvp.view.core.BaseActivity.class, true,
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onProcessMainEvent", BaseMainEvent.class, ThreadMode.MAIN),
new SubscriberMethodInfo("onProcessAsyncEvent", BaseAsyncEvent.class, ThreadMode.ASYNC),
new SubscriberMethodInfo("onProcessBgEvent", BaseBackgroundEvent.class, ThreadMode.BACKGROUND),
new SubscriberMethodInfo("onProcessPostEvent", BasePostEvent.class),
}));
putIndex(new SimpleSubscriberInfo(com.idealist.tbfungeek.core.mvp.view.core.BaseFragment.class, true,
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onProcessMainEvent", BaseMainEvent.class, ThreadMode.MAIN),
new SubscriberMethodInfo("onProcessAsyncEvent", BaseAsyncEvent.class, ThreadMode.ASYNC),
new SubscriberMethodInfo("onProcessBgEvent", BaseBackgroundEvent.class, ThreadMode.BACKGROUND),
new SubscriberMethodInfo("onProcessPostEvent", BasePostEvent.class),
}));
}
private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}
这里有个SUBSCRIBER_INDEX,每个当中存放着一个SimpleSubscriberInfo对象,每个SimpleSubscriberInfo对象由一个全路径名表示的class对象。这个class对象指明了当前是哪个订阅者,然后还包含着一个SubscriberMethodInfo数组,这里面存放的是每个订阅者的信息,包括方法名,事件类。threadMode等信息。我们上面也看到了在创建EventBusBuilder的时候通过addindex将这个生成的索引类对象注入。在findUsingInfo方法中调用索引类的getSubscriberInfo方法从SUBSCRIBER_INDEX中取出对应的SubscriberInfo也就是刚刚存放到里面的SimpleSubscriberInfo,我们看下SimpleSubscriberInfo的定义: |
public class SimpleSubscriberInfo extends AbstractSubscriberInfo {
private final SubscriberMethodInfo[] methodInfos;
public SimpleSubscriberInfo(Class subscriberClass, boolean shouldCheckSuperclass, SubscriberMethodInfo[] methodInfos) {
super(subscriberClass, null, shouldCheckSuperclass);
this.methodInfos = methodInfos;
}
@Override
public synchronized SubscriberMethod[] getSubscriberMethods() {
int length = methodInfos.length;
SubscriberMethod[] methods = new SubscriberMethod[length];
for (int i = 0; i < length; i++) {
SubscriberMethodInfo info = methodInfos[i];
methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode,
info.priority, info.sticky);
}
return methods;
}
}
|
protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType,
ThreadMode threadMode,int priority, boolean sticky) {
try {
//从订阅类中获取指定方法名和事件类型的方法
Method method = subscriberClass.getDeclaredMethod(methodName, eventType);
return new SubscriberMethod(method, eventType, threadMode, priority, sticky);
} catch (NoSuchMethodException e) {
throw new EventBusException(“Could not find subscriber method in “ + subscriberClass +
“. Maybe a missing ProGuard rule?”, e);
}
}
|
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
/某某类中的某某方法 用于处理某个事件/
//获取当前订阅方法的事件类型(事件类型为订阅方法的参数)
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber/订阅类/, subscriberMethod/该订阅类的某个订阅方法/);
//从subscriptionsByEventType获取事件类型为eventType的订阅者,看下该事件是否已经订阅了
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
//如果为空(表示该事件还没找到订阅者)那么新建一个空的传进去
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//如果不为空表示该事件已经有订阅者了,如果当前订阅者信息中已经有同样方法已经订阅了,那么抛出异常
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//subscriptionsByEventType 按照事件类别对订阅者进行分类
// 事件类1
// |-----订阅类1 ---- 方法1
// |-----订阅类2 ---- 方法2
// |-----订阅类3 ---- 方法3
// |-----订阅类4 ---- 方法4
// |-----订阅类5 ---- 方法5
// |-----订阅类6 ---- 方法6
// |-----订阅类7 ---- 方法7
//优先级从大到小,适当的位置插入
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
//typesBySubscriber
// |--------事件类型
// |--------事件类型
// |--------事件类型
// |--------事件类型
// |--------事件类型
//当前订阅者订阅了哪些事件集合.
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
//将当前的事件类型添加到当前订阅者订阅的事件集合
subscribedEvents.add(eventType);
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
//如果当前事件类型是属于sticky事件类型,那么在注册的时候将其发送到订阅者
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
看完上面代码注释后我们来介绍下EventBus中三个重要的列表: |
subscriptionsByEventType: key是某个事件类型,value是订阅这个事件类型的订阅者列表(按照优先级顺序排列) 这个是最重要,事件分发到时候就是依赖这个表
typesBySubscriber:key 是某个订阅者,value是这个订阅者所订阅的事件类型。
stickyEvents:sticky事件列表
|
public void post(Object event) {
//获取当前posting线程的状态
PostingThreadState postingState = currentPostingThreadState.get();
//获取事件队列
List
发布过程如下: |
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
//事件类
Class> eventClass = event.getClass();
//是否找到订阅者
boolean subscriptionFound = false;
/*如果eventInheritance 为true 那么当前事件以及接口,子接口父类事件都会被post*/
if (eventInheritance) {
//获取当前事件以及接口,子接口,以及父类,比如当前的事件类型为MotionEvents ,那么MotionEvents本身,以及它的接口子接口,以及父类都会被添加到eventTypes
List
//找到对应的事件类型以及子类型调用postSingleEventForEventType
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
//调用postSingleEventForEventType
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
//如果没有找到订阅者
if (!subscriptionFound) {
//如果打开没有订阅者的Log发出对应的Log
if (logNoSubscriberMessages) {
Log.d(TAG, “No subscribers registered for event “ + eventClass);
}
//发送NoSubscriberEvent,我们可以注册这个来处理这个事件
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
|
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList
synchronized (this) {
//获取订阅这个事件的订阅者们,可以有多个
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
//将事件传递给各个订阅类
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
//重置状态
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
//根据订阅线程模式使用不同的poster进行订阅
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
//当前进程中执行
invokeSubscriber(subscription, event);
break;
case MAIN:
//在主线程中执行
if (isMainThread) {
//如果当前线程是主线程那么直接在当前线程中运行
invokeSubscriber(subscription, event);
} else {
//如果当前线程不是主线程,那么使用主线程Handler运行
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
//如果当前线程是主线程,那么使用backgroundPoster运行
backgroundPoster.enqueue(subscription, event);
} else {
//如果当前是后台线程,那么直接在这个后台线程中运行
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
//如果是异步的都归到asyncPoster运行
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException(“Unknown thread mode: “ + subscription.subscriberMethod.threadMode);
}
}
当事件发送到事件总线中后会使用事件类型从我们第一阶段形成的subscriptionsByEventType列表中获取对应的subscription,需要注意的是一个事件可能有多个subscription,所以获取到的是subscription列表。然后再根据subscription中的threadMode的情况来触发invokeSubscriber方法。在这个方法中实际上是调用subscription中method对象的invoke方法来出发点对应的订阅方法。 |
void invokeSubscriber(Subscription subscription, Object event) {
try {
//触发订阅方法,并将事件类型作为参数传给它
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException(“Unexpected exception”, e);
}
}
(2) MAIN: |
final class HandlerPoster extends Handler {
//……………..
void enqueue(Subscription subscription, Object event) {
//从池中获取一个PendingPost
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
//入队
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException(“Could not send handler message”);
}
}
}
}
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}
|
final class PendingPost {
private final static List
Object event;
Subscription subscription;
PendingPost next;
private PendingPost(Object event, Subscription subscription) {
this.event = event;
this.subscription = subscription;
}
static PendingPost obtainPendingPost(Subscription subscription, Object event) {
synchronized (pendingPostPool) {
int size = pendingPostPool.size();
if (size > 0) {
//从尾部取出后填充并返回
PendingPost pendingPost = pendingPostPool.remove(size - 1);
pendingPost.event = event;
pendingPost.subscription = subscription;
pendingPost.next = null;
return pendingPost;
}
}
//否则新建一个返回
return new PendingPost(event, subscription);
}
static void releasePendingPost(PendingPost pendingPost) {
pendingPost.event = null;
pendingPost.subscription = null;
pendingPost.next = null;
synchronized (pendingPostPool) {
//释放后放入池中
// Don't let the pool grow indefinitely
if (pendingPostPool.size() < 10000) {
pendingPostPool.add(pendingPost);
}
}
}
}
我们回过头看mainThreadPoster,当我们post一个事件到mainThreadPoster的时候,会触发向Handler 发送消息,收到消息后会在handleMessage不断循环调用invokeScriber.直到队列中的PandingPost处理完,或者处理事件超过设定的最大事件处理时间。整个流程如下所示: |
final class BackgroundPoster implements Runnable {
//..................
public void enqueue(Subscription subscription, Object event) {
//从池中获取一个可复用的PendingPost
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
//将其添加到队列中
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
//将线程池设置为阻塞状态
eventBus.getExecutorService().execute(this);
}
}
}
@Override
public void run() {
try {
try {
//这里会不断从队列中获取可执行的,一直执行直到队列为空的时候退出。
while (true) {
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}
}
![](/开源代码分析之EventBus/background.png)
最后我们再看AsyncPoster:
整个大体的结构和backgroundPoster很相似,但是它们之间的区别在于,AsyncPoster每次会从线程池中获取一个线程来执行,而backgroundPoster会在同一个现场中完成。
class AsyncPoster implements Runnable {
//............
public void enqueue(Subscription subscription, Object event) {
//从池中取出一个可复用的PendingPost对象
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
//插入到队列中
queue.enqueue(pendingPost);
//调用线程池从线程池中取出一个运行
eventBus.getExecutorService().execute(this);
}
@Override
public void run() {
//取出队列中的第一个
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
//出发订阅者订阅方法
eventBus.invokeSubscriber(pendingPost);
}
}
![](/开源代码分析之EventBus/async.png)
到此整个EventBus的源码分析结束:
老规矩最后以一个图来总结整个流程:
首先我们需要先通过register来订阅某些事件,在调用register的时候,SubscriberFinder从订阅类中找出所有的订阅处理方法,并将其挂接在事件分发中心EventBus的subscriptionsByEventType列表中,如果有事件触发那么就会将事件发送到事件分发中心,事件分发中心就会从subscriptionsByEventType列表中查找订阅者方法。然后根据对应的threadMode在不同的poster中调用invokeScriber来执行订阅者方法。
![](/开源代码分析之EventBus/final.png)
作为Android应用开发工程师估计没有人没听过Butterknife吧,我们可以借助它来简化view的查找过程以及通过注释将监听器绑定到事件响应方法上,这样可以减少很多代码量。对于Butterknife的用法在之前的博客中已经做了相应的介绍,这部分主要是分析Butterknife的源码,在之后的博文中还会对编译时注解解析器的实现做对应的介绍,如果想要对这部分内容有比较深入的了解,可以看下 《Java 进阶系列之注解》 以及《一步一步编写编译时注解框架》以及该篇博客。
好了言归正传,我们在介绍之前看下Butterknife 的工作原理:
在没有Butterknife的情况下我们用的是 Android 原生的API方法findViewById,而有了Butterknife后我们就可以直接使用@BindView(R.id.text) , 但是如果仔细看过源码的同学,会发现到最后还是调用findViewById,只不过这个是编译时注解框架帮我们生成了对应的代码,换句话说Butterknife以带有注解的源码文件作为javac编译器输入,在编译的时候会调用注解解析工具,对这些源码中的注解进行解析,解析后再通过代码生成工具来产生对应的源码文件,最后将我们自己的源码文件和注解解析工具产生的源码一同作为编译器输入进行编译。
ok 了解了整个原理我们就可以进行深入研究了。
我们将分成两大部分进行分析:
在绑定实用的是bind方法,bing方法实现如下,它的实现十分简单,但是在介绍bind时候,必须要先大概知道什么是target,什么是source,简单讲target就是一个实体,它承载着source,目前butterknife支持的target 包括activity,dialog,以及view。source就是我们实际要查找控件的地方。也就是findViewById 应用的对象。下面是以activity为target,所以对应的source就是对应activity的DecorView.
|
接着我们来看下createBinding方法,在方法开始的时候会使用target的class对象来获取对应的ViewBinding的构造方法,target的ViewBinding对象是由ButterknifeProcessor生成的,这个会在后面介绍ButterknifeProcessor的时候进行详细展开介绍。获取到对应的ViewBinding对象后调用该对象的构造方法并返回。
|
findBindingConstructorForClass 的作用就是加载对应targetClass的ViewBinding对象的构造方法:
@Nullable @CheckResult @UiThread |
为了提高整体性能,这里使用了BINDINGS这个Map作为内存缓存。BINDINGS的定义如下:
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>(); |
它的键值为我们的target 的类对象,我们以MainActivity为例子,那么key为MainActivity.class 对应的值呢?就是MainActivity_ViewBinding的构造方法,这个构造方法只有一个参数(View).
这个阶段的整个过程如下:
拿着target类对象到BINDINGS缓存中去匹配,看下是否有缓存,如果有缓存那么使用缓存,如果没有那么就获取JavaPoet产生的MainActivity_ViewBinding(View view)的构造方法,并添加到缓冲中,供后续使用,为啥要缓存?
因为这里获取MainActivity_ViewBinding的构造方法使用的是反射的方式,对性能有所影响,使用缓存可以减少反射的使用次数,但是这里有一个疑问就是会不会因为Butterknife的大量使用从而导致缓存占用很大的空间,这个问题又是如何克服的?我们带着这个问题继续。获取到构造方法后我们new出一个实例对象,这里可以看出 MainActivity_ViewBinding.java继承的是Unbinder,而Unbinder中的unbind方法有可能就是处理缓存清除的工作。
我们看下Butterknife sample例子中产生的一个代码:
public class G_ViewBinding<T extends G> extends E_ViewBinding<T> { |
我们先不看上面的具体代码,只看unbind方法。它会递归地将对应的view对象设置成null,从而在gc的时候释放掉资源。
从上面代码看整个butterknife包的源码是十分简单的,最重要的在于xxxx_ViewBinding.class的生成,这就需要对butterknife-compiler包中的源码进行研究了,我们所有的注解都在butterknife-annotations这个包中,我们这里只以BindView注解作为分析对象:
具体的注解相关内容见《Java进阶之注解》 这篇博客。
@Retention(CLASS) @Target(FIELD) |
从上面我们可以看出@BindView注解应用于属性定义中,并且只在源码级别以及class级别中存在,它的值为一个id类型的值。不允许其他类型的值作为参数。
从上面一小结介绍可以看出Butterknife.java 中的工作很简单就是拿着targetClass找到对应的ViewBinding,至于ViewBinding怎么来的则是这一小结的介绍重点,在介绍ButterKnife注解解析器之前我们再次回顾下注解解析器在整个编译过程中的的作用:
我们知道Java使用Javac处理编译过程的,在Java的编译时期,Javac会先调用java注解处理器来进行处理。因此我们可以自定义注解处理器来做一些处理。注解处理器通常以java源码或者已编译的字节码作为输入,然后生成一些源码文件作为输出。在这个过程可以在已有的代码上添加一些方法,来帮我们做一些有用的事情。这些生成的 java 文件跟其他手动编写的java源码一样,会被 javac 编译。编译时解析是注解强大的地方之一。我们可以利用它在编译时帮你生成一些代码逻辑,避免了运行时利用反射解析所带来的性能开销。
下面是ButterKnifeProcessor的大体结构:
Processor.class) ( |
下面我们就顺着这个框架对ButterKnifeProcessor进行分析:
我们首先看下
@AutoService(Processor.class)的作用:一旦在Processor类上加上注解@AutoService(Processor.class),它自动帮你将这个类添加到META-INF/service/javax.naaotation.processing.Processor下Java的ServiceLoader会自己去找到这个类并进行编译处理。
接着是init方法:
init方法是在Processor创建时被javac调用并执行初始化操作。通过ProcessingEnvironment参数我们可以获得如下元素:
Messager :用于打印Log,在编译的时候会在Message窗口中输出。
Elements :用于处理编程元素的辅助类
Filer :用于注解处理器生成新文件的
Locale :用于获取时区信息的类
Types :用于操作类型的辅助方法
其中最常用的就是Elements以及Types和打印Log用到的Messager.
|
接着就是整个注解解析器的关键方法process,每次执行时,处理器方法被调用,并且传入了当前要处理的注解类型。可以在这个方法中扫描和处理注解,并生成Java代码。我们看下面的process方法,在这个方法中主要完成了两项任务,一个就是调用findAndParseTargets来获取bindingMap
bindingMap 是什么呢?它其实对应的是每个有调用bind绑定的target,举个例子来说,我们在MainActicity的onCreate方法调用了Butterknife的bind方法,那么ButterknifeProcessor就会产生一个对应的BindingSet,也就是说有多少个bind,bindingMap里面就有多少个BindingSet。然后再使用Javapost来为每个BingSet生成一个_ViewBinding.class.
|
我们先来看下findAndParseTarets,它的任务分成如下几个部分:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { |
首先看下scanForRClasses:
它的作用是解析R文件将获取到的数据存入symbols,用于作为后续标识每个元素的key。R 元素中的每一个变量对应symols中的一个元素。
private void scanForRClasses(RoundEnvironment env) { |
查找R文件使用的是RClassScanner,它是一个TreeScanner,它会找到整个项目的R文件:
private static class RClassScanner extends TreeScanner { |
在找到全部的R文件后在scanForRClass中会调用parseRClass对扫描到的R文件进行解析:
private void parseRClass(String respectivePackageName, String rClass) { |
我们从上面代码中可以看到在parseRClass中会使用IdScanner 对当前扫描的R文件进行扫描:
private static class IdScanner extends TreeScanner { |
在IdScanner 中会对R文件中的每个内部类进行扫描,判断当前的内部类是否是所支持的,如果是那么就调用VarScanner进行进一步扫描。
private static class VarScanner extends TreeScanner { |
VarScanner 也是一个TreeScanner,它的所用就是在R 文件内部类中扫描,并将扫描到的id 添加到symbos中。
介绍到这里我们做下简要,首先我们会通过RCScanner找到项目的R文件,再使用IdScanner对R文件中的内部类进行扫描,每个内部类对应一类资源,最后通过VarScanner对id进行扫描然后存入sybols这个map.
symbols定义如下:
private final Map<QualifiedId, Id> symbols = new LinkedHashMap<>(); |
Key: |
介绍完scanForRClass我们继续了解下注解的解析过程:
在进行解析之前会先对注解所应用的对象做初步检查:
如果校验无误,那么会先查询bingMap中是否已经有对应的BindingSet了,如果没有就创建一个并添加到bingMap中。如果有的话就完对应的BindingSet添加Field这些Field会在JavaPost创建对应源码文件的时候用于生成对应的成员变量。这个后面会进行详细介绍。
我们下面深入看两点:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { |
BindingSet的创建是通过调用getOrCreateBindingBuilder方法来完成的:
它会先判断当前target类型是activity,dialog,还是一般的view,以及生成target对应的ViewBinding文件名。
//builderMap 中存放的形式 key: 对应的包名 + 对应的BindingSet.Builder |
/** |
那BindingSet的结构是怎样的呢?我们继续往下看:
static final class Builder { |
我们从BindingSet的Builder可以看出它有如下的成员变量:
parentBinding 指向父类的BindingSet。 |
它有如下方法:
void addField(Id id, FieldViewBinding binding) 添加Field |
介绍完BindView 我也把事件绑定的方法贴出来了,下面有较为详细的注释。
private void findAndParseListener(RoundEnvironment env, Class<? extends Annotation> annotationClass, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { |
使用JavaPoest为每个BindingSet创建对应的_ViewBinding文件:
我们在此回到process方法
|
我们上面了解了如何从R文件中获取对应的信息构成后续作为Id使用的symbols。再对代码中使用到的Butterknife注解进行解析,获取到对应的View 绑定,事件方法绑定,资源绑定信息,添加到对应BingSet中,最后我们要了解的是最后一步 – 使用JavaPost来将BindingSet中收集到的View 绑定,事件方法绑定,资源绑定信息生成对应的源码:
我们先来看下brewJava 方法:
JavaFile brewJava(int sdk) { |
brewJava 方法中最重要的就是createType,它是生成_ViewBinding的核心代码,下面给出了详细的注释:
private TypeSpec createType(int sdk) { |
那么我们在Butterknife bind方法中调用的构造函数是哪个呢:就是createBindingConstructor
private MethodSpec createBindingConstructor(int sdk) { |
上面的代码已经注释很清楚了,我们直接讲最重要的:addViewBinding,在调用addViewBinding之前会调用hasViewBindings来判断当前是否有view绑定以及事件方法绑定,如果有才会调用addViewBinding.
在addViewBinding方法中会判断是否isRequired来决定是调用butterknife.internal.Utils的findRequiredViewAsType 还是findOptionalView
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) { |
findOptionalViewAsType 和 findRequiredViewAsType 有什么区别呢?我们从下面代码可以看出findRequiredViewAsType会在source中获取对应的控件,后判断是否获取到,如果没有获取到会抛出如下异常:
throw new IllegalStateException("Required view '" |
而最终的类型转换是通过cls.cast 方法来完成的。
public static <T> T findOptionalViewAsType(View source, String who, int id, |
到此为止整个Butterknife源码分析完毕。老规矩上图:
我们之前在介绍Java注解的时候提到了注解有两种类型,一种是运行时注解,一种是编译时注解,运行时注解是在运行的时候通过反射解析注解,针对对应的注解采取对应的动作,编译时注解是在编译时通过收集附加在代码上的注解来标记一些内容,然后在编译的时候通过识别这些注解来动态生成一些代码。很显然运行时注解会因为java反射而引起较为严重的性能问题,所以尽量避免使用,而编译时注解的魅力在于:编译时按照预先使用注解规定的方案生成代码来避免编写重复代码,提高开发效率,且不影响性能。ß目前使用编译时注解的较为著名的开源框架有Butterknife,Dagger2等。
Annotation Processor Tool是用于编译期扫描和处理注解的工具,目前被集成在javac中。在编译的时候,javac通常会找到你定义的注解处理器,并执行注解处理。