https://github.com/greenrobot/EventBus
EventBus原理解析笔记以及案例实战(结合demo)
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;
@Override
protected 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() {
@Override
public 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
*/
@Override
protected 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
mButton = findViewById(R.id.bt_sendMsg);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 点击按钮,发布事件
EventBus.getDefault().post(new MyEvent("Event From Publisher"));
}
});
}
}
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官方原理图
发布者(Publisher)只需要post一个event之后就不用管了,EventBus内部会将event逐一分发给订阅此event的订阅者(Subscriber)
EventBus用于应用内消息事件传递,方便快捷,耦合性低
代码实例
订阅者(Subscriber)
public class EventBusMain extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_main);
EventBus.getDefault().register(this);
}
- 订阅的事件 onEvent1
@Subscribe
public void onEvent1(RemindBean bean){
}
- 订阅的事件 onEvent2
@Subscribe
public void onEvent2(UserInfo bean){
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}
发布者(Publisher)
EventBus.getDefault().post(new RemindBean())
源码解析
EventBus.getDefault().register(this);
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
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:
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/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
- 2. 循环遍历所有的方法
for (Method method : methods) {
- 3. 获取方法的修饰符
int modifiers = method.getModifiers();
- 4.判断修饰符,订阅方法的修饰符不能是private,static
if ((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. 获取第一个参数eventType
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
- 10. 获取注解的mode,就是我们在注解上标识的,
有mainThread,Posting,background,async
ThreadMode 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");
}
}
}
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 是否在 posting
if (!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;
}
}
}
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;
}
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);
}
}
OkHttp是一个处理网络请求的开源项目,是Android端最火热的轻量级框架,由移动支付Square公司贡献用于替代HttpUrlConnection和Apache HttpClient。
之所以可以赢得如此多开发者的喜爱,主要得益于如下特点:
SPDY(读作“SPeeDY”)是Google开发的基于TCP的应用层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY是对HTTP协议的加强。新协议的功能包括数据流的多路复用、支持服务器推送技术、请求优先级、HTTP报头压缩以及强制使用SSL传输协议。
public void getDatasync(){
new Thread(new Runnable() {
@Override
public 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();
}
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() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public 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 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;
}
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件事:
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 分布依次是:
在这里,位置决定了功能,最后一个 Interceptor 一定是负责和服务器实际通讯的,重定向、缓存等一定是在实际通讯之前的。
责任链模式在这个Interceptor链条中得到了很好的实践。
责任链包含了一些命令对象和一系列的处理对象,每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。
对于把Request变成Response这件事来说,每个Interceptor都可能完成这件事,所以我们循着链条让每个Interceptor自行决定能否完成任务以及怎么完成任务(自力更生或者交给下一个Interceptor)。这样一来,完成网络请求这件事就彻底从RealCall类中剥离了出来,简化了各自的责任和逻辑。两个字:优雅!
责任链模式在安卓系统中也有比较典型的实践,例如 view 系统对点击事件(TouchEvent)的处理。
Interceptor 实际上采用了一种分层的思想,每个Interceptor都是一层。分层简化了每一层的逻辑,每层只需要关注自己的责任(单一原则思想),而各个层之间通过约定的接口/协议进行合作(面向接口编程思想),共同完成复杂的任务。
这种分层的思想在TCP/IP协议(4层协议)中体现的淋漓尽致。
OkHttp 主要通过ConnectInterceptor和CallServerInterceptor和服务器的进行实际通信。
@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 操作。
@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() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public 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实现。
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
HttpCodec#openResponseBody
提供具体 HTTP 协议版本的响应 body
HttpCodec 利用 Okio 实现具体的数据 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根据一个缓存策略,来决定选择缓存数据,还是网络请求数据:
由于okhttp是偏底层的网络请求类库,返回结果的回调方法仍然执行在子线程中,需要自己跳转到UI线程,使用麻烦。为了使用方便需要对OKHttp进行再次封装。
guozhengXia/OkHttpUtils
最简单的okhttp封装,CallBack方法执行在UI线程。支持get请求,post请求,支持文件上传和下载。
Glide,一个被google所推荐的图片加载库,作者是bumptech。这个库被广泛运用在google的开源项目中,包括2014年的google I/O大会上发布的官方app。
Glide滑行的意思,可以看出这个库的主旨就在于让图片加载变的流畅。
implementation 'com.github.bumptech.glide:glide:3.7.0'
implementation 'com.android.support:support-v4:23.2.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);
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;
}
@Override
protected 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;
}
@Override
public 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;
}
@Override
protected 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);
}
@Override
public 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
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<scale
android: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);
Glide.with(this).load(url).into(imageView);
得到一个RequestManager对象(实现request和Activity/Fragment生命周期的关联)
Glide再根据传入的with()方法的参数确定图片加载的生命周期:
Application类型参数——应用程序生命周期
非Application类型参数——Activity/Fragment生命周期
load(url)
实质上还是做前期的数据准备,主要就是构造对象,封装对象。
得到一个DrawableTypeRequest对象(extends DrawableRequestBuilder)
into比较复杂,其涉及了“准备数据”,“异步处理”,“切换到主线程”这三大步的内容。包括:
(1)准备过程 第一阶段:构造出GenericRequest对象(封装了Glide中所有的相关对象)
(2)准备过程 第二阶段:使用 第一阶段 生成的GenericRequest对象,从GenericRequest对象取出各种需要的对象,传递给Engine的load函数,最终构造出了decodeJob对象。
(3)异步调用 : 使用decodeJob 对象进行: