Android 面试开源框架篇

本文是Android面试题整理中的一篇,内容包括html

  • LeakCanary
  • Okhttp
  • Retrofit
  • Fresco 使用
  • EventBus

LeakCanary


  1. 在Application中注册一个ActivityLifecycleCallbacks来监听Activity的销毁
  2. 经过IdleHandler在主线程空闲时进行检测
  3. 检测是经过WeakReference实现的,若是没有被回收会再次调用gc再确认一遍
  4. 确认有泄漏后,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. 封装

由于如下缘由,因此咱们须要封装:android

  • 避免重复代码编写
  • 请求的回调改成UI线程
  • 其余须要的逻辑:例如加解密等

OkHttp中的设计模式

  1. Builder模式:OkHttpClient 和Request等都是经过Builder模式建立的
  2. 责任链模式:拦截器经过责任链模式进行工做
  3. 门面模式:总体采用门面模式,OkHttpClient为门面,向子系统委派任务
  4. 享元模式:链接池等采用了享元模式
  5. 其余:工厂模式、代理模式等

源码分析

1. Call

  • Call的实现类为RealCall
  • 在执行execute或者enqueue时,会取出okHttpClient中的Dispatcher执行对应的方法
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
复制代码

2. Diapatcher

  • Diapatcher在OkHttpClient build时进行初始化
  • Dispatcher负责进行任务调度,内部维护一个线程池,处理并发请求
  • Dispatcher内部有三个队列
/** 将要运行的异步请求队列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**正在运行的异步请求队列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在运行的同步请求队列 */
private final Deque<RealCall> 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是用责任链的方式,执行拦截器,对请求和请求结果进行处理git

  • getResponseWithInterceptorChain 中建立拦截器,并建立第一个RealInterceptorChain,执行其proceed方法
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 (!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中取得github

1. Connection

  • Connection 是客户端和服务器创建的数据通路,一个Connection上可能存在几个连接
  • Connection的实现类是RealConnection,是socket物理链接的包装
  • Connection内部维持着一个List<Reference>引用

2. StreamAllocation

StreamAllocation是Connection维护的链接,如下是类内注解web

<ul>
 *     <li><strong>Connections:</strong> 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.
 *     <li><strong>Streams:</strong> 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.
 *     <li><strong>Calls:</strong> 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.
 * </ul>
复制代码

3. ConnectionPool

ConnectionPool经过Address等来查找有没有能够复用的Connection,同时维护一个线程池,对Connection作回收工做面试

Retrofit


Retrofit帮助咱们对OkHttp进行了封装,使网络请求更加方便segmentfault

使用

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")
Call<User> getUser(@Header("Authorization") String authorization)
复制代码

4. 建立Call

GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
//对 发送请求 进行封装
Call<Reception> call = request.getCall();
复制代码

5. 执行Call的请求方法

//发送网络请求(异步) call.enqueue(new Callback<Translation>() { 
//请求成功时回调
 @Override 
public void onResponse(Call<Translation> call, Response<Translation> response) { 
   //请求处理,输出结果
    response.body().show(); 
 } 
 //请求失败时候的回调 
 @Override 
 public void onFailure(Call<Translation> call, Throwable throwable) { 
     System.out.println("链接失败"); 
 } 
 });
 
 // 发送网络请求(同步) Response<Reception> response = call.execute();

复制代码

源码解析

1. Retrofit

Retrofit 经过builder模式建立,咱们能够对其进行各类设置:设计模式

  • baseUrl:请求地址的头部,必填
  • callFactory:网络请求工厂(不进行设置的话默认会生成一个OkHttpClient)
  • adapterFactories:网络请求适配器工厂的集合,这里有适配器由于Retrofit不只支持Android,还支持Ios等其余平台(不进行设置的话会根据平台自动生成)
  • converterFactories:数据转换器工厂的集合(将网络返回的数据转换成咱们须要的类)
  • callbackExecutor:回调方法执行器(Android平台默认经过Handler发送到主线程执行)

2. Call

咱们的每一个method对应一个Call, Call的建立分为两步:api

  • retorfit.create(myInfterfaceClass.class)建立咱们网络请求接口类的实例
  • 调用对应方法拿到对应网络请求的Call

关键在第一步,第一步是经过动态代理实现的缓存

public <T> T create(final Class<T> 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方法以下

public <R> Call<R> adapt(Call<R> call) { 
    return new ExecutorCallbackCall<>(callbackExecutor, call); 
} 


ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {

 this.delegate = delegate; 
 // 把上面建立并配置好参数的OkhttpCall对象交给静态代理delegate 
 // 静态代理和动态代理都属于代理模式 
 // 静态代理做用:代理执行被代理者的方法,且可在要执行的方法先后加入本身的动做,进行对系统功能的拓展 
 
 this.callbackExecutor = callbackExecutor; 
 // 传入上面定义的回调方法执行器 
 // 用于进行线程切换 }

复制代码

ExecutorCallbackCall对OkHttpCall进行了装饰,会调用CallBackExcutor对OkHttpCall执行的返回结果进行处理,使其位于主线程

自定义Convert和CallAdapter

Fresco


Fresco是一个图片加载库,能够帮助咱们加载图片显示,控制多线程,以及管理缓存和内存等

Fresco使用

  1. 引入依赖
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'
}

复制代码
  1. 初始化
Fresco.initialize(Context context);
复制代码
  1. 使用SimpleView
<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/my_image_view"
    android:layout_width="130dp"
    android:layout_height="130dp"
    fresco:placeholderImage="@drawable/my_drawable"
  />
复制代码
  1. 加载图片
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);

复制代码
  1. 以上是Fresco的基本加载流程,此外,咱们能够定制加载和显示的各个环节

Fresco由两部分组成,Drawees负责图片的呈现,ImagePipeline负责图片的下载解码和内存管理

Drawees

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加载图片流程

  1. 检查内存缓存,若有,返回
  1. 后台线程开始后续工做
  2. 检查是否在未解码内存缓存中。若有,解码,变换,返回,而后缓存到内存缓存中。
  3. 检查是否在磁盘缓存中,若是有,变换,返回。缓存到未解码缓存和内存缓存中。
  4. 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。

ImagePipeline的线程池

Image pipeline 默认有3个线程池:

  1. 3个线程用于网络下载
  1. 2个线程用于磁盘操做: 本地文件的读取,磁盘缓存操做。
  2. 2个线程用于CPU相关的操做: 解码,转换,以及后处理等后台操做。

ImagePipeline的 缓存

ImagePipeLine有三级缓存

  1. 解码后的Bitmap缓存
  2. 未解码图片的内存缓存
  3. 磁盘缓存

对比

功能

Fresco 相对于Glide/Picaso等拥有更多的功能,如图片的渐进式加载/动图/圆角等,

性能

Fresco采用三级缓存:

  1. 解码后的Bitmap缓存
  2. 未解码图片的内存缓存
  3. 磁盘缓存

Glide两级缓存:

  1. 根据ImageView控件尺寸得到对应的大小的bitmap来展现,能够缓存原始数据或者resize后数据
  2. 磁盘缓存

使用

Fresco经过CloseableReference管理图片,经过图片控件DraweeView来显示图片和控制图片释放,虽然扩展性高,可是扩展起来麻烦;对项目有必定侵入性

EventBus


EventBus使用了观察者模式,方便咱们项目中进行数据传递和通讯

使用

  1. 添加依赖
compile 'org.greenrobot:eventbus:3.0.0'
复制代码
  1. 注册和解绑
EventBus.getDefault().register(this);

EventBus.getDefault().unregister(this);
复制代码
  1. 添加订阅消息方法
@Subscribe(threadMode = ThreadMode.MAIN) 
public void onEvent(MessageEvent event) {
    /* Do something */
}
复制代码
  1. 发送消息
EventBus.getDefault().post(new MessageEvent("Hello !....."));
    
复制代码

@Subscribe注解

该注解内部有三个成员,分别是threadMode、sticky、priority。

  1. threadMode表明订阅方法所运行的线程
  2. sticky表明是不是粘性事件
  3. priority表明优先级

threadMode

  1. POSTING:表示订阅方法运行在发送事件的线程。
  2. MAIN:表示订阅方法运行在UI线程,因为UI线程不能阻塞,所以当使用MAIN的时候,订阅方法不该该耗时过长。
  3. BACKGROUND:表示订阅方法运行在后台线程,若是发送的事件线程不是UI线程,那么就使用该线程;若是发送事件的线程是UI线程,那么新建一个后台线程来调用订阅方法。
  4. ASYNC:订阅方法与发送事件始终不在同一个线程,即订阅方法始终会使用新的线程来运行。

sticky 粘性事件

在注册以前便把事件发生出去,等到注册以后便会收到最近发送的粘性事件(必须匹配)。注意:只会接收到最近发送的一次粘性事件,以前的会接受不到,demo

源码解析

参见连接

性能

  1. EventBus经过反射的方式对@Subscribe方法进行解析。
  2. 默认状况下,解析是运行时进行的,可是咱们也能够经过设置和加载依赖库,使其编译时造成索引,其性能会大大提高
相关文章
相关标签/搜索