本文是中的一篇,内容包括
- LeakCanary
- Okhttp
- Retrofit
- Fresco 使用
- EventBus
LeakCanary
- 在Application中注册一个ActivityLifecycleCallbacks来监听Activity的销毁
- 通过IdleHandler在主线程空闲时进行检测
- 确认有泄漏后,dump hprof文件,并开启一个进程IntentService通过HAHA进行分析
OkHttp(基于3.9版本)
1. 在gradle中添加依赖
compile 'com.squareup.okhttp3:okhttp:3.9.0'compile 'com.squareup.okio:okio:1.13.0'复制代码
2. 创建OkHttpClient,并对timeout等进行设置
File sdcache = getExternalCacheDir();int cacheSize = 10 * 1024 * 1024;OkHttpClient.Builder builder = new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .writeTimeout(20, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));OkHttpClient mOkHttpClient=builder.build();复制代码
3. 创建Request
- get请求
Request request = new Request.Builder() .url("http://www.baidu.com") .build();复制代码
- post请求(post需要传入requsetBody)
RequestBody formBody = new FormEncodingBuilder() .add("size", "10") .build(); Request request = new Request.Builder() .url("http://api.1-blog.com/biz/bizserver/article/list.do") .post(formBody) .build();复制代码
4. 创建Call并执行(okHttp的返回结果并没有在ui线程)
Call call = mOkHttpClient.newCall(request);复制代码
- 同步执行
Response mResponse=call.execute(); if (mResponse.isSuccessful()) { return mResponse.body().string(); } else { throw new IOException("Unexpected code " + mResponse); }复制代码
- 异步执行
call.enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { } @Override public void onResponse(Response response) throws IOException { String str = response.body().string(); Log.i("wangshu", str); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show(); } }); } });复制代码
4. 封装
因为以下原因,所以我们需要封装:
- 避免重复代码编写
- 请求的回调改为UI线程
- 其他需要的逻辑:例如加解密等
OkHttp中的设计模式
- Builder模式:OkHttpClient 和Request等都是通过Builder模式创建的
- 责任链模式:拦截器通过责任链模式进行工作
- 门面模式:整体采用门面模式,OkHttpClient为门面,向子系统委派任务
- 享元模式:连接池等采用了享元模式
- 其他:工厂模式、代理模式等
1. Call
- Call的实现类为RealCall
- 在执行execute或者enqueue时,会取出okHttpClient中的Dispatcher执行对应的方法
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));复制代码
2. Diapatcher
- Diapatcher在OkHttpClient build时进行初始化
- Dispatcher负责进行任务调度,内部维护一个线程池,处理并发请求
- Dispatcher内部有三个队列
/** 将要运行的异步请求队列 */private final DequereadyAsyncCalls = new ArrayDeque<>();/**正在运行的异步请求队列 */private final Deque runningAsyncCalls = new ArrayDeque<>();/** 正在运行的同步请求队列 */private final Deque runningSyncCalls = new ArrayDeque<>();复制代码
- 执行时,线程会调用AsyncCall的excute方法
3. AsyncCall
- AsyncCall是RealCall的一个内部类,实现了Runnalbe接口
- AsyncCall 通过 getResponseWithInterceptorChain方法取得Response
- 执行完毕后通过client.dispatcher().finished(this);将自身从dispatcher队列中取出,并取出下一个加入相应队列
//AsyncCall 的excute方法@Override protected void execute() { boolean signalledCallback = false; try { Response response = getResponseWithInterceptorChain(forWebSocket); if (canceled) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e); } else { responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); }}复制代码
4. getResponseWithInterceptorChain
getResponseWithInterceptorChain是用责任链的方式,执行拦截器,对请求和请求结果进行处理
- getResponseWithInterceptorChain 中创建拦截器,并创建第一个RealInterceptorChain,执行其proceed方法
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. Listinterceptors = 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 (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); }复制代码
- RealInterceptorChain的proceed方法中,会取出拦截器,并创建下一个Chain,将其作为参数传给拦截器的intercept方法
// If there's another interceptor in the chain, call that. if (index < client.interceptors().size()) { Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket); //从拦截器列表取出拦截器 Interceptor interceptor = client.interceptors().get(index); Response interceptedResponse = interceptor.intercept(chain); if (interceptedResponse == null) { throw new NullPointerException("application interceptor " + interceptor + " returned null"); } return interceptedResponse; } // No more interceptors. Do HTTP. return getResponse(request, forWebSocket);}复制代码
拦截器
1.
- 自定义拦截器分为两类,interceptor和networkInterceptor(区别:networkInterceptor处理网络相关任务,如果response直接从缓存返回了,那么有可能不会执行networkInterceptor)
- 自定义方式:实现Interceptor,重写intercept方法,并注册拦截器
2.
- RetryAndFollowUpInterceptor:进行失败重试和重定向
- BridgeInterceptor:添加头部信息
- CacheInterceptor:处理缓存
- ConnectInterceptor:获取可用的connection实例
- CallServerInterceptor:发起请求
在ConnectInterceptor中,我们获取到了connection的实例,该实例是从ConnectionPool中取得
1. Connection
- Connection 是客户端和服务器建立的数据通路,一个Connection上可能存在几个链接
- Connection的实现类是RealConnection,是socket物理连接的包装
- Connection内部维持着一个List<Reference>引用
2. StreamAllocation
StreamAllocation是Connection维护的连接,以下是类内注解
- *
- Connections: physical socket connections to remote servers. These are * potentially slow to establish so it is necessary to be able to cancel a connection * currently being connected. *
- Streams: logical HTTP request/response pairs that are layered on * connections. Each connection has its own allocation limit, which defines how many * concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream * at a time, HTTP/2 typically carry multiple. *
- Calls: a logical sequence of streams, typically an initial request and * its follow up requests. We prefer to keep all streams of a single call on the same * connection for better behavior and locality. *
3. ConnectionPool
ConnectionPool通过Address等来查找有没有可以复用的Connection,同时维护一个线程池,对Connection做回收工作
Retrofit
Retrofit帮助我们对OkHttp进行了封装,使网络请求更加方便
1. 添加依赖
dependencies { compile 'com.squareup.retrofit2:retrofit:2.0.2' }复制代码
2. 创建Retrofit实例
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://fanyi.youdao.com/") // 设置网络请求的Url地址 .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平台 .build();复制代码
3. 创建网络接口
@GET("user")CallgetUser(@Header("Authorization") String authorization)复制代码
4. 创建Call
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);//对 发送请求 进行封装Callcall = request.getCall();复制代码
5. 执行Call的请求方法
//发送网络请求(异步) call.enqueue(new Callback() { //请求成功时回调 @Override public void onResponse(Call call, Response response) { //请求处理,输出结果 response.body().show(); } //请求失败时候的回调 @Override public void onFailure(Call call, Throwable throwable) { System.out.println("连接失败"); } }); // 发送网络请求(同步) Response response = call.execute();复制代码
1. Retrofit
Retrofit 通过builder模式创建,我们可以对其进行各种设置:
- baseUrl:请求地址的头部,必填
- callFactory:网络请求工厂(不进行设置的话默认会生成一个OkHttpClient)
- adapterFactories:网络请求适配器工厂的集合,这里有适配器因为Retrofit不仅支持Android,还支持Ios等其他平台(不进行设置的话会根据平台自动生成)
- converterFactories:数据转换器工厂的集合(将网络返回的数据转换成我们需要的类)
- callbackExecutor:回调方法执行器(Android平台默认通过Handler发送到主线程执行)
2. Call
我们的每个method对应一个Call, Call的创建分为两步:
- retorfit.create(myInfterfaceClass.class)创建我们网络请求接口类的实例
- 调用对应方法拿到对应网络请求的Call
关键在第一步,第一步是通过动态代理实现的
publicT create(final Class service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class [] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod serviceMethod = loadServiceMethod(method);//1 OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } });}复制代码
- 通过loadServiceMethod方法生成mehtod对应的ServiceMethod
- 将ServiceMethod和方法参数传进OkHttpCall生成OkHttpCall
- 调用callAdapter方法对OkHttpCall进行处理并返回
1. ServiceMethod
loadServiceMethod方法会首先在缓存里查找是否有该method对应的ServiceMethod,没有的话调用build方法创建一个
ServiceMethod loadServiceMethod(Method method) { ServiceMethod result; // 设置线程同步锁 synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); // ServiceMethod类对象采用了单例模式进行创建 // 即创建ServiceMethod对象前,先看serviceMethodCache有没有缓存之前创建过的网络请求实例 // 若没缓存,则通过建造者模式创建 serviceMethod 对象 if (result == null) { // 下面会详细介绍ServiceMethod生成实例的过程 result = new ServiceMethod.Builder(this, method).build(); serviceMethodCache.put(method, result); } } return result;}复制代码
ServiceMethod的创建过程即是对method的解析过程,解析过程包括:对注解的解析,寻找合适的CallAdapter和Convert等
2. OkHttpCall
OkHttpCall实现了Call接口,当执行excute或enqueue请求命令时,内部通过传入的CallFactory(OkHttpClient)执行网络请求
3. callAdapter
如果我们没有对CallAdapter进行设置,它的值将是Android平台的默认设置,其adapt方法如下
publicCall adapt(Call call) { return new ExecutorCallbackCall<>(callbackExecutor, call); } ExecutorCallbackCall(Executor callbackExecutor, Call delegate) { this.delegate = delegate; // 把上面创建并配置好参数的OkhttpCall对象交给静态代理delegate // 静态代理和动态代理都属于代理模式 // 静态代理作用:代理执行被代理者的方法,且可在要执行的方法前后加入自己的动作,进行对系统功能的拓展 this.callbackExecutor = callbackExecutor; // 传入上面定义的回调方法执行器 // 用于进行线程切换 }复制代码
ExecutorCallbackCall对OkHttpCall进行了装饰,会调用CallBackExcutor对OkHttpCall执行的返回结果进行处理,使其位于主线程
Fresco是一个图片加载库,可以帮助我们加载图片显示,控制多线程,以及管理缓存和内存等
- 引入依赖
dependencies { // 其他依赖 compile 'com.facebook.fresco:fresco:0.12.0' // 在 API < 14 上的机器支持 WebP 时,需要添加 compile 'com.facebook.fresco:animated-base-support:0.12.0' // 支持 GIF 动图,需要添加 compile 'com.facebook.fresco:animated-gif:0.12.0' // 支持 WebP (静态图+动图),需要添加 compile 'com.facebook.fresco:animated-webp:0.12.0' compile 'com.facebook.fresco:webpsupport:0.12.0' // 仅支持 WebP 静态图,需要添加 compile 'com.facebook.fresco:webpsupport:0.12.0'}复制代码
- 初始化
Fresco.initialize(Context context);复制代码
- 使用SimpleView
复制代码
- 加载图片
Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/logo.png");SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);draweeView.setImageURI(uri);复制代码
- 以上是Fresco的基本加载流程,此外,我们可以定制加载和显示的各个环节
Fresco由两部分组成,Drawees负责图片的呈现,ImagePipeline负责图片的下载解码和内存管理
Drawees 负责图片的呈现。它由三个元素组成,有点像MVC模式。
DraweeView
-
继承于 View, 负责图片的显示。
-
一般情况下,使用 SimpleDraweeView 即可。 你可以在 XML 或者在 Java 代码中使用它,通过 setImageUri 给它设置一个 URI 来使用,这里有简单的入门教学:开始使用
-
你可以使用 XML属性来达到各式各样的效果。
DraweeHierarchy
-
DraweeHierarchy 用于组织和维护最终绘制和呈现的 Drawable 对象,相当于MVC中的M。
-
你可以通过它来在Java代码中自定义图片的展示
DraweeController
-
DraweeController 负责和 image loader 交互( Fresco 中默认为 image pipeline, 当然你也可以指定别的),可以创建一个这个类的实例,来实现对所要显示的图片做更多的控制。
-
如果你还需要对Uri加载到的图片做一些额外的处理,那么你会需要这个类的。
DraweeControllerBuilder
- DraweeControllers 由 DraweeControllerBuilder 采用 Builder 模式创建,创建之后,不可修改。具体参见: 使用ControllerBuilder。
Listeners
- 使用 ControllerListener 的一个场景就是设置一个 Listener监听图片的下载。
ImagePipeline
-
Fresco 的 Image Pipeline 负责图片的获取和管理。图片可以来自远程服务器,本地文件,或者Content Provider,本地资源。压缩后的文件缓存在本地存储中,Bitmap数据缓存在内存中。
-
在5.0系统以下,Image Pipeline 使用 pinned purgeables 将Bitmap数据避开Java堆内存,存在ashmem中。这要求图片不使用时,要显式地释放内存
-
SimpleDraweeView自动处理了这个释放过程,所以没有特殊情况,尽量使用SimpleDraweeView,在特殊的场合,如果有需要,也可以直接控制Image Pipeline。
-
ImagePipeline加载图片流程
- 检查内存缓存,如有,返回
- 后台线程开始后续工作
- 检查是否在未解码内存缓存中。如有,解码,变换,返回,然后缓存到内存缓存中。
- 检查是否在磁盘缓存中,如果有,变换,返回。缓存到未解码缓存和内存缓存中。
- 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。
ImagePipeline的线程池
Image pipeline 默认有3个线程池:
- 3个线程用于网络下载
- 2个线程用于磁盘操作: 本地文件的读取,磁盘缓存操作。
- 2个线程用于CPU相关的操作: 解码,转换,以及后处理等后台操作。
ImagePipeline的 缓存
ImagePipeLine有三级缓存
- 解码后的Bitmap缓存
- 未解码图片的内存缓存
- 磁盘缓存
功能
Fresco 相对于Glide/Picaso等拥有更多的功能,如图片的渐进式加载/动图/圆角等,
性能
Fresco采用三级缓存:
- 解码后的Bitmap缓存
- 未解码图片的内存缓存
- 磁盘缓存
Glide两级缓存:
- 根据ImageView控件尺寸获得对应的大小的bitmap来展示,可以缓存原始数据或者resize后数据
- 磁盘缓存
使用
Fresco通过CloseableReference管理图片,通过图片控件DraweeView来显示图片和控制图片释放,虽然扩展性高,但是扩展起来麻烦;对项目有一定侵入性
EventBus
EventBus使用了观察者模式,方便我们项目中进行数据传递和通信
- 添加依赖
compile 'org.greenrobot:eventbus:3.0.0'复制代码
- 注册和解绑
EventBus.getDefault().register(this);EventBus.getDefault().unregister(this);复制代码
- 添加订阅消息方法
@Subscribe(threadMode = ThreadMode.MAIN) public void onEvent(MessageEvent event) { /* Do something */}复制代码
- 发送消息
EventBus.getDefault().post(new MessageEvent("Hello !.....")); 复制代码
@Subscribe注解
该注解内部有三个成员,分别是threadMode、sticky、priority。
- threadMode代表订阅方法所运行的线程
- sticky代表是否是粘性事件
- priority代表优先级
threadMode
- POSTING:表示订阅方法运行在发送事件的线程。
- MAIN:表示订阅方法运行在UI线程,由于UI线程不能阻塞,因此当使用MAIN的时候,订阅方法不应该耗时过长。
- BACKGROUND:表示订阅方法运行在后台线程,如果发送的事件线程不是UI线程,那么就使用该线程;如果发送事件的线程是UI线程,那么新建一个后台线程来调用订阅方法。
- ASYNC:订阅方法与发送事件始终不在同一个线程,即订阅方法始终会使用新的线程来运行。
sticky 粘性事件
在注册之前便把事件发生出去,等到注册之后便会收到最近发送的粘性事件(必须匹配)。注意:只会接收到最近发送的一次粘性事件,之前的会接受不到,demo
参见链接
- EventBus通过反射的方式对@Subscribe方法进行解析。
- 默认情况下,解析是运行时进行的,但是我们也可以通过设置和加载依赖库,使其编译时形成索引,其性能会大大提升