原文链接:Glide核心设计二:缓存管理

引言

Glide作为一个优秀的图片加载框架,缓存管理是必不可少的一部分,这篇文章主要通过各个角度、从整体设计到代码实现,深入的分析Glide的缓存管理模块,力求在同类分析Glide缓存的分析文章中脱颖而出。关于Glide的生命周期绑定,可查看Glide系列文章Glide核心设计一:皮皮虾,我们走。

前提

  1. 本文分析Glide缓存管理,将以使用Glide加载网络图片为例子,如加载本地图片、Gif资源等使用不是本文的重点。因不管是何种使用方式,缓存模块都是一样的,只抓住网络加载图片这条主线,逻辑会更清晰。
  2. 本文将先给出Glide缓存管理整体设计的结论,然后再分析源码。

整体设计

缓存类型

Glide的缓存类型分为两大类,一类是Resource缓存,一类是Bitmap缓存。

Resource缓存

为什么需要缓存图片Resource,很好理解,因为图片从网络加载,将图片缓存到本地,当需要再次使用时,直接从缓存中取出而无需再次请求网络。

三层缓存

Glide在缓存Resource使用三层缓存,包括:

  1. 一级缓存:缓存被回收的资源,使用LRU算法(Least Frequently Used,最近最少使用算法)。当需要再次使用到被回收的资源,直接从内存返回。
  2. 二级缓存:使用弱引用缓存正在使用的资源。当系统执行gc操作时,会回收没有强引用的资源。使用弱引用缓存资源,既可以缓存正在使用的强引用资源,也不阻碍系统需要回收无引用资源。
  3. 三级缓存:磁盘缓存。网络图片下载成功后将以文件的形式缓存到磁盘中。

Bitmap缓存

Bitmap所占内存大小

Bitmap所占的内存大小由三部分组成:图片的宽度分辨率、高度分辨率和Bitmap质量参数。公式是:Bitmap内存大小 = (宽pix长pix)质量参数所占的位数。单位是字节B。

Bitmap压缩质量参数

质量参数决定每一个像素点用多少位(bit)来显示:

  1. ALPHA_8就是Alpha由8位组成(1B)
  2. ARGB_4444就是由4个4位组成即16位(2B)
  3. ARGB_8888就是由4个8位组成即32位(4B)
  4. RGB_565就是R为5位,G为6位,B为5位共16位(2B)

Glide默认使用RGB_565,比系统默认使用的ARGB_8888节省一半的资源,但RGB_565无法显示透明度。
举个例子:在手机上显示100pix*200pix的图片,解压前15KB,是使用Glide加载(默认RGB_565)Bitmap所占用的内存是:(100x200)x2B = 40000B≈40Kb,比以文件的形成存储的增加不少,因为png、jpg等格式的图片经过压缩。正因为Bitmap比较消耗内存,例如使用Recyclerview等滑动控件显示大量图片时,将大量的创建和回收Bitmap,导致内存波动影响性能。

Bitmap缓存算法

在Glide中,使用BitmapPool来缓存Bitmap,使用的也是LRU算法。当需要使用Bitmap时,从Bitmap的池子中取出合适的Bitmap,若取不到合适的,则再新创建。当Bitmap使用完后,不直接调用Bitmap.recycler()回收,而是放入Bitmap的池子。

缓存的Key类型

Glide的缓存使用的形式缓存,Resource和Bitmap都是作为Value的部分,将value存储时,必须要有一个Key标识缓存的内容,根据该Key可查找、移除对应的缓存。

  1. 从对比中可看出,Resource三层缓存所使用的key的构造形式是一样的,包括图片id(图片的Url地址),宽高等参数来标识。对于其他参数,举一个例子理解:图片资源从网络加载后,经过解码(decode)、缓存到磁盘、从磁盘中取出、变换资源(加圆角等,transformation)、磁盘缓存变换后的图片资源、转码(transcode)显示。
  2. Bitmap的缓存Key的构造相对简单得多,由长、宽的分辨率以及图片压缩参数即可唯一标示一个回收的Bitmap。当需要使用的bitmap时,在BitmapPool中查找对应的长、宽和config都一样的Bitmap并返回,而无需重新创建。

Resource缓存流程

Resource包括三层缓存,通过流程图看它们之间的关系:

因为内存缓存优于磁盘缓存,所以当需要使用资源时,先从内存缓存中查找(一级缓存和二级缓存都是内存缓存,其功能不一样,一级缓存用于在内存中缓存不是正在使用的资源,二级缓存是保存正在使用的资源),再从磁盘缓存中查找。若都找不到,则从网络加载。

滑动控件多图的性能优化

不论是Resource还是Bitmap缓存,若显示的仅是部分照片,并且不存在频繁使用的场景,则使用Glide没有太大的优势。设计缓存的目的就是为了在重复显示时,更快、更省的显示图片资源。Glide有针对ListView、Recyclerview等控件加载多图时进行优化。此处讨论最常见的场景:Recyclerview显示多图,简略图如下。

如上图所示,当图5划入界面时,会复用图一的Item,设置新的图片之前,会先清空原有图片的资源,清空时会把Resource资源放入一级缓存待将来复用,同时会将回收的Bitmap放入BitmapPool中;当图5向下隐藏,图一出现时,图5的资源会放到一级缓存中,图一的资源则从一级缓存中取出,无须重新网络请求,同时所需要的Bitmap也无须重新创建,直接复用。

LRU算法

BitmapPool的LRU算法流程图如下:

类图

在进行代码分析前,先给出跟Glide缓存管理相关的类图(省略类的大部分变量和方法)。

Glide缓存管理类图大图地址

代码实现

根据以上的Glide缓存管理的结论及类图,可自主跟源码,跳过以下内容。

Glide.with(Context).load(String).into(ImageView)

Glide.with(Context)

返回RequestManager,主要实现和Fragment、Activity生命周期的绑定,详情请看Glide核心设计一:皮皮虾,我们走。

.load(String)

RequestManager的load(String)方法返回DrawableTypeRequest,根据图片地址返回一个用于创建图片请求的Request的Builder,代码如下:

public DrawableTypeRequest<String> load(String string) {return (DrawableTypeRequest<String>) fromString().load(string); //调用fromString()和load()方法}

fromString()方法调用loadGeneric()方法,代码如下:

public DrawableTypeRequest<String> fromString() {return loadGeneric(String.class); }private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =Glide.buildFileDescriptorModelLoader(modelClass, context);if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"+ " which there is a registered ModelLoader, if you are using a custom model, you must first call"+ " Glide#register with a ModelLoaderFactory for your custom model class");}return optionsApplier.apply(  //传递的参数中创建了一个DrawableTypeRequest并返回该对象new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,glide, requestTracker, lifecycle, optionsApplier));  }

DrawableTypeRequest的load()方法如下:

@Overridepublic DrawableRequestBuilder<ModelType> load(ModelType model) {super.load(model);return this;}

DrawableTypeRequest父类是DrawableRequestBuilder,父类的父类是GenericRequestBuilder,调用super.load()方法如下:

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {this.model = model;isModelSet = true;return this;}

以上代码可知,缓存管理的主要实现代码并不在.load(Sting)代码,接下来继续分析.into(ImageView)代码。

.into(ImageView)

GenericRequestBuilder的into(ImageView)代码如下:

public Target<TranscodeType> into(ImageView view) {Util.assertMainThread();if (view == null) { throw new IllegalArgumentException("You must pass in a non null View");}if (!isTransformationSet && view.getScaleType() != null) {switch (view.getScaleType()) {  //根据图片的scaleType做相应处理case CENTER_CROP:applyCenterCrop();break;case FIT_CENTER:case FIT_START:case FIT_END:applyFitCenter();break;//$CASES-OMITTED$default:// Do nothing.}}//调用buildImageViewTarget()方法创建了一个Target类型的对象return into(glide.buildImageViewTarget(view, transcodeClass));  }

以上代码主要有两个功能:

  1. 根据ScaleType进行图片的变换
  2. 将ImageView转换成一个Target

继续查看into(Target)的代码:

public <Y extends Target<TranscodeType>> Y into(Y target) {Util.assertMainThread();if (target == null) {throw new IllegalArgumentException("You must pass in a non null Target");}if (!isModelSet) {throw new IllegalArgumentException("You must first set a model (try #load())");}Request previous = target.getRequest();  //获取请求体Requestif (previous != null) { //若ImageView是复用过的,则previous不为空previous.clear(); //调用clear()方法清空ImageView上的图片资源,此方法会将回收的Resource放入内存缓存中,并不在内存中清空该资源。requestTracker.removeRequest(previous); //移除老的请求previous.recycle(); //回收Request使用}Request request = buildRequest(target); //获取新的Requesttarget.setRequest(request); //将新的request设置到target中lifecycle.addListener(target); //添加生命周期的监听requestTracker.runRequest(request); //启动Requestreturn target;}

以上代码,主要将图片加载的Request绑定到Target中,若原有Target具有旧的Request,得先处理旧的Request,再绑定上新的Request。target.setRequest()和target.getRequest()最终会调用ViewTarget的setRequest()方法和getRequest()方法,代码如下:

public void setRequest(Request request) {setTag(request);}private void setTag(Object tag) {if (tagId == null) {isTagUsedAtLeastOnce = true;view.setTag(tag);//调用view的setTag方法,将Request和view做绑定} else {view.setTag(tagId, tag);//调用view的setTag方法,将Request和view做绑定}}public Request getRequest() {Object tag = getTag(); //获取view 的tagRequest request = null;if (tag != null) {if (tag instanceof Request) {  //若该tag是Request的一个实例request = (Request) tag; } else {  //用户不能给view设置tag,因为该view的tag要用于保存Glide的Request对象,否则抛出异常throw new IllegalArgumentException("You must not call setTag() on a view Glide is targeting");}}return request;}

以上代码可知,Request通过setTag的方式和View进行绑定,当View是复用时,则Request不为空,通过Request可对原来的资源进行缓存与回收。此处通过View的setTag()方法绑定Request,可谓妙用。

以上代码创建了一个Request,requestTracker.runRequest(request);启动了Request,调用Request的begin()方法,该Request实例是GenericRequest,begin()代码如下:

@Overridepublic void begin() {startTime = LogTime.getLogTime();if (model == null) {onException(null);return;}status = Status.WAITING_FOR_SIZE; //设置等待图片size的宽高状态if (Util.isValidDimensions(overrideWidth, overrideHeight)) { //必须要确定图片的宽高,确定了则调用onSizeReadyonSizeReady(overrideWidth, overrideHeight);} else { //设置回调,监听界面的绘制,当检测到宽高有效时,回调onSizeReady方法target.getSize(this);}if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {target.onLoadStarted(getPlaceholderDrawable());}if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished run method in " + LogTime.getElapsedMillis(startTime));}}

加载图片前,必须要确定图片的宽高,因为需要根据确定的宽高来获取资源。onSizeReady代码如下:

@Overridepublic void onSizeReady(int width, int height) {if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));}if (status != Status.WAITING_FOR_SIZE) {//宽高没准备好,返回return;}status = Status.RUNNING;  //状态改为加载运行中width = Math.round(sizeMultiplier * width);height = Math.round(sizeMultiplier * height);ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);if (dataFetcher == null) {onException(new Exception("Failed to load model: \'" + model + "\'"));return;}ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));}loadedFromMemoryCache = true;//真正的加载任务交给engineloadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,priority, isMemoryCacheable, diskCacheStrategy, this);loadedFromMemoryCache = resource != null;if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));}}

以上代码可知,在确定宽高后,将图片加载的任务交给类型为Engine的对象engine,并调用其load方法,代码如下:

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {Util.assertMainThread();long startTime = LogTime.getLogTime();final String id = fetcher.getId(); //该id为图片的网络地址//缓存key的组成部分,使用工厂模式EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());//使用一级缓存,从回收的内存缓存中查找EngineResourceEngineResource<?> cached = loadFromCache(key, isMemoryCacheable);if (cached != null) { //命中则直接返回cb.onResourceReady(cached);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Loaded resource from cache", startTime, key);}return null;}//从二级缓存中查找EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);if (active != null) {//命中则直接返回cb.onResourceReady(active);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Loaded resource from active resources", startTime, key);}return null;}EngineJob current = jobs.get(key);if (current != null) {//该任务已经在执行,只需要添加回调接口,在任务执行完后调用接口告知即可current.addCallback(cb);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Added to existing load", startTime, key);}return new LoadStatus(cb, current);}//一级缓存和二级缓存都不命中的情况下,启动新的任务EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);//创建EngineJobDecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,transcoder, diskCacheProvider, diskCacheStrategy, priority); //创建DecodeJobEngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority); jobs.put(key, engineJob);engineJob.addCallback(cb);engineJob.start(runnable); //启动EngineRunnable runnable,使用线程池FifoPriorityThreadPoolExecutor管理if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Started new load", startTime, key);}return new LoadStatus(cb, engineJob);}

分析至此,我们终于看到实现一级缓存和二级缓存的相关代码,可以猜测三级缓存的实现跟EngineRunnable有关。engineJob.start(runnable)会启动EngineRunnable的start()方法。代码如下:

@Overridepublic void run() {if (isCancelled) {return;}Exception exception = null;Resource<?> resource = null;try {resource = decode();  //调用decode()方法} catch (Exception e) {if (Log.isLoggable(TAG, Log.VERBOSE)) {Log.v(TAG, "Exception decoding", e);}exception = e;}if (isCancelled) { //请求被取消if (resource != null) {resource.recycle();}return;}if (resource == null) { //加载失败onLoadFailed(exception);} else { //加载成功onLoadComplete(resource);}}

查看decode()方法如下:

private Resource<?> decode() throws Exception {if (isDecodingFromCache()) {return decodeFromCache();  //从磁盘缓存中获取} else {return decodeFromSource(); //从网络中获取资源}}

至此,我们看到磁盘缓存和网络请求获取图片资源的代码。查看onLoadFailed()的代码逻辑可知,默认先从磁盘获取,失败则从网络获取。

BitmapPool缓存逻辑

以上就是Resource三层缓存的代码,接下来看BitmapPool的缓存实现代码。
在decodeFromSource()的代码中,会返回一个类型为BitmapResource的对象。在RecyclerView的例子中,当ImageView被复用时,会在Tag中取出Request,调用request.clear()代码。该方法最终会调用BitmapResource的recycler()方法,代码如下:

public void recycle() {if (!bitmapPool.put(bitmap)) {bitmap.recycle();}}

该代码调用bitmapPool.put(bitmap),bitmapPool的实例是LruBitmapPool代码如下:

public synchronized boolean put(Bitmap bitmap) {if (bitmap == null) {throw new NullPointerException("Bitmap must not be null");}if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize || !allowedConfigs.contains(bitmap.getConfig())) {if (Log.isLoggable(TAG, Log.VERBOSE)) {Log.v(TAG, "Reject bitmap from pool"+ ", bitmap: " + strategy.logBitmap(bitmap)+ ", is mutable: " + bitmap.isMutable()+ ", is allowed config: " + allowedConfigs.contains(bitmap.getConfig()));}return false;}final int size = strategy.getSize(bitmap);strategy.put(bitmap);//该strategy的实例是Lru算法tracker.add(bitmap); //log跟踪puts++; //缓存的bitmap数量标记加一currentSize += size;//缓存bitmap的总大小if (Log.isLoggable(TAG, Log.VERBOSE)) {Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));}dump(); //仅用于Logevict();  //判断是否超出指定的内存大小,若超出则移除return true;}

可以看出,正常情况下调用put方法返回true,证明缓存该Bitmap成功,缓存成功则不调用bitmap.recycler()方法。当需要使用Bitmap时,先从Bitmap中查找是否有符合条件的Bitmap。在RecyclerView中使用Glide的例子中,将大量复用宽高及Bitmap.Config都相等的Bitmap,极大的优化系统内存性能,减少频繁的创建回收Bitmap。

小结

Glide的缓存管理至此就分析完了,主要抓住Resource和Bitmap的缓存来讲解。在代码的阅读中还发现了工厂、装饰者等设计模式。Glide的解耦给开发者提供很大的便利性,可根据自身需求设置缓存参数,例如默认Bitmap.Config、BitmapPool缓存大小等。最后,针对Glide的缓存设计,提出几点小建议:

  1. Glide虽然默认使用的Bitmap.Config是RGB_565,但在进行transform(例如圆角显示图片)时往往默认是ARGB_8888,因为RGB_565没有透明色,此时可重写圆角变换的代码,继续使用RGB_565,同时给canvas设置背景色。
  2. BitmapPool缓存的Bitmap大小跟Bitmap的分辨率也有关系,在加载图片的过程中,可调用.override(width, height)指定图片的宽高,再调整ImageView控件的大小适应布局。
  3. Resource的一级缓存和Bitmap都是内存缓存,虽然极大的提升了复用,但也会导致部分内存在系统执行GC时无法释放。若内存达到手机性能瓶颈,应在合适的时机调用Glide.get(this).clearMemory()释放内存。

Glide核心设计一:皮皮虾,我们走

发表于 2017-02-20   |   暂无评论   |   阅读次数 130

原文链接:Glide核心设计一:皮皮虾,我们走

引言

皮皮虾,又名虾姑,是淡水中的强者。其头部的两个锤节,可以轻易破坏贝类的外壳,身体上的步足可以保证快速移动。这些优秀的品质,使它被表情包盯上。

Glide,作为Android最优秀的图片加载框架之一,能和Activity和Fragment的生命周期绑定,是区别于其它网络框架的核心特征,也是本文分析的重点。

我们将此特征和皮皮虾表情包做一个类比:

图片网络请求紧跟Activity、Fragment的生命周期,当页面不可交互时自动停止加载,当回到可交互状态时,继续加载。就像表情包(Activity、Fragment)控制皮皮虾(图片请求)一样。

框架设计

简单使用

Glide.with(Context).load(String).into(ImageView)可实现从网络中获取图片并展示到ImageView当中。其中和页面作生命周期绑定的主要入口是Glide.with(Context)。按照一般的分析逻辑应该先分析源码,才得出结论,但因一入源码深似海,不利于整体把握,所以先给出结论。Glide.with(Context)返回的是一个RequestManager,我们来看RequestManager的类的说明注释。

A class for managing and starting requests for Glide. Can use activity, fragment and connectivity lifecycle events to intelligently stop, start, and restart requests. Retrieve either by instantiating a new object, or to take advantage built in Activity and Fragment lifecycle handling, use the static Glide.load methods with your Fragment or Activity.

由此可知,该类就是用于将请求Activity或Framgent的生命周期做绑定。

类图

将和生命周期相关的类图如下(省略大部分类的变量和方法):

类的简单介绍

  1. RequestManagerRetriever:该类用于创建RequestManager或在Activity、Fragment中找出已创建的RequestManager,RequestManagerRetriever是一个单例。
  2. RequestManagerFragment:继承Fragment,不可见,仅用于保存RequestManager,还有一个SupportRequestManagerFragment继承v4包的Fragment,功能类似。
  3. LifecycleListener:用于监听Activity、Fragment的生命周期事件。
  4. Lifecycle:用于添加LifecycleListener。
  5. ActivityFragmentLifecycle:实现Livecycle接口,用于通知Activity、Fragment的生命周期事件。
  6. RequestTracker:该类用于跟踪、取消和重新启动执行中、已完成和失败的请求。
  7. ConnectivityMonitor: 监听网络事件的接口,当网络状态发生变化时,影响网络请求状态,继承LifecycleListener。
  8. DefaultConnectivityMonitor: ConnectivityMonitor的实现类,实现监听网络状态的逻辑。
  9. RequestManagerConnectivityListener: 实现ConnectivityListener接口,将网络事件传递给RequestTracker。

类的联系

以上对各类有一个简单的了解,接下来将重点讲清楚各类之间的联系。整个生命周期的绑定分为四部分。

  1. 调用Glide.with(Context),根据传入的Context类型创建RequestManager。Context可以为Activity、Fragment和Application。
  2. 在传入的参数Activity、或者Fragment中,添加一个不可见的Fragment,监听不可见Fragment的生命周期并将该事件传递给和Fragment一一绑定的RequestManager。
  3. RequestManager监听到生命事件后,管理图片请求做出响应。
  4. 监听当网络从无到有时,RequestManager要重新启动图片请求。

代码解读

根据以上内容可直接跟代码可跳过以下内容,印象更加深刻。

第一部分:Glide.with(Context)

public static RequestManager with(Context context) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(context);}
`

调用RequestManagerRetriever的get方法如下:

public RequestManager get(Context context) {if (context == null) {throw new IllegalArgumentException("You cannot start a load on a null Context");} else if (Util.isOnMainThread() && !(context instanceof Application)) {if (context instanceof FragmentActivity) {  //传入的是Fragmentreturn get((FragmentActivity) context);} else if (context instanceof Activity) {  //传入的是Acitivityreturn get((Activity) context);} else if (context instanceof ContextWrapper) { //传入的是Applicationreturn get(((ContextWrapper) context).getBaseContext());}}return getApplicationManager(context);}

以传入参数为Activity类型为例,代码如下:

public RequestManager get(Activity activity) {if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {return get(activity.getApplicationContext());} else {assertNotDestroyed(activity);android.app.FragmentManager fm = activity.getFragmentManager();   //获取FragmentManagerreturn fragmentGet(activity, fm); }}

主要调用fragmentGet方法,代码如下:

RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {RequestManagerFragment current = getRequestManagerFragment(fm); RequestManager requestManager = current.getRequestManager();  //根据RequestManagerFragment获取RequestManager,一个RequestManagerFragment包含一个RequestManagerif (requestManager == null) {  //若RequestManager为空,则新建一个并且设置到RequestManagerFragment中requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());current.setRequestManager(requestManager);}return requestManager;}

getRequestManagerFragment(fm)函数主要是根据FragmentManager获取Fragment,代码如下:

RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) {RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);  //通过Tag查找if (current == null) { //若为空,从缓存pendingRequestManagerFragments中查找current = pendingRequestManagerFragments.get(fm);if (current == null) { //缓存中也不存在,则新建一个RequestManagerFragment,并且添加到页面中。current = new RequestManagerFragment();pendingRequestManagerFragments.put(fm, current);fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();}}return current;}

以上就是根据传入的Context类型创建RequestManager的代码部分。

第二部分:监听不可见Fragment的生命周期并传递给RequestManager

添加不可见Fragment的目的,就是因为该Fragment和父类的Activity具有同样的生命周期,无须改动原有Activity的代码,即可实现生命周期的监听。
RequestManagerFragment生命周期相关的代码如下:

@Overridepublic void onStart() {super.onStart();lifecycle.onStart(); //执行lifecycle的onStart方法}@Overridepublic void onStop() {super.onStop();lifecycle.onStop();//执行lifecycle的onStop方法}@Overridepublic void onDestroy() {super.onDestroy();lifecycle.onDestroy();//执行lifecycle的onDestroy方法}

可以看出,Fragment的声明周期的监听都转移到类型是ActivityFragmentLifecycle的变量lifecycle中的对应方法执行。查看ActivityFragmentLifecycle的代码:

void onStart() {isStarted = true;//循环set集合lifecycleListeners中所有LifecycleListener,执行对应的onStartfor (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {lifecycleListener.onStart();}}//省略onStop()和onDestroy()方法,和onStart()方法类似。

set集合的LifecycleListener是如何添加进去的,看ActivityFragmentLifecycle中的代码:

@Overridepublic void addListener(LifecycleListener listener) {lifecycleListeners.add(listener);if (isDestroyed) {//如果当前页面已经被destroy,则调用对应的onDestroylistener.onDestroy();} else if (isStarted) {//如果当前页面已经开启,则调用对应的onStartlistener.onStart();} else {  //其他情况调用onStop方法listener.onStop();}}

addListener(LifecycleListener listener)方法是接口Lifecycle的方法。RequestManagerFragment提供一个公有方法:

ActivityFragmentLifecycle getLifecycle() {return lifecycle;}

回看第一部分创建RequestManager时:

requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());

RequestManagerFragment中的Lifecycle作为RequestManager的构造函数的参数传递给RequestManager。RequestManager构造函数如下:

RequestManager(Context context, final Lifecycle lifecycle, RequestManagerTreeNode treeNode,RequestTracker requestTracker, ConnectivityMonitorFactory factory) {this.context = context.getApplicationContext();this.lifecycle = lifecycle;this.treeNode = treeNode;this.requestTracker = requestTracker;this.glide = Glide.get(context);this.optionsApplier = new OptionsApplier();//监听网络变化的类ConnectivityMonitor connectivityMonitor = factory.build(context,new RequestManagerConnectivityListener(requestTracker));// If we're the application level request manager, we may be created on a background thread. In that case we// cannot risk synchronously pausing or resuming requests, so we hack around the issue by delaying adding// ourselves as a lifecycle listener by posting to the main thread. This should be entirely safe.if (Util.isOnBackgroundThread()) {new Handler(Looper.getMainLooper()).post(new Runnable() {@Overridepublic void run() {//在主线程中将当前类实现的LifecycleListener添加到lifecycle中。lifecycle.addListener(RequestManager.this);}});} else {//在主线程中将当前类实现的LifecycleListener添加到lifecycle中。lifecycle.addListener(this);}lifecycle.addListener(connectivityMonitor);}

由此可见,lifecycle添加的就是RequestManager实现的LifecycleListener接口。

第三部分:RequestManager实现LifecycleListener

接着查看RequestManager实现LifecycleListener的方法:

@Overridepublic void onStart() {// onStart might not be called because this object may be created after the fragment/activity's onStart method.resumeRequests();}@Overridepublic void onStop() {pauseRequests();}@Overridepublic void onDestroy() {requestTracker.clearRequests();}

继续进入resumeRequests()、pauseRequests()和requestTracker.clearRequests()方法可知,都是调用RequestTracker相应的方法,RequestTracker类包含一个集合的Request,该集合包含一个Activity获取一个
Fragment的所以图片请求,将根据RequestManagerFragment的生命周期,统一管理图片请求。

第四部分:监听网络状态并作出相应

RequestManager的构造函数有如下方法:

 //省略部分代码...//监听网络变化的类ConnectivityMonitor connectivityMonitor = factory.build(context,new RequestManagerConnectivityListener(requestTracker));//省略部分代码...
lifecycle.addListener(connectivityMonitor);         //省略部分代码...

以上代码可看出,ConnectivityMonitor也实现了LifecycleListener。继续跟踪代码发现,factory的实例是ConnectivityMonitorFactory,在该工厂中会检查网络权限,同时创建ConnectivityMonitor的实例DefaultConnectivityMonitor。LifecycleListener接口的实现如下:

@Overridepublic void onStart() {register(); //register()方法为注册广播监听网络变化}@Overridepublic void onStop() {unregister(); //解除监听广播}@Overridepublic void onDestroy() {// Do nothing.}

广播接收器代码如下:

private final BroadcastReceiver connectivityReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {boolean wasConnected = isConnected;isConnected = isConnected(context);if (wasConnected != isConnected) {  //当网络状态发生变化时,才调用listener.onConnectivityChanged()方法listener.onConnectivityChanged(isConnected);}}};

ConnectivityListener 的实例的类型是RequestManager的内部类,代码如下:

private static class RequestManagerConnectivityListener implements ConnectivityMonitor.ConnectivityListener {private final RequestTracker requestTracker;public RequestManagerConnectivityListener(RequestTracker requestTracker) {this.requestTracker = requestTracker;}@Overridepublic void onConnectivityChanged(boolean isConnected) {if (isConnected) { //如果当前状态是链接状态requestTracker.restartRequests(); //重新开启图片请求}}}

小结

以上就是Glide实现图片加载和Activity、Fragment生命周期绑定的全部分析。会发现使用了观察者模式和工厂模式进行解耦,其中创建一个不可见的Fragment设置到需要被监控生命周期的Activity、Fragment中,最为精彩。接下来将分析Glide核心设计二:图片缓存。敬请期待。

送一个内存泄漏给2016的记几

发表于 2016-12-23   |   2条评论   |   阅读次数 139

原文链接:送一个内存泄漏给2016的记几

背景

年底了,看公司项目的友盟的bug列表,发现java.lang.OutOfMemoryError的问题不少,也该是时候还了。

问题描述

在解决内存泄漏的过程中,遇到一个静态变量导致的内存泄漏,关键是这代码还是自己敲的,明明已经使用弱引用,为什么还是内存泄漏了。看代码:

public class RtHttp {public static RtHttp instance = new RtHttp();public Context context;public static void with(Context context){WeakReference<Context> wrContext = new WeakReference<Context>(context);instance.context = wrContext.get();}
}

RtHttp为封装客户端网络框架请求的入口,假设两个条件:

  1. 其中传入的Context对象只能是Activity,不能使用context.getApplicationContext()(因为此RtHttp还封装了显示网络加载对话框的代码)。
  2. 不能在Activity onDestroy()方法中调用RtHttp.instance.context = null;

已经使用了弱引用,为什么还是内存泄漏呢?
记几想不通,还到谷歌搜索使用弱引用依然内存泄漏WeakReference useless,无果。


如果你已经看出问题了,请直接到评论吐槽。
如果你没看出问题,请把引用的文章看一遍。

总结

一个内存泄漏的问题,反映了Java基础不牢固,在学习知识和解决问题上也不够深入。一直想写一篇2016的总结,希望以此内存泄漏的问题为界,实现在2017的一个技术提升。

引用

Android 内存泄漏总结
Java内存、Android 内存泄漏
Java gc(垃圾回收机制)小结,以及Android优化建议
JVM GC垃圾回收机制

模拟服务器返回数据

发表于 2016-12-15   |   暂无评论   |   阅读次数 259

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
原文链接:模拟服务器返回数据

背景

模拟服务器返回的数据,在以下场景具有实际意义:

和服务器开发协商好开发接口,但服务器API尚未部署,想接口定义好就进行开发;
服务器已部署,返回的数据不能测试到各种情况,希望返回期待数据测试边界情况;

如果客户端开发人员能不走服务器,通过模拟数据返回,能提升开发效率和程序质量。

实现思路

本文主要讲解两种实现方式:

  1. 使用网络分析工具拦截客户端请求,并返回伪造数据。
    优点:无需改变客户端代码;不依赖客户端平台,android和ios都通用;
    缺点:依赖网络分析工具;调试相对不灵活;

  2. 使用客户端网络框架拦截请求并返回。
    优点:返回数据由客户端代码决定,灵活易于调试;
    缺点:需要改变客户端代码;需要根据客户端网络框架进行响应处理,不同的网络框架处理不一样;

对于方案一,主要使用网络分析工具Charles进行拦截并返回,对于方案二,主要讲解使用OkHttp作为网络框架,利用拦截器机制实现模拟返回。

使用Charles模拟数据

准备条件

  1. 客户端需要连接到和电脑同一个网络(手机连接电脑发出的wifi)
  2. 官网下载安装

配置

配置方法参考Charles:移动端设备网络抓包
完成配置后,可以在Charles中检测到手机的网络请求和响应。

转接服务器地址


转接服务器地址是指,当客户端请求地址B时,本应该向指定的服务器请求数据,但Charles可拦截此ip地址,使不向服务器地址请求,并且返回另外一台服务器模拟的数据。
首先,我们来生成模拟返回数据的api接口;
打开mocky网址,输入想伪造Body数据,点击Generate my HTTP Response按钮生成http的url地址。

如图,当点击http://www.mocky.io/v2/58592298240000ba087c5a92 时,返回json格式的数据。
有了模拟数据的api地址,接着设置需要模拟的api接口。经过配置后,Charles可检测手机的网络请求,选择需要模拟返回数据的网络请求接口,右键选择Map Remote…
Map From为需要拦截的接口,Map To为模拟的api接口,此处我们填入http://www.mocky.io/v2/58592298240000ba087c5a92,如下图:

选择标题栏Tool工具图标,取消选择Map Remote,再勾选Map Remote,让设置的ip地址生效。此时,当客户端请求原地址时,都会返回模拟ip地址的数据,效果图如下:

小结

以上,使用Mocky网络和Charles工具实现模拟数据返回,无需改变客户端原有代码,但是,当需要改变客户端返回的数据时,则需要重新生成http模拟地址,再次设置Charles Map to内容。

自定义OkHttp Interceptor模拟返回

以下内容假设用户掌握OkHttp的简单使用,重点讲解自定义OkHttpInterceptor模拟返回数据。

OkHttp拦截器


如图,OkHttp可在Request和Response中设置任意个数的Intercepor(图中用圆圈标识),对请求体和响应体进行处理。借助OkHttp Interceptor机制,创建一个MockIntercepor,模拟返回一个Response,虚线部分为模拟的Response。

代码实现

MockInterceptor代码如下:

public class MockInterceptor implements Interceptor{@Overridepublic Response intercept(Chain chain) throws IOException {Gson gson = new Gson();Response response = null;Response.Builder responseBuilder = new Response.Builder().code(200).message("").request(chain.request()).protocol(Protocol.HTTP_1_0).addHeader("content-type", "application/json");Request request = chain.request();if(request.url().equals("http://url_whitch_need_to_mock")) { //拦截指定地址String responseString = "{\n" +    //模拟数据返回body"\t\"code\": 200,\n" +"\t\"message\": \"success\",\n" +"\t\"data\": {}\n" +"}";responseBuilder.body(ResponseBody.create(MediaType.parse("application/json"), responseString.getBytes()));//将数据设置到body中response = responseBuilder.build(); //builder模式构建response}else{response = chain.proceed(request);}return response;}
}

在debug模式下,将此Interceptor添加到网络请求的OkHttp中,即可对指定的api地址进行拦截,并且返回特定的数据。

小结

使用OkHttp的拦截机制,可实现改变部分代码则可模拟返回数据,返回的数据可在代码中设置,可使用工厂模式将模拟数据的生成变动代码放到Factory中。依赖网络请求框架,若原项目使用OkHttp或Retrofit作为网络框架,可轻易实现模拟接口。

引用

Charles:移动端设备网络抓包
利用charles模拟Http请求和响应
Hack Retrofit (2) 之 Mock Server
OkHttp Interceptor

创建OkHttp自定义Log

发表于 2016-12-10   |   暂无评论   |   阅读次数 27

原文链接:创建OkHttp自定义Log

背景

本文重点讲解如何在使用OkHttp作为网络请求框架的前提下,如何自定义一个适合自己项目的Http Log,从而提升网络Api开发、调试效率。

Http协议

只有对Http协议有基本的了解,才能更好的调试网络接口。

一个故事

男女主一次偶然的机会,开始信件传情,但他们的信件并不是直接寄给对方,而是先寄到某个地点A,由地点A的主人转发。(好吧,这是《北京遇上西雅图之不二情书》的情节)。我们来看男主发信的过程:

  1. 先购买有效邮票;
  2. 填写信件的收件人、收件地址等信息;
  3. 把信件放到信封里寄出去。

显然,女主收到信后回复信件也是同样的流程。

类比

我们现在将Http协议的消息结构和故事主人公收发信的过程做一个类比:

下面是使用Fiddler工具抓取www.xitu.io的网络请求和响应图:

Http消息结构

由前面的分析可知,Http请求消息由三部分组成:

  1. 请求行由3部分组成:①请求的方法,POST还是GET等;②请求路径;③Http协议
  2. 请求头,每一行都是name:value的结构,包含各种来自请求客服端的信息
  3. 请求体,提交给服务器的信息,GET方法没有此项。

Http响应体跟请求体格式大致一样。

  1. 请求体有协议和响应码组成,200为响应成功
  2. 响应头,每一行都是name:value的结构,包含各种来自服务器的信息
  3. 响应体,返回客户端需要的数据

自定义LogInterceptor

OkHttp Interceptor

OkHttp的一大特点就是可以在发出请求体和收到响应体之间添加任意个数任意任意功能的拦截器,对请求体或者响应体进行操作。还是用《北京遇上西雅图之不二情书》的故事来说,那么地点A的主人就是充当拦截器的角色,在故事中他不仅转发信件,还阅读了信件的内容。

根据需求确定需要Log的信息

Log的信息和划分两类,一类是跟业务相关的信息,一类是与业务无关。

  1. 业务相关包括:
    请求地址url;
    请求头:token、sessionId;
    请求体:POST提交的内容;
    响应头:token/sessionId;
    响应体:服务器返回数据;

  2. 业务无关包括:
    网络状态码:200为正常反应;
    网络请求时间:从发出网络请求到响应所消耗的时间;
    网络协议:http1、http2等;
    网络Method:POST、GET等;
    不管是业务相关的数据还是业务无关的数据,都是来自于Http请求体和响应体的消息结构中。

Log效果图

以公司项目的测试服务器自动登录接口,log效果如下:

 POST
acid->1075
userId->-1
network code->200
url->http://mobileapi.app100688440.twsapp.com/app/open/open.do?ACID=1075&userId=-1&vendorId=7999&VERS=6.5.1&fromType=1110
time->84.473
request headers->sessionId:
request->{"data":{"pagenum":0,"uid":-1,"flag":"00000000"},"requeststamp":"20161212151841836466"}
body->{"code":200,"responsestamp":"20161212151841836466","data":{"uid":-1,"nickname":"游客258","uploadUrl":"http://mobileapi.app100688440.twsapp.com/uploadservlet","wxUrl":"http://wx.app100688440.twsapp.com","isEmcee":0,"canLive":0,"goodnum":0,"index":{},"revGift":{},"msgRemind":{},"freshmanMission":{},"account":{},"onlineLimit":"600","userSecretKey":"brjefjzw37ocw46"}}

log解释:
POST:此接口使用POST方法;
acid:标识此接口的id,每个接口有唯一的acid,根据acid可查询到此接口的功能,例如此接口acid = 1075为自动登录接口;
userId:用户id
network code:返回200证明服务器响应成功;
url:此接口请求的url地址;
time:为响应时间,如果某接口响应时间过长,排除网络环境的原因,就可以跟服务端商量是否可优化;
request header:此项目需要传sessionId;
request:此处打印Post方法的请求体,若后台返回参数错误,检查此行log即可;
body:后台数据返回,采用json格式;

代码实现


/*** 添加Log*/
public class LogInterceptor implements Interceptor {private static final String TAG = "LogInterceptor";private static final Charset UTF8 = Charset.forName("UTF-8"); //urf8编码@Overridepublic Response intercept(Chain chain) throws IOException {  //实现Interceptor接口方法Log.d(TAG,"before chain,request()");Request request = chain.request();  //获取requestString acid = request.url().queryParameter("ACID"); //在url中获取ACID的参数值;Response response;try {long t1 = System.nanoTime();response = chain.proceed(request); //OkHttp链式调用long t2 = System.nanoTime();double time = (t2 - t1) / 1e6d;   //用请求后的时间减去请求前的时间得到耗时String userId = request.url().queryParameter("userId");String type = "";if (request.method().equals("GET")) {    //判断Method类型type = "GET";} else if (request.method().equals("POST")) {type = "POST";} else if (request.method().equals("PUT")) {type = "PUT";} else if (request.method().equals("DELETE")) {type = "DELETE";}BufferedSource source = response.body().source();source.request(Long.MAX_VALUE); // Buffer the entire body.Buffer buffer = source.buffer();String logStr = "\n--------------------".concat(TextUtils.isEmpty(acid) ? "" : acid).concat("  begin--------------------\n").concat(type).concat("\nacid->").concat(TextUtils.isEmpty(acid) ? "" : acid).concat("\nuserId->").concat(TextUtils.isEmpty(userId) ? "" : userId).concat("\nnetwork code->").concat(response.code() + "").concat("\nurl->").concat(request.url() + "").concat("\ntime->").concat(time + "").concat("\nrequest headers->").concat(request.headers() + "").concat("request->").concat(bodyToString(request.body())).concat("\nbody->").concat(buffer.clone().readString(UTF8)); //响应体转StringLog.i(TAG, logStr);} catch (Exception e) {Log.d(TAG,e.getClass().toString()+", error:acid = "+acid);  //网络出错,log 出错的acidthrow e; //不拦截exception,由上层处理网络错误}return response;}/*** 请求体转String* @param request* @return*/private static String bodyToString(final RequestBody request) {try {final Buffer buffer = new Buffer();request.writeTo(buffer);return buffer.readUtf8();} catch (final IOException e) {return "did not work";}}}

开源项目okhttp-logging-interceptor

如果不想自己编写代码,也可以使用开源项目okhttp-logging-interceptor,支持:1.设置Log的等级;2.使用自定义Log。此开源项目也仅是一个Interceptor。但个人觉得log的样式并不适合调试使用。同时log的内容比较通用,若想突出对应项目的信息,建议还是自定义Http Log。

Okhttp+Retrofit+Rxjava

此Interceptor配合OkHttp使用,关于Okhttp+Retrofit+Rxjava的整合,可查看RxJava+Retrofit+OkHttp封装

引用

okhttp-logging-interceptor
okhttp Interceptors
Http协议详解

RxJava+Retrofit+OkHttp封装

发表于 2016-11-26   |   10条评论   |   阅读次数 2264

原文链接:rxjava+retrofit+OkHttp封装

前言

本文假设读者对RxJava、Retrofit和OkHttp具有基本的了解,以项目为例,重点讲解如何优雅的封装一个网络请求框架,以适应实际项目中的使用需求。

要解决的问题

  1. 支持切换网络请求地址,例如项目有分区功能,不同的分区对应不同的服务器地址
  2. 服务器返回接口格式不一致,有的接口是返回json格式,有的返回String格式
  3. url地址中,包含固定的参数,例如版本号,渠道信息等,怎么统一添加
  4. url地址中,包含动态地参数,如何动态添加
  5. 是否添加网络请求的Log
  6. 是否添加header(sessionId)
  7. 网络请求可配置是否显示网络加载动画
  8. 封装Observalbe进行数据处理的步骤,可动态改变数据处理的过程
  9. 封装错误处理,区分网络错误和应用层响应错误。

优雅封装

相互联系

在三者的关系中,Retrofit主要负责将网络请求接口化,真正的网络请求和响应交给OkHttp,而RxJava在网络框架中扮演数据响应后进行数据处理的角色。做个简单比喻:假设用户需要从上海飞往北京中关村办事,需要办理登机、做飞机、到达北京后前往中关村。登机手续由Retrofit负责,真正的运输是由飞机(Okhttp)完成,客户到达(数据返回)选择交通工具(RtHttp)到达中关村。

设计图


从设计图中可看出,RtHttp为网络请求总入口:
问题1、2交给Retrofit Builder模式解决;
问题3、4、5、6交给OkHttpClient的Builer解决;
问题7将封装到RtHttp类中;
问题8由BaseApi中的Observable Builder解决;
问题9由ApiSubscriber的OnError方法解决。

设计代码

使用

首先,我们来看封装后的网络请求使用:

RtHttp.with(this) //设置Context.setShowWaitingDialog(true) //设置显示网络加载动画.setObservable(MobileApi.response(map,ProtocolUtils.PROTOCOL_MSG_ID_LOGIN))//MobileApi.response 返回一个Observalbe.subscriber(new ApiSubscriber<JSONObject>() { //设置Subscriber,ApiSubscriber封装Subscriber;返回JSONObject仅是因为适配替换成Retrofit前的老代码@Overridepublic void onNext(JSONObject result) {    //只实现OnNext方法//具体业务逻辑}});

RtHttp

封装后,RtHttp支持链式调用,我们来看RtHttp的代码:

/*** 网络请求总入口*/
public class RtHttp{public static final String TAG = "RtHttp";public static RtHttp instance = new RtHttp(); //单例模式private Observable observable;private static WeakReference<Context> wrContext;private boolean isShowWaitingDialog;/**设置Context,使用弱引用* @param ct* @return*/public static RtHttp with(Context ct){wrContext = new WeakReference<Context> (ct);return instance;}/**设置是否显示加载动画* @param showWaitingDialog* @return*/public RtHttp setShowWaitingDialog(boolean showWaitingDialog) {isShowWaitingDialog = showWaitingDialog;return instance;}/**设置observable* @param observable* @return*/public  RtHttp setObservable(Observable observable) {this.observable = observable;return instance;}/**设置ApiSubscriber* @param subscriber* @return*/public RtHttp subscriber(ApiSubscriber subscriber){subscriber.setmCtx(wrContext.get());  //给subscriber设置Context,用于显示网络加载动画subscriber.setShowWaitDialog(isShowWaitingDialog); //控制是否显示动画observable.subscribe(subscriber); //RxJava 方法return instance;}/*** 使用Retrofit.Builder和OkHttpClient.Builder构建NetworkApi*/public static class NetworkApiBuilder{private String baseUrl;  //根地址private boolean isAddSession; //是否添加sessionidprivate HashMap<String,String> addDynamicParameterMap; //url动态参数private boolean isAddParameter; //url是否添加固定参数private Retrofit.Builder rtBuilder; private OkHttpClient.Builder okBuild;private Converter.Factory convertFactory; public NetworkApiBuilder setConvertFactory(Converter.Factory convertFactory) {this.convertFactory = convertFactory;return this;}public NetworkApiBuilder setBaseUrl(String baseUrl) {this.baseUrl = baseUrl;return this;}public NetworkApiBuilder addParameter(){isAddParameter = true;return this;}public NetworkApiBuilder addSession() {isAddSession = true;return this;}public NetworkApiBuilder addDynamicParameter(HashMap map) {addDynamicParameterMap = map;return this;}public NetworkApi build(){rtBuilder= new Retrofit.Builder();okBuild = new OkHttpClient().newBuilder();if(!TextUtils.isEmpty(baseUrl)){rtBuilder.baseUrl(baseUrl);}else{rtBuilder.baseUrl(Mobile.getBaseUrl());}if(isAddSession){okBuild.addInterceptor(new HeaderInterceptor(wrContext.get()));}if(isAddParameter){okBuild.addInterceptor(new ParameterInterceptor());}if(addDynamicParameterMap!=null){okBuild.addInterceptor(new DynamicParameterInterceptor(addDynamicParameterMap));}//warning:must in the last intercepter to log the network;if(Log.isDebuggable()){ //改成自己的显示log判断逻辑okBuild.addInterceptor(new LogInterceptor());}if(convertFactory!=null){rtBuilder.addConverterFactory(convertFactory);}else{rtBuilder.addConverterFactory(GsonConverterFactory.create());}rtBuilder.addCallAdapterFactory(RxJavaCallAdapterFactory.create()).client(okBuild.build());return rtBuilder.build().create(NetworkApi.class);}}
}

RtHttp的代码很简洁,NetworkApiBuilder使用builder模式创建NetWorkApi,可以动态地配置Retrofit和OkHttpClient的参数。
Retrofit可配置参数:

  1. baseUrl:可通过设置baseUrl产生不同的retrofit
  2. addConverterFactory:通过设置addConverterFactory可适配后台接口返回不同的数据类型,例如json和String

OkHttp可添加任意Interceptor实现网络请求的处理:

  1. HeaderInterceptory用于添加header(sessionid)
  2. ParameterInterceptor用于添加url固定的参数
  3. DynamicParameterInterceptor用于url添加动态参数
  4. LogInterceptor 用户显示log

MobileApi

下面我们来看Observable的创建:

public class MobileApi extends BaseApi{public static NetworkApi networkApi;public static Observable obserable;public static NetworkApi getNetworkApi() { //使用NetworkApiBuilder创建networkApiif(networkApi==null ){networkApi = new RtHttp.NetworkApiBuilder().addSession()               //添加sessionId.addParameter()             //添加固定参数.build();}return networkApi;}public static Observable getObserable(Observable observable) {   obserable = new ObserableBuilder(observable).addApiException()   //添加apiExcetion过滤.build();return obserable;}public static   Observable response(HashMap map, int protocolId) {RequestBody body = toBody(map);return getObserable(getNetworkApi().response(protocolId, body));}
}

getNetworkApi()方法可以创建特定的NetworkApi,getObserable添加数据返回后特定的处理。

NetWorkApi接口定义

public interface NetworkApi {@POST("open/open.do")Observable<Object> post(@Query("ACID") int acid, @Body RequestBody  entery);@POST("open/open.do")Observable<ResponseInfo<Object>> response(@Query("ACID") int acid, @Body RequestBody  entery);
}

acid是用于区分接口功能,RequestBody为请求的body参数。

BaseApi

public abstract class BaseApi {public static RequestBody toBody(HashMap map) {Gson gson = new Gson();ImiRequestBean requestBean= new ImiRequestBean();requestBean.setRequeststamp(ProtocolUtils.getTimestamp());requestBean.setData(map);return RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"), gson.toJson(requestBean));}public static RequestBody toBody(JSONObject jsonObject) {return RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"), (jsonObject).toString());}public static class ObserableBuilder{private Observable observable;private boolean apiException;private boolean toJSONJbject;private boolean isWeb;private Scheduler subscribeScheduler;private Scheduler obscerveScheduler;public void setObscerveScheduler(Scheduler obscerveScheduler) {this.obscerveScheduler = obscerveScheduler;}public void setSubscribeScheduler(Scheduler subscribeScheduler) {this.subscribeScheduler = subscribeScheduler;}public ObserableBuilder(Observable o) {this.observable = o;}public ObserableBuilder addApiException(){apiException = true;return this;}public ObserableBuilder addToJSONObject(){toJSONJbject = true;return this;}public ObserableBuilder isWeb() {isWeb = true;return this;}public Observable build(){if(isWeb){observable = observable.map(new StringToJSONObjectFun1());}if(apiException){observable = observable.flatMap(new ApiThrowExcepitionFun1());}if(toJSONJbject){observable = observable.map(new ObjectToJSONObjectFun1());}if(subscribeScheduler!=null){observable = observable.subscribeOn(subscribeScheduler);}else {observable = observable.subscribeOn(Schedulers.io());}if(obscerveScheduler!=null){observable = observable.observeOn(obscerveScheduler);}else{observable = observable.observeOn(AndroidSchedulers.mainThread());}return observable;}}
}

BaseApi提供toBody的方法,支持将JSONObject和HashMap转换成RequestBody。ObserableBuilder用于处理NetworkApi返回的Observalbe对象。使用ObserableBuilder可返回不同的observalbe。默认设置数据请求在子线程,处理完返回OnNext方法使用主线程。

WebApi

webApi跟MobileApi请求地址以及返回数据的数据都不一样,WebApi返回的数据类型是String,我们来看WebApi的代码:

public class WebApi extends BaseApi {public static final int ROLLER = 1;public static final int FRUIT = 2;public static final int WX = 3;public static NetworkApi networkApi;public static Observable observable;public static NetworkApi getNetworkApi(String baseUrl, HashMap map) {networkApi = new RtHttp.NetworkApiBuilder().setBaseUrl(baseUrl).addDynamicParameter(map).setConvertFactory(StringConverFactory.create()).build();return networkApi;}public static NetworkApi getRollerApi(HashMap map) {return getNetworkApi(Web.getRollerUrl(), map);}public static NetworkApi getFruitApi(HashMap map) {return getNetworkApi(Web.getFruitUrl(), map);}public static NetworkApi getWxApi(HashMap map) {return getNetworkApi(Web.getWXUrl(), map);}public static Observable getObserable(Observable observable) {observable = new ObserableBuilder(observable).isWeb().build();return observable;}public static Observable webPost(HashMap map, String action, int type) {NetworkApi networkApi = null;if (type == ROLLER) {networkApi = getRollerApi(map);} else if (type == FRUIT) {networkApi = getFruitApi(map);} else if (type == WX) {networkApi = getWxApi(map);}String[] str = action.split("/");if (str.length == 1) {observable = networkApi.webPost(str[0]);} else if (str.length == 2) {observable = networkApi.webPost(str[0], str[1]);} else {return null;}return getObserable(observable);}
}

getNetworkApi的参数时baseUrl和设置动态url参数 的map。getObserable的方法不使用addApiException的方法,而是使用isWeb()的方法。可以看出,变化的代码都封装在BaseApi的子类中。通过创建不同的子类,实现不同的网络请求及数据处理逻辑。

ApiSubscriber

ApiSubscriber封装了是否显示加载动画和对onError()的默认处理。

public abstract class ApiSubscriber<T> extends Subscriber<T> {private  Context mCtx;private WaitingDialog waitingDialog;  //加载dialogprivate boolean isShowWaitDialog;public void setShowWaitDialog(boolean showWaitDialog) {isShowWaitDialog = showWaitDialog;}@Overridepublic void onStart() {super.onStart();if(isShowWaitDialog){showWaitDialog();}}public void setmCtx(Context mCtx) {this.mCtx = mCtx;}@Overridepublic void onCompleted() {if(isShowWaitDialog){dismissDialog();}}/*** 对 onError进行处理* @param e*/@Overridepublic void onError(Throwable e) {if(isShowWaitDialog){dismissDialog();}Throwable throwable = e;if(Log.isDebuggable()){Log.i(RtHttp.TAG,throwable.getMessage().toString());}/*** 获取根源 异常*/while (throwable.getCause() != null){e = throwable;throwable = throwable.getCause();}if(e instanceof HttpException){//对网络异常 弹出相应的toastHttpException httpException = (HttpException) e;if(TextUtils.isEmpty(httpException.getMessage())){ToastUtil.showToast(mCtx, R.string.imi_toast_common_net_error);}else {String errorMsg = httpException.getMessage();if(TextUtils.isEmpty(errorMsg)){ToastUtil.showToast(mCtx, R.string.imi_toast_common_net_error);}else {ToastUtil.showToast(mCtx, errorMsg);}}}else if(e instanceof ApiException){//服务器返回的错误onResultError((ApiException) e);}else if(e instanceof JsonParseException|| e instanceof JSONException|| e instanceof ParseException){//解析异常ToastUtil.showToast(mCtx, R.string.imi_toast_common_parse_error);}else if(e instanceof UnknownHostException){ToastUtil.showToast(mCtx, R.string.imi_toast_common_server_error);}else if(e instanceof SocketTimeoutException) {ToastUtil.showToast(mCtx, R.string.imi_toast_common_net_timeout);}else {e.printStackTrace();ToastUtil.showToast(mCtx, R.string.imi_toast_common_net_error);}}/*** 服务器返回的错误* @param ex*/protected  void onResultError(ApiException ex){switch (ex.getCode()){  //服务器返回code默认处理case 10021:ToastUtil.showToast(mCtx, R.string.imi_login_input_mail_error);break;case 10431:ToastUtil.showToast(mCtx, R.string.imi_const_tip_charge);break;default:String msg = ex.getMessage();if(TextUtils.isEmpty(msg)){ToastUtil.showToast(mCtx, R.string.imi_toast_common_net_error);}else {ToastUtil.showToast(mCtx, msg);}}}private void dismissDialog(){if(waitingDialog!=null) {if(waitingDialog.isShowing()) {waitingDialog.dismiss();}}}private void showWaitDialog(){if (waitingDialog == null) {waitingDialog = new WaitingDialog(mCtx);waitingDialog.setDialogWindowStyle();waitingDialog.setCanceledOnTouchOutside(false);}waitingDialog.show();}}

ApiThrowExcepitionFun1

使用ObservalbeBuilder中通过addApiException()的方可以法添加对服务器返回code的处理,下面来看抛出异常的代码:

/*** 用来统一处理Http的resultCode,并将HttpResult的Data部分剥离出来返回给subscriber** @param <T> Subscriber真正需要的数据类型,也就是Data部分的数据类型*/
public class ApiThrowExcepitionFun1<T> implements Func1<ResponseInfo<T>, Observable<T>>{@Overridepublic Observable<T> call(ResponseInfo<T> responseInfo) {if (responseInfo.getCode()!= 200) {  //如果code返回的不是200,则抛出ApiException异常,否则返回data数据return Observable.error(new ApiException(responseInfo.getCode(),responseInfo.getMessage()));}return Observable.just(responseInfo.getData());}
}

ResponseInfo

public class ResponseInfo<T> {private int code;private String message;private T data;private String responsestamp;public String getResponsestamp() {return responsestamp;}public void setResponsestamp(String responsestamp) {this.responsestamp = responsestamp;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public T getData() {return data;}public void setData(T data) {this.data = data;}
}

ApiException

public class ApiException extends Exception {int code;public ApiException(int code,String s) {super(s);this.code = code;}public int getCode() {return code;}
}

ParameterInterceptor

public class ParameterInterceptor implements Interceptor{@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();//get请求后面追加共同的参数HttpUrl httpUrl = request.url().newBuilder()   //使用addQueryParameter()在url后面添加参数.addQueryParameter("userId", CommonData.getUid()+"").build();request = request.newBuilder().url(httpUrl).build();return chain.proceed(request);}
}

DynamicParameterInterceptor

public class DynamicParameterInterceptor implements Interceptor{private HashMap<String, String> map;public DynamicParameterInterceptor(HashMap<String, String> map) {this.map = map;}@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();//get请求后面追加共同的参数HttpUrl.Builder bulider = request.url().newBuilder();Iterator iter = map.entrySet().iterator();while (iter.hasNext()) {Map.Entry entry = (Map.Entry) iter.next();bulider.addQueryParameter((String) entry.getKey(), (String) entry.getValue());}request = request.newBuilder().url(bulider.build()).build();return chain.proceed(request);}
}

HeadInterceptor

public class HeaderInterceptor implements Interceptor{private Context context;public HeaderInterceptor(Context context) {this.context = context;}@Overridepublic Response intercept(Chain chain) throws IOException {Request original = chain.request();Request.Builder requestBuilder = original.newBuilder().header("sessionId", CommonData.getUserInfo(context).sessionId); //添加sessionIdRequest request = requestBuilder.build();return chain.proceed(request);}
}

LogInterceptor

public class LogInterceptor implements Interceptor {private static final String TAG = "LogInterceptor";private static final Charset UTF8 = Charset.forName("UTF-8");@Overridepublic Response intercept(Chain chain) throws IOException {Log.d(TAG,"before chain,request()");Request request = chain.request();Response response;try {long t1 = System.nanoTime();response = chain.proceed(request);long t2 = System.nanoTime();double time = (t2 - t1) / 1e6d;String acid = request.url().queryParameter("ACID");     //本项目log特定参数项目接口acidString userId = request.url().queryParameter("userId"); //本项目log特定参数用户idString type = "";if (request.method().equals("GET")) {type = "GET";} else if (request.method().equals("POST")) {type = "POST";} else if (request.method().equals("PUT")) {type = "PUT";} else if (request.method().equals("DELETE")) {type = "DELETE";}BufferedSource source = response.body().source();source.request(Long.MAX_VALUE); // Buffer the entire body.Buffer buffer = source.buffer();String logStr = "\n--------------------".concat(TextUtils.isEmpty(acid) ? "" : acid).concat("  begin--------------------\n").concat(type).concat("\nacid->").concat(TextUtils.isEmpty(acid) ? "" : acid).concat("\nuserId->").concat(TextUtils.isEmpty(userId) ? "" : userId).concat("\nnetwork code->").concat(response.code() + "").concat("\nurl->").concat(request.url() + "").concat("\ntime->").concat(time + "").concat("\nrequest headers->").concat(request.headers() + "").concat("request->").concat(bodyToString(request.body())).concat("\nbody->").concat(buffer.clone().readString(UTF8));Log.i(RtHttp.TAG, logStr);} catch (Exception e) {throw e;}return response;}private static String bodyToString(final RequestBody request) {try {final Buffer buffer = new Buffer();request.writeTo(buffer);return buffer.readUtf8();} catch (final IOException e) {return "did not work";}}}

小结

我们来看,网络请求返回的log如下:

RtHttp: --------------------  begin--------------------POSTacid->userId->306448537network code->200url->http://*******/user/loadInitData?userId=306448537&userSecretKey=ckj5k3vrxao***ekblcru5v3rtime->160.708437request headers->request->body->({"data":{"getTime":1,"prize":[{"id":4,"name":"跑车, 价值8000金豆","num":"x1"},{"id":9,"name":"生日蛋糕, 价值80000金豆","num":"x1"},{"id":11,"name":"爱的火山, 价值80000金豆","num":"x1"},{"id":15,"name":"68888金豆","num":"68888"},{"id":63,"name":"炫酷边框,发言与众不同!","num":"x1"},{"id":25,"name":"中幸运石, 5分钟内中奖率中幅提升","num":"x1"},{"id":28,"name":"1888阳光, 价值944金豆","num":"1888"},{"id":30,"name":"自行车, 价值80000金豆","num":"x1"},{"id":59,"name":"红玫瑰, 价值1000金豆","num":"10"},{"id":34,"name":"超级靓号宝箱,价值700000金豆","num":"x1"},{"id":36,"name":"花精灵520个, 女神的最爱","num":"x520"},{"id":38,"name":"幸运星, 30分钟内中奖率极大提升","num":"x1"},{"id":40,"name":"钻石项链, 价值12000金豆","num":"x3"},{"id":42,"name":"水晶鞋, 价值2400金豆","num":"x3"}],"resetTime":6,"userId":306448537},"msg":"OK","result":1})

以上是封装的代码,通过使用Builder模式可以创建不同的Networkapi实例,从而满足项目中的需求及能够更好的应对变化的需求。

引用

Handling API exceptions in RxJava
RxJava 与 Retrofit 结合的最佳实践
给 Android 开发者的 RxJava 详解
Retrofit+RxJava+OkHttp链式封装
HTTP 协议入门
OkHttp官网

不是学习工厂模式最简单的指南

发表于 2016-11-09   |   暂无评论   |   阅读次数 141

引言

工厂模式,简单的理解,就是封装通过new方式创建对象的代码。工厂模式可分为三类:

简单工厂(Simple Factory)
工厂方法(Factory Method)
抽象工厂(Abstract Factory)

本文的目的,就是通过举例理解区分三种工厂模式。

没有工厂模式

场景

如果用户要购买Iphone手机,在没有工厂模式的情况下,用户只能自己根据手机型号来创建手机,客户代码如下:

public class Customer {public Iphone getIphone(String type) {switch (type) {case "iphone5":return new Iphone5();case "iphone6":return new Iphone6();}return null;}
}

问题

当现在需要把ipone5下架,推出iphone6时,Customer代码如下:

public class Customer {public Iphone getIphone(String type) {switch (type) {case "iphone6":return new Iphone6();case "iphone7":return new Iphone7();}return null;}
}

简单的修改Customer端的代码,就能满足新的需求,但是,这违背了一个原则:

设计应该”对扩展开发,对修改关闭”

每次有新的型号,都需要改变Customer的代码,这明显是不合理,于是该普通工厂模式出现了。

普通工厂

把创建手机变化的部分封装到一个新的类SimpleIphoneFactory,Customer代码如下:

public class Customer {public Iphone getIphone(String type) {SimpleIphoneFactory simpleIphoneFactory = new SimpleIphoneFactory();return  simpleIphoneFactory.creatIphone(type);}
}

SimpleIphoneFactory的代码如下:

public class SimpleIphoneFactory {public Iphone creatIphone(String type) {switch (type){case "iphone6":return new Iphone6();case "iphone7":return new Iphone7();}return null;}
}

改变后,感觉代码并没有太大的变化。当iphone6下架,iphone8上架,还是得改变SimpleIphoneFactory的代码。
但是,此时Customer的代码无须改动,简单工厂方法的目的,就是把具体实例化的代码,从客户端删除

问题

当Iphone的型号越来越多时,SimpleIphoneFactory的代码依然需要改变,Customer类符合开闭原则,SimpleIphoneFactory不符合开闭原则。下面,我们采用工厂方法,把获取手机的方法getIphone()移回Customer,解决SimpleIponeFactory依赖过多Iphone实体对象的问题。

工厂方法

把用户变成抽象类,他的子类决定实例化什么类型的手机:

public abstract class Customer {public abstract Iphone getIphone();
}

Iphone5消费者:

public class Iphone5Customer extends Customer{@Overridepublic Iphone getIphone() {return new Iphone5();}
}

Iphone6消费者:

public class Iphone6Customer extends Customer{@Overridepublic Iphone getIphone() {return new Iphone6();}e
}

下面来看不同用户获取手机的代码:

//购买5的用户
Customer iphone5Customer = new Iphone5Customer();
iphone5Customer.getIphone();
//购买6的用户
Customer iphone6Customer = new Iphone6Customer();
iphone5Customer.getIphone();

问题

用户获取手机,是为了使用,我们给手机添加一个startUp()方法启动手机:

public abstract class Iphone {/*** 电池毫安数*/protected int power;/*** 电池*/protected Battery battery;/*** 设置电池* @param battery*/public void setBattery(Battery battery){this.battery = battery;};/*** 开机*/public abstract void startUp();
}

Iphone抽象类提供一个开机的抽象方法,由子类实现。我们开看Iphone5的实体类:

public class Iphone5 extends Iphone{private static final String TAG = "Iphone5";@Overridepublic void startUp() {if(battery.power() == 5000){Log.d(TAG,"startUp success");}else{Log.d(TAG,"Boom!!!!");}}
}

可以看到Iphone5实体类,当调用startUp方法时,需要判断电池的毫安数,当等于5000时,成功启动;否则会爆炸。Iphone依赖Battery,下面来看Battery抽象类:

public abstract class Battery {public abstract int power();
}

抽象类定义了一个抽象power()方法,调用此方法返回电池的毫安数。来看Iphone5Battery和Iphone6Battery的类:

public  class Iphone5Battery extends Battery{@Overridepublic int power() {return 5000;}
}public  class Iphone6Battery extends Battery{@Overridepublic int power() {return 10000;}
}

假设,用户把iphone5的手机配上iphone6的电池(假设电池外形一样,只是毫安数不一样),代码如下:

Customer iphone5Customer = new Iphone5Customer();
Iphone iphone5 = iphone5Customer.getIphone();
iphone5.setBattery(new Iphone6Battery());
iphone5.startUp();

毫无疑问,这会发生爆炸。Log.d(TAG,"Boom!!!!")。为了防止爆炸,生产手机时,必须要配套生产同类型的电池。当需要约束产品类之间的关系时,抽象工厂出场了。

抽象工厂

Iphone稳定的产能,需要各个代工厂的生产,苹果公司制定了一套生产手机的框架来保证手机的质量,例如Iphone6的手机只能使用Iphone6的电池。苹果公司可不想像三星手机那样因电池原因发生爆炸事件。
我们修改Customer类如下:

public  class Customer {
public void startUp(IphoneFactory iphoneFactory){iphoneFactory.startUp();}
}

Customer类提供了一个启动手机的方法,传入一个IphoneFactory对象,由IphoneFactory创建手机和对应的电池,防止因电池型号不对导致的爆炸意外。
IphoneFactory类如下:

public abstract class IphoneFactory {public abstract Iphone creatIphone();public abstract Battery creatBattery();public void startUp(){Iphone iphone = createIphone();Battery battery = createBattery();iphone.setBattery(battery);iphone.startUp();}
}

IphoneFactory是一个抽象类,startUp方法确定了Iphone和Battery的关系,子类实现创建Iphone和Battery的方法。看Iphone5Factory的类:

public class Iphone5Factory extends IphoneFactory {@Overridepublic Iphone creatIphone() {return new Iphone5();}@Overridepublic Battery creatBattery() {return new Iphone4Battery();}
}

最后我们来看启动Iphone5手机的代码:

IphoneFactory iphone5Factory = new Iphone5Factory();
Customer customer = new Customer();
customer.startUp(iphone5Factory);

可以看到,iphone5手机成功启动的Log。因为IphoneFactory封装了startUp的方法,明确了Iphone和Battery的关系,用户不能自主组装Iphone和Battery,防止了因装错电池导致事故的发生。

类图

简单工厂

工厂方法

抽象工厂

一句话小结

简单工厂:将创建代码从客户端移至工厂类。
工厂方法:用继承的方式实现,一种产品对应一个工厂类。
抽象工厂:系统存在产品族,且产品之间存在关系。

引用

设计模式(一)工厂模式Factory(创建型)
抽象工厂模式和工厂模式的区别?

Android编译时注解实践指南

发表于 2016-10-19   |   暂无评论   |   阅读次数 214

概述

Android注解分为两种,一种是运行时注解,一种是编译时注解。RxJava就是运行时注解,而butterKnife和EventBus是编译时注解,啃代码的时候经常碰到注解,所以只能乖乖的学习。本文主要讲解演示如何在Android Studio上运行一个编译时注解的Demo,被注解的对象打印出该对象的信息。

1.新建一个Java Library项目

在Android Studio中先新建一个Android project。在Android project 中选中File->New->New Module…选择Java Library新建一个module。因为注解中用到Java库,所以必须要导入Java Library.

新建的Android项目名叫At(Annotation),Java Library名字是at2.

2.编写注解类

在at2项目中,新建一个PrintInject,加入以下代码声明一个注解类,注解类的类型是@interface

1
2
3
4
5
6
7
8
9
10
/**
* Author : yuanjunli
* Create Time : 2016/10/19 13:50
* Email : 878715255@qq.com
*/
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) //声明此类可以注解的对象
@Retention(RetentionPolicy.CLASS) //编译时运行
public @interface PrintInject { //@interface声明
int value(); //定义有一个int参数
}

@Target(ElementType.TYPE) //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法

3.注册声明方法

在他项目中,新建一个PrintInjectProcessor,键入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SupportedAnnotationTypes("com.example.PrintInject")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class PrintInjectProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = processingEnv.getMessager();
for (TypeElement te : annotations) {
for (Element e : roundEnv.getElementsAnnotatedWith(te)) {
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.toString());
}
}
return true;
}
}

@SupportedAnnotationTypes:指定该注解起作用的类;
@SupportedSourceVersion(SourceVersion.RELEASE_7):指定jdk的版本;
AbstractProcessor:自定义的声明需要继承AbstractProcessor,重点实现process方法;
processingEnv:注解框架提供的工具集合,在此demo中取到message对象打印信息。打印的信息在android studio中Gradle Console切页中显示;
TypeElement:是注解的类型;
以上的代码就是获取所有自定义的注解并且通过e.toString()打印注解类的信息。

4.创建路径文件

在resources路径文件夹下创建一个META-INF文件夹,META-INF下面创建一个services文件夹,在里边创建一个javax.annotation.processing.Processor文件,此文件路径不能出错。在文件中写入注解路径的声明,本项目路径是com.example.PrintInjectProcessor

如图检查Processor放置的路径是否一样。

5.生成编译注解at2的jar包

经过以上操作,编写了一个@PrintInject的声明,当对象被@PrintInject声明时,在项目编译时就会打印对象的信息。接下来单独编译运行at2 这个module,导出该jar包。

在android studio 右侧打开Gradle切页,如果没有显示项目的gradle,点击gradle按钮编译一下,如上图,打开:at2->Tasks ->build 双击里边的build文件。编译成功后会如下图所示在项目build->libs目录下生成一个at2.jar.

6.demo测试

将at2.jar copy 到at主项目的libs文件夹下,生成android项目时在app/build.gradle文件下有compile fileTree(dir: 'libs', include: ['*.jar'])为自动导入jar。编写MainActivity代码如下:

1
2
3
4
5
6
7
8
9
10
public class MainActivity extends AppCompatActivity {
@Override
@PrintInject(1)
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}

@PrintInject(1)在方法onCreate()上添加注解,int参数为1。接着看编译运行demo后,message的输出并不在Message切页,也不知Log里边,是在Gradle Console下。需要注意,第一次运行项目的时候会有message输出,当再次运行时并没有,因为第二次无须再次编译,如果需要再次看到输出的信息,可以Build->Clean Project。

7.总结

以上就是编译注解demo的全部内容,并不涉及注解的深入使用,只在于跑通注解的流程。

8.引用

Android 打造编译时注解解析框架 这只是一个开始
如何实现自定义Java编译时注解功能–初步印象

当把EventBus比喻成苹果卖手机

发表于 2016-10-18   |   暂无评论   |   阅读次数 144

概况

EventBus一直都知道这个库,但是没有接触,看到国内 Top500 Android 应用分析报告使用情况,觉得有必要掌握此库。EventBus是android的一个用于消息传递的库,属于订阅者模式,让发布者和订阅者解耦。本文通过对EventBus的使用,分析其内部实现。

简单使用

关于EventBus的使用不是本文的重点,EventBus的使用请查看EventBus使用详解。

做个比喻

为了更好的理解EventBus这个库的流程,以下将根据下图做一个比喻。

如上图,EventBus充当苹果公司销售部的角色。当苹果公司(Publisher发布者)生产(调用post方法)出某一款手机(EventType)时,消息会传递到销售部,由销售部告诉用户(Subscriber订阅者)。例如用户想购买iphone8,该用户必须要预约(方法被@Subscriber注解),当销售部接到公司说iphone8开始售卖了,那么只有预约手机的用户可以购买(订阅方法被调用)。

阅读源码

EventBus.getDefault().register(this);

public static EventBus getDefault() {if (defaultInstance == null) {synchronized (EventBus.class) {if (defaultInstance == null) {defaultInstance = new EventBus();  //调用默认构造函数构建单例}}}return defaultInstance;}

看new EventBus做了什么初始化工作:

public EventBus() {this(DEFAULT_BUILDER);}

空构造函数引用带参数的构造函数,参数类型是EventBusBuilder:

EventBus(EventBusBuilder builder) {subscriptionsByEventType = new HashMap<>();typesBySubscriber = new HashMap<>();          stickyEvents = new ConcurrentHashMap<>();     mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);  backgroundPoster = new BackgroundPoster(this);                    asyncPoster = new AsyncPoster(this);            indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,builder.strictMethodVerification, builder.ignoreGeneratedIndex);logSubscriberExceptions = builder.logSubscriberExceptions;   logNoSubscriberMessages = builder.logNoSubscriberMessages;sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;sendNoSubscriberEvent = builder.sendNoSubscriberEvent;throwSubscriberException = builder.throwSubscriberException;eventInheritance = builder.eventInheritance;executorService = builder.executorService;}

通过使用默认构造器EventBusBuilder,默认配置了一些参数,部分重点参数会接下来在阅读源码中提到。

register(Object subscriber)

public void register(Object subscriber) {Class<?> subscriberClass = subscriber.getClass();List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod);}}
}

调用register方法,可以看到参数是一个Object,EventBus支持Activity、Fragment、Service、BroadcastReceiver等之间传递消息。
调用subscriber.getClass()返回subscriber对象的运行时类的Java.lang.Class。
根据subscriber运行时的类获取该类所以的SubscriberMethod,就是该类被@Subscriber注解的所有方法。
通过循环该类所有的的被注解的方法,通过调用subscriber(subscriber, subscriberMethod)方法绑定subscriber和subscriberMethod的关系。

subscribe(subscriber, subscriberMethod);

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {//获取subsrciberMethod传递的自定义EventType参数的运行时的类Class<?> eventType = subscriberMethod.eventType;//Subscription用于绑定subscriber和sucriberMethod,一个订阅者可以有多个subscriberMethodSubscription newSubscription = new Subscription(subscriber, subscriberMethod);//根据EventType的运行时类取到该类所有的subscriptioins,subscriptionsByEventType是HashMap中的keyCopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);if (subscriptions == null) {subscriptions = new CopyOnWriteArrayList<>();//若根据EventType找不到subscriptions,则eventType作key,subscriptions作value添加到subscriptionByEventType中。subscriptionsByEventType.put(eventType, subscriptions);} else {if (subscriptions.contains(newSubscription)) {//已经存在newSubscription,抛出异常该订阅者已经注册,不可重复注册同一个subscriberthrow new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType);}}int size = subscriptions.size();for (int i = 0; i <= size; i++) {//循环subscriptions,根据标记优先级的priority从高到低,将新的subscription插入到subscriptions中if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {subscriptions.add(i, newSubscription);break;}}//typesBySubscriber是一个HashMap,根据subscriber做key,获取该subscriber对应的所有的订阅事件的类型List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {subscribedEvents = new ArrayList<>();//该订阅者之前的订阅事件类型列表为空,则将当前订阅类型添加到typesBySubscriber中typesBySubscriber.put(subscriber, subscribedEvents);}subscribedEvents.add(eventType);//如果该方法被标识为sticky事件if (subscriberMethod.sticky) {if (eventInheritance) { eventInheritance标识是否考虑EventType的类层次结构//循环所有的sticky黏性事件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();heckPostStickyEventToSubscription(newSubscription, stickyEvent);}}} else {Object stickyEvent = stickyEvents.get(eventType);checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}
}

关键代码以上都有注释。
黏性事件,主要使用场景是:当订阅者尚未创建,先调用EventBus.getDefault().postSticky()方法发送一个sticky事件,该事件会被stickyEvents缓存起来,当订阅该事件的类调用register()方法时,同样可以收到该事件。而调用EventBus.getDefault().post()则必须先调用register(),才能收到事件。

checkPostStickyEventToSubscription(newSubscription, stickyEvent);

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {if (stickyEvent != null) {// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)// --> Strange corner case, which we don't take care of here.postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());}}

跳转postToSubscription方法;

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {//根据@subscriber中threadMode进行区分,POSTING为当前线程执行,//MAIN为主线程,BACKGROUND为子进程,ASYNC为异步执行。switch (subscription.subscriberMethod.threadMode) {case POSTING:invokeSubscriber(subscription, event);break;case MAIN:if (isMainThread) {invokeSubscriber(subscription, event);} else {mainThreadPoster.enqueue(subscription, event);}break;case BACKGROUND:if (isMainThread) {backgroundPoster.enqueue(subscription, event);} else {invokeSubscriber(subscription, event);}break;case ASYNC:asyncPoster.enqueue(subscription, event);break;default:throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);}}

invokeSubscriber(subscription, event)

下面我们来分析threadMode = ThreadMode.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);}}

EventBus.getDefault().register(this)小结

回到苹果公司卖手机的例子,EventBus.getDefault().register(this)中,this指的就是买手机的顾客,顾客通过调用此方法告诉销售部说我想登记买手机,登记的流程包括:
A.找到该用户所有的购买手机型号的预约信息(根据subscriber找到所有subscriberMethorssubscriberMethodFinder.findSubscriberMethods(subscriberClass));
B.将新登记的预约信息和之前的预约信息做比对,如果之前没有预约信息subscriptions == null,则添加到预约信息中;若之前有预约过的信息,检查是否有相同的预约信息(subscriber和subscriberMethod都一样)subscriptions.contains(newSubscription),若有,则预约失败,并且丢给用户一个臭脸throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType);
C.添加到预约信息列表的时候,根据优先级由高到低,优先级相同插入到最后边的原则放入列表。
D.若用户想预约的手机不是新款手机,而是一款老手机subscriberMethod.sticky=ture,则无需预约,可直接购买。

EventBus.getDefault().post(EventType);

post()和postSticky()的区别

发布者调用post(EventType)方法,若订阅者需要接收到此EventType,则订阅者必须要先注册并且注解接收此方法,否则收不到该方法;而调用postSticky(EventType),及时先发布者先发送消息,订阅者在注册事件,也能收到消息。
例如苹果公司出iphone8新款手机了,那么用户需要先预约手机,不预约的不能购买,此时苹果公司调用post(iphone8)方法;如果苹果公司推出的是一款旧手机iphone5,iphone5无须预约,顾客到店就可以直接购买,这时候调用postSticky(iphone5)方法。

post(EventType);

public void post(Object event) {//获取当前发送状态PostingThreadState postingState = currentPostingThreadState.get();List<Object> eventQueue = postingState.eventQueue;//将事件添加到列表中eventQueue.add(event);//如果当前不是在发送事件if (!postingState.isPosting) {postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();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;}}}

将当前发送事件添加到待处理的事件列表,如果当前不是在发送事件状态,则循环事件列表,调用postSingleEvent(eventQueue.remove(0), postingState)发送每一个事件。

postSingleEvent(eventQueue.remove(0), postingState);

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {Class<?> eventClass = event.getClass();boolean subscriptionFound = false;//当需要处理事件的继承关系时if (eventInheritance) {List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();for (int h = 0; h < countTypes; h++) {Class<?> clazz = eventTypes.get(h);subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);}} else {subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);}if (!subscriptionFound) {//没有订阅该事件if (logNoSubscriberMessages) {Log.d(TAG, "No subscribers registered for event " + eventClass);}//如果该事件没有subscriberif (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&eventClass != SubscriberExceptionEvent.class) {post(new NoSubscriberEvent(this, event));}}}

主要处理调用postSingleEventForEventType()遇到的错误。

postSingleEventForEventType()

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {CopyOnWriteArrayList<Subscription> subscriptions;synchronized (this) {//根据eventType运行时的类找到所有的subscripitionssubscriptions = 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;}

以上代码主要是将该事件传递给订阅了该事件的订阅者,每一个订阅者都尝试将该事件作为参数,调用所有的订阅的方法。

post()方法小结

以上逻辑并不复杂,就直接做比喻好了:
苹果公司发布iphone8的流程:
A.在发布iphone8的时候,先检查一下iphone8之前的手机是否都已经发布了,将iphone8添加到发布列表中;
B.若当前没有手机在发布流程中,则从发布列表中取出接下来待发布的手机,假设iphone8之前的都已经发布,那iphone8就进入发布流程;
C.根据iphone8找出所有iphone8的预约信息,预约了iphone8的用户可以购买手机;
D.处理特殊的情况:如果没有人预约,或者之前预约的人不购买。

总结

以上分析了EventBus的两个主要入口代码,做的比喻可能并不恰当,但应该能对理解源码的逻辑有很大的帮助。

引用

EventBus 源码解析
EventBus 3.0 源代码分析

Head First设计模式思维导图

发表于 2016-10-17   |   暂无评论   |   阅读次数 31

这么多设计模式,看书经常看完后面的就把前面的忘记了。所以在读Head First时,特意边读边做笔记,用思维导图有利于描述设计模式之间的区别和联系。图不是很清晰,可以打开Head First设计模式思维导图查看大图。

Glide核心设计二:缓存管理相关推荐

  1. Glide核心设计一:皮皮虾,我们走

    原文链接:Glide核心设计一:皮皮虾,我们走 引言 皮皮虾,又名虾姑,是淡水中的强者.其头部的两个锤节,可以轻易破坏贝类的外壳,身体上的步足可以保证快速移动.这些优秀的品质,使它被表情包盯上. Gl ...

  2. HDFS中心缓存管理

    一 HDFS 中心缓存背景 缓存HDFS中的热点公共资源文件和短期临时的热点数据文件 情况一: 公共资源文件. 这些文件可以是一些存放于HDFS中的依赖资源jar包,或是一些算法学习依赖.so文件等等 ...

  3. 架构设计 | 缓存管理模式,监控和内存回收策略

    本文源码:GitHub·点这里 || GitEE·点这里 一.缓存设计 1.缓存的作用 在业务系统中,查询时最容易出现性能问题的模块,查询面对的数据量大,筛选条件复杂,所以在系统架构中引入缓存层,则是 ...

  4. 优酷暗黑模式(二):如何建立设计语言标准化管理体系

    伴随着行业的成熟与竞争加剧,中国互联网产品中心化.平台化的趋势越加明显.越来越多的公司对产品的设计体系与效率提出了更高的要求.为了更高效地服务多样的业务场景,快速应对未来市场竞争的变化,需要我们跳出设 ...

  5. Java核心知识点 --- 线程中如何创建锁和使用锁 Lock , 设计一个缓存系统

    理论知识很枯燥,但这些都是基本功,学完可能会忘,但等用的时候,会发觉之前的学习是非常有意义的,学习线程就是这样子的. 1.如何创建锁? Lock lock = new ReentrantLock(); ...

  6. go二维map_mirrorlang——从0设计二维内存寻址语言及vm(五.内存管理的思考)

    目录 鹏鹏李李:mirrorlang--从0设计二维内存寻址语言及vm [目录]​zhuanlan.zhihu.com 由一段函数开始思考内存布局 func longestPalindrome(s s ...

  7. SpringBoot缓存管理(二) 整合Redis缓存实现

    SpringBoot支持的缓存组件 觅波小说网 https://www.3812.info 在SpringBoot中,数据的缓存管理存储依赖于Spring框架中cache相关的org.springfr ...

  8. 软件设计体系-简单工厂模式实例二---权限管理

    在某OA系统中,系统根据对比用户在登录时输入的账号和密码以及在数据库中存储的账号和密码是否一致来进行身份验证,如果验证通过,则取出存储在数据库中的用户权限等级(以整数形式存储),根据不同的权限等级创建 ...

  9. Kafka核心设计与实践原理总结:基础篇

    作者:未完成交响曲,资深Java工程师!目前在某一线互联网公司任职,架构师社区合伙人! 一.基本概念 1.体系架构 Producer:生产者 Consumber:消费者 Broker:服务代理节点(k ...

最新文章

  1. SAP MM 并非奇怪现象之MB5B报表里期初库存余额或者期末库存余额为负数?
  2. python培训班 北京-北京python培训机构那个好?这几个坑千万别踩
  3. Docker从入门到实践笔记(一)
  4. 人工智能 | 自然语言处理(NLP)(国内外研究组)
  5. QT的QScriptEngine类的使用
  6. c++用什么软件编程_为什么要学习“C”编程语言?
  7. 【编译原理】编译是怎么一个过程?
  8. mac抹掉磁盘重装系统未能与服务器取得联系_Mac重装系统不再难:苹果电脑重装系统教程...
  9. Java笔记-通过反射获取注解及Spring中获取某包下的所有class文件
  10. 【蓝桥杯嵌入式】【STM32】8_USART之响应上位机指令发送实时时间
  11. 程序员,活得是本事:30 岁后的 人生建议
  12. python常用格式化_python的常用三种格式化方法
  13. 基于深度学习的自然场景文字检测及端到端的OCR中文文字识别
  14. php画弧,75、PHP图像处理之画圆、弧线、网站饼状统计图绘制
  15. JUnit 4 vs JUnit 5
  16. python基于django的校园公寓宿舍报修管理系统设计与实现
  17. 暗黑复制服务器物品,暗黑2战网环境下复制dupe物品装备
  18. POI 模板生成word PDF——牛X神器
  19. 码农的奋斗之路 富爸爸穷爸爸系列--提高你的财商 读后感
  20. 【成神之路】Redis相关面试题

热门文章

  1. CODEVS——T 3736 【HR】万花丛中2
  2. 天载网上炒股抱团股继续走低
  3. 相见恨晚的强大截图软件:Snipaste
  4. 黑客马拉松巅峰对决,展示Web3强大功能。
  5. 【转】机器学习@美团 ——吃喝玩乐中的算法问题
  6. 通过云计算机管理档案,云计算下档案信息管理的研究
  7. Java外接Matlab程序(详细步骤)
  8. 黑鹰易语言培训课程 [ 06.17更新 ] [ 翻录版本,免key ]
  9. 深度学习(5)数据处理-resize
  10. Codeforces 1634 B. Fortune Telling —— 简单思维,奇偶性