第十二章 Android第三方库源码
文章目录
- EventBus
- EventBus理解
- EventBus源码分析
- EventBus项目实战
- okHttp
- 简介
- 使用
- 工作原理
- 源码解读
- 总结
- 设计模式
- 封装
- 网络框架对比
- Glide
- 简介
- 使用
- 工作原理(非重点)
- 图片加载框架对比
EventBus
https://github.com/greenrobot/EventBus
EventBus理解
EventBus原理解析笔记以及案例实战(结合demo)
- 什么是 EventBus
EventBus(事件总线)是一个Android端优化的 发布/订阅 消息总线。简化了应用程序内各个组件、组件与后台线程间(活动Activity、碎片Fragment、进程Thread、服务Service等)的通信方式;
使用 发布/订阅 机制对代码进行解耦,移除了不必要的依赖,使APP项目用更少的代码量实现更好的质量;
优点:
(1)简化应用程序内各个组件、组件与后台线程间通讯方式
(2)分离事件发布者和订阅者,实现完全解耦
(3)移除不必要的依赖关系(避免易错的生命周期问题)
(4)可继承、优先级、粘滞性
(5)代码更简洁、性能更好、移动应用更快、更小
缺点:
(1)EventBus中的事件分发是通过事件类名(订阅方法的参数类型)决定的,这就导致了当接受者过多或相同参数时很难理清消息流。
(2)EventBus中发布者(publisher)只能单向广播,无法获得订阅者(subscriber)对事件处理的相应 - EventBus 三个角色
- Event:事件。可以是任意类型,EventBus根据事件类型进行全局通知。
- Subscriber:事件订阅者。事件方法名任意取名,需要加上注解@subscribe,并指定线程模型。
EventBus 3.0 有四种线程模型(订阅者 & 发布者可位于任意线程),分别是:
- POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程。
- MAIN:表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。若当前线程不是主线程,则通过Handler将消息发送给主线程。
- BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。
- ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。
- Publisher:事件发布者。可在任意线程里发布事件
- EventBus 的使用
(1)引入依赖
implementation 'org.greenrobot:eventbus:3.1.1'
(2)定义事件
定义一个事件的封装对象。在程序内部就使用该对象作为通信的信息
public class MyEvent {public String msg;public MyEvent(String msg) {this.msg = msg;}
}
(3)定义订阅者,注册事件 & 处理事件
public class MainActivity extends AppCompatActivity {private Button mButton;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//使用EventBus的接收方法的Activity,需要注册监听EventBus.getDefault().register(this);mButton = findViewById(R.id.bt_toAc2);mButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent(MainActivity.this, SecondActivity.class);startActivity(intent);}});}/*** 注册onEvent()监听事件* 加入注解加入注解Subscribe并指定线程模型为主线程MAIN(默认为POSTING)*/@Subscribe(threadMode = ThreadMode.MAIN)public void onEvent(MyEvent event) {popOutToast("接收到Event:" + event.msg);}/*** onDestroy中从EventBus中取消注册该Activity*/@Overrideprotected void onDestroy() {super.onDestroy();EventBus.getDefault().unregister(this);}/*** 封装弹出短时Toast提示* @param text 企图弹出的文本内容*/private void popOutToast(String text) {Toast.makeText(MainActivity.this,text,Toast.LENGTH_SHORT).show();}
}
(4)定义发布者,发布事件
public class SecondActivity extends AppCompatActivity {private Button mButton;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_second);mButton = findViewById(R.id.bt_sendMsg);mButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 点击按钮,发布事件EventBus.getDefault().post(new MyEvent("Event From Publisher"));}});}
}
- EventBus & BroadcastReceiver & Handler
EventBus、BroadcastReceiver和Handler均是Android中进程间消息传递的方式
EventBus | BroadcastReceiver | Handler | |
---|---|---|---|
定义 | EventBus是一个Android端优化的publish/subscribe消息总线 | Android四大组件之一,广播,全局监听器 | Handler运行在主线程中,通过Message与子线程进行数据传递,解决子线程无法更新UI问题 |
范围 | 应用内组件间、线程间通信 |
同一app内部的同一组件内的消息通信(单个或多个线程之间); 同一app内部的不同组件之间的消息通信(单个进程); 同一app具有多个进程的不同组件之间的消息通信; 不同app之间的组件之间消息通信 |
同一进程中不同线程间通信(主要是UI线程 & 子线程) |
消息 | 事件,可以是任意类型对象 | intent | Message类型 |
适用场景 | 应用内的消息事件广播 |
系统内全局性的消息传递,尤其包括: 1. 系统事件监听(电量、网络等) 2. 多进程通信 |
UI线程 与 子线程 之间消息传递 |
优点 |
1. 订阅者和发布者耦合度低,调度灵活,不依赖Context 2. 可继承、优先级、粘滞性 3. 轻量 |
1. 四大组件之一,与sdk链接紧密,监听系统广播 2. 跨进程通信 |
适用于目标具体明确的调度,处理简单的耗时操作 |
缺点 | EventBus中的事件分发是通过注解函数的参数类型决定的,这就导致了当接受者过多或相同参数时很难理清消息流 | 1. 资源占用多,依赖Context 2. 同一进程不同线程消息传递回调函数复杂(适用EventBus/Observer) | 消息高度绑定,发布者与接受者高度耦合,代码冗余 |
EventBus源码分析
参考链接:EventBus源码解析
EventBus官方原理图
发布者(Publisher)只需要post一个event之后就不用管了,EventBus内部会将event逐一分发给订阅此event的订阅者(Subscriber)
EventBus用于应用内消息事件传递,方便快捷,耦合性低
代码实例
订阅者(Subscriber)
public class EventBusMain extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.content_main);EventBus.getDefault().register(this);}- 订阅的事件 onEvent1@Subscribepublic void onEvent1(RemindBean bean){}
- 订阅的事件 onEvent2@Subscribepublic void onEvent2(UserInfo bean){}@Overrideprotected void onDestroy() {super.onDestroy();EventBus.getDefault().unregister(this);}
}
发布者(Publisher)
EventBus.getDefault().post(new RemindBean())
源码解析
- register
EventBus.getDefault().register(this);
- getDefault() :Subscriber获取EventBus实例
EventBus 是一个单例模式,懒汉式,双重校验锁判断解决线程不安全问题
public static EventBus getDefault() {if (defaultInstance == null) {synchronized (EventBus.class) {if (defaultInstance == null) {defaultInstance = new EventBus();}}}return defaultInstance;}
- register():在EventBus中将Subscriber与其订阅的event(使用@Subscribe进行注解)关联
public void register(Object subscriber) {- 1.先拿到这个订阅者(subscriber)类的字节码Class<?> subscriberClass = subscriber.getClass();- 2. 通过这个类的字节码,拿到所有的订阅的 event,存放在List中List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {- 3. 循环遍历所有的订阅的方法,完成subscriber 和 subscriberMethod 的关联for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod);}}}
List findSubscriberMethods(Class<?> subscriberClass):内部利用反射机制(findUsingReflectionInSingleClass)通过订阅者的类的字节码文件 获取 订阅者所有的订阅事件event:
- 通过反射扫描字节码中当前类声明的所有方法,匹配订阅方法(修饰符public、只有1个参数、含有subscribe注解)。并获取订阅方法的一系列信息如:方法第一个参数eventType(Event事件的封装对象,作为通信的信息),ThreadMode(线程模型,包括mainThread,Posting,background,async),priority(优先级),是否粘性等信息。
private void findUsingReflectionInSingleClass(FindState findState) {Method[] methods;try {// This is faster than getMethods, especially when subscribers are fat classes like Activities- 1. 通过订阅者的字节码查找当前类中所有生命的方法methods = findState.clazz.getDeclaredMethods();} catch (Throwable th) {// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149methods = findState.clazz.getMethods();findState.skipSuperClasses = true;}- 2. 循环遍历所有的方法for (Method method : methods) {- 3. 获取方法的修饰符int modifiers = method.getModifiers();- 4.判断修饰符,订阅方法的修饰符不能是private,staticif ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {- 5. 获取方法的所有的参数Class<?>[] parameterTypes = method.getParameterTypes();- 6.判断参数的个数,只能有1个参数,订阅方法中if (parameterTypes.length == 1) {- 7.获取方法上具有subscribe 注解Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);- 8.含有subscribe注解的方法,就是该类订阅的方法,其它不符合的可能就是普通的方法if (subscribeAnnotation != null) {- 9. 获取第一个参数eventTypeClass<?> eventType = parameterTypes[0];if (findState.checkAdd(method, eventType)) {- 10. 获取注解的mode,就是我们在注解上标识的,有mainThread,Posting,background,asyncThreadMode threadMode = subscribeAnnotation.threadMode();- 11. 将订阅方法的一系列信息(方法名称,threadMode,优先级,是否是粘性等)添加到集合subscriberMethods中去findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,subscribeAnnotation.priority(), subscribeAnnotation.sticky()));}}} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {- 12. 参数是多个的时候抛出异常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)) {- 13. 方法的修饰符不是public的,抛出异常 String methodName = method.getDeclaringClass().getName() + "." + method.getName();throw new EventBusException(methodName +" is a illegal @Subscribe method: must be public, non-static, and non-abstract");}}}
- 将订阅的事件最终保存在HashMap subscriptionsByEventType(key:eventType(事件)、value:Subscription(subscriber,subscribMethod(method,threadMode,eventType),priority)即订阅方法的一系列信息)
- post
EventBus.getDefault().post(new RemindBean("2018-02-12","happy"));
post(Event)
/** Posts the given event to the event bus. */public void post(Object event) {- 1.获取当前线程的postingThreadState 对象PostingThreadState postingState = currentPostingThreadState.get();- 2. 获取里面那个事件队列List<Object> eventQueue = postingState.eventQueue;- 3. 将事件添加到队列中去eventQueue.add(event);- 4. 判断当前的event 是否在 postingif (!postingState.isPosting) {- 5. 是否是主线程postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();postingState.isPosting = true;- 6. 判断是否取消if (postingState.canceled) {throw new EventBusException("Internal error. Abort state was not reset");}try {while (!eventQueue.isEmpty()) {//不为空,进入循环- 7.按照顺序,post一个 remove一个postSingleEvent(eventQueue.remove(0), postingState);}} finally {postingState.isPosting = false;postingState.isMainThread = false;}}}
- 遍历eventTypes(Event类及其父类),并通过postSingleEventForEventType查找所有的订阅者subscriptions。
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {CopyOnWriteArrayList<Subscription> subscriptions;synchronized (this) {- 1.根据字节码取出subscriptions,还记得我们之前在subscribe这个方法的时候,
把subscrber,subscriberMethod 封装成一个subscription 对象。// subscriptionsByEventType中 key 为 eventType, value 是subscriptions对象// register 中 subscribe(subscriber,subscriberMethod):// subscriptionsByEventType.put(eventType, subscriptions);subscriptions = subscriptionsByEventType.get(eventClass);}if (subscriptions != null && !subscriptions.isEmpty()) {for (Subscription subscription : subscriptions) {- 2. 取出每一个subscription 对象postingState.event = event;postingState.subscription = subscription;boolean aborted = false;try {- 3. post到相应的线程中回调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;}
- 遍历每个subscription,根据定义的不同线程模式,反射调用相关方法postToSubscription
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {switch (subscription.subscriberMethod.threadMode) {case POSTING://一般没定义的,这个就是post在哪个线程,响应就在哪个线程执行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
void invokeSubscriber(Subscription subscription, Object event) {try {- 反射拿到字节码clazz 反射调用方法,就收到消息了// Method类的invoke()方法:对带有指定参数的指定对象,调用此Method对象表示的底层方法// public native Object invoke(Object obj, Object... args)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项目实战
- 请求网络时候,等网络返回时通过Handler或Broadcast通知UI;
- 两个Fragment之间需要通过Listener通讯;
okHttp
简介
OkHttp是一个处理网络请求的开源项目,是Android端最火热的轻量级框架,由移动支付Square公司贡献用于替代HttpUrlConnection和Apache HttpClient。
之所以可以赢得如此多开发者的喜爱,主要得益于如下特点:
- 支持HTTPS/HTTP2/WebSocket(在OkHttp3.7中已经剥离对Spdy的支持,转而大力支持HTTP2)
SPDY(读作“SPeeDY”)是Google开发的基于TCP的应用层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY是对HTTP协议的加强。新协议的功能包括数据流的多路复用、支持服务器推送技术、请求优先级、HTTP报头压缩以及强制使用SSL传输协议。
- 内部维护任务队列线程池,友好支持并发访问
- 内部维护socket连接池,支持多路复用(共享同一个Socket处理同一个服务器所有请求:同一域名的所有请求stream共享同一个tcp连接),减少连接创建开销(减少握手次数 & 请求延时)
- socket创建支持最佳路由 & 自动重连
- 提供拦截器链(InterceptorChain),实现request与response的分层处理(如透明GZIP压缩,logging等)
- 基于headers的缓存策略减少重复的网络请求
使用
- 同步请求(GET)
对于同步请求在请求时需要开启子线程,请求成功后需要跳转到UI线程修改UI
public void getDatasync(){new Thread(new Runnable() {@Overridepublic void run() {try {OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象Request request = new Request.Builder().url("http://www.baidu.com")//请求接口。如果需要传参拼接到接口后面。.build();//创建Request 对象Response response = null;response = client.newCall(request).execute();//得到Response 对象if (response.isSuccessful()) {//此时的代码执行在子线程,修改UI的操作请使用handler跳转到UI线程。String result = response.body().string();Message msg = Message.obtain();msg.obj = result;handler.sendMessage(msg);}} catch (Exception e) {e.printStackTrace();}}
}).start();
}
- 异步请求(POST)
这种方式不用再次开启子线程,但回调方法是执行在子线程中,所以在更新UI时还要跳转到UI线程中。
private void postDataAsync() {OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象。FormBody.Builder formBody = new FormBody.Builder();//创建表单请求体formBody.add("username","zhangsan");//传递键值对参数Request request = new Request.Builder()//创建Request 对象。.url("http://www.baidu.com").post(formBody.build())//传递请求体.build();// 异步请求不需要开启子线程,enqueue方法会自动将网络请求部分放入子线程中执行。client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {if(response.isSuccessful()){//回调的方法执行在子线程,修改UI的操作请使用handler跳转到UI线程。String result = response.body().string();Message msg = Message.obtain();msg.obj = result;handler.sendMessage(msg);}}});
}
工作原理
源码解读
- 创建okHttpClient对象
OkHttpClient client = new OkHttpClient();
构造函数
public OkHttpClient() {this(new Builder());
}
为了方便我们使用,提供了一个“快捷操作”,对OkHttpClient.Builder的类成员 全部使用了默认的配置。
public Builder() {dispatcher = new Dispatcher();protocols = DEFAULT_PROTOCOLS;connectionSpecs = DEFAULT_CONNECTION_SPECS;proxySelector = ProxySelector.getDefault();cookieJar = CookieJar.NO_COOKIES;socketFactory = SocketFactory.getDefault();hostnameVerifier = OkHostnameVerifier.INSTANCE;certificatePinner = CertificatePinner.DEFAULT;proxyAuthenticator = Authenticator.NONE;authenticator = Authenticator.NONE;connectionPool = new ConnectionPool();dns = Dns.SYSTEM;followSslRedirects = true;followRedirects = true;retryOnConnectionFailure = true;connectTimeout = 10_000;readTimeout = 10_000;writeTimeout = 10_000;
}
- 发起 HTTP 请求(同步/异步请求)
- 同步网络请求
Request request = new Request.Builder().url("http://www.baidu.com").build();Response response = client.newCall(request).execute();String result = response.body().string();
OkHttpClient实现了Call.Factory,负责根据请求创建新的Call。通过newCall创建RealCall类实例,由RealCall负责进行网络请求操作
@Override public Call newCall(Request request) {return new RealCall(this, request);
}
RealCall#execute:
@Override public Response execute() throws IOException {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed"); // (1)executed = true;}try {client.dispatcher().executed(this); // (2)Response result = getResponseWithInterceptorChain(); // (3)if (result == null) throw new IOException("Canceled");return result;} finally {client.dispatcher().finished(this); // (4)}
}
RealCall 做了4件事:
- 检查这个 call 是否已经被执行:每个 call 只能被执行一次,如果想要一个完全一样的 call,可以利用call#clone方法进行克隆。
- 利用client.dispatcher().executed(this)通知开始执行
- 调用getResponseWithInterceptorChain()函数发出网络请求,并解析返回 HTTP 结果。
- 利用client.dispatcher().finished(this)通知已经执行完毕。
dispatcher是OkHttpClient.Builder的成员之一,dispatcher是用于异步 HTTP请求的执行策略,在同步请求它中只用于通知执行状态。
真正发出网络请求,解析返回结果的,还是getResponseWithInterceptorChain:
private Response getResponseWithInterceptorChain() throws IOException {// Build a full stack of interceptors.List<Interceptor> interceptors = new ArrayList<>();interceptors.addAll(client.interceptors());interceptors.add(retryAndFollowUpInterceptor);interceptors.add(new BridgeInterceptor(client.cookieJar()));interceptors.add(new CacheInterceptor(client.internalCache()));interceptors.add(new ConnectInterceptor(client));if (!retryAndFollowUpInterceptor.isForWebSocket()) {interceptors.addAll(client.networkInterceptors());}interceptors.add(new CallServerInterceptor(retryAndFollowUpInterceptor.isForWebSocket()));Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest);return chain.proceed(originalRequest);
}
the whole thing is just a stack of built-in interceptors.
—— OkHttp 作者
Interceptor是 OkHttp 最核心的一个东西,它不仅只负责拦截请求进行一些额外的处理(如cookie),实际上它把实际的网络请求、缓存、透明压缩等功能统一了起来,每个功能都是一个Interceptor,连接起来成了一个Interceptor.Chain,环环相扣,最终完成一次完整的网络请求。
Interceptor.Chain 分布依次是:
- 在配置OkHttpClient时设置的interceptors;
- 负责失败重试以及重定向的RetryAndFollowUpInterceptor;
- 负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的BridgeInterceptor;
- 负责读取缓存直接返回、更新缓存的CacheInterceptor;
- 负责和服务器建立连接的ConnectInterceptor;
- 配置OkHttpClient时设置的networkInterceptors;
- 负责向服务器发送请求数据、从服务器读取响应数据CallServerInterceptor。
在这里,位置决定了功能,最后一个 Interceptor 一定是负责和服务器实际通讯的,重定向、缓存等一定是在实际通讯之前的。
责任链模式在这个Interceptor链条中得到了很好的实践。
责任链包含了一些命令对象和一系列的处理对象,每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。
对于把Request变成Response这件事来说,每个Interceptor都可能完成这件事,所以我们循着链条让每个Interceptor自行决定能否完成任务以及怎么完成任务(自力更生或者交给下一个Interceptor)。这样一来,完成网络请求这件事就彻底从RealCall类中剥离了出来,简化了各自的责任和逻辑。两个字:优雅!
责任链模式在安卓系统中也有比较典型的实践,例如 view 系统对点击事件(TouchEvent)的处理。
Interceptor 实际上采用了一种分层的思想,每个Interceptor都是一层。分层简化了每一层的逻辑,每层只需要关注自己的责任(单一原则思想),而各个层之间通过约定的接口/协议进行合作(面向接口编程思想),共同完成复杂的任务。
这种分层的思想在TCP/IP协议(4层协议)中体现的淋漓尽致。
OkHttp 主要通过ConnectInterceptor和CallServerInterceptor和服务器的进行实际通信。
- 建立连接 ConnectInterceptor
@Override
public Response intercept(Chain chain) throws IOException {RealInterceptorChain realChain = (RealInterceptorChain) chain;Request request = realChain.request();StreamAllocation streamAllocation = realChain.streamAllocation();// We need the network to satisfy this request. Possibly for validating a conditional GET.boolean doExtensiveHealthChecks = !request.method().equals("GET");HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);RealConnection connection = streamAllocation.connection();return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
实际上建立连接就是创建了一个HttpCodec对象,用于后面通信中发送和接受数据。它是对HTTP协议操作的抽象,有两个实现:Http1Codec和Http2Codec,分别对应 HTTP/1.1 和 HTTP/2 版本的实现。
创建HttpCodec对象过程就是找到一个可用的RealConnection,再利用RealConnection的输入输出(BufferdSource 和 BufferedSink)创建HttpCodec对象,供后续步骤使用。
在Http1Codec中,它利用Okio对Socket的读写操作进行封装,Okio是对java.io和java.nio进行了封装,让我们更便捷高效的进行
IO 操作。
- 发送和接受数据 CallServerInterceptor
@Override
public Response intercept(Chain chain) throws IOException {HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();Request request = chain.request();long sentRequestMillis = System.currentTimeMillis();httpCodec.writeRequestHeaders(request);if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);request.body().writeTo(bufferedRequestBody);bufferedRequestBody.close();}httpCodec.finishRequest();Response response = httpCodec.readResponseHeaders().request(request).handshake(streamAllocation.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();if (!forWebSocket || response.code() != 101) {response = response.newBuilder().body(httpCodec.openResponseBody(response)).build();}if ("close".equalsIgnoreCase(response.request().header("Connection"))|| "close".equalsIgnoreCase(response.header("Connection"))) {streamAllocation.noNewStreams();}// 省略部分检查代码return response;
}
向服务器发送 request header;如果有 request body,就向服务器发送;
读取 response header,先构造一个Response对象;如果有 response body,就在 header的基础上加上 body 构造一个新的Response对象;
核心操作由HttpCodec对象完成,HttpCodec封装Okio,Okio封装Socket。完成网络的通信。异步网络请求
Request request = new Request.Builder().url("http://www.baidu.com").get().build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {System.out.println(response.body().string());}});
实际调用RealCall#enqueue
@Override public void enqueue(Callback responseCallback) {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}client.dispatcher().enqueue(new AsyncCall(responseCallback));
}// Dispatcher#enqueue
synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);executorService().execute(call);} else {readyAsyncCalls.add(call);}
}
dispatcher 用于对异步请求进行分发、执行。
如果当前还能执行一个并发请求,那就立即执行,否则加入readyAsyncCalls队列。
正在执行的请求执行完毕之后,会调用promoteCalls()函数,来把readyAsyncCalls队列中的AsyncCall “提升” 为runningAsyncCalls,并开始执行。
这里的AsyncCall是RealCall的一个内部类,它实现了Runnable,所以可以被提交到ExecutorService上执行,而它在执行时会调用getResponseWithInterceptorChain()函数,并把结果通过responseCallback传递给上层使用者。
这样看来,同步请求和异步请求的原理是一样的,都是在getResponseWithInterceptorChain()函数中通过Interceptor链条来实现的网络请求逻辑,而异步则是通过ExecutorService实现。
- 获取 HTTP 响应
在上述同步(Call#execute()执行之后)或者异步(Callback#onResponse()回调中)请求完成之后,我们就可以从Response对象中获取到响应数据了,包括 HTTP status code,status message,response header,response body 等。这里 body 部分最为特殊,因为服务器返回的数据可能非常大,所以必须通过数据流的方式来进行访问(当然也提供了诸如string()和bytes()这样的方法将流内的数据一次性读取完毕),而响应中其他部分则可以随意获取。
- 发送和接受数据 CallServerInterceptor
if (!forWebSocket || response.code() != 101) {response = response.newBuilder().body(httpCodec.openResponseBody(response)).build();
}HttpCodec#openResponseBody
提供具体 HTTP 协议版本的响应 body
HttpCodec 利用 Okio 实现具体的数据 IO 操作
- HTTP缓存
- 读取 & 缓存数据 CallServerInterceptor
在建立连接、和服务器通讯之前,检查响应是否已经被缓存、缓存是否可用,如果是则直接返回缓存的数据,否则就进行后面的流程,并在响应返回之前,把网络的数据写入缓存。
具体的缓存逻辑 OkHttp 内置封装了一个Cache类,它利用DiskLruCache,用磁盘上的有限大小空间进行缓存,按照 LRU 算法进行缓存淘汰。
总结
- OkHttpClient实现Call.Factory,负责为Request创建Call;
- RealCall为具体的Call实现,其enqueue()异步接口通过Dispatcher利用ExecutorService + AsyncCall队列 实现,而最终进行网络请求时和同步execute()接口一致,都是通过getResponseWithInterceptorChain()函数实现;
- getResponseWithInterceptorChain()中利用Interceptor拦截链,将缓存、透明压缩、网络 IO 等功能统一起来,层层实现,最终完成一次完整的网络请求。
设计模式
- 建造者模式
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {......final @Nullable Cache cache;......//从Builder中获取属性值 OkHttpClient(Builder builder) {......this.cache = builder.cache; }//构造者public static final class Builder {Cache cache;......//构造cache属性值public Builder cache(@Nullable Cache cache) {this.cache = cache;return this;}//在build方法中真正创建OkHttpClient对象,并传入前面构造的属性值public OkHttpClient build() {return new OkHttpClient(this);}}
}
//在创建OkHttpClient的时候
OkHttpClient client = new OkHttpClient.Builder().cache(/*创建cache对象*/).build();
- 工厂模式
public interface Call extends Cloneable {Request request();Response execute() throws IOException; void enqueue(Callback responseCallback);void cancel(); boolean isExecuted(); boolean isCanceled(); Call clone();//创建Call实现对象的工厂interface Factory {//创建新的Call,里面包含了Request对象。Call newCall(Request request);}
}
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {@Override public Call newCall(Request request) {return RealCall.newRealCall(this, request, false /* for web socket */);}
}
final class RealCall implements Call {......
}
- 观察者模式
源码中的EventListener对请求/响应过程中的每一个Event通过方法回调的方式通知前方用户,用户需要自己实现EventListener中的所需要的方法:
public abstract class EventListener {...public void requestHeadersStart(Call call) {}public void requestHeadersEnd(Call call, Request request) {}public void requestBodyStart(Call call) {}public void requestBodyEnd(Call call, long byteCount) {}public void responseHeadersStart(Call call) {}public void responseHeadersEnd(Call call, Response response) {}public void responseBodyStart(Call call) {}public void responseBodyEnd(Call call, long byteCount) {}...
}
- 单例模式
创建OkHttpClient对象的时候,就推荐使用单例模式,防止创建多个OkHttpClient对象,损耗资源;
- 策略模式
在CacheInterceptor中,在响应数据的选择中使用了策略模式,选择缓存数据还是选择网络访问。
CacheInterceptor根据一个缓存策略,来决定选择缓存数据,还是网络请求数据:
- 请求头包含 “If-Modified-Since” 或 “If-None-Match” 暂时不走缓存
- 客户端通过 cacheControl 指定了无缓存,不走缓存
- 客户端通过 cacheControl 指定了缓存,则看缓存过期时间,符合要求走缓存。
- 如果走了网络请求,响应状态码为 304(只有客户端请求头包含 “If-Modified-Since” 或 “If-None-Match” ,服务器数据没变化的话会返回304状态码,不会返回响应内容), 表示客户端继续用缓存。
- 责任链模式
okhttp可以针对请求配置很多拦截器,而这些拦截器正是通过责任链模式链接起来,并最终返回处理的结果。
封装
由于okhttp是偏底层的网络请求类库,返回结果的回调方法仍然执行在子线程中,需要自己跳转到UI线程,使用麻烦。为了使用方便需要对OKHttp进行再次封装。
guozhengXia/OkHttpUtils
最简单的okhttp封装,CallBack方法执行在UI线程。支持get请求,post请求,支持文件上传和下载。
网络框架对比
Glide
简介
Glide,一个被google所推荐的图片加载库,作者是bumptech。这个库被广泛运用在google的开源项目中,包括2014年的google I/O大会上发布的官方app。
Glide滑行的意思,可以看出这个库的主旨就在于让图片加载变的流畅。
- 多样化媒体加载
Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video - 生命周期绑定 & 动态管理
- 高效缓存策略
(1)支持Memory和Disk图片缓存
(2)内存开销小(Glide根据ImageView大小缓存图片 & 使用合理的解码方式)
使用
- 导入
implementation 'com.github.bumptech.glide:glide:3.7.0' implementation 'com.android.support:support-v4:23.2.1'
- 基础使用
(1)基本方法
String url = "http://img1.dzwww.com:8080/tupian_pl/20150813/16/7858995348613407436.jpg";
ImageView imageView = (ImageView) findViewById(R.id.imageView);
Glide.with(context).load(url).into(imageView);
- with(Context context) 决定Glide加载图片的生命周期
可以使用 Activity、FragmentActivity、android.support.v4.app.Fragment、android.app.Fragment 等对象。将 Activity/Fragment 对象作为参数的好处是,图片的加载会和 Activity/Fragment 的生命周期保持一致,例如:onPaused 时暂停加载,onResume 时又会自动重新加载。所以在传参的时候建议使用 Activity/Fragment 对象,而不是 Context。 - load(String url) 加载图片URL
url包括网络图片、本地图片、应用资源、二进制流、Uri对象等等(重载) - into(ImageView imageView) 需要显示图片的目标 ImageView
(2)扩展方法
Glide.with(context).load(url)// 占位符:出现图片加载慢或者加载不出来的情况.placeholder(R.drawable.place_image)// 加载图片过程占位符,加载完成会替换占位符.error(R.drawable.error_image) // 加载图片错误占位符.thumbnail( 0.2f ) // 显示原始图片20%大小作为略缩图.crossFade() // 开启Glide默认图片淡出淡入动画.override(width,height) // 指定图片大小(Glide会自动判断ImageView的大小,然后将对应的图片像素加载本地,节省内存开支).centerCrop() // 将图片按比例缩放到可以完全填充ImageView,图片可能显示不完整.fitCenter() // 将图片缩放到小于等于ImageView尺寸,图片一定显示完整.diskCacheStrategy(DiskCacheStrategy.RESULT) // 图片缓存策略:只缓存最终加载图.diskCacheStrategy(DiskCacheStrategy.NONE) // 禁用Glide缓存机制.diskCacheStrategy(DiskCacheStrategy.SOURCE) // 只缓存全尺寸加载图.priority(Priority.HIGH) // 对于同一时间加载多个图片,优先加载对于用户更重要的图片.asGif() // 只显示动态图.asBitmap() // 只显示静态图(不设置时,Glide会自动判断图片格式).into(imageView);
- 进阶使用
Target
Transformations
通过 Transformations 操作 bitmap 来实现,我们可以修改图片的任意属性:尺寸,范围,颜色,像素位置等等。fitCenter 和 centerCrop ,这两个是 Glide 已经实现的Transformations。
自定义Transformation,继承BitmapTransformation接口
图片切圆角操作
public class RoundTransformation extends BitmapTransformation {private float radius = 0f;public RoundTransformation(Context context) {this(context, 4);}public RoundTransformation(Context context, int px) {super(context);this.radius = px;}@Overrideprotected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {return roundCrop(pool, toTransform);}private Bitmap roundCrop(BitmapPool pool, Bitmap source) {if (source == null)return null;Bitmap result = pool.get(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);if (result == null) {result = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);}Canvas canvas = new Canvas(result);Paint paint = new Paint();paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));paint.setAntiAlias(true);RectF rectF = new RectF(0f, 0f, source.getWidth(), source.getHeight());canvas.drawRoundRect(rectF, radius, radius, paint);return result;}@Overridepublic String getId() {return getClass().getName() + Math.round(radius);}}
图片顺时针旋转90度操作
public class RotateTransformation extends BitmapTransformation {private float rotateRotationAngle = 0f;public RotateTransformation(Context context, float rotateRotationAngle) {super( context );this.rotateRotationAngle = rotateRotationAngle;}@Overrideprotected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {Matrix matrix = new Matrix();matrix.postRotate(rotateRotationAngle);return Bitmap.createBitmap(toTransform, 0, 0, toTransform.getWidth(), toTransform.getHeight(), matrix, true);}@Overridepublic String getId() {return getClass().getName() + Math.round(rotateRotationAngle);}
}
使用
// 单个Transformation
Glide.with(context).load(mUrl).transform(new RoundTransformation(context , 20)).into(mImageView);// 多个Transformation不能使用链式形式多次调用,否则之前的配置会被覆盖
// 把一个图片切圆角,然后做了顺时针旋转90度处理。
Glide.with(context).load(mUrl).transform(new RoundTransformation(context , 20) , new RotateTransformation(context , 90f)).into(mImageView);
这里有一个 GLide Transformations 的库,它提供了很多 Transformation 的实现,非常值得去看,不必重复造轮子对吧!
wasabeef/glide-transformations
- Animate
自定义缩放动画
<set xmlns:android="http://schemas.android.com/apk/res/android"android:fillAfter="true"><scaleandroid:duration="@android:integer/config_longAnimTime"android:fromXScale="0.1"android:fromYScale="0.1"android:pivotX="50%"android:pivotY="50%"android:toXScale="1"android:toYScale="1"/>
</set>
使用
Glide.with(context).load(mUrl).transform(new RoundTransformation(this , 20)).animate( R.anim.zoom_in ).into(mImageView);
- Modules
工作原理(非重点)
- 主流程
三件大事:
- 准备数据
Glide中存在大量的类和对象,Glide在初始时,就尽量把所有需要使用的对象构造并封装,层层传递。
第一阶段:构建GenericRequest对象(面向用户构建,受用户调用API或修改配置所影响)
第二阶段:从GenericRequest对象中,解封得到需要对象,构建出decodeJob对象,是异步处理中核心对象(面向Glide构建) - 异步处理
经过前面大量准备工作,这一步,Glide在工作线程中进行图片数据请求,包括三步:
(1)发起网络请求,拿到数据流;
(2)将数据流解码成bitmap对象;
(3)将bitmap对象转码成Drawable对象 - 切换到主线程
切换为主线程,将Drawable对象显示
- 源码解读
Glide.with(this).load(url).into(imageView);
- with(Context/Activity/Fragment)
得到一个RequestManager对象(实现request和Activity/Fragment生命周期的关联)
Glide再根据传入的with()方法的参数确定图片加载的生命周期:
Application类型参数——应用程序生命周期
非Application类型参数——Activity/Fragment生命周期
load(url)
实质上还是做前期的数据准备,主要就是构造对象,封装对象。
得到一个DrawableTypeRequest对象(extends DrawableRequestBuilder)
- into(imageView)
into比较复杂,其涉及了“准备数据”,“异步处理”,“切换到主线程”这三大步的内容。包括:
(1)准备过程 第一阶段:构造出GenericRequest对象(封装了Glide中所有的相关对象)
(2)准备过程 第二阶段:使用 第一阶段 生成的GenericRequest对象,从GenericRequest对象取出各种需要的对象,传递给Engine的load函数,最终构造出了decodeJob对象。
(3)异步调用 : 使用decodeJob 对象进行:
- 发起网络请求,拿到数据流
- 将数据流解码成bitmap对象
- 将bitmap对象转码成Drawable对象(保证静图和动图的类型一致性(动图的类型是Drawable))
(4)切换到主线程,显示Drawable对象
通过Handler机制,Glide从工作线程切换到主线程,并最终将Drawable对象显示到ImageView上。
图片加载框架对比
第十二章 Android第三方库源码相关推荐
- 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高...
第十一章 搭建云端服务器 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结. 第十三章 Android实例提高 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所 ...
- 别人家SDK的设计模式——Android Retrofit库源码解读
作者:网易合作产品部·李若昆 我们在日常编写代码中免不了会用到各种各样第三方库,网络请求.图片加载.数据库等等.有些lib接入可能方便到几行代码搞定,有些lib可能从demo.文档到测试都是坑(比如l ...
- 【SpringBoot】十二、@Scheduled定时任务(源码)
@Scheduled是Spring task的基于注解的使用方法.Spring task是spring自主开发的轻量级定时任务框架.但是本身不支持持久化,也没有推出官方的分布式集群模式,只能靠开发者在 ...
- Android 第三方库--2017年Android开源项目及库汇总
转自:http://blog.csdn.net/jsonnan/article/details/62215287 东西有点多,但是资源绝对nice,自己都全部亲身体验过了,大家可放心使用 github ...
- Android实战技巧之十二:Android Studio导入第三方类库、jar包和so库
第三方类库源码 将一网友的XMPP代码从ADT转到AS时,发现其使用了第三方类库,源码放在了lib下,直接在AS中Import project,第三方类库并没有自动导入进来,看来需要自己动手了. 项目 ...
- android studio导入jar包和so库,Android实战技巧之十二:Android Studio导入第三方类库、jar包和so库(示例代码)...
第三方类库源码 将一网友的XMPP代码从ADT转到AS时,发现其使用了第三方类库,源码放在了lib下,直接在AS中Import project,第三方类库并没有自动导入进来,看来需要自己动手了. 项目 ...
- Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验
Android群英传笔记--第十二章:Android5.X 新特性详解,Material Design UI的新体验 第十一章为什么不写,因为我很早之前就已经写过了,有需要的可以去看 Android高 ...
- 唐 库利超级计算机,第七卷 乖离性 百万亚瑟王_第二百五十二章 绝望中的希望...
第七卷 乖离性 百万亚瑟王_第二百五十二章 绝望中的希望 赫布里底训练大厅. "诸位,结果已经分析出来了,丘库林,也就是被你们捉到的闯入者,他身上的神装的确是断绝时代的遗物不错,而且,这件神 ...
- android studio开发十一 Library第三方类库源码, Jar包和SO库
原创: 发现android里面不熟悉的东西太多了,查了好多资料,终于搞清楚 Library第三方类库源码, Jar包和SO库这些都是干什么的了,怎么使用它们了. [Library第三方类库源码] Ja ...
- Android群英传读书笔记——第十二章:Android 5.X新特性详解
第十二章目录 12.1 Android5.X UI设计初步 12.1.1 材料的形态模拟 12.1.2 更加真实的动画 12.1.3 大色块的使用 12.2 Material Design主题 12. ...
最新文章
- Java非阻塞I/O模型之NIO说明
- python迭代器和生成器_python中迭代器和生成器。
- Hibernate一对一关联------主键关联(亲测成功)
- 字节跳动专家会_字节跳动招聘直播策略运营专家/经理/海外财务AR BP,ACCA优先...
- 相信什么,生命就走向什么
- LeetCode 1457. 二叉树中的伪回文路径(位运算+递归)
- 项目架构之传统三层架构和领域模型三层架构
- 陈硕网络编程实战视频教程目录
- pythonthreading模块_python多线程之 threading模块详解
- HTTP和HTTPS有什么区别? 什么是SSL证书?使用ssl证书优势?
- ISA 发布内网 NLB
- LaTeX的长度单位
- python爬取招聘网站视频教程_Python爬取拉钩招聘网
- STM32学习笔记---触摸屏
- dubbo启动失败,不报错 Stopping service [Tomcat] was destroying! has been built.
- 海德汉 LSV2 协议采集 2
- 虚拟机 linux无法与本地计算机互通的处理办法
- HP-UX Samba服务配置手册
- 如何把Windows10打造成Mac
- 【设计模式】一、是什么,为什么,怎么学
热门文章
- 一个简单的爬虫例子(代码)
- oracle sql列转行_oracle 行转列 列转行 转载
- CC1101/CC1100、CC2540/CC2541的比较
- java520.1314表白_520表白套路对话大全 表白算式521.1314大全
- ”教你如何抓住短视频时代风口,进行流量红利变现
- 南京邮电大学电子电路课程设计可编程音乐自动演奏电路
- 苹果4s怎么越狱教程_苹果手机:iPhone 11/XS系列iOS13-13.3越狱教程
- INSAR学习(小白笔记一)
- 模拟、数字基带/频带通信系统:编码、信源/信道编码、调制、码间串扰
- 怎么做好论文查重,分享几个重查方法