今天要和大家一起分析的是图片加载开源库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) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
Context applicationContext = context.getApplicationContext();
//使用ManifestParser来解析AndroidMenifest中的GlideModule配置
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
//创建一个GlideBuilder
GlideBuilder builder = new GlideBuilder(applicationContext);
for (GlideModule module : modules) {
//将从AndroidMannifest解析出来的GlideMode组件的配置添加到GlideBuilder
module.applyOptions(applicationContext, builder);
}
//创建出对应的Glide
glide = builder.createGlide();
for (GlideModule module : modules) {
//将从AndroidMannifest解析出来的GlideMode组件添加到glide.registry
module.registerComponents(applicationContext, glide.registry);
}
}
}
}
return glide;
}

实际的Glide是通过GlideBuilder的createGlide中创建的,这个套路在Picasso中也应用过,在这里主要判断加载图片所需的部件是否都进行了设置如果没有设置那么就赋给它默认的对象。
Glide中的主要可设置的组件有如下:

1. 线程池
2. 设备缓存计算器
3. Bitmap 对象池
4. 字节数组对象池
5. 内存缓存
6. 磁盘缓存
7. Glide图片加载引擎
8. 解码格式

这些会在涉及到的环节进行展开介绍。需要注意的是在解码格式的设置上,如果图片不支持透明度那么就使用565的格式,如果有透明度那么就使用8888的格式。这两种在保存的图片大小上会有影响。很显然8888的格式保存下来文件会相对大。但是效果会好很多。

Glide createGlide() {

if (sourceService == null) {
//判断线程池是否已经设置,如果没有设置,那么会使用默认的先进先出优先级队列线程池,最大的线程数为当前唤醒的CPU核数
//它主要用于当从缓存中没有找到对应的对象时执行Glide的加载,解码和转换任务
final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
sourceService = new FifoPriorityThreadPoolExecutor("source", cores);
}

if (diskCacheService == null) {
//它主要用于当从缓存中有对应的对象时,用来执行Glide的加载,解码和转换任务
//如果缓存线程池没有特殊指定那么将会使用最大线程数为1的先入先出优先级队列线程池
diskCacheService = new FifoPriorityThreadPoolExecutor("disk-cache", 1);
}

if (memorySizeCalculator == null) {
//某个设备缓存计算器,计算结果取决于一些常量和当前设备的信息(宽,高,像素密度)
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}

if (bitmapPool == null) {
//Bitmap对象池初始化
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
//通过memorySizeCalculator计算出缓存大小
int size = memorySizeCalculator.getBitmapPoolSize();
if (DecodeFormat.REQUIRE_ARGB_8888) {
bitmapPool = new LruBitmapPool(size, Collections.singleton(Bitmap.Config.ARGB_8888));
} else {
bitmapPool = new LruBitmapPool(size);
}
} else {
bitmapPool = new BitmapPoolAdapter();
}
}

if (byteArrayPool == null) {
//基于LRU算法的数组缓存池
byteArrayPool = new LruByteArrayPool();
}

if (memoryCache == null) {
//内存缓存
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}

if (diskCacheFactory == null) {
//磁盘缓存
diskCacheFactory = new InternalCacheDiskCacheFactory(context, Glide.DEFAULT_DISK_CACHE_SIZE);
}

if (engine == null) {
//负责开启加载任务以及管理活跃的或者缓存的图片资源
engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
}

//解码格式
if (decodeFormat == null) {
//图片的加载格式(ARGB_8888或RGB_565),默认ARGB_8888,判断规则如下
//如果支持透明或者使用了透明则使用ARGB_8888
//如果不支持透明则使用ARGB_565
decodeFormat = DecodeFormat.DEFAULT;
}
return new Glide(engine, memoryCache, bitmapPool, byteArrayPool, context, decodeFormat);
}

紧接着就是调用Glide 构造方法来创建对象。在Glide 构造方法中最主要的任务就是注册一系列内置的图片资源编解码器,创建ImageViewTargetFactory,新建RequestOptions,并为其设置图片编码格式。最后将全局中比较常用的Glide,Register,RequestOption,Engine存放到GlideContext 中供后续使用,这个也是比较常用的方式。注册器会在后面进行进一步介绍。这里先主要看下整个流程。

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, ByteArrayPool byteArrayPool, Context context, DecodeFormat decodeFormat) {
this.engine = engine;
this.bitmapPool = bitmapPool;
this.byteArrayPool = byteArrayPool;
this.memoryCache = memoryCache;
//BitmapPool的预填充器
bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool, decodeFormat);
Resources resources = context.getResources();
//从给定的inputstream中解码图片
Downsampler downsampler = new Downsampler(resources.getDisplayMetrics(), bitmapPool, byteArrayPool);
//Gif图片资源的解码器
ByteBufferGifDecoder byteBufferGifDecoder = new ByteBufferGifDecoder(context, bitmapPool, byteArrayPool);
//用于管理组件注册器,我们还可以自己自定义组件通过GlideModule registerComponents来进行注册
registry = new Registry(context)
//添加到encoderRegistry public <Data> Registry register(Class<Data> dataClass, Encoder<Data> encoder)
.register(ByteBuffer.class, new ByteBufferEncoder())//将ByteBuffers 数据写入到文件中
.register(InputStream.class, new StreamEncoder(byteArrayPool))//将inputStream数据写入到文件中
//..................
/* Transcoders */
.register(Bitmap.class, BitmapDrawable.class, new BitmapDrawableTranscoder(resources, bitmapPool))
.register(Bitmap.class, byte[].class, new BitmapBytesTranscoder())
.register(GifDrawable.class, byte[].class, new GifDrawableBytesTranscoder());
//用于负责生成特定类型的Target
ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
//设置解码格式
RequestOptions options = new RequestOptions().format(decodeFormat);
//通过Context形式暴露出来
glideContext = new GlideContext(context, registry, imageViewTargetFactory, options, engine, this);

}

到目前为止,Glide对象创建结束,我们回顾下整个过程:

  1. 首先是从AndroidManifest查找是否有用户自定义的GlideModule,如果有的话将其解析出来,每个GlideModule一般有两个方法,applyOptions和registerComponents ,applyOptions负责将自定义GlideModule中的配置添加到GlideBuilder中,而registerComponents 负责将当前GlideModule添加到Register中。
  2. 然后在GlideBuilder 的createGlide方法中为没有设置的Glide重要组件设置默认状态。
  3. 最后调用Glide构造方法创建对象。在构造方法中主要是完成各种资源编解码器的注册,以及Target的构建工厂对象的创建。以及设置解码格式和Glide图片加载引擎。
  4. 通过 GlideContext将后续处理常用的组件暴露出来。

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) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}

第一阶段我们先看下怎么获取到RequestManagerFragment,首先我们应该明白RequestManagerFragment和我们开发过程中常见的Fragment的区别是它是一个没有界面不可见的一个Fragment,但是它有Fragment所拥有的生命周期。我们可以借助这一点来实现前面提到的图片加载请求状态随着生命周期的改变而改变。

public RequestManager get(Activity activity) {
if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return get(activity.getApplicationContext());
} else {
//获取它的FragmentManager,并且将activity以及FragmentManager传入fragmentGet获取RequestManagerFragment
android.app.FragmentManager fm = activity.getFragmentManager();
//获取RequestManagerFragment
return fragmentGet(activity, fm);
}
}

那么对于Activity而言这个Fragment藏在哪里呢?它和Activty所拥有的一般Fragment一样都归到Activity FragmentManager中统一管理。我们看下这个过程:

RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
//从指定的FragmentManager获取对应的RequestManagerFragment,
//如果没有那么就直接新建一个RequestManagerFragment添加到FragmentManager
RequestManagerFragment current = getRequestManagerFragment(fm);
//从RequestManagerFragment中获取RequestManager
RequestManager requestManager = current.getRequestManager();
//如果都没获取到那么就新建一个RequestManager将其设置到RequestManagerFragment
if (requestManager == null) {
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}

从上面代码可以看出,这个阶段首先会先从FragmentManger 中获取,如果没获取到就会新建一个并添加到FragmentManager,获取完RequestManagerFragment后就从中获取RequestManager,下面是获取RequestManagerFragment的过程代码:

RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) {
//使用Fragment查找TAG为com.bumptech.glide.manager的Fragment
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
//尝试从pendingRequestManagerFragments获取,有可能还在缓存中
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
//新建一个RequestManagerFragment,添加到pendingRequestManagerFragments缓存
current = new RequestManagerFragment();
pendingRequestManagerFragments.put(fm, current);
//将新建的RequestManagerFragment添加到FragmentManager中
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
//提交成功后移除pendingRequestManagerFragments中的RequestManagerFragment
//但是为什么不直接调用pendingRequestManagerFragments.remove方法呢?
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}

我们上面介绍了RequestManagerFragment的获取方式,那么RequestManagerFragment又是怎么将请求和生命周期给对应起来的呢?在回答这个问题之前我们先来看下RequestManagerFragment的结构,RequestManagerFragment中有两个重要对象ActivityFragmentLifecycle,RequestManager
前者是生命周期的触发者,需要响应生命周期的部件都需要监听它,而RequstManger负责请求的管理,它里面有个RequestTracker 用于实行请求的管理。RequstManaer是生命周期事件的响应者,RequestManagerFragment触发对应生命周期后通过ActivityFragmentLifecycle传出来,ActivityFragmentLifecycle再通知监听它的监听者。对应的监听者在不同的生命周期作出不同的响应。

我们来看下这部分的代码
ActivityFragmentLifecycle 是在RequestManagerFragment构造方法中创建的,如下所示:

public RequestManagerFragment() {
this(new ActivityFragmentLifecycle());
}

RequestManager是通过setRequestManager进行注入的,这个可以看之前的代码介绍。

public void setRequestManager(RequestManager requestManager) {
this.requestManager = requestManager;
}

我们再看下RequestManagerFragment的生命周期中做了什么,和之前说的一样它在对应的生命周期中触发lifecycle事件,

@Override
public void onStart() {
super.onStart();
lifecycle.onStart();
}

@Override
public void onStop() {
super.onStop();
lifecycle.onStop();
}

@Override
public void onDestroy() {
super.onDestroy();
lifecycle.onDestroy();
}

ActivityFragmentLifecycle 很简单就是注册对应生命周期事件的观察者,一旦ActivtyFragmentLifeCyble被RequestManagerFragment 生命周期所触发,那么ActivtyFragmentLifeCycle也会将对应的事件通知到各个观察者。

@Override
public void addListener(LifecycleListener listener) {
//添加监听器,并将当前状态通知到其观察者
lifecycleListeners.add(listener);
if (isDestroyed) {
listener.onDestroy();
} else if (isStarted) {
listener.onStart();
} else {
listener.onStop();
}
}

@Override
public void removeListener(LifecycleListener listener) {
lifecycleListeners.remove(listener);
}

void onStart() {
isStarted = true;
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onStart();
}
}

void onStop() {
isStarted = false;
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onStop();
}
}

void onDestroy() {
isDestroyed = true;
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onDestroy();
}
}

ActivtyFragmentLifeCycle 有两个重要的观察者:RequestManager 和 ConnectivityMonitor。所以这个阶段的整个过程如下图所示:

到此为止with阶段代码分析完毕,我们来简单回顾下:
这个阶段主要是完成请求随着生命周期的状态改变而改变状态的工作。主要涉及到四个重要对象一个是RequestManagerFragment 一个是 ActivityFragmentLifeCycle ,还有两个是RequestManger,以及ConnectivityMonitor。三者之间的关系以及如何实现请求随着生命周期的状态改变而改变状态这个在上面已经详细介绍过了。这个需要重点了解下。

接下来是as阶段:

load 有多种实现,现将这些实现都罗列如下:

/**
* 将资源指定加载为Bitmap,即使实际上可能是一个动画也会将资源加载为Bitmap
*/
public RequestBuilder<Bitmap> asBitmap() {
return as(Bitmap.class).transition(new BitmapTransitionOptions()).apply(DECODE_TYPE_BITMAP);
}

/**
* 尝试将资源总是以GifDrawable形式加载
* 这种情况如果数据不是GIF,则会失败。因此,这只能使用与下面的情况:
* 如果数据代表动画GIF,并且调用者想要与GifDrawable进行交互
* 通常情况只需要使用asDrawable就足够了,因为它会确定给定的数据是否代表动画GIF并返回相应的Drawable
*/
public RequestBuilder<GifDrawable> asGif() {
return as(GifDrawable.class).transition(new DrawableTransitionOptions()).apply(DECODE_TYPE_GIF);
}

/**
* 尝试使用注册的ResourceDecoder来加载指定的资源
* 默认情况下,返回的要么是个BitmapDrawable要么是个GifDrawable 但是如果其他用于解码Drawable的解码器有注册,那么它们有可能也会被返回
*/
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class).transition(new DrawableTransitionOptions());
}

上面最终都是调用如下方法:

/**
* 尝试使用任何注册的能够解析给定的资源class或者给定资源class子类的ResourceDecoder来加载资源
*/
public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
return new RequestBuilder<>(context, this, resourceClass);
}

从这里可以看出load阶段的工作就是创建一个RequestBuilder。

创建了一个RequestBuilder后就可以调用load开始加载了。我们来看下load阶段:

和as以及with阶段一样load也有各种重载,但是最终还是调用:

private RequestBuilder<TranscodeType> loadGeneric(Object model) {
//这个很简单设置model并且将isModelSet设置为true
this.model = model;
isModelSet = true;
return this;
}

这里只是简单得设置了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) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}

这里使用Glide对象构造阶段创建的ImageViewTargetFactory来创建出我们需要的Target,具体需要创建什么Target需要根据transcodeClass来决定。

public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (Target<Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (Target<Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}

而transcodeClass 是在创建RequestBuilder的时候传入的,而RequestBuilder是在as方法中创建的,所以这里的resourceClass就是buildTarget所对应的transcodeClass。所以我们调用asBitmap 的时候这里传入的就是Bitmap.class,调用asGif的时候这里传入的就是GifDrawable.class,如果我们调用asDrawable的时候,这里传入的就是Drawable.class。

public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
return new RequestBuilder<>(context, this, resourceClass);
}

不管怎样buildTarget 返回的就两个对象要么是BitmapImageViewTarget要么是DrawableImageViewTarget

BitmapImageViewTarget –> ImageViewTarget
DrawableImageViewTarget –> ImageViewTarget

但是我们前面已经提到asDrawable 和asGif两个都是对应的DrawableImageViewTarget那么这两个会有什么区别呢?我们看下DrawableImageViewTarget,两种区别在于是否继承自Animatable,GifDrawable是继承自Animatable 所以在资源获取结束后会调用start方法让它动起来。

@Override
public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();

if (!(resource instanceof Animatable) && layoutParams != null && layoutParams.width > 0
&& layoutParams.height > 0) {
resource = new FixedSizeDrawable(resource, layoutParams.width, layoutParams.height);
}
super.onResourceReady(resource, transition);
if (resource instanceof Animatable) {
((Animatable) resource).start();
}
}

@Override
public void onStart() {
if (resource instanceof Animatable) {
((Animatable) resource).start();
}
}

@Override
public void onStop() {
if (resource instanceof Animatable) {
((Animatable) resource).stop();
}
}

我们再继续顺着继承树看下去,

BitmapImageViewTarget –> ImageViewTarget –> ViewTarget –> BaseTarget
DrawableImageViewTarget –> ImageViewTarget –> ViewTarget –> BaseTarget

ViewTarget 中有几个重要的回调函数onLoadStarted,onLoadFailed ,onLoadCleared ,onResourceReady
分别在启动加载,加载失败,清除加载,资源获取完毕的情况下回调,主要用于设置占位图片,错误图片以及加载完的图片

@Override
public void onLoadStarted(Drawable placeholder) {
setResource(null);
setDrawable(placeholder);
}

@Override
public void onLoadFailed(Drawable errorDrawable) {
setResource(null);
setDrawable(errorDrawable);
}

@Override
public void onLoadCleared(Drawable placeholder) {
setResource(null);
setDrawable(placeholder);
}

@Override
public void onResourceReady(Z resource, Transition<? super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
setResource(resource);
}
}

这个继承树根真深,还得继续刨:
ViewTarget有两个重要的任务就是获取这个view的Size以及设置tag:
这里有个重要的对象SizeDeterminer,获取View的尺寸就是通过这个类来完成的。它首先会先通过getWidth()和getHeight()来获取View的宽高,如果有一个为0的话,它会检查View的LayoutParams从中获取宽高大小,如果还是没有那么就会监听OnPreDrawListener回调从而等到在绘制之前进行测量的时候回调。

public void getSize(SizeReadyCallback cb) {
//调用View的getWidth 和 getHeight获取,如果获取不到尝试LayoutParams中获取
int currentWidth = getViewWidthOrParam();
int currentHeight = getViewHeightOrParam();
//如果通过上述两种方式能够获取到宽高,那么就直接调用onSizeReady将宽高传出
if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
} else {
//如果通过上述两种方式都不能获取到高度那么只能通过添加OnPreDrawListener来等到该View 测量结束后返回宽高
if (!cbs.contains(cb)) {
cbs.add(cb);
}
if (layoutListener == null) {
final ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
}
}
}

在测量结束的时候会调用checkCurrentDimens来检查当前的宽高

@Override
public boolean onPreDraw() {

SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
if (sizeDeterminer != null) {
sizeDeterminer.checkCurrentDimens();
}
return true;
}

在checkCurrentDimens 会重新获取并检查View的宽高如果获取到了就通过notifyCbs来通知对应的监听者。
并将其从OnPreDrawListener监听队列中返回。

private void checkCurrentDimens() {
if (cbs.isEmpty()) {
return;
}
int currentWidth = getViewWidthOrParam();
int currentHeight = getViewHeightOrParam();
if (!isSizeValid(currentWidth) || !isSizeValid(currentHeight)) {
return;
}
notifyCbs(currentWidth, currentHeight);
ViewTreeObserver observer = view.getViewTreeObserver();
if (observer.isAlive()) {
observer.removeOnPreDrawListener(layoutListener);
}
layoutListener = null;
}

所以整个继承树的完成工作如下:
DrawableImageViewTarget –> ImageViewTarget –> ViewTarget –> BaseTarget
将加载的图片设置到Target,如果是Gif启动播放Gif –> 设置占位图,错误图缩率图等 –> 获取图片尺寸,设置Tag –> 存储Request
有了上述的了解我们再继续返回看into的流程:

public <Y extends Target<TranscodeType>> Y into(Y target) {
Util.assertMainThread();
//Target不能为空
if (target == null) {
throw new IllegalArgumentException("You must pass in a non null Target");
}
//model必须设置
if (!isModelSet) {
throw new IllegalArgumentException("You must first put a model (try #load())");
}
//获取当前target中的请求
Request previous = target.getRequest();
//如果当前target存在请求,那么清除target对应的请求
if (previous != null) {
requestManager.clear(target);
}
//锁定requestOptions不让在加载过程中修改
requestOptions.lock();
//创建请求
Request request = buildRequest(target);
//将request 设置给 target
target.setRequest(request);
//requestManager开始跟踪这个请求
requestManager.track(target, request);
return target;
}

into方法中会首先检查当前target是否有请求正在进行如果有那么先清除原先的请求。调用buildRequest来创建该次请求,并调用setRequest将请求与target进行绑定(也就是为当前View打上request的标签,并将request保存下来)。然后通过ReqestManager对该请求进行track。

这里关注两点

  1. buildRequest怎么创建出请求。
  2. ReqestManager怎么使用创建出来的请求进行处理.
    private Request buildRequest(Target<TranscodeType> target) {
    return buildRequestRecursive(target, null, transitionOptions, requestOptions.getPriority(), requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight());
    }

    private Request buildRequestRecursive(Target<TranscodeType> target,
    if (thumbnailBuilder != null)
    {

    //.....
    } else {
    // Base case: no thumbnail.
    //没有缩率图的情况
    return obtainRequest(target, requestOptions, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight);
    }
    }

    private Request obtainRequest(Target<TranscodeType> target,
    BaseRequestOptions<?> requestOptions, RequestCoordinator requestCoordinator,
    TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority,
    int overrideWidth, int overrideHeight)
    {

    requestOptions.lock();
    RequestContext<?, TranscodeType> requestContext = new RequestContext<>(context, model, transcodeClass, requestOptions, priority, overrideWidth, overrideHeight);
    return SingleRequest.obtain(requestContext, target, requestListener, requestCoordinator, context.getEngine(), transitionOptions.getTransitionFactory());
    }

为了避免频繁创建和释放请求对象,这里使用了一个请求对象池,最多可以缓存150个请求。每次会从这个请求池中优先获取,只有在请求池为空的情况下才会新建一个请求,获取到一个请求对象后,就可以使用当前的请求参数初始化获取到的请求对象。这样就可以在这次请求中使用了。

public static <R> SingleRequest<R> obtain(RequestContext<?, R> requestContext, Target<R> target,
RequestListener<R> requestListener, RequestCoordinator requestCoordinator, Engine engine,
TransitionFactory<? super R> animationFactory) {

//尝试从对象池中获取
@SuppressWarnings("unchecked")
SingleRequest<R> request = (SingleRequest<R>) REQUEST_POOL.acquire();
//获取失败则新建一个请求
if (request == null) {
request = new SingleRequest<>();
}
//初始化请求
request.init(requestContext, target, requestListener, requestCoordinator, engine, animationFactory);
return request;
}

了解了请求的创建过程后我们继续看下如果使用获取的请求进行加载图片:
我们看下RequestManager track方法:

void track(Target<?> target, Request request) {
//监听生命周期
lifecycle.addListener(target);
//跟踪请求
requestTracker.runRequest(request);
}

track中处理很简单,就是让target监听生命周期后,调用requestTracker.runRequest。还记得上面介绍Target的时候有提到生命周期对Target的影响了吧– 在Target为GifDrawble的时候Target会随着生命周期启动和停止播放动画。

Ok 我们继续看runRequest,上面已经知道request是一个SingleRequest,SingleRequest中启动请求是通过begin方法来完成的。

public void runRequest(Request request) {
//添加请求到列表中
requests.add(request);
if (!isPaused) {
//如果是非暂停状态,那么就启动请求
request.begin();
} else {
//如果是暂停状态那么就加入到pendingRequests中
pendingRequests.add(request);
}
}

begin 方法中会先获取View的尺寸,然后通过onSizeReady传出,实际的加载也是在onSizeReady中进行的。
在开始加载之前先调用onLoadStarted显示占位图片。

public void begin() {
//没有设置Model的情况
if (requestContext.getModel() == null) {
onLoadFailed();
return;
}
//等待分配尺寸
status = Status.WAITING_FOR_SIZE;
//先获取RequestContext 中的宽高,看下是否有效
int overrideWidth = requestContext.getOverrideWidth();
int overrideHeight = requestContext.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
//如果有效那么直接通过回调将其传出
onSizeReady(overrideWidth, overrideHeight);
} else {
//如果无效那么调用之前介绍的getSize来获取View的尺寸
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
//显示占位图片
target.onLoadStarted(requestContext.getPlaceholderDrawable());
}
}

看下onSizeReady,转了大半圈感觉终于走到了正道,之前都是各种做铺垫,在onSizeReady中会通过engine.load进行图片的加载。关于请求的信息都放在了requestContext中。

public void onSizeReady(int width, int height) {
if (status != Status.WAITING_FOR_SIZE) {
return;
}
//将状态切换到RUNNING状态
status = Status.RUNNING;
//.............
//表示当前是从内存缓存中加载的,这个用于在onResourceReady返回的时候告诉回调这个资源是来自哪里的。
loadedFromMemoryCache = true;
//使用Engine加载图片
loadStatus = engine.load(requestContext, width, height, this);
//根据最终获取到的资源是否为空来设置loadedFromMemoryCache
loadedFromMemoryCache = resource != null;
}

在看load代码之前我先给大家剧透下整个加载流程,大家可以结合下面的代码注释对细节进行查看:
整个流程如下:

  1. 检查内存缓存,如果内存缓存中有需要的数据那么就直接使用内存缓存中的数据
  2. 如果内存缓存中没有想要的图像数据,那么检查最近的活跃资源(ActiveResources)是否有我们想要的资源
  3. 如果ActiveResources也没有,我们就继续检查最近的加载任务,如果存在将回调添加到正在加载的任务中,
  4. 上面都没办法获取到我们需要的资源那么就,启动新的加载任务开始加载

可能大家都听说过两级缓存,内存缓存,磁盘缓存,但是Glide在这基础上添加了一层活跃资源缓存,那么什么是活跃资源呢?
活跃资源指的是那些不止一次被加载并没有进行过资源释放的图片,一旦被释放,那么该资源则会从近期活跃资源中删除并进入到内存缓存中,但是如果该资源再次从内存缓存中读取,则会重新添加到活跃资源中

public <Z, R> LoadStatus load(RequestContext<?, R> requestContext, int width, int height,
ResourceCallback cb) {

requestContext.setDimens(width, height);
//根据各个属性生成的内存缓存唯一键值
EngineKey key = keyFactory.buildKey(requestContext, width, height);
//使用生成的key从缓存中尝试获取缓存资源
EngineResource<?> cached = loadFromCache(key,requestContext.isMemoryCacheable()/*如果为false那么将会跳过从内存缓存中获取的步骤*/);
if (cached != null) {
//如果缓存中有可用资源,那么将其返回给回调,通知资源已经准备结束
cb.onResourceReady(cached);
return null;
}
//如果内存缓存中没有对应的资源,那么就尝试从ActiveResource 中尝试获取
EngineResource<?> active = loadFromActiveResources(key,requestContext.isMemoryCacheable()/*如果为false那么将会跳过从内存缓存中获取的步骤*/);
if (active != null) {
cb.onResourceReady(active);
return null;
}

//尝试从线程池中获取特定的任务
EngineJob current = jobs.get(key);
if (current != null) {
//找到有正在下载符合当前要求的资源,那么就不重复下载该资源,直接通过添加回调来复用这个资源
current.addCallback(cb);
return new LoadStatus(cb, current);
}

//如果在线程池中没有找到符合要求的特定资源,那么就通过engineJobFactory这个工厂类来生成一个EngineJob,
EngineJob<R> engineJob = engineJobFactory.build(key, requestContext.isMemoryCacheable());
DecodeJob<R> decodeJob = new DecodeJob<>(requestContext, key, width, height, diskCacheProvider, engineJob);
//将engineJob添加到job池中
jobs.put(key, engineJob);
//添加回调
engineJob.addCallback(cb);
//启动decodeJob
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}

我们先看下内存缓存部分:

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
//从缓存中获取EngineResource
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
//从缓存中获取到后将资源通过一个软引用添加到active resource缓存中。
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}

在内存缓存获取到我们所需要的数据后会将其添加到Active Resource中.

private EngineResource<?> getEngineResourceFromCache(Key key) {
//将匹配的数据从缓存中取出
Resource<?> cached = cache.remove(key);
final EngineResource result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource) cached;
} else {
result = new EngineResource(cached, true /*isMemoryCacheable*/);
}
return result;
}

接着我们再看下Active Resource 缓存中获取数据的过程:
Active Resource 的缓存数据存在activeResources中,使用弱引用来持有。在内存不足的时候这部分会被gc掉。

private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = null;
WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
if (activeRef != null) {
active = activeRef.get();
if (active != null) {
active.acquire();
} else {
activeResources.remove(key);
}
}
return active;
}

介绍完内存缓存以及Active Resource缓存后我们看下磁盘缓存:
在磁盘缓存开始前会先在当前获取数据的队列中查看是否先前已经启动,如果先前已经启动的话就不重新创建了。这样可以达到复用数据的目的。最后的最后才会启动从磁盘中获取缓存数据的任务。

DecodeJob是一个Runnable的实现类,主要负责从磁盘加载数据,调用start方法后,这个线程就会run起来。
它的作用如下:

  1. 确定数据的加载来源(Resource,Data,Source)
  2. 创建对应来源的DataFetcherGenerator
  3. 执行DataFetcherGenerator 获取数据
public void run() {
try {
//运行加载任务
runWrapped();
} catch (RuntimeException e) {
callback.onLoadFailed();
throw e;
}
}

private void runWrapped() {
switch (runReason) {
case INITIALIZE:
//第一次我们从这里开始分析
stage = getNextStage(Stage.INITIALIZE);//确定资源的加载来源
generator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE://从硬盘获取资源失败 ,尝试重新获取
runGenerators();
break;
case DECODE_DATA://从一个不属于我们的线程中获取数据,然后切换到我们自己的线程中处理数据
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}

我们第一次进入的时候runReason为INITIALIZE,这时候会先通过getNextStage来获取当前阶段的下一阶段。首先在看代码之前我们需要先明白Data和Resource的区别:
Resource:原始的图片(或gif)数据
Data:经过处理(旋转,缩放)后的数据

该方法的大致逻辑如下:

  1. 如果是初始状态,则判断是否解码已缓存的Resource,true是解码Resource。false的话则会通过递归进入第二个判断分支
  2. 判断是否解码已缓存的Data,true是解码Data,false的话则会通过递归进入第三个判断分支
  3. 该阶段则需要从数据源去解码。

简单的来说,就是根据Resource—>Data—>source的顺序去解码加载数据,该阶段Stage的确定,影响着下一阶段DataFetcherGenerator相应子类的实例创建

private Stage getNextStage(Stage current) {
if (current == null) {
return null;
}
DiskCacheStrategy strategy = requestContext.getDiskCacheStrategy();
switch (current) {
case INITIALIZE:
return strategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return strategy.decodeCachedData() ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
return Stage.SOURCE;
default:
return null;
}
}

通过getNextStage已经获取到适当的状态后紧接着就是通过getNextGenerateor生成获取数据的DataFetcherGenerator

private DataFetcherGenerator getNextGenerator() {
if (stage == null) {
return null;
}
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(width, height, diskCacheProvider.getDiskCache(), requestContext, this);
case DATA_CACHE:
return new DataCacheGenerator(requestContext.getCacheKeys(), width, height, diskCacheProvider.getDiskCache(), requestContext, this);
case SOURCE:
return new SourceGenerator<>(width, height, requestContext, diskCacheProvider.getDiskCache(), this);
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}

DataFetcherGenerator使用已注册的ModelLoaders和Model来生成一系列的DataFetcher。有如下实现类
ResourceCacheGenerator:经过处理的资源数据缓存文件(采样转换等处理)
DataCacheGenerator:未经处理的资源数据缓存文件
SourceGenerator:源数据的生成器,包含了根据来源创建的ModelLoader和Model(文件路径,URL…)

下面我们一一来看下这些DataFetcherGenerator

####### ResourceCacheGenerator

public boolean startNext() {
//获取当前的缓存的key列表
List<Key> sourceIds = requestContext.getCacheKeys();
//获取当前注册的Resource class
List<Class<?>> resourceClasses = requestContext.getRegisteredResourceClasses();

while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
if (resourceClassIndex >= resourceClasses.size()) {
sourceIdIndex++;
if (sourceIdIndex >= sourceIds.size()) {
return false;
}
resourceClassIndex = 0;
}

Key sourceId = sourceIds.get(sourceIdIndex);
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation<?> transformation = requestContext.getTransformation(resourceClass);

//生成对应的key
Key key = new ResourceCacheKey(sourceId, requestContext.getSignature(), width, height,transformation,resourceClass, requestContext.getOptions());
//从磁盘缓存中获取对应的缓存文件
cacheFile = diskCache.get(key);
if (cacheFile != null) {
//存在缓存文件
this.sourceKey = sourceId;
//获取ModeLoaders 获取到之后modelLoaders != null 那么就退出循环,这个过程主要是从Register中获取到注册的ModeLoader
modelLoaders = requestContext.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}

fetcher = null;
while (fetcher == null && hasNextModelLoader()) {
//获取ModeLoader
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
//获取Fetcher,如果获取到了就不再循环了
fetcher = modelLoader.buildLoadData(cacheFile, width, height, requestContext.getOptions()).fetcher;
if (fetcher != null) {
//使用Fetcher加载数据
fetcher.loadData(requestContext.getPriority(), this);
}
}
return fetcher != null;
}

DataCacheGenerator
public boolean startNext() {

//第一次调用的时候modelLoaders == null 这里只执行一次
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= sourceIds.size()) {
return false;
}
//获取要加载的数据源的Id
Key sourceId = sourceIds.get(sourceIdIndex);
//生成缓存key
Key originalKey = new DataCacheKey(sourceId, requestContext.getSignature());
//使用key值获取缓存文件
cacheFile = diskCache.get(originalKey);
if (cacheFile != null) {
//如果找到缓存文件,那么使用缓存文件类型来获取已经注册的Model的加载器
this.sourceKey = sourceId;
modelLoaders = requestContext.getModelLoaders(cacheFile);
//重置加载器列表index
modelLoaderIndex = 0;
}
}

fetcher = null;
//如果有一次fetcher != null就停止,如果没有fetcher 并且全部已经遍历结束则退出循环
while (fetcher == null && hasNextModelLoader()) {
//获取ModelLoader
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
//获取fetcher
fetcher = modelLoader.buildLoadData(cacheFile, width, height, requestContext.getOptions()).fetcher;
//使用fetcher获取数据
if (fetcher != null) {
fetcher.loadData(requestContext.getPriority(), this);
}
}
return fetcher != null;
}
SourceGenerator
public boolean startNext() {
//如果已经有缓存数据那么就切换到DataCacheGenerator
if (dataToCache != null) {
cacheData();
dataToCache = null;
}
//如果之前有缓存的话这里的sourceCacheGenerator 为 DataCacheGenerator,那么这里将会调用startNext获取到Fetcher而后调用loadData加载数据。
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}

//如果没有缓存就会继续走到这里
sourceCacheGenerator = null;

loadData = null;
while (loadData == null && hasNextModelLoader()) {
loadData = loadDataList.get(loadDataListIndex++);
if (loadData != null) {
//加载数据
loadData.fetcher.loadData(requestContext.getPriority(), this);
}
}
return loadData != null;
}
private void cacheData() {
try {
//获取对应的编码器
Encoder<Object> encoder = requestContext.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer = new DataCacheWriter<>(encoder, dataToCache, requestContext.getOptions());
//生成DataCacheKey
Key originalKey = new DataCacheKey(loadData.sourceKey, requestContext.getSignature());
//将数据写入到磁盘缓存中
diskCache.put(originalKey, writer);
} finally {
loadData.fetcher.cleanup();
}
//将Generator切换到DataCacheGenerator
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), width, height,
diskCache, requestContext, this);
}

这里需要注意下,SourceGenerator可以根据磁盘缓存策略选择是直接返回还是先写到磁盘再从缓存文件中加载。

public void onDataReady(Object data) {
//根据当前磁盘缓存策略,如果需要对数据进行缓存,那么将数据赋给dataToCache
DiskCacheStrategy diskCacheStrategy = requestContext.getDiskCacheStrategy();
//这个分支是先将数据写入到磁盘缓存,再从缓存文件中加载。
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
// 这时候我们可能在其他线程中进行加载的,但是在做任何操作的时候必须重新切换到Glide线程
cb.reschedule();
} else {
//如果当前缓存策略不要求对数据进行缓存那么直接通过回调返回
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource());
}
}

看完上面的三个DataFetcher后我们看下在DecodeJob中怎么通过这些Fetcher进行获取数据,这就涉及到runGenerators这个方法:
如果获取成功则直接回调onDataFetcherReady,如果失败则通过reschedule重新调度

private void runGenerators() {
currentThread = Thread.currentThread();
while (!isCancelled && generator != null
&& !generator.startNext()/*如果fetch成功获取并且通过通过fetch能够成功获取到数据则返回true这时候不执行while当中的循环*/) {
stage = getNextStage(stage);
generator = getNextGenerator();
if (stage == Stage.SOURCE) {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
return;
}
}
// We've run out of stages and generators, give up.
if (stage == null) {
callback.onLoadFailed();
}
}

看完上面代码密密麻麻的,但是最主要的代码就一句generator.startNext()也就是上面介绍DataFetcher的时候重点注解的那个方法,在那个方法中会调用对应的Fetcher来获取数据。后续的部分会着重介绍一个从网络上获取数据的Fetcher,这里先着重介绍流程。好了我们继续:
不论是哪种Fetcher,获取完数据后都会回调DecodeJob里面的onDataFetcherReady

public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource)
{

//各个Generator加载数据结束的时候会到这里
this.currentSourceKey = sourceKey; //对应的数据key
this.currentData = data; //解码前的原始数据
this.currentFetcher = fetcher; //获取数据的fetcher
this.currentDataSource = dataSource;//数据类别
if (Thread.currentThread() != currentThread) {
runReason = RunReason.DECODE_DATA;
callback.reschedule(this);
} else {
//从原始数据中对数据进行解码
decodeFromRetrievedData();
}
}

private void decodeFromRetrievedData() {
//对原始数据进行解码
Resource resource = decodeFromData(currentFetcher, currentData, currentDataSource);
if (resource != null) {
//通过回调进行返回
callback.onResourceReady(resource);
cleanup();
} else {
runGenerators();
}
}

private Resource decodeFromData(DataFetcher<?> fetcher, Data data, DataSource dataSource) {
try {
if (data == null) {
return null;
}
Resource result = decodeFromFetcher(data, dataSource);
return result;
} finally {
fetcher.cleanup();
}
}

private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource) {
//从requestContext中获取当前数据类型的解码器
LoadPath<Data, ?, R> path = requestContext.getLoadPath((Class<Data>) data.getClass());
if (path != null) {
return runLoadPath(data, dataSource, path);
} else {
return null;
}
}

getLoadPath的任务是从注册表中获取特定数据类型,转换类型的图像解码器:

<Data> LoadPath<Data, ?, TranscodeClass> getLoadPath(Class<Data> dataClass) {
return glideContext.getRegistry().getLoadPath(dataClass, getResourceClass(), transcodeClass);
}

这里调用getLoadPath了从loadPathCache中获取对应数据类型的解码器,loadPathCache 是一个缓存,这个大家见怪不怪了:

  public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
Class<Data> dataClass, Class<TResource> resourceClass, Class<Transcode> transcodeClass) {

LoadPath<Data, TResource, Transcode> result = loadPathCache.get(dataClass, resourceClass, transcodeClass);
if (result == null && !loadPathCache.contains(dataClass, resourceClass, transcodeClass)) {

//获取解码器列表
List<DecodePath<Data, TResource, Transcode>> decodePaths = getDecodePaths(dataClass, resourceClass,transcodeClass);
//有可能从数据类中没法解码或者转换当前类型
if (decodePaths.isEmpty()) {
result = null;
} else {
result = new LoadPath<>(dataClass, decodePaths);
}
//添加到loadPathCache
loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
}
return result;
}

下面是从注册表中获取符合要求的解码器列表的实现:

private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths(
Class<Data> dataClass, Class<TResource> resourceClass, Class<Transcode> transcodeClass) {
//从注册表中获取解码器信息列表
List<DecodePath<Data, TResource, Transcode>> decodePaths = new ArrayList<>();
List<Class<TResource>> registeredResourceClasses = decoderRegistry.getResourceClasses(dataClass, resourceClass);
for (Class<TResource> registeredResourceClass : registeredResourceClasses) {
//获取图像变换列表
List<Class<Transcode>> registeredTranscodeClasses =
transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);
//获取到对应类型,对应变换的解码器
for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {
List<ResourceDecoder<Data, TResource>> decoders =
decoderRegistry.getDecoders(dataClass, registeredResourceClass);
//这里就是我们需要的解码器
ResourceTranscoder<TResource, Transcode> transcoder =
transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
//添加到符合要求的解码器列表
decodePaths.add(new DecodePath<>(dataClass, decoders, transcoder));
}
}
return decodePaths;
}

经过层层调用后我们就获取到了当前数据的解码器,拿到解码器后不用说要做的事情就是对数据进行解码了,我们看下这部分内容:

private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path) {
return path.load(data, requestContext, width, height, new DecodeCallback<ResourceType>(dataSource));
}

我们接下来看下LoadPath的load方法,这里最关键的部分就是path.decode这个方法,它就是调用从注册表中获取到的解码器到decode方法对数据进行解码的。

public Resource<Transcode> load(Data data, RequestContext<?, Transcode> context,
int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) {
Preconditions.checkNotNull(data);

Resource<Transcode> result = null;
Options options = context.getOptions();
DataRewinder<Data> rewinder = context.getRewinder(data);
try {
int size = decodePaths.size();
for (int i = 0; i < size; i++) {
DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
result = path.decode(rewinder, width, height, options, decodeCallback);
if (result != null) {
break;
}
}
} finally {
rewinder.cleanup();
}
return result;
}

到这里为止整个数据获取,解码都完成了,那么我们接下来顺着原路返回,看下怎么将这些经过解码后的图片设置到对应的Target上,我们先回到Engine类:
它有一个叫做onEngineJobComplete的回调,是在上面加载数据,解码数据之后对调的。

public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
jobs.remove(key);
}

这里处理很简单就是将加载后的数据添加到activeResources,下一次的时候就可以从activeResources中获取了。

还记得前面介绍Engine.load的时候如果从内存缓存以及Active Resource缓存中获取到数据后是怎么处理的吧。是的就是调用:

cb.onResourceReady(cached);

cb 是啥,看代码可以看出SingleRequest,所以我们看下SingleRequest的onResourceReady

@Override
public void onResourceReady(Resource<?> resource) {
Class<R> transcodeClass = requestContext.getTranscodeClass();
if (resource == null) {
onLoadFailed();
return;
}
Object received = resource.get();
if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
releaseResource(resource);
onLoadFailed();
return;
}
if (!canSetResource()) {
releaseResource(resource);
status = Status.COMPLETE;
return;
}
//这里是关键
onResourceReady((Resource<R>) resource, (R) received);
}

private void onResourceReady(Resource<R> resource, R result) {
boolean isFirstResource = isFirstReadyResource();
status = Status.COMPLETE;
this.resource = resource;
if (requestListener == null
|| !requestListener.onResourceReady(result, requestContext.getModel(), target,loadedFromMemoryCache, isFirstResource)) {
//这里是终点,终于快结束了,累死我了。
Transition<? super R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);
target.onResourceReady(result, animation);
}
notifyLoadSuccess();
}

上面终于看到target了。既然都讲到这了我就再回头看下吧,比如我们当前是一个GifDrawable,那么Target就是DrawableImageTarget
我们就再来看下它的onResourceReady

public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
if (!(resource instanceof Animatable) && layoutParams != null && layoutParams.width > 0
&& layoutParams.height > 0) {
resource = new FixedSizeDrawable(resource, layoutParams.width, layoutParams.height);
}
super.onResourceReady(resource, transition);
if (resource instanceof Animatable) {
((Animatable) resource).start();
}
}

它调用了父类也就是ImageViewTarget的onResourceReady,在这里会调用传入的Transition对图像进行一次转换,然后调用setResource设置到对应的Target上。

public void onResourceReady(Z resource, Transition<? super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
setResource(resource);
}
}

所以总的就是先将图像转换,设置到ImageView上,然后如果是gif就调用start方法开始播放。整个流程结束了,真他妈累。对了好像还忘记给大家介绍HttpUrlFetcher了。实在讲不动了就贴个标有注释的代码给大家吧。

public class HttpUrlFetcher implements DataFetcher<InputStream> {

HttpUrlFetcher(GlideUrl glideUrl, int timeout, HttpUrlConnectionFactory connectionFactory) {
this.glideUrl = glideUrl;
this.timeout = timeout;
this.connectionFactory = connectionFactory;
}

@Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
InputStream result = null;
try {
result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
} catch (IOException e) {
}
//将数据通过回调返回
callback.onDataReady(result);
}

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {
//重定向次数超过5次
if (redirects >= MAXIMUM_REDIRECTS) {
throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new IOException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
//创建HttpURLConnection
urlConnection = connectionFactory.build(url);
//往HttpURLConnection 添加 Head参数
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
//设置超时时间
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
//设置不使用缓存
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
//连接
urlConnection.connect();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (statusCode / 100 == 2) {
//获取内容大小
String contentLength = urlConnection.getHeaderField(CONTENT_LENGTH_HEADER);
//获取输入流InputStream
stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
return stream;
} else if (statusCode / 100 == 3) {
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new IOException("Received empty or null redirect url");
}
//从连接中获取Location字段进行重定向
URL redirectUrl = new URL(url, redirectUrlString);
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else {
//请求失败
if (statusCode == -1) {
throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
}
throw new IOException("Request failed " + statusCode + ": "
+ urlConnection.getResponseMessage());
}
}

interface HttpUrlConnectionFactory {
HttpURLConnection build(URL url) throws IOException;
}

private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
@Override
public HttpURLConnection build(URL url) throws IOException {
//获取HttpURLConnection
return (HttpURLConnection) url.openConnection();
}
}
}

1. 在项目中引入Dagger2

Dagger2 搞Android开发以及Java开发的同学估计即使没有使用过也应该听说过这个开源库吧。它是一个依赖注入库.源码地址如下所示:
Dagger2 github地址

在项目中引入Dagger2
project的build.gradle添加

dependencies {
... // 其他classpath
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //添加apt命令
}

module的build.gradle添加

// 添加其他插件
apply plugin: 'com.neenbedankt.android-apt'//添加apt命令
dependencies {
apt 'com.google.dagger:dagger-compiler:2.0.2' //指定注解处理器
compile 'com.google.dagger:dagger:2.0.2' //dagger公用api
provided 'org.glassfish:javax.annotation:10.0-b28' //添加android缺失的部分javax注解
}

2. Dagger2简介

在介绍如何使用Dagger2之前我们必须先搞清楚一件事–为甚么需要使用Dagger2,也就是Dagger2的功能,刚刚提到了Dagger2 是一个依赖注入框架,那么什么是依赖注入呢?为什么要使用依赖注入呢?

我们传统的开发过程中如果某个类中需要一个依赖可以通过在这个类中创建需要的依赖,这样的缺点显而易见,比方我们需要在一个已经开发成熟的项目中更换某个类的实现,再具体点,比如我们在
现有项目中使用的是Picasso作为图像处理框架,但是某天项目中需要显示gif,这时候我们可能考虑到Glide库能够支持Gif播放,所以我们想要将图像处理框架更换为Glide
如果原先没有设计好的话可能改动的代码就很庞大。而且极为容易出错。但是如果使用了依赖注入框架,那么这个问题就变得很简单了,只需要修改对应的Module就可以了,对代码不需要大幅的改动。(这里的前提条件是这两个库接口上是相似的)

那么什么是依赖注入呢?这里我就谈谈自己对依赖注入的理解:
依赖注入就是将使用依赖的代码部分与依赖生成的部分分开,这样做的好处就是:因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。并且可以通过注入这些依赖的Mock对象来进行模拟测试。从而使得对项目的测试更加方便。

有Dagger2就说明有Dagger的存在,关于这段历史大家如果感兴趣的话可以去网上了解下,这里就不展开介绍了,Dagger2的最大改进就是,它使用了Java注解处理器,完全去除了反射机制,在编译的时候检查并分析依赖关系。使得在效率上得到很大的提升。

3. Dagger2结构

下面是Dagger2的大致结构:

整个依赖注入体系分成三个部分:

  1. 依赖提供方: 用于生成并提供依赖对象的一方。
  2. 依赖需求方: 需要使用注入依赖的一方
  3. 依赖注入器: 连接依赖提供方和依赖需求方的注入器。它负责将依赖提供方生成的依赖对象注入到依赖需求方。换个角度来说就是依赖需求方需要注入依赖的时候可以顺着这个注入器找到依赖提供方。

4. Dagger2 重要的注解

了解了大体的结构后我们就需要了解下Dagger2中常用的一些注解,其实Dagger2的注解并不太多,但是需要注意的是这些注解的理解。

@Inject:

我们看到上图中依赖注入器左右两端各有一个@Inject注解也就是说@Inject既可以用在提供方也可以用在依赖需求方,用在依赖提供方的时候一般用来注解待注入对象的构造函数,用在依赖需求方的时候一般用来注解需要Dagger2进行依赖注入的成员变量。

@Module:
我们看到依赖提供方还有个@Module注解,它的作用是什么呢?我们知道提供方已经有了一个@Inject为什么还需要@Module呢?我们考虑一个情景我们现有项目中使用了第三方的类库,在不采用导入第三方类库源码或者源代码非开源的情况,如果用Inject要怎么处理,根本不可能使用Inject注解加入这些类中是吧,那这还怎么办呢?这时候就需要@Module出场了。Modules类是由一系列专门提供依赖的方法组成,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的依赖。

那么这两种方式有没优先级区分呢?
有的,它的查找规则如下:

  1. 首先会先从Module的@Provides方法集合中查找
  2. 如果查找不到,则查找成员变量类型是否有@Inject构造方法。
    也就是说@Module中的优先级会比@Inject注解的构造方法优先级更高,还有个需要注意的是@Modules不仅仅只用于那些第三方项目中不可见源码的对象注入,可以使用@Inject注入的对象,使用@Module一样可以注入。
@Provide

提到@Module就不得不提到 @Provide,我们用这个注解来告诉Dagger2被这个注解的方法是被用来提供依赖的,具体提供哪种依赖对象是由返回值决定的,一般这类方法规定以provide作为开头,后面的可以随意。Module中@Provides方法可以是带输入参数的方法,其参数由Module集合中的其他@Provides方法提供,或者自动调用构造方法,也就是说如果找不到@Provides方法提供对应参数的对象,Dagger2就会自动调用带@Inject参数的构造方法生成相应对象。

下面是一个最基本的@Module的写法

@Module
//1 注明本类属于Module
public class FruitModule{
@Provides
//2 注明该方法是用来提供依赖对象的特殊方法
// 返回值(被依赖的类类型)
// 方法名(provideXxx必须以provide开头,后面随意)
public Fruit provideFruit(){
return new Apple(Color.RED,Size.BIG);
}
}

@Named

如果待注入方需要依赖同个类的两种不同的对象的时候,那要怎么办,我们可能会想就写两个@Provides方法,而且这两个@Provides方法都是返回需要的类型,但是我们前面提到过Dagger2是靠返回值的类型来判断具体选择哪个@Provide方法来提供依赖的,现在有两个provide方法返回同一个类型,那就比较尴尬了,这种现象也有专门的叫法叫做注入迷失,为了解决这个问题这就需要使用@Named来进行区分了:

@Module
public class FruitModule{
@Named("typeA")
@Provides
public Fruit provideApple(){ //提供Apple给对应的mFruitA
return new Apple();
}
@Named("typeB")
@Provides
public Fruit provdeBanana(){ //提供Banana给对应的mFruitB
return new Banana()
}
}

在待注入方也要使用@Named来标记到底使用的是哪个依赖,具体的待注入方以及注入会在下面进行介绍。

@Named("typeA") //添加标记@Name("typeA"),只获取对应的@Name("typeA")的依赖   @Inject
Fruit mFruitA;
@Named("typeB") //添加标记@Name("typeA"),只获取对应的@Name("typeA")的依赖 @Inject
Fruit mFruitB;

@Qualifier

上面的方式只能使用字符串作为区分标签,一般来说是够用的,但是如果你需要其他的方式作为区分标签可以使用Qualifier进行定义了:

@Qualifier   //必须,表示IntNamed是用来做区分用途
@Documented //规范要求是Documented,当然不写也问题不大,但是建议写,做提示作用
@Retention(RetentionPolicy.RUNTIME) //规范要求是Runtime级别
public @interface IntNamed{
int value();
}

用法和@Named注解类似就不展开介绍了。

@Component:

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={××××,×××}) 
2. @Module(includes={××××,×××})这种方法一般用于构建更高层的Module时候使用

假设ComponentA依赖ComponentB,B必须定义带返回值的方法来提供A缺少的依赖
ComponentA依赖ComponentB的代码如下

//定义ComponentB
@Component(modules={××××××××})
interface ComponentB{
...
}

//定义ComponentA
@Component(dependencies={ComponentB.class},modules={××××××××})//使用dependencies
interface ComponentA{
...
}

这样,当使用ComponentA注入Container时,如果找不到对应的依赖,就会到ComponentB中查找。但是,ComponentB必须显式把这些A找不到的依赖提供给A。怎么提供呢,只需要在ComponentB中添加方法即可,如下

@Component(modules={××××××××})
interface ComponentB{
// 假设A中module中找不到apple,banana,oranges,但是B的module有,B必须提供带返回值的方法如下
Apple apple();
Banana banana();
Oranges oranges();
}

Component 当中定义的方法可以分成两类:

  1. 注入的目标对象以injectXXX作为方法名开始, 同一个Component可以有多个inject方法,也即是说可以注入到多个目标对象。注意inject的参数不能是父类,必须是你注入的那个类,因为这里写啥,Dagger就回去对应的类中寻找@Inject注解进行注入
  2. 需要暴露给依赖components的方法,如果不在这里列出那么使用dependencies方式的时候就不会暴露出来。

下面是一个最基本的Component 定义方法

@Component(modules={FruitModule.class})     //指明Component在哪些Module中查找依赖
public interface FruitComponent{ //接口,Dagger2框架将自动生成Component的实现类,对应的类名是Dagger×××××
void inject(Container container); //注入方法,在待注入容器中调用
}

@Component的注入

Component注入有两种方式:

  1. 基本方式
public Container{
public void init(){
DaggerFruitComponent.create().inject(this); //使用FruitComponent的实现类注入
}
}

上面简单例子中,当调用DaggerFruitComponent.create()实际上等价于DaggerFruitComponent.builder().build()。在构建的过程中,默认使用Module无参构造器产生实例。
如果需要传入特定的Module实例,可以使用

DaggerFruitComponent.builder()
.moduleA(new ModuleA()) //指定Module实例
.moduleB(new ModuleB())
.build()

如果Module只有有参构造器,则必须显式传入Module实例。

这里还留有一个问题等到讲 Component 依赖以及子Component的时候讲,那就是在Component依赖以及子Component的情况下怎么进行依赖注入。

@Scope && @Singleton

在学Dagger2的时候最难理解的部分就是@Scope 以及 Component依赖,子Component.还有就是如何在项目中组织Component。
我们接下来先来看下@Scope的作用,在不使用@Scope 的时候我们的例子如下,我们注入到MainActivity后将这两个对象打印出来,

public class Apple {
}

@Module
public class FruidModule {
@Provides
public Apple provideApple() {
return new Apple();
}
}
@Component(modules = {FruidModule.class})
public interface FruidComponent {
void inject(MainActivity activity);
}
@Inject
Apple apple1;

@Inject
Apple apple2;

@Override
protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerFruidComponent.builder().build().inject(this);
LogManager.getInstance().info(apple1.toString());
LogManager.getInstance().info(apple2.toString());

}

打印出来的结果如下:

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
07-18 20:24:15.355 3059-3059/com.idealist.tbfungeek.mvpframework I/MainActivity: class -> MainActivity method -> onCreate() line -> 30 [ Message ] com.idealist.tbfungeek.mvpframework.Apple@1956ae56

接着我们再做个对比实验:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface FruidScope {
}
@Module
public class FruidModule {
@FruidScope
@Provides
public Apple provideApple() {
return new Apple();
}
}
@FruidScope
@Component(modules = {FruidModule.class})
public interface FruidComponent {
void inject(MainActivity activity);
}
@FruidScope
@Inject
Apple apple1;

@FruidScope
@Inject
Apple apple2;


@Override
protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BaseApplication.getAppDelegate().getRefWatcher().watch(this);
DaggerFruidComponent.builder().build().inject(this);
LogManager.getInstance().info(apple1.toString());
LogManager.getInstance().info(apple2.toString());

}

结果如下:

07-18 20:27:36.364 5818-5818/? V/ActivityLifeCycleManager$1: class -> ActivityLifeCycleManager$1 method -> onActivityCreated() line -> 61 [ Message ] onCreate --> MainActivity
07-18 20:27:36.388 5818-5818/? I/MainActivity: class -> MainActivity method -> onCreate() line -> 28 [ Message ] com.idealist.tbfungeek.mvpframework.Apple@3dd96071
07-18 20:27:36.388 5818-5818/? I/MainActivity: class -> MainActivity method -> onCreate() line -> 29 [ Message ] com.idealist.tbfungeek.mvpframework.Apple@3dd96071

发现了什么没?我们在没有用scope注解的时候两个对象实际是不同的两个对象,但是如果用scope注解标记后两个返回的是同一个对象。

最早看到Singleton注解的时候我第一反应就是只要用上这个注解就可以实现单例模式了,但是它并非我们通常以为的单例,Java中,单例通常保存在一个静态域中,这样的单例往往要等到虚拟机关闭时候,该单例所占用的资源才释放。但是,Dagger通过Singleton创建出来的单例并不保持在静态域上,而是保留在Component实例中。也就是这种单例只是针对对应的Component。如果要实现传统意义上的单例模式,那么就需要通过一定的方法保证对应的Component是全局单例的。

下面是来自网络上的一个很经典的例子,估计看过后大家一定会豁然开朗:

在实际开发中我们可能还需要一种局部单例的控件(这个应该是更常用),比如说我们有三个Activity,MainActivity,BActivity和CActivity,我们想让MainActivity和BActivity共享同一个实例,而让CActivity获取另外一个实例,这又该怎么实现呢?在Dagger2中,我们可以通过自定义Scope来实现局部单例。那就动手吧:
首先让我们先来定义一个局部作用域:

@Scope  
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}

然后在我们的UserModule和ActivityComponent中应用该局部作用域:

@Module  
public class UserModule {
@Provides
@UserScope
User providesUser() {
return new User();
}
}

@UserScope  
@Component(modules = UserModule.class)
public interface ActivityComponent {
void inject(MainActivity activity);

void inject(BActivity activity);
}

大家注意,我的ActivityComponent作为一个注入器只可以向MainActivity和BActivity两个Activity中注入依赖,不可以向CActivity中注入依赖。最后,要让该局部作用域产生单例效果,需要我们在自定义的Appliation类中来初始化这个Component,如下:

public class MyApp extends Application {  
ActivityComponent activityComponent;
@Override
public void onCreate() {
super.onCreate();
activityComponent = DaggerActivityComponent.builder().userModule(new UserModule()).build();
}

ActivityComponent getActivityComponent(){
return activityComponent;
}
}

接下来我们在MainActivity和BActivity中注入依赖,MainActivity如下:

@Inject  
User user;
@Inject
User user2;
private TextView tv;
private TextView tv2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((MyApp) getApplication()).getActivityComponent().inject(this);
tv = ((TextView) findViewById(R.id.tv));
tv2 = ((TextView) findViewById(R.id.tv2));
tv.setText(user.toString());
tv2.setText(user2.toString());
}

BActivity如下:

@Inject  
User user;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
((MyApp) getApplication()).getActivityComponent().inject(this);
TextView tv = (TextView) findViewById(R.id.tv);
tv.setText(user.toString());
}

那么如果我还想在CActivity中使用User对象该怎么办呢?再来一个CUserModule和CActivityComponent呗!
CUserModule如下:

@Module  
public class CUserModule {
@Provides
User providesUser() {
return new User();
}
}

这里我没有再注明单例了哦!
CActivityComponent如下:

@Component(modules = CUserModule.class)  
public interface CActivityComponent {
void inject(CActivity activity);
}

在CActivity中注入依赖:

@Inject  
User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_c);
DaggerCActivityComponent.builder().cUserModule(new CUserModule()).build().inject(this);
TextView tv = (TextView) findViewById(R.id.tv);
tv.setText(user.toString());
}

大家看到,MainActivity和BActivity是同一个实例,而CActivity则是另外一个实例。

同时还需要注意一点就是:一个@Module和component中可以有多个scope对象。这些scope将一个component划分成多个不同的区域:

@Module
public class FruidModule {
@FruidScope
@Provides
public Apple provideApple() {
return new Apple();
}
@FruidScope1
@Provides
public Pear providePear() {
return new Pear();
}
}
@FruidScope1
@FruidScope
@Component(modules = {FruidModule.class})
public interface FruidComponent {
void inject(MainActivity activity);
}
@FruidScope
@Inject
Apple apple1;

@FruidScope
@Inject
Apple apple2;


@FruidScope1
@Inject
Pear apple3;


@FruidScope1
@Inject
Pear apple4;


@Override
protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BaseApplication.getAppDelegate().getRefWatcher().watch(this);
DaggerFruidComponent.builder().build().inject(this);
LogManager.getInstance().info(apple1.toString());
LogManager.getInstance().info(apple2.toString());
LogManager.getInstance().info(apple3.toString());
LogManager.getInstance().info(apple4.toString());
}

输出结果:

07-18 21:09:07.245 5676-5676/? I/MainActivity: class -> MainActivity method -> onCreate() line -> 36 [ Message ] com.idealist.tbfungeek.mvpframework.Apple@3dd96071
07-18 21:09:07.245 5676-5676/? I/MainActivity: class -> MainActivity method -> onCreate() line -> 37 [ Message ] com.idealist.tbfungeek.mvpframework.Apple@3dd96071
07-18 21:09:07.245 5676-5676/? I/MainActivity: class -> MainActivity method -> onCreate() line -> 38 [ Message ] com.idealist.tbfungeek.mvpframework.Pear@1956ae56
07-18 21:09:07.246 5676-5676/? I/MainActivity: class -> MainActivity method -> onCreate() line -> 39 [ Message ] com.idealist.tbfungeek.mvpframework.Pear@1956ae56

在使用scope的时候我们还需要注意如下两点:

  1. 编译器会检查 Component管理的Modules,若发现标注Component的自定义Scope注解与Modules中的标注创建类实例方法的注解不一样,就会报错。所以Component和Modules中的scope必须匹配。

  2. 如果两个Component间有依赖关系,那么它们不能使用相同的Scope。

@Subcomponent && dependencies

如果一个Component的功能不能满足你的需求,我们需要对它进行拓展,这时候有两种方法

  1. 使用Component(dependencies=××.classs)
  2. 使用@Subcomponent,Subcomponent用于拓展原有component。这时候注意子component同时具备两种不同生命周期的scope。子Component具备了父Component拥有的Scope,也具备了自己的Scope。

那么它们的不同之处在哪里呢?@Component 只能获取到依赖的 Component 所暴露出来的对象,而 @Subcomponent 则可以获取到父类所有的对象。

Subcomponent其功能效果优点类似component的dependencies。但是使用@Subcomponent不需要在父component中显式添加子component需要用到的对象,只需要添加返回子Component的方法即可,子Component能自动在父Component中查找缺失的依赖。


//父Component:
@PerApp
@Component(modules=××××)
public AppComponent{
SubComponent subcomponent(); //1.只需要在父Component添加返回子Component的方法即可
}

//子Component:
@PerAcitivity //2.注意子Component的Scope范围小于父Component
@Subcomponent(modules=××××) //3.使用@Subcomponent
public SubComponent{
void inject(SomeActivity activity);
}

//使用
public class SomeActivity extends Activity{
public void onCreate(Bundle savedInstanceState){
...
App.getComponent().subCpmponent().inject(this);//4.调用subComponent方法创建出子Component
}
}

通过Subcomponent,子Component就好像同时拥有两种Scope,当注入的元素来自父Component的Module,则这些元素会缓存在父Component,当注入的元素来自子Component的Module,则这些元素会缓存在子Component中。

5. 参考文章

下面是较好的文章,如果看了该博客还是不大明白可以通过下面的文章来进一步阅读

  1. “一盘沙拉”带你入门Dagger2

  2. Android常用开源工具-Dagger2入门

  3. 从零开始的Android新项目4 - Dagger2篇

  4. 解锁Dagger2使用姿势
    Android:dagger2让你爱不释手-重点概念讲解、融合篇

简介

提到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的版本上并行使用反射和编译时注解两种方式,我们可以根据自己的实际需求来选择采用哪种方式。这个在后面源代码分析的时候会进行介绍。

在项目中引入EventBus 3.x

这里推荐大家使用编译时注解方式。我这边为了方便起见,也只介绍使用编译时注解的方式引入。
这里需要注意的是目前很多教程注解预编译所采用的是android-apt的方式,不过随着Android Gradle 插件 2.2 版本的发布,Android Studio推出了编译时预处理官方插件,所以Apt工具的作者也就宣布不再维护该工具了。目前使用的是annotationProcessor来取代android-apt方式。
如果大家使用的是还是android-apt方式的话,建议通过如下方式来切换到annotationProcess方式。

切换步骤:
首先要确保Android Gradle插件版本是2.2以上:

  1. 修改Project 的build.gradle配置

android-apt方式

dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}

修改后annotationProcessor 方式

dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
}

也就是把原先的

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 去掉

  1. 修改module的build.gradle配置

android-apt方式

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
`

apply plugin: ‘com.neenbedankt.android-apt’

dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
apt'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

修改后annotationProcessor 方式,只保留dependencies 里面的引用并且把apt 换成annotationProcessor就可以了

dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

  1. EventBus 3.0 Index类的制定方式

android-apt方式

apt  {
arguments {
eventBusIndex "org.greenrobot.eventbus.demo.MyEventBusIndex"
}
}

修改后annotationProcessor 方式

defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ eventBusIndex : 'org.greenrobot.eventbus.demo.MyEventBusIndex' ]
}
}
}

紧接着rebuild下项目,就会发现在build目录下上面指定报名下生成了一个MyEventBusIndex,这个文件是怎么生成的我们在介绍源码的时候会进行介绍,这里先不管这些。我们接下来就需要将生成的索引添加到EventBus中供事件分发是时候查找。我们知道EventBus默认有一个单例,可以通过getDefault()获取,但是这里我要做的是使用EventBusBuilder对其进行配置后再将Builder生成的EventBus作为默认的EventBus.

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

EventBus 的使用:

  1. 定义订阅方法:
    EventBus使用注解@Subscribe的方式来定义订阅方法。这里面我们需要重点了解下threadMode,sticky,priority这三个属性的含义:
@Subscribe(threadMode = ThreadMode.POSTING,sticky = false,priority = 1)
public void onMessageEvent(MessageEvent event) {
tv.setText(event.message);
}

(1)ThreadMode :

这时候订阅者执行的线程与事件的发布者所在的线程为同一个线程。也就是说

ThreadMode: POSTING
事件由哪个线程发布的,订阅者就在哪个线程中执行。这时候订阅者执行的线程与事件的发布者所在的线程为同一个线程,这个是EventBus默认的线程模式

ThreadMode: MAIN
订阅方法是在Android的主线程中运行的。如果提交的线程也是主线程,那么他就和ThreadMode.POSTING一样了。当然在由于是在主线程中运行的,所以在这里就不能执行一些耗时的任务。

ThreadMode: BACKGROUND
这种模式下,我们的订阅者将会在后台线程中执行。如果发布者是在主线程中进行的事件发布,那么订阅者将会重新开启一个子线程运行,若是发布者在不是在主线程中进行的事件发布,那么这时候订阅者就在发布者所在的线程中执行任务。

ThreadMode: ASYNC
这种模式下,订阅者将会独立运行在一个线程中。不管发布者是在主线程还是在子线程中进行事件的发布,订阅者都是在重新开启一个线程来执行任务。

(2)priority:

在订阅者中我们也可以为其设置优先级,优先级高的将会首先接收到发布者所发布的事件。并且我们还能在高优先中取消事件,这时候的优先级的订阅者将接收不到事件。这类似于BroadcastReceiver中的取消广播。不过这里有一点我们要注意,对于订阅者的优先级只是针对于相同的ThreadMode中。

(3) sticky
sticky与一般的事件的区别是sticky事件发送事件之后再订阅该事件还能收到所订阅事件的最新事件。一般事件在事件发生后才订阅是不会收到之前发送的事件的。
注解方法定义还需要注意,必须是public方法,必须只有一个参数,不能是抽象或者static方法。

  1. 订阅事件:
    register(this) 这个就不需要介绍了。对于sticky事件在注册的时候会立刻收到最新的事件。
  2. 取消订阅:
    unregister(this) 一定要记得取消订阅否则会继续接收到事件。这是一个很尴尬的事情。
  3. 发布事件:
    post(xxxx) postSticky 将事件发布到事件总线上让EventBus来调度。
EventBus源码解析:
  1. EventBus 实例的创建
    EventBus 的实例创建使用的是单例模式 + 建造者模式:
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;
}
  1. 注册订阅者

要将当前某个类作为某个事件的订阅者需要调用EventBus的register方法:

public void register(Object subscriber) {
//获取订阅者的class对象
Class<?> subscriberClass = subscriber.getClass();
//在这个类中查找对应的订阅方法以及父类的订阅方法

List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {

for (SubscriberMethod subscriberMethod : subscriberMethods) {
//针对每个订阅方法调用subscribe进行订阅
subscribe(subscriber, subscriberMethod);
}

}
}

这里会使用SubscriberMethodFinder对作为参数传入对象的class进行查找,找到对应的订阅方法以及父类的订阅方法。我们先看下这部分代码:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//先从缓存中获取某个订阅类的订阅方法
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {

//如果缓存中有那么就直接使用缓存中的缓存方法
return subscriberMethods;
}

//使用不同的方式来寻找订阅方法,EventBus3.0版本提供了EventBusAnnotationProcessor这个类,用于在编译期获取并缓存@Subscribe注解的方法
//ignoreGeneratedIndex = false 的时候使用编译期间获取到的Subscribe注释的方法
if (ignoreGeneratedIndex) {

//使用’findUsingReflection(Class<?> subscriberClass)‘方法,进行反射来获取
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;
}

}

查找订阅方法有两类一种是使用反射的方式来获取到@SubScriber注解的方法,另一种是EventBus3.0 版本之后会使用编译时注解方式在编译的时候获取并缓存@Subscribe注解的方法,后一种大家在上一篇介绍Butterknife源码的时候介绍了大致的方式,这里我还会带大家对EventBusAnnotationProcessor 这个注解处理器进行分析。

找到当前类包含@Subscribe方法后会将这些方法添加到内存缓存中,以便后续查找方便,因为特别是反射方式是一种十分耗性能的方式,这个解决方案在Butterknife上我们也看到过。
METHOD_CACHE 这个缓冲是以当前类的class对象作为key,每个key对应的值是当前类的所有使用@Subscribe注解的方法,也就是事件订阅方法。这里需要注意的是如果在当前类以及父类中没找到任何的订阅方法抛出异常。这是我第一次使用EventBus遇到的一个问题,记忆犹新。哈。

接下来我们来看下这两种获取订阅方法的流程,首先我们看下通过反射的方式 — findUsingReflection

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
/** 为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();
}

我们先看下FindState的结构

FindState用于记录当前查的结果,我们首先会调用initForSubscriber来给FindState“打个标签”用于说明当前的FindState对象用于存放哪个订阅类的查找结果,然后在查找过程中会将查找结果先调用checkAdd以及checkAddWithMethodSignature对查找到的结果进行两级检测,然后再添加到subscriberMethods 中。

我们接下来看下如何使用反射来查找订阅方法:

//对注册对类的方法进行遍历,必须是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<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
//从池中取出一个不为空的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);
}

上面的流程和之前介绍的用反射的流程一致,只不过在获取方式上存在差别:
我们重点看下getSubscriberInfo 方法:

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将编译生成的类添加进来。

接下来我们就需要深究EventBusAnnotationProcessor 以及它生成的代码了:
我们先看它的process方法,这个方法会在编译的时候执行。首先它会要求我们在build.gradle中有如下配置:

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;
}

collectSubscribers 方法检查并收集订阅方法,如果有订阅方法那么就调用createInfoIndexFile生成对应的index类。

首先看下collectSubscribers 方法,我们会对传入注解处理器的所有方法注解进行便利。然后调用checkHasNoErrors对其进行检查。这个和FindState对象的checkAdd类似。如果检查通过了就会将其添加到methodsByClass中。其中该注解所处的类作为key。对应的方法作为value。

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<Class<?>, SubscriberInfo>();\n\n");
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;
}
}

这里的SubscriberMethodInfo 就是该订阅类的订阅订阅方法。在回头看下findUsingInfo方法。findUsingInfo中会继续调用SubscriberMethodInfo的getSubscriberMethods
来获得SubscriberMethod。如果认真看的话我们会发现刚刚添加SubscriberMethodInfo的时候方法实用的是方法名称,而SubscriberMethod中存放的是Method对象,也就是说这里还必须有个转换。这个转换就发生在createSubscriberMethod中。

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);
}
}

之后的逻辑就和之前的一样了,先进行检查后添加到FindState中的scribeMethod列表中,然后再拷贝出来并将FindState放回对象池。

整个SubscriberFinder的流程如下图所示:
不论是通过反射还是编译时注解大体的流程都一致的,都是先从FindState对象池中获取一个FindState用于存放查找的中间结果,然后调用SubscribeFinder针对订阅类中的订阅方法进行查找,查找到对应的订阅方法后,调用checkAdd进行检查是否有相同的方法订阅同一个事件,经过上述的步骤后获取到我们想要的订阅方法列表,最后将这个列表从FindState中拷贝出来,存放到subscribeMethods列表中,并将FindState放回到对象池中,以便下一次使用。

我们获得当前订阅类中的订阅方法后我们还需要将其添加到事件总线中,以便事件总线根据对应的关系来分发事件源发送到事件。我们就来看下这部分代码:

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事件列表

关于这些列表的实际作用我们介绍完事件分发后再来看看这个在事件分发过程中是如何起作用的。

  1. 事件源发布事件:
    事件的发布是通过post以及postSticky来发布的,我们先来看下通过post发布普通事件的流程:
public void post(Object event) {
//获取当前posting线程的状态
PostingThreadState postingState = currentPostingThreadState.get();
//获取事件队列
List<Object> eventQueue = postingState.eventQueue;
//将事件添加到事件队列中
eventQueue.add(event);
if (!postingState.isPosting) {
//是否是在主线程中
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
//将postingState置为true
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
//将队头的元素发布出去
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}

发布过程如下:
(1) 将当前事件添加到事件队列列表中
(2) 查看事件队列列表是否有缓存的事件,如果有那么将队头的事件发布出去

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<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
//找到对应的事件类型以及子类型调用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));
}
}
}

首先这里面有个关键变量eventInheritance 这个变量如果是true,那么调用post的时候不但会讲当前事件发布出去,也会将其父类以及接口类作为事件发布出去,这个值默认是true,这个需要注意下。
紧接着最关键的代码在postSingleEventForEventType以及postToSubscription 中

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
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方法来出发点对应的订阅方法。

我们来详细看下不同threadMode是如何处理的:
(1)POSTING:
这种情况最为简单就是直接调用invokeSubscriber

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:
这种情况会先判断当前线程是否是主线程,如果是的话就直接调用invokeSubscriber,如果不是的话就必须依靠mainThreadPoster了,我们看下mainThreadPoster:

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;
}
}
}

在介绍mainPoster之前我们先来看下PendingPost,这里有个对象池,最大的大小为10000,每次发布事件的时候不是直接通过new一个PendingPost而是从对象池中取出一个已经存在的PendingPost,通过这种复用对象池来缓解频繁创建对象带来的性能问题。

final class PendingPost {
private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();

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处理完,或者处理事件超过设定的最大事件处理时间。整个流程如下所示:

接着我们看下BackgroundPoster,这里会从线程池中获取一个线程,然后调用execute(this) 触发BackgroundPoster的run方法,在run方法中会从PendingPostQueue中不断取出PendingPost调用invokeSubscriber

 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;
}
}
}

最后我们再看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的源码分析结束:
老规矩最后以一个图来总结整个流程:
首先我们需要先通过register来订阅某些事件,在调用register的时候,SubscriberFinder从订阅类中找出所有的订阅处理方法,并将其挂接在事件分发中心EventBus的subscriptionsByEventType列表中,如果有事件触发那么就会将事件发送到事件分发中心,事件分发中心就会从subscriptionsByEventType列表中查找订阅者方法。然后根据对应的threadMode在不同的poster中调用invokeScriber来执行订阅者方法。

作为Android应用开发工程师估计没有人没听过Butterknife吧,我们可以借助它来简化view的查找过程以及通过注释将监听器绑定到事件响应方法上,这样可以减少很多代码量。对于Butterknife的用法在之前的博客中已经做了相应的介绍,这部分主要是分析Butterknife的源码,在之后的博文中还会对编译时注解解析器的实现做对应的介绍,如果想要对这部分内容有比较深入的了解,可以看下 《Java 进阶系列之注解》 以及《一步一步编写编译时注解框架》以及该篇博客。

好了言归正传,我们在介绍之前看下Butterknife 的工作原理:
在没有Butterknife的情况下我们用的是 Android 原生的API方法findViewById,而有了Butterknife后我们就可以直接使用@BindView(R.id.text) , 但是如果仔细看过源码的同学,会发现到最后还是调用findViewById,只不过这个是编译时注解框架帮我们生成了对应的代码,换句话说Butterknife以带有注解的源码文件作为javac编译器输入,在编译的时候会调用注解解析工具,对这些源码中的注解进行解析,解析后再通过代码生成工具来产生对应的源码文件,最后将我们自己的源码文件和注解解析工具产生的源码一同作为编译器输入进行编译。

ok 了解了整个原理我们就可以进行深入研究了。

我们将分成两大部分进行分析:

  • 绑定View以及事件
  • 注解解析器

[项目结构]:

[绑定过程]:

在绑定实用的是bind方法,bing方法实现如下,它的实现十分简单,但是在介绍bind时候,必须要先大概知道什么是target,什么是source,简单讲target就是一个实体,它承载着source,目前butterknife支持的target 包括activity,dialog,以及view。source就是我们实际要查找控件的地方。也就是findViewById 应用的对象。下面是以activity为target,所以对应的source就是对应activity的DecorView.

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}

接着我们来看下createBinding方法,在方法开始的时候会使用target的class对象来获取对应的ViewBinding的构造方法,target的ViewBinding对象是由ButterknifeProcessor生成的,这个会在后面介绍ButterknifeProcessor的时候进行详细展开介绍。获取到对应的ViewBinding对象后调用该对象的构造方法并返回。


private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
//获取target的Class对象,比如 XXXActivity.class
Class<?> targetClass = target.getClass();
//获取targetClass对应ViewBinding的构造方法
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

if (constructor == null) {

return Unbinder.EMPTY;
}
//这样一来,ButterKnife.bind(this)传递进去的MainActivity会通过反射生成MainActivity_ViewBinding实例。
//在这个实例的构造函数内,进行findViewById、setOnclickListener等操作
try {

return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {

throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InstantiationException e) {

throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InvocationTargetException e) {

Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException("Unable to create binding instance.", cause);
}
}

findBindingConstructorForClass 的作用就是加载对应targetClass的ViewBinding对象的构造方法:

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//首先将传入XXXXActivity 类对象 作为参数尝试从缓存中获取通过JavaPoet产生的ViewBinding的构造方法
//内存缓存BINDINGS 的组成是以targetClass为key,targetClass ViewBinding 构造方法为value的一个Map
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
//由于Butterknife 不能用于注释android以及java这些标准sdk的类所以先进行这种类型的过滤
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//使用类加载器加载在编译阶段由JavaPoet生成的XXXX_ViewBinding类对象
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//获取对应的构造方法类,并且会将得到的Constructor缓存起来,避免反射的性能问题。
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e) {
//如果没有找到那么找它对应的父类ViewBinding的构造方法
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
//将其添加到缓存中,后续使用的时候就不需要进行再次使用反射了从而加快了效率
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}

为了提高整体性能,这里使用了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> {
private View view16908290;

@UiThread
public G_ViewBinding(final T target, View source) {
super(target, source);

View view;
target.button2 = Utils.findRequiredView(source, android.R.id.button2, "field 'button2'");
view = Utils.findRequiredView(source, android.R.id.content, "method 'onClick'");
view16908290 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onClick();
}
});

Context context = source.getContext();
Resources res = context.getResources();
Resources.Theme theme = context.getTheme();
target.grayColor = Utils.getColor(res, theme, android.R.color.darker_gray);
}

@Override
public void unbind() {
T target = this.target;
super.unbind();
target.button2 = null;
view16908290.setOnClickListener(null);
view16908290 = null;
}
}

我们先不看上面的具体代码,只看unbind方法。它会递归地将对应的view对象设置成null,从而在gc的时候释放掉资源。

从上面代码看整个butterknife包的源码是十分简单的,最重要的在于xxxx_ViewBinding.class的生成,这就需要对butterknife-compiler包中的源码进行研究了,我们所有的注解都在butterknife-annotations这个包中,我们这里只以BindView注解作为分析对象:
具体的注解相关内容见《Java进阶之注解》 这篇博客。

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}

从上面我们可以看出@BindView注解应用于属性定义中,并且只在源码级别以及class级别中存在,它的值为一个id类型的值。不允许其他类型的值作为参数。

[Butterknife注释解析器ButterknifeProcessor]:

从上面一小结介绍可以看出Butterknife.java 中的工作很简单就是拿着targetClass找到对应的ViewBinding,至于ViewBinding怎么来的则是这一小结的介绍重点,在介绍ButterKnife注解解析器之前我们再次回顾下注解解析器在整个编译过程中的的作用:
我们知道Java使用Javac处理编译过程的,在Java的编译时期,Javac会先调用java注解处理器来进行处理。因此我们可以自定义注解处理器来做一些处理。注解处理器通常以java源码或者已编译的字节码作为输入,然后生成一些源码文件作为输出。在这个过程可以在已有的代码上添加一些方法,来帮我们做一些有用的事情。这些生成的 java 文件跟其他手动编写的java源码一样,会被 javac 编译。编译时解析是注解强大的地方之一。我们可以利用它在编译时帮你生成一些代码逻辑,避免了运行时利用反射解析所带来的性能开销。

下面是ButterKnifeProcessor的大体结构:

@AutoService(Processor.class)
//要解析编译时注释,可以通过创建一个继承自AbstractProcessor的注解处理器,然后实现相关方法。
public final class ButterKnifeProcessor extends AbstractProcessor {
//指定支持的 java 版本,通常返回 SourceVersion.latestSupported()
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override public Set<String> getSupportedOptions() {
return Collections.singleton(OPTION_SDK_INT);
}

//init方法是在Processor创建时被javac调用并执行初始化操作。
//做一些初始化操作,可以在这里获取Filer,Elements等辅助类
//@param processingEnv 提供一系列的注解处理工具。
public synchronized void init(ProcessingEnvironment env) {
}

//注解处理器要处理的注解类型,值为完全限定名(就是带所在包名和路径的类全名) **/
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
//将支持的注释类型添加到types集合中
types.add(annotation.getCanonicalName());
}
return types;
}
//注解处理需要执行一次或者多次。每次执行时,处理器方法被调用,并且传入了当前要处理的注解类型。可以在这个方法中扫描和处理注解,并生成Java代码。
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
return true;
}

}

下面我们就顺着这个框架对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.

@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);

//获取sdk 版本
String sdk = env
.getOptions().get(OPTION_SDK_INT);
if (sdk != null) {

try {
this.sd
k = Integer.parseInt(sdk);
} catch (NumberFormatException e) {
env.getMessager().printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '" + sdk + "'. Falling back to API 1 support.");
}
}

elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();

try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}

接着就是整个注解解析器的关键方法process,每次执行时,处理器方法被调用,并且传入了当前要处理的注解类型。可以在这个方法中扫描和处理注解,并生成Java代码。我们看下面的process方法,在这个方法中主要完成了两项任务,一个就是调用findAndParseTargets来获取bindingMap
bindingMap 是什么呢?它其实对应的是每个有调用bind绑定的target,举个例子来说,我们在MainActicity的onCreate方法调用了Butterknife的bind方法,那么ButterknifeProcessor就会产生一个对应的BindingSet,也就是说有多少个bind,bindingMap里面就有多少个BindingSet。然后再使用Javapost来为每个BingSet生成一个_ViewBinding.class.

@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.
getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {

javaFile.writeTo(filer);
} catch (IOExceptio
n e) {
error(typeElement,
"Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}

我们先来看下findAndParseTarets,它的任务分成如下几个部分:

  1. 调用scanForRClasses 分别通过RClassScanner IdScanner VarScanner 解析R 文件并将解析结果存放到symbols 中,至于symbols的作用是什么我们后面介绍
  2. 调用一系列parseXXXX方法解析各种注解
  3. 调用findAndParseListener 来解析各种事件注解
  4. 整理BindingSet的继承关系
    private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    //建立view与R的id的关系
    //比如在Butterknife 编译出来的代码MainActivity_ViewBinding里持有一个全局变量view8131034512,
    // 这个其实就是MainActivity的tvTitle,后面的8131034512就是对应在R文件的id。

    //分别通过RClassScanner IdScanner VarScanner 解析R 文件并将解析结果存放到symbols 中
    scanForRClasses(env);

    // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
    // we don't SuperficialValidation.validateElement(element)
    // so that an unresolved View type can be generated by later processing rounds
    try {
    parseBindView(element, builderMap, erasedTargetNames);
    } catch (Exception e) {
    logParsingError(element, BindView.class, e);
    }
    }

    // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
    findAndParseListener(env, listener, builderMap, erasedTargetNames);
    }
    Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet());
    Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
    while (!entries.isEmpty()) {

    //取出第一个元素
    Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
    TypeElement type = entry.getKey();
    BindingSet.Builder builder = entry.getValue();

    TypeElement parentType = findParentType(type, erasedTargetNames);
    if (parentType == null) {
    bindin
    gMap.put(type, builder.build());
    } else {
    BindingSet parentBinding = bindingMap.get(parentType);
    if (parentBinding != null) {
    builder.setParent(parentBinding);
    bindingMap.put(type, builder.build());
    } else {
    // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
    entries.addLast(entry);
    }
    }
    }
    return bindingMap;
    }

首先看下scanForRClasses:
它的作用是解析R文件将获取到的数据存入symbols,用于作为后续标识每个元素的key。R 元素中的每一个变量对应symols中的一个元素。

private void scanForRClasses(RoundEnvironment env) {
if (trees == null) return;
RClassScanner scanner = new RClassScanner();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
//遍历全部支持的注解,找出使用给定注释类型注释的元素 比如这里可以返回全部使用@BindView注解的元素
for (Element element : env.getElementsAnnotatedWith(annotation)) {
JCTree tree = (JCTree) trees.getTree(element, getMirror(element, annotation));
if (tree != null) {
//获取对应元素(使用BindView元素)的包名的完全限定名称
//首先根据element获取到包名,再利用RClassScanner寻找到R文件,在R文件里利用IdScanner寻找到内部类id,
//在id类里利用VarScanner寻找到tvTitle的id。最后就可以得到view2131034112。
String respectivePackageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//扫描指定的包
scanner.setCurrentPackageName(respectivePackageName);
tree.accept(scanner);
}
}
}

//包名 + R Class
for (Map.Entry<String, Set<String>> packageNameToRClassSet : scanner.getRClasses().entrySet()) {
String respectivePackageName = packageNameToRClassSet.getKey();
//获取对应的R类
for (String rClass : packageNameToRClassSet.getValue()) {
//解析R类
//respectivePackageName 元素的包名
parseRClass(respectivePackageName/*key*/, rClass/*value*/);
}
}
}

查找R文件使用的是RClassScanner,它是一个TreeScanner,它会找到整个项目的R文件: 

private static class RClassScanner extends TreeScanner {
private final Map<String, Set<String>> rClasses = new LinkedHashMap<>();
private String currentPackageName;
//数据结构 包名 + RC 类名集合
//rClasses ---| String 包名(currentPackageName) + RC 类名集合(rClassSet) Set<String> -> symbol.getEnclosingElement().getEnclosingElement().enclClass().className()
// | String 包名(currentPackageName) + RC 类名集合(rClassSet) Set<String> -> symbol.getEnclosingElement().getEnclosingElement().enclClass().className()
// | String 包名(currentPackageName) + RC 类名集合(rClassSet) Set<String> -> symbol.getEnclosingElement().getEnclosingElement().enclClass().className()
// | String 包名(currentPackageName) + RC 类名集合(rClassSet) Set<String> -> symbol.getEnclosingElement().getEnclosingElement().enclClass().className()
@Override
public void visitSelect(JCTree.JCFieldAccess jcFieldAccess) {
Symbol symbol = jcFieldAccess.sym;
if (symbol != null
&& symbol.getEnclosingElement() != null
&& symbol.getEnclosingElement().getEnclosingElement() != null
&& symbol.getEnclosingElement().getEnclosingElement().enclClass() != null) {
//从缓存中获取对应的包名对应的 RClass 集合
Set<String> rClassSet = rClasses.get(currentPackageName);
if (rClassSet == null) {
rClassSet = new HashSet<>();
rClasses.put(currentPackageName, rClassSet);
}
// getEnclosingElement 返回封装此元素(非严格意义上)的最里层元素。这里是对应的R Class 文件
rClassSet.add(symbol.getEnclosingElement().getEnclosingElement().enclClass().className());
}
}
/**
* 获取当前包的rClasses对象
* @return
*/

Map<String, Set<String>> getRClasses() {
return rClasses;
}
/**
* 设置当前扫描的包名
* @param respectivePackageName
*/

void setCurrentPackageName(String respectivePackageName) {
this.currentPackageName = respectivePackageName;
}
}

在找到全部的R文件后在scanForRClass中会调用parseRClass对扫描到的R文件进行解析:

private void parseRClass(String respectivePackageName, String rClass) {
Element element;
try {
//对应的R元素
element = elementUtils.getTypeElement(rClass);
} catch (MirroredTypeException mte) {
element = typeUtils.asElement(mte.getTypeMirror());
}
//对应的R文件
JCTree tree = (JCTree) trees.getTree(element);
if (tree != null) {
IdScanner idScanner = new IdScanner(symbols,
elementUtils.getPackageOf(element).getQualifiedName().toString()/*R class 全路径*/,
respectivePackageName/*目标元素的包名*/);
tree.accept(idScanner);
} else {
parseCompiledR(respectivePackageName, (TypeElement) element);
}
}

我们从上面代码中可以看到在parseRClass中会使用IdScanner 对当前扫描的R文件进行扫描:

private static class IdScanner extends TreeScanner {
private final Map<QualifiedId, Id> ids;
private final String rPackageName;
private final String respectivePackageName;

IdScanner(Map<QualifiedId, Id> ids, String rPackageName/*R class 全路径*/, String respectivePackageName/*对应的包名*/) {
this.ids = ids;
this.rPackageName = rPackageName;
this.respectivePackageName = respectivePackageName;
}

@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
for (JCTree tree : jcClassDecl.defs) {
//每个指的是R 文件中的一个类 比如anim
if (tree instanceof ClassTree) {
ClassTree classTree = (ClassTree) tree;
//返回对应的类名比如 public static final class anim 返回的是 anim
String className = classTree.getSimpleName().toString();
//看下当前的类型是否在支持的范围内
if (SUPPORTED_TYPES.contains(className)) {
//获取到资源类型的全限定名 比如com.demo.example.R.anim
ClassName rClassName = ClassName.get(rPackageName, "R", className);
VarScanner scanner = new VarScanner(ids, rClassName /* 这里是某个资源类的全限定名*/, respectivePackageName);
((JCTree) classTree).accept(scanner);
}
}
}
}
}

在IdScanner 中会对R文件中的每个内部类进行扫描,判断当前的内部类是否是所支持的,如果是那么就调用VarScanner进行进一步扫描。

private static class VarScanner extends TreeScanner {
private final Map<QualifiedId, Id> ids;
private final ClassName className;
private final String respectivePackageName;

private VarScanner(Map<QualifiedId, Id> ids, ClassName className/*com.demo.example.R.anim*/, String respectivePackageName) {
this.ids = ids;
this.className = className;
this.respectivePackageName = respectivePackageName;
}

@Override public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
if ("int".equals(jcVariableDecl.getType().toString())) {
//资源id
int id = Integer.valueOf(jcVariableDecl.getInitializer().toString());
//资源名
String resourceName = jcVariableDecl.getName().toString();
// 元素所在包名,资源id
QualifiedId qualifiedId = new QualifiedId(respectivePackageName, id);
//[获取到资源类型的全限定名比如com.demo.example.R.anim] [资源名abc_fade_in] [0x7f040000]
ids.put(qualifiedId, new Id(id, className, resourceName));
}
}
}

VarScanner 也是一个TreeScanner,它的所用就是在R 文件内部类中扫描,并将扫描到的id 添加到symbos中。

介绍到这里我们做下简要,首先我们会通过RCScanner找到项目的R文件,再使用IdScanner对R文件中的内部类进行扫描,每个内部类对应一类资源,最后通过VarScanner对id进行扫描然后存入sybols这个map.

symbols定义如下:

private final Map<QualifiedId, Id> symbols = new LinkedHashMap<>();

Key:

QualifiedId ---|--- respectivePackageName 当前资源所在对包
|--- id 当前资源id

Value ---|--- id 当前资源id 比如 0x7f040000
|--- className 获取到资源类型的全限定名 比如com.demo.example.R.anim
|--- resourceName 资源名 比如 abc_fade_in

介绍完scanForRClass我们继续了解下注解的解析过程:
在进行解析之前会先对注解所应用的对象做初步检查:

  • 验证对应的修饰符是否是static 或者 private ,注解是否正在修饰局部变量,是否处于私有类中,以及检查包名是否是在android以及java标准API包内,如果有上述的情况就返回false
  • 获取使用该注解的元素对应类型是否继承自View

如果校验无误,那么会先查询bingMap中是否已经有对应的BindingSet了,如果没有就创建一个并添加到bingMap中。如果有的话就完对应的BindingSet添加Field这些Field会在JavaPost创建对应源码文件的时候用于生成对应的成员变量。这个后面会进行详细介绍。
我们下面深入看两点:

  1. BindingSet是怎么创建的
  2. addFeild是怎么添加的
    private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    //校验一:解析前初步校验
    //验证对应的修饰符是否是static 或者 private ,注解是否正在修饰局部变量,是否处于私有类中,以及检查包名是否是在android以及java标准API包内,如果有上述的情况就返回false
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element);

    //校验二:获取使用该注解的元素对应类型是否继承自View
    TypeMirror elementType = element.asType();
    // 是否是一个变量类型
    if (elementType.getKind() == TypeKind.TYPEVAR) {
    TypeVariable typeVariable = (TypeVariable) elementType;
    //获取它的父类
    elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    //如果不是View类型并且不是接口那么就返回类型失败的错误
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
    //解析不了的类型
    if (elementType.getKind() == TypeKind.ERROR) {
    note(element, "@%s field with unresolved type (%s) " + "must elsewhere be generated as a View or interface. (%s.%s)", BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
    } else {
    //不是View的子类或者不是接口
    error(element, "@%s fields must extend from View or be an interface. (%s.%s)", BindView.class.getSimpleName(), qualifiedName, simpleName);
    hasError = true;
    }
    }

    //如果解析错误那么不继续解析
    if (hasError) {
    return;
    }

    //获取BindView注解中的id值
    int id = element.getAnnotation(BindView.class).value();
    //用包名和id生成一个QualifiedId:symble中存放的内容是以QualifiedId为键值
    QualifiedId qualifiedId = elementToQualifiedId(element, id);

    //从缓存中获取对应元素所在类的BindingSet.Builder,每个注解元素所在类都只有一个BindingSet.Builder
    //比如我们当前的元素位于MainActivity中,那么解析的时候就获取MainActivity对应的BindingSet.Builder
    BindingSet.Builder builder = builderMap.get(enclosingElement/*MainActivity*/);

    //判断是否存在同一个id重复绑定的问题
    if (builder != null) {
    String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
    //重复绑定的情况
    if (existingBindingName != null) {
    error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
    BindView.class.getSimpleName(), id, existingBindingName,
    enclosingElement.getQualifiedName(), element.getSimpleName());
    return;
    }
    } else {
    //元素所处类对应包名/*MainActivity*/ + BindingSet.Builder
    builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    //控件名 比如mTextView
    String name = simpleName.toString();
    //获取对应的类型 TextView
    TypeName type = TypeName.get(elementType);
    //当前元素是否是必须的
    boolean required = isFieldRequired(element);
    //将当前的成员变量添加到BuildSet中 比如上述的例子: id / TextView mTextView false
    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
    }

BindingSet的创建是通过调用getOrCreateBindingBuilder方法来完成的:
它会先判断当前target类型是activity,dialog,还是一般的view,以及生成target对应的ViewBinding文件名。

//builderMap 中存放的形式  key: 对应的包名 + 对应的BindingSet.Builder
private BindingSet.Builder getOrCreateBindingBuilder(Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {

BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {

builder = BindingSet.newBuilder(enclosingElement/*MainActivity*/);
builderMap.put(enclosingElement, builder);
}

return builder;
}

/**
* 创建一个Builder,每个文件对应一个Builder,这里主要判断当前是否是View,是否是Activity,是否是Dialog,是否是Final,以及对应的报名
* 参数为注解所处上层元素
*/

static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
//判断当前是否View
boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
//判断当前是否Activity
boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
//判断当前是否Dialog
boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);
//获取上层类的类型
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
//获取包名
String packageName = getPackage(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getQualifiedName().toString().substring(packageName.length() + 1).replace('.', '$');
//生成源码的文件名
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}

那BindingSet的结构是怎样的呢?我们继续往下看:

static final class Builder {
private final TypeName targetTypeName;
private final ClassName bindingClassName;
private final boolean isFinal;
private final boolean isView;
private final boolean isActivity;
private final boolean isDialog;
private BindingSet parentBinding;
private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
private final ImmutableList.Builder<FieldCollectionViewBinding> collectionBindings = ImmutableList.builder();
private final ImmutableList.Builder<ResourceBinding> resourceBindings = ImmutableList.builder();

/**
* 添加Field
*/

void addField(Id id, FieldViewBinding binding) {
ViewBinding.Builder viewBinding = getOrCreateViewBindings(id);
viewBinding.setFieldBinding(binding);
}

/**
* 添加方法
*/

boolean addMethod(Id id, ListenerClass listener, ListenerMethod method, MethodViewBinding binding) {
ViewBinding.Builder viewBinding = getOrCreateViewBindings(id);
if (viewBinding.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) {
return false;
}
viewBinding.addMethodBinding(listener, method, binding);
return true;
}

/**
* 添加Field集合
*/

void addFieldCollection(FieldCollectionViewBinding binding) {
collectionBindings.add(binding);
}

/**
* 添加资源
*/

void addResource(ResourceBinding binding) {
resourceBindings.add(binding);
}
void setParent(BindingSet parent) {
this.parentBinding = parent;
}
/**
* 当前id是否已经被绑定了
* @param id
* @return
*/

String findExistingBindingName(Id id) {
ViewBinding.Builder builder = viewIdMap.get(id);
if (builder == null) {
return null;
}
FieldViewBinding fieldBinding = builder.fieldBinding;
if (fieldBinding == null) {
return null;
}
return fieldBinding.getName();
}
/**
* 往viewIdMap中获取ViewBinding.Builder
*/

private ViewBinding.Builder getOrCreateViewBindings(Id id) {
ViewBinding.Builder viewId = viewIdMap.get(id);
if (viewId == null) {
viewId = new ViewBinding.Builder(id);
viewIdMap.put(id, viewId);
}
return viewId;
}

BindingSet build() {
ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder();
for (ViewBinding.Builder builder : viewIdMap.values()) {
viewBindings.add(builder.build());
}
//将当前的绑定数据传到BindingSet
return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog,
viewBindings.build(), collectionBindings.build(), resourceBindings.build(), parentBinding);
}
}

我们从BindingSet的Builder可以看出它有如下的成员变量:

parentBinding       指向父类的BindingSet。
viewIdMap 与View相关的绑定信息,包括view对象以及事件方法。
collectionBindings 多个view集体绑定的情况
resourceBindings 资源绑定

它有如下方法:

void addField(Id id, FieldViewBinding binding)  添加Field
boolean addMethod(Id id, ListenerClass listener, ListenerMethod method, MethodViewBinding binding) 添加事件方法
void addFieldCollection(FieldCollectionViewBinding binding) 添加Field集合
void addResource(ResourceBinding binding) 添加资源

介绍完BindView 我也把事件绑定的方法贴出来了,下面有较为详细的注释。

private void findAndParseListener(RoundEnvironment env, Class<? extends Annotation> annotationClass, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
for (Element element : env.getElementsAnnotatedWith(annotationClass)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);
} catch (Exception e) {
StringWriter stackTrace = new StringWriter();
e.printStackTrace(new PrintWriter(stackTrace));
error(element, "Unable to generate view binder for @%s.\n\n%s",
annotationClass.getSimpleName(), stackTrace.toString());
}
}
}

private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) throws Exception {
// This should be guarded by the annotation's @Target but it's worth a check for safe casting.
//首先检查当前元素是否是方法类型
if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) {
throw new IllegalStateException(String.format("@%s annotation must be on a method.", annotationClass.getSimpleName()));
}

ExecutableElement executableElement = (ExecutableElement) element;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//获取注解中的id数组
Annotation annotation = element.getAnnotation(annotationClass);
Method annotationValue = annotationClass.getDeclaredMethod("value");
if (annotationValue.getReturnType() != int[].class) {
throw new IllegalStateException(String.format("@%s annotation value() type not int[].", annotationClass));
}
int[] ids = (int[]) annotationValue.invoke(annotation);
//方法名字
String name = executableElement.getSimpleName().toString();
//是否必须
boolean required = isListenerRequired(executableElement);

//校验是否错误
boolean hasError = isInaccessibleViaGeneratedCode(annotationClass, "methods", element);
hasError |= isBindingInWrongPackage(annotationClass, element);

//查找是否有重复的id
Integer duplicateId = findDuplicate(ids);
if (duplicateId != null) {
error(element, "@%s annotation for method contains duplicate ID %d. (%s.%s)",
annotationClass.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}

//获取到对应ListenerClass
ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
if (listener == null) {
throw new IllegalStateException(
String.format("No @%s defined on @%s.", ListenerClass.class.getSimpleName(),
annotationClass.getSimpleName()));
}
/*@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
type = "butterknife.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View"
)
)*/
ListenerMethod method;
ListenerMethod[] methods = listener.method();
//获取onClick注解中@ListenerClass中的method
if (methods.length > 1) {
//不允许有两个以上的method
throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.", annotationClass.getSimpleName()));
} else if (methods.length == 1) {
//method 不允许和callbacks共存
if (listener.callbacks() != ListenerClass.NONE.class) {
throw new IllegalStateException(String.format("Both method() and callback() defined on @%s.", annotationClass.getSimpleName()));
}
//获取到methods[0]
method = methods[0];
} else {
//如果没有method就获取callback
Method annotationCallback = annotationClass.getDeclaredMethod("callback");
Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
Field callbackField = callback.getDeclaringClass().getField(callback.name());
method = callbackField.getAnnotation(ListenerMethod.class);
if (method == null) {
throw new IllegalStateException(
String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(),
annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(),
callback.name()));
}
}

//验证回调方法的参数(实际被注释的方法参数不能大于注解中提供的参数个数)
// Verify that the method has equal to or less than the number of parameters as the listener.
//获取实际的方法参数
List<? extends VariableElement> methodParameters = executableElement.getParameters();
//如果注解目标参数大于ListenerMethod注解指定的参数个数就报错
if (methodParameters.size() > method.parameters().length) {
error(element, "@%s methods can have at most %s parameter(s). (%s.%s)",
annotationClass.getSimpleName(), method.parameters().length,
enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}

// 验证返回类型(实际被注释的方法参数需要等于注解中提供的参数类型)
// Verify method return type matches the listener.
TypeMirror returnType = executableElement.getReturnType();
if (returnType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) returnType;
returnType = typeVariable.getUpperBound();
}
if (!returnType.toString().equals(method.returnType())) {
error(element, "@%s methods must have a '%s' return type. (%s.%s)",
annotationClass.getSimpleName(), method.returnType(),
enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}

//校验结束,如果有问题就不在继续
if (hasError) {
return;
}

Parameter[] parameters = Parameter.NONE;
//省略参数的创建过程

//name 名字 parameters 参数 required 是否必须
MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
//创建一个BindingSet.Builder
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
for (int id : ids) {
//为每个id绑定一个方法
QualifiedId qualifiedId = elementToQualifiedId(element, id);
//ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
if (!builder.addMethod(getId(qualifiedId), listener, method, binding)) {
error(element, "Multiple listener methods with return value specified for ID %d. (%s.%s)", id, enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
}
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}

使用JavaPoest为每个BindingSet创建对应的_ViewBinding文件:
我们在此回到process方法

@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {

javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}

我们上面了解了如何从R文件中获取对应的信息构成后续作为Id使用的symbols。再对代码中使用到的Butterknife注解进行解析,获取到对应的View 绑定,事件方法绑定,资源绑定信息,添加到对应BingSet中,最后我们要了解的是最后一步 – 使用JavaPost来将BindingSet中收集到的View 绑定,事件方法绑定,资源绑定信息生成对应的源码:
我们先来看下brewJava 方法:

JavaFile brewJava(int sdk) {
return JavaFile
.builder(bindingClassName.packageName()/*com.package.xxxx.MainActivity_BindingView*/, createType(sdk))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}

brewJava 方法中最重要的就是createType,它是生成_ViewBinding的核心代码,下面给出了详细的注释:

private TypeSpec createType(int sdk) {

//使用JavaPost 生成 "public final class MainActivity_BindingView"
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()).addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(FINAL);
}

//生成 public final class MainActivity_BindingView extends Unbinder
//或者 public final class MainActivity_BindingView extends XXXXActivity_BindingView
//查看是否父类有绑定,添加绑定类的父类
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
} else {
result.addSuperinterface(UNBINDER);
}

//是否有方法或者值绑定,如果有那么添加target成员,在只有资源绑定的情况下就没有target成员变量
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}

//创建对应的构造方法
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}

//添加一个废弃的方法用于在Butterknife.bind方法反射时候使用
if (!constructorNeedsView()) {
result.addMethod(createBindingViewDelegateConstructor());
}
result.addMethod(createBindingConstructor(sdk));

//添加unbind方法
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}

那么我们在Butterknife bind方法中调用的构造函数是哪个呢:就是createBindingConstructor

private MethodSpec createBindingConstructor(int sdk) {
//添加一个无参数的构造方法
//@UiThread
//public class MainActivity_BindingView {
// MainActivity_BindingView(Activity target,View source) {
// super(target, source);
// this.target = target
// }
//}
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);

if (hasMethodBindings()) {

constructor.addParameter(targetTypeName, "target", FINAL);
} else {

constructor.addParameter(targetTypeName, "target");
}

//如果跟View相关那么添加成员变量source 否则添加成员变量context
if (constructorNeedsView()) {

constructor.addParameter(VIEW, "source");
} else {

constructor.addParameter(CONTEXT, "context");
}

if (hasUnqualifiedResourceBindings()) {

// Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "ResourceType")
.build());
}

//是否有onTouch事件绑定
if (hasOnTouchMethodBindings()) {

constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
.addMember("value", "$S", "ClickableViewAccessibility")
.build());
}

//添加对父类构造方法的调用
if (parentBinding != null) {

if (parentBinding.constructorNeedsView()) {
constructor.addStatement("super(target, source)");
} else if (constructorNeedsView()) {

constructor.addStatement("super(target, source.getContext())");
} else {

constructor.addStatement("super(target, context)");
}
constructor.addCode("\n");
}

//给target赋值
if (hasTargetField()) {

constructor.addStatement("this.target = target");
constructor.addCode("\n");
}

//添加View 绑定
if (hasViewBindings()) {

if (hasViewLocal()) {
// Local variable in which all views will be temporarily stored.
constructor.addStatement("$T view", VIEW);
}
//添加绑定的View,这里是重点
for (ViewBinding binding : viewBindings) {

addViewBinding(constructor, binding);
}
for (FieldCollectionViewBinding binding : collectionBindings) {

constructor.addStatement("$L", binding.render());
}
if (!resourceBindings.isEmpty()) {

constructor.addCode("\n");
}
}

//添加其他绑定
if (!resourceBindings.isEmpty()) {

if (constructorNeedsView()) {
constructor.addStatement("$T context = source.getContext()", CONTEXT);
}
if (hasResourceBindingsNeedingResource(sdk)) {

constructor.addStatement("$T res = context.getResources()", RESOURCES);
}
for (ResourceBinding binding : resourceBindings) {

constructor.addStatement("$L", binding.render(sdk));
}
}
return constructor.build();
}

上面的代码已经注释很清楚了,我们直接讲最重要的:addViewBinding,在调用addViewBinding之前会调用hasViewBindings来判断当前是否有view绑定以及事件方法绑定,如果有才会调用addViewBinding.
在addViewBinding方法中会判断是否isRequired来决定是调用butterknife.internal.Utils的findRequiredViewAsType 还是findOptionalView

private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {

if (binding.isSingleFieldBinding()) {
FieldViewBinding fieldBinding = binding.getFieldBinding();
CodeBlock.Builder builder = CodeBlock.builder().add("target.$L = ", fieldBinding.getName());
//生成target.mTextBtn =

//是否需要强制转换
boolean requiresCast = requiresCast(fieldBinding.getType());

if (!requiresCast && !fieldBinding.isRequired()) {
//生成 target.mTextBtn = source.findViewById(id)
builder.add("source.findViewById($L)", binding.getId().code);
} else {
builder.add("$T.find", UTILS);
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
//调用butterknife.internal.Utils的findRequiredViewAsType 或者findOptionalView
if (requiresCast) {
builder.add("AsType");
}
builder.add("(source, $L", binding.getId().code);
if (fieldBinding.isRequired() || requiresCast) {
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
builder.add(", $T.class", fieldBinding.getRawType());
}
builder.add(")");
}
result.addStatement("$L", builder.build());
return;
}
List<MemberViewBinding> requiredBindings = binding.getRequiredBindings();
if (requiredBindings.isEmpty()) {
result.addStatement("view = source.findViewById($L)", binding.getId().code);
} else if (!binding.isBoundToRoot()) {
result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,
binding.getId().code, asHumanDescription(requiredBindings));
}
//添加成员变量绑定
addFieldBinding(result, binding);
//添加方法绑定
addMethodBindings(result, binding);
}

findOptionalViewAsType 和 findRequiredViewAsType 有什么区别呢?我们从下面代码可以看出findRequiredViewAsType会在source中获取对应的控件,后判断是否获取到,如果没有获取到会抛出如下异常:

throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");

而最终的类型转换是通过cls.cast 方法来完成的。

public static <T> T findOptionalViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = source.findViewById(id);
return castView(view, id, who, cls);
}

public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}

public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");
}

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
try {
return cls.cast(view);
} catch (ClassCastException e) {
String name = getResourceEntryName(view, id);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}

到此为止整个Butterknife源码分析完毕。老规矩上图:

我们之前在介绍Java注解的时候提到了注解有两种类型,一种是运行时注解,一种是编译时注解,运行时注解是在运行的时候通过反射解析注解,针对对应的注解采取对应的动作,编译时注解是在编译时通过收集附加在代码上的注解来标记一些内容,然后在编译的时候通过识别这些注解来动态生成一些代码。很显然运行时注解会因为java反射而引起较为严重的性能问题,所以尽量避免使用,而编译时注解的魅力在于:编译时按照预先使用注解规定的方案生成代码来避免编写重复代码,提高开发效率,且不影响性能。ß目前使用编译时注解的较为著名的开源框架有Butterknife,Dagger2等。

Annotation Processor Tool是用于编译期扫描和处理注解的工具,目前被集成在javac中。在编译的时候,javac通常会找到你定义的注解处理器,并执行注解处理。

本章知识点

  • JDK内置系统注解
  • 元注释以及自定义注释
  • 注解解析器实现
  • Android中的专用注解

JDK内置系统注解

Java提供了三种内建注解。

@Override
这个估计开发中是最常见的一个注解了,它用于表示所注解的方法是一个用于覆盖父类的一个方法,一旦存在任何的拼写错误导致方法签名对不上被覆盖的方法,编译器就会发出错误提示。
@Deprecated
这个一般用于标记某个方法不在使用了,通过这个注释,开发者可以了解到哪些接口是处于遗弃状态的。
@SuppressWarnings
这个注释用于忽视某个警告信息。

元注释以及自定义注释

Java中提供了四种元注释用于定义自定义注释,如下所示:

@Target 表示该注解可以用在什么地方,由ElementType枚举定义 ,当注解未指定Target值时,该注解可以使用任何元素之上

PACKAGE:包声明
TYPE:类、接口 注解类型,enum声明
ANNOTATION_TYPE:注解声明(应用于另一个注解上)
FIELD:域声明(包括enum实例)
CONSTRUCTOR:构造器的声明
METHOD:方法声明
LOCAL_VARIABLE:局部变量声明
PARAMETER:参数声明
TYPE_PARAMETER:类型参数声明(1.8新加入)
TYPE_USE:类型使用声明(1.8新加入)

@Retention 表示需要在什么级别保存该注解信息,由RetentionPolicy枚举定义,当注解未定义Retention值时,默认值是CLASS

SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机(JVM)中)
RUNTIME:VM将在运行期也保留注解信息,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息)

@Documented 表示注解会被包含在javaApi文档中
@Inherited 允许子类继承父类的注解,表示注解类型能被自动继承。 如果一个类使用了 @Inherited 类型的注解,则此类的子类也将含有该注解。

自定义注解语法:

import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface AnatationTest {
}

需要注意的是注解也将会编译成class文件,我们通过javac将上述的注解编译为字节码,然后使用javap对其进行反编译,将会得到如下的代码:

public interface AnatationTest extends java.lang.annotation.Annotation {
}

也就是说使用@interface 关键字声明一个注解,它会自动继承 java.lang.annotation.Annotaion 接口。

自定义注解的时候需要注意如下几点:

  • 参数成员访问修饰符只能使用 public 或者 default 这个和枚举是一样的
  • 注解方法不能有参数。
  • @interface 里面的每一个方法表示声明了一个可配置的参数,方法名即位参数名。返回值类型就是参数的类型,下面是能够允许的类型:

    所有基本类型(int,float,boolean,byte,double,char,long,short)
    String
    Class
    enum
    Annotation
    以及上述类型所组成的 数组
  • 如果需要声明默认值可以使用default 关键字。

  • 注解元素必须有确定的值,要么在定义注解元素时默认值指定,要么使用此注解时指定。非基本类型注解元素的值不可为 null

注解解析器实现

注释的解析有两种方式:

  • 运行时解析
    运行时注解指的是@Retention的值为RUNTIME的注解。java.lang.reflect包中有一个AnnotatedElement接口,这个接口定义了用于获取注解信息的几个方法:
    getAnnotation(Class annotationClass) 当存在该元素的指定类型注解,则返回相应注释,否则返回null
    getAnnotations() 返回此元素上存在的所有注解
    getDeclaredAnnotations() 返回直接存在于此元素上的所有注解。
    isAnnotationPresent(Class<? extends Annotation> annotationClass) 当存在该元素的指定类型注解,则返回true,否则返回false
    使用这些接口就可以在运行时解析对应的注释。

下面是运行时解析的例子:

@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PackageAnnotation {
String desc() default "";
}


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeAnnotation {
String desc() default "";
}


@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConstructAnnotaion {
String desc() default "";

}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnnotation {
String desc() default "";
}


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnnotation {
String desc() default "";
}

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamAnnotation {
String desc() default "";
}
@TypeAnnotation(desc = "This is TypeAnnotation")
public class TestClass {
@FieldAnnotation(desc = "This is FieldAnnotation")
public String mTestField = "mTestField";

@ConstructAnnotaion(desc = "This is ConstructAnnotaion")
public TestClass() {
}

@MethodAnnotation(desc = "This is MethodAnnotation")
public void method(@ParamAnnotation(desc = "This is ParamAnnotation")String param) {
String localvalue = "";
System.out.println("method!!!!!"+param);
}
}

package-info.java

@PackageAnnotation(desc = "This is a package annotation!")
package com.javabase.annotationbase.testclass;

import com.javabase.annotationbase.annotations.PackageAnnotation;

public class RuntimeAnnotationProcessor {

public void testPackageAnnotation(Class<?> clzz) {
Package packageName = clzz.getPackage();
if(packageName.isAnnotationPresent(PackageAnnotation.class)) {
PackageAnnotation packageAnnotation = packageName.getAnnotation(PackageAnnotation.class);
System.out.println(packageAnnotation.desc());
}
}

public void testClassAnnotation(Class<?> clz) {
if (clz.isAnnotationPresent(TypeAnnotation.class)) {
TypeAnnotation typeAnnotation = clz.getAnnotation(TypeAnnotation.class);
System.out.println(typeAnnotation.desc());
}
}

public void testConstructureAnnotation(Class<?> clz) {
Constructor[] constructors = clz.getConstructors();
for(Constructor constructor : constructors) {
if(constructor.isAnnotationPresent(ConstructAnnotaion.class)) {
ConstructAnnotaion annotation = (ConstructAnnotaion) constructor.getAnnotation(ConstructAnnotaion.class);
System.out.println(annotation.desc());
}
}
}

public void testFieldAnnotation(Class<?> clz) {
Field[] fields = clz.getFields();
if(fields != null && fields.length > 0) {
for (Field field: fields) {
if(field.isAnnotationPresent(FieldAnnotation.class)) {
FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println(fieldAnnotation.desc());
}
}
}
}

public void testMethodAnnotation(Class<?> clz) {
Method[] methods = clz.getMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
if (method.isAnnotationPresent(MethodAnnotation.class)) {
MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
try {
method.invoke(clz.newInstance(), "");
} catch (IllegalAccessException | InvocationTargetException
| InstantiationException e) {
e.printStackTrace();
}
System.out.println(methodAnnotation.desc());
}
}
}
}

public void testParamAnnotation(Class<?> clz) {
Method[] methods = clz.getMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
if (method.isAnnotationPresent(MethodAnnotation.class)) {
Parameter[] parameters = method.getParameters();
if (parameters != null && parameters.length > 0) {
for (Parameter parameter : parameters) {
if(parameter.isAnnotationPresent(ParamAnnotation.class)) {
ParamAnnotation paramAnnotation = parameter.getAnnotation(ParamAnnotation.class);
System.out.println(paramAnnotation.desc());
}
}
}
}
}
}
}

}
  • 编译时解析
    编译时注解指的是@Retention的值为CLASS的注解。对于这类注解的解析,我们需要自定义一个派生自 AbstractProcessor的“注解处理类”并重写process 函数。

  • APT(Annotation Processing Tool)是一种处理注释的工具
    Annotation处理器在处理注释时可以根据源文件中的Annotation生成额外的源文件和其它的文件,APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。

  • 整个注解处理器的可以由如下几个部分构成:
    注解处理器(AbstractProcess)+代码处理(JavaPoet)+处理器注册(AutoService)+apt
  • 整个过程分成如下几个部分:
    1.定义注解(如@automain)
    2.定义注解处理器
    3.在处理器里面完成处理方式,通常是生成java代码,这个就需要用到代码处理。
    4.注册处理器,这里就需要用到处理器注册。
    5.利用APT进一步处理。

建立一个APT 项目,并添加apt依赖:

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

添加apt 插件

//添加APT plugin
apply plugin: 'com.neenbedankt.android-apt'

新建一个model 命名为compiler 用于存放注解处理器:
并添加autoservice 和javapoet依赖:

apply plugin: 'java'

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.squareup:javapoet:1.8.0'
compile 'com.google.auto.service:auto-service:1.0-rc2'
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

创建AnnotationProcessor

package com.example;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.Collections;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;

@AutoService(Processor.class)
public class AnnotationProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(TestAnnotation.class.getCanonicalName());
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}

这时候编译后会在/app/build/generated/source/apt/debug/目录下生成HelloWorld.java文件

参考材料
http://www.race604.com/annotation-processing/
http://www.jianshu.com/p/1942ad208927
http://blog.csdn.net/lmj623565791/article/details/43452969
https://github.com/taoweiji/DemoAPT?utm_source=tuicool&utm_medium=referral
http://www.cnblogs.com/lbangel/p/3523741.html
http://www.jianshu.com/p/94979c056b20
http://alighters.com/blog/2016/05/10/apt-code-generate/