欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android基础面试题整理

程序员文章站 2024-03-24 18:24:40
...

文章目录

0、Android基础

0.1 【腾讯Bugly干货分享】Android ListView 与 RecyclerView 对比浅析—缓存机制

【腾讯Bugly干货分享】Android ListView 与 RecyclerView 对比浅析—缓存机制

1、第三方库

1.1 OkHttp

OkHttp内部逻辑流程图:
Android基础面试题整理

先来一发灵魂拷问四连击:

  • addInterceptoraddNetworkInterceptor有什么区别?
  • 网络缓存如何实现的?
  • 网络连接怎么实现复用
  • OkHttp如何做网络监控

1.1.1 OkHttp基本实现原理(责任链)

OkHttp的内部实现通过一个责任链模式完成,将网络请求的各个阶段封装到各个链条中,实现了各层的解耦

1.1.2 OkHttp源码分析

OkHttp源码分析

核心类:

  • OkHttpClient
  • Request 和Response
  • RealCall
1.1.2.1 OkHttpClient

这个是整个OkHttp的核心管理类,所有的内部逻辑和对象归OkHttpClient统一来管理,它通过Builder构造器生成,构造参数和类成员很多。

1.1.2.2 Request 和Response
  • Request是我们发送请求封装类,内部有url, header , methodbody等常见的参数

  • Response是请求的结果,包含code, message, header,body

这两个类的定义是完全符合Http协议所定义的请求内容和响应内容。

1.1.2.3 RealCall
  • 负责请求的调度(同步的话走当前线程发送请求,异步的话则使用OkHttp内部的线程池进行);
  • 同时负责构造内部逻辑责任链,并执行责任链相关的逻辑,直到获取结果。

虽然OkHttpClient是整个OkHttp的核心管理类,但是真正发出请求并且组织逻辑的是RealCall类,它同时肩负了调度和责任链组织的两大重任

RealCall类并不复杂,有两个最重要的方法,execute() 和 enqueue(),一个是处理同步请求,一个是处理异步请求。跟进enqueue的源码后发现,它只是通过异步线程和callback做了一个异步调用的封装,最终逻辑还是会调用到execute()这个方法,然后调用了getResponseWithInterceptorChain()获得请求结果。

1.1.3 OkHttp拦截器(List顺序)

#RealCall
fun getResponseWithInterceptorChain(): Response {
    //创建拦截器数组
    val interceptors = mutableListOf<Interceptor>()
    //添加应用拦截器
    interceptors += client.interceptors
    //添加重试和重定向拦截器
    interceptors += RetryAndFollowUpInterceptor(client)
    //添加桥接拦截器
    interceptors += BridgeInterceptor(client.cookieJar)
    //添加缓存拦截器
    interceptors += CacheInterceptor(client.cache)
    //添加连接拦截器
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      //添加网络拦截器
      interceptors += client.networkInterceptors
    }
    //添加请求拦截器
    interceptors += CallServerInterceptor(forWebSocket)

    //创建责任链
    val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
        client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)
    ...
    try {
      //启动责任链
      val response = chain.proceed(originalRequest)
      ...
      return response
    } catch (e: IOException) {
      ...
    }
  }

不包括自定义的拦截器的话,系统默认存在五个拦截器。

1.1.3.1 retryAndFollowUpInterceptor 失败和重定向拦截器

Android基础面试题整理

  • 当请求内部抛出异常时,判定是否需要重试
  • 当响应结果是3xx重定向时,构建新的请求并发送请求

重试规则:

  • 规则1: client的retryOnConnectionFailure参数设置为false,不进行重试
  • 规则2: 请求的body已经发出,不进行重试
  • 规则3: 特殊的异常类型不进行重试(如ProtocolExceptionSSLHandshakeException等)
  • 规则4: 没有更多的route(包含proxy和inetaddress),不进行重试
1.1.3.2 BridgeInterceptor 封装request和response拦截器

BridageInterceptor 拦截器的功能如下:

  • 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应,是从应用程序代码到网络代码的桥梁
  • 设置内容长度,内容编码
  • 设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦
  • 添加cookie
  • 设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现连接复用的必要步骤
1.1.3.3 CacheInterceptor 缓存相关的过滤器,负责读取缓存直接返回、更新缓存

CacheInterceptor 拦截器的逻辑流程如下:

  • 通过Request尝试到Cache中拿缓存,当然前提是OkHttpClient中配置了缓存,默认是不支持的。
  • 根据response,time,request创建一个缓存策略,用于判断怎样使用缓存。
  • 如果缓存策略中设置禁止使用网络,并且缓存又为空,则构建一个Response直接返回,注意返回码=504
  • 缓存策略中设置不使用网络,但是又缓存,直接返回缓存
  • 接着走后续过滤器的流程,chain.proceed(networkRequest)
  • 当缓存存在的时候,如果网络返回的Resposne为304,则使用缓存的Resposne。
  • 构建网络请求的Resposne
  • 当在OkHttpClient中配置了缓存,则将这个Resposne缓存起来。
  • 缓存起来的步骤也是先缓存header,再缓存body
    返回Resposne
1.1.3.4 ConnectInterceptor 连接服务,负责和服务器建立连接 这里才是真正的请求网络

socket连接和tls连接

讲解太长,看这里吧

最终会获得 一个Exchange的类,这个类有两个实现,一个是Http1ExchangeCodec,一个是Http2Exchangecodec,分别对应的是Http1协议和Http2协议。

1.1.3.5 CallServerInterceptor 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据 进行http请求报文的封装与请求报文的解析

传输http的头部body数据

CallServerInterceptor由以下步骤组成:

  • 向服务器发送 request header
  • 如果有 request body,就向服务器发送
  • 读取 response header,先构造一个 Response 对象
  • 如果有 response body,就在 3 的基础上加上 body 构造一个新的 Response 对象

这里我们可以看到,核心工作都由 HttpCodec 对象完成,而 HttpCodec 实际上利用的是 Okio,而 Okio 实际上还是用的 Socket,只不过一层套一层,层数有点多。

1.1.4 问题

1.1.4.1 addInterceptor与addNetworkInterceptor的区别

二者通常的叫为应用拦截器网络拦截器,从整个责任链路来看:

  • 应用拦截器是最先执行的拦截器,也就是用户自己设置request属性后的原始请求
  • 网络拦截器位于ConnectInterceptorCallServerInterceptor之间,此时网络链路已经准备好,只等待发送请求数据。

首先,应用拦截器在RetryAndFollowUpInterceptorCacheInterceptor之前,所以一旦发生错误重试或者网络重定向,网络拦截器可能执行多次,因为相当于进行了二次请求,但是应用拦截器永远只会触发一次。另外如果在CacheInterceptor中命中了缓存就不需要走网络请求了,因此会存在短路网络拦截器的情况。

其次,除了CallServerInterceptor,每个拦截器都应该至少调用一次realChain.proceed方法。实际上在应用拦截器这层可以多次调用proceed方法(本地异常重试)或者不调用proceed方法(中断),但是网络拦截器这层连接已经准备好,可且仅可调用一次proceed方法。

最后,从使用场景看,应用拦截器因为只会调用一次,通常用于统计客户端的网络请求发起情况;而网络拦截器一次调用代表了一定会发起一次网络通信,因此通常可用于统计网络链路上传输的数据

1.1.4.2 网络缓存机制 CacheInterceptor(OKHttp的缓存实现)

这里涉及到HTTP缓存知识。

  • 第一次拿到响应后根据头信息决定是否缓存。
  • 下次请求时判断是否存在本地缓存,是否需要使用对比缓存、封装请求头信息等等。
  • 如果缓存失效或者需要对比缓存则发出网络请求,否则使用本地缓存。

OKHttp内部使用Okio来实现缓存文件的读写

缓存文件分为CleanFiles和DirtyFiles,CleanFiles用于读,DirtyFiles用于写,他们都是数组,长度为2,表示两个文件,即缓存的请求头和请求体;同时记录了缓存的操作日志,记录在journalFile中。

开启缓存需要在OkHttpClient创建时设置一个Cache对象,并指定缓存目录和缓存大小,缓存系统内部使用LRU作为缓存的淘汰算法。

最后需要注意的一点是,OKHttp默认只支持get请求的缓存

1.1.5 OkHttp设计模式

1.1.5.1 单例模式

OkHttpClient, 可以通过 new OkHttpClient() 或 new OkHttpClient.Builder() 来创建对象, 但是—特别注意, OkHttpClient() 对象最好是共享的, 建议使用单例模式创建。 因为每个 OkHttpClient 对象都管理自己独有的线程池和连接池。

1.1.5.2 外观模式

OKHttpClient 里面组合了很多的类对象。其实是将OKHttp的很多功能模块,全部包装进这个类中,让这个类单独提供对外的API,这种设计叫做外观模式(外观模式:隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口

1.1.5.3 建造者模式(Builder)

OkHttpClient 比较复杂, 太多属性, 而且客户的组合需求多样化, 所以OKhttp使用建造者模式(Build模式:使用多个简单的对象一步一步构建成一个复杂的对象,一个 Builder 类会一步一步构造最终的对象

1.1.5.4 责任链模式

OkHttp3 的拦截器链中, 内置了5个默认的拦截器,分别用于重试、请求对象转换、缓存、链接、网络读写(责任链模式:为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。)

1.1.5.5 策略模式

CacheInterceptor 实现了数据的选择策略, 来自网络还是来自本地? 这个场景也是比较契合策略模式场景, CacheInterceptor 需要一个策略提供者提供它一个策略(锦囊), CacheInterceptor 根据这个策略去选择走网络数据还是本地缓存。

1.1.6 总结

Android基础面试题整理

1.2 EventBus

EventBus核心其实就是三幅图,这三幅图涉及的是三个HashMap表,弄懂这三幅图那么EventBus就懂了。

1.2.1 订阅者Subscribe 和订阅事件 Event(typeBySubscriber)

onStart{
  EventBus.getDefault().register(this);
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent1(Event1 event) {
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent2(Event2 event) {
}

onStop{
  EventBus.getDefault().register(this);
}

注册监听的是activity,称为subscriber,在activity中监听了Event1和Event2两个事件.

Android基础面试题整理
一个Subscribe对应多个Event,Subsribe就是上面通过register方法注册的对象,比如activity。这幅图对应EventBus中一个Map结构:

private final Map<Object, List<Class<?>>> typesBySubscriber;

EventBus会在对象register时,使用反射机制,遍历对象的方法,将带有@Subscribe标签并且合法的方法加入到typesBySubscriber。typesBySubscriber是HashMap形式,key是注册的对象本身,由于一个注册的对象中可能有多个监听事件,所以value是用list保存的Event。

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
    // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
    }

上面的代码主要做:

  • 通过反射遍历注册对象的方法,获取其中带有@Subscribe标签的方法并且放在一个列表中,最后以注册对象为key,@Subscribe的方法列表作为value放在HashMap中,就是上图的形式。
1.2.1.1 1、为什么要将注册监听对象作为key,监听事件列表作为value放在HashMap中?

要弄懂一个问题,EventBus是观察者模式,上面的activity也就是subscribe是订阅者,activity中的event是订阅事件,一个订阅者可以订阅多个事件,移除一个订阅者的监听事件时,应该将其中所有的event的事件移除。 也就是说在反注册的时候,会通过Subsribe来查找到其中所有event进行反注册。

1.2.2 订阅事件 Event 和订阅者 Subscription

Android基础面试题整理
这种表关系是eventsubsciption的对应关系,比如在Android中多个activity可能会注册监听同一个event事件,所以在执行:

EventBus.getDefault().post(new Event1());

的时候所有注册监听了Event1的监听都会要会收到回调,看下subsciption的结构
Android基础面试题整理
subsciption中包含,订阅的事件订阅者本身

1.2.2.1 为什么需要保存Event和subsribe对应的关系表?

这是因为一个Event可能会有被多个subsribe订阅,所以有当执行post(Event)的时候会查找到所有订阅了Event事件的subscribe并调用其中的event方法。下面看下post方法:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            subscriptions = 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和postSticky主要都会调用到上面的方法,上面方法中subscriptionsByEventType.get(eventClass)就是通过event的类型找上面的表中找到对应的subscriptions进行通知的。

1.2.3 postSticky (延迟)

在看第三幅图之前思考一个问题,postSticky到底是怎么执行的?为什么先执行postSticky,后执行register还是可以监听到event事件? 先看postSticky代码:

public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
}

原来执行postSticky的时候会将event.getclass和event保存起来,然后再看下subscribe代码:

if (subscriberMethod.sticky) {
            if (eventInheritance) {
                // Existing sticky events of all subclasses of eventType have to be considered.
                // Note: Iterating over all events may be inefficient with lots of sticky events,
                // thus data structure should be changed to allow a more efficient lookup
                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                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();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
} else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
  • 先判断注册监听的event是不是sticky的如果是就会用stickEvents表中找到stickyEvent
  • 如果注册的事件event和stickyEvent一样那么就会执行一次postToSubscription方法,也就是调用注册的方法执行。

Android基础面试题整理

1.2.4 缺点

  • 使用的时候有定义很多event类
  • event在注册的时候会调用反射去遍历注册对象的方法在其中找出带有@subscriber标签的方法,性能不高。
  • 需要自己注册和反注册,如果忘了反注册就会导致内存泄漏

1.2.5 总结

  • 要理解EventBus就要从register,unRegister,post,postSticky方法入手。要理解register实质上是将订阅对象(比如activity)中的每个带有subscriber的方法找出来,最后获得调用的就是这些方法。订阅对象(比如activity)是一组event方法的持有者。
  • 后注册的对象中sticky方法能够收到之前的stickyEvent方法的原因是EventBus中维护了stickyEvent的hashMap表,在subsribe注册的时候就遍历其中有没有注册监听stickyEvent如果有就会执行一次回调。

1.3 Retrofit

参考文档

Retrofit 是一个 RESTfulHTTP 网络请求框架的封装。注意这里并没有说它是网络请求框架,主要原因在于网络请求的工作并不是 Retrofit 来完成的。Retrofit 2.0 开始内置 OkHttp,前者专注于接口的封装,后者专注于网络请求的高效,二者分工协作,宛如古人的『你耕地来我织布』,小日子别提多幸福了。
Android基础面试题整理
我们的应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作,在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,后者根据用户的需求对结果进行解析的过程。

Retrofitting OkHttp

1.3.1 Retrofit基本使用

例子:


// 定义
 public interface GitHubService {
   @GET("users/{user}/repos")
   Call<List<Repo>> listRepos(@Path("user") String user);
 }

// 构造
 Retrofit retrofit = new Retrofit.Builder()
     .baseUrl("https://api.github.com/")
     .build();

 GitHubService service = retrofit.create(GitHubService.class);

// 调用
Call<List<Repo>> repos = service.listRepos("octocat");

// 同步调用
 List<Repo> data = repos.execute(); 

 // 异步调用
 repos.enqueue(new Callback<List<Repo>>() {
             @Override
             public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
                 List<Repo> data = response.body();
             }

             @Override
             public void onFailure(Call<List<Repo>> call, Throwable t) {
                 t.printStackTrace();
             }
});
1.3.1.1 Url 配置

支持 GET/POST/PUT/DELETE/HEAD/PATCH

Android基础面试题整理

1.3.1.2 参数类型

发请求时,需要传入参数,Retrofit 通过注解的形式令 Http 请求的参数变得更加直接,而且类型安全。

1.3.1.2.1 Query & QueryMap
@GET("/list")
 Call<ResponseBody> list(@Query("page") int page);

Query 其实就是 Url 中 ‘?’ 后面的 key-value.

@GET("News")
Call<NewsBean> getItem(@QueryMap Map<String, String> map);
1.3.1.2.2 Field & FieldMap

使用 POST 提交表单的场景是刚需了,怎么提呢?

@FormUrlEncoded
@POST("/")
Call<ResponseBody> example(
       @Field("name") String name,
       @Field("occupation") String occupation
       );
@POST("/")
@FormUrlEncoded
Call<WeatherBeans> requestWeatherBeans(@FieldMap Map<String, String> fields);

1.3.1.2.3 Part & PartMap

这个是用来上传文件的。话说当年用 HttpClient 上传个文件老费劲了,一会儿编码不对,一会儿参数错误(也怪那时段位太低吧TT)。。。可是现在不同了,自从有了 Retrofit,妈妈再也不用担心文件上传费劲了~~~

public interface FileUploadService {  
     @Multipart
     @POST("upload")
     Call<ResponseBody> upload(@Part("description") RequestBody description,
                               @Part MultipartBody.Part file);
 }

如果你需要上传文件,和我们前面的做法类似,定义一个接口方法,需要注意的是,这个方法不再有 @FormUrlEncoded 这个注解,而换成了 @Multipart,后面只需要在参数中增加 Part 就可以了。也许你会问,这里的 Part 和 Field 究竟有什么区别,其实从功能上讲,无非就是客户端向服务端发起请求携带参数的方式不同,并且前者可以携带的参数类型更加丰富,包括数据流。也正是因为这一点,我们可以通过这种方式来上传文件,下面我们就给出这个接口的使用方法:

//先创建 service
 FileUploadService service = retrofit.create(FileUploadService.class);

 //构建要上传的文件
 File file = new File(filename);
 RequestBody requestFile =
         RequestBody.create(MediaType.parse("application/otcet-stream"), file);

 MultipartBody.Part body =
         MultipartBody.Part.createFormData("aFile", file.getName(), requestFile);

 String descriptionString = "This is a description";
 RequestBody description =
         RequestBody.create(
                 MediaType.parse("multipart/form-data"), descriptionString);

 Call<ResponseBody> call = service.upload(description, body);
 call.enqueue(new Callback<ResponseBody>() {
   @Override
   public void onResponse(Call<ResponseBody> call,
                          Response<ResponseBody> response) {
     System.out.println("success");
   }

   @Override
   public void onFailure(Call<ResponseBody> call, Throwable t) {
     t.printStackTrace();
   }
 });
1.3.1.3 Converter,让你的入参和返回类型丰富起来
1.3.1.3.1 RequestBodyConverter

上面传输文件中,我们:
Android基础面试题整理
改造一下:

public interface FileUploadService {  
     @Multipart
     @POST("upload")
     Call<ResponseBody> upload(@Part("description") RequestBody description,
         //注意这里的参数 "aFile" 之前是在创建 MultipartBody.Part 的时候传入的
         @Part("aFile") File file);
 }

(默认使用 GsonRequestBodyConverter)接下来我们就自己实现一个 FileRequestBodyConverter:

static class FileRequestBodyConverterFactory extends Converter.Factory {
     @Override
     public Converter<File, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
       return new FileRequestBodyConverter();
     }
   }

   static class FileRequestBodyConverter implements Converter<File, RequestBody> {

     @Override
     public RequestBody convert(File file) throws IOException {
       return RequestBody.create(MediaType.parse("application/otcet-stream"), file);
     }
   }
addConverterFactory(new FileRequestBodyConverterFactory())
1.3.1.3.2 ResponseBodyConverter

Retrofit 也支持自定义 ResponseBodyConverter(默认 GsonResponseBodyConverter)。

问题来了,如果请求得到的 Json 字符串与返回值类型不对应,比如:

{"err":0, "content":"This is a content.", "message":"OK"}

而实体类型为:

class Result{
     int code;//等价于 err
     String body;//等价于 content
     String msg;//等价于 message
 }

自定义Converter解决:

   static class ArbitraryResponseBodyConverterFactory extends Converter.Factory{
     @Override
     public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
       return super.responseBodyConverter(type, annotations, retrofit);
     }
   }

   static class ArbitraryResponseBodyConverter implements Converter<ResponseBody, Result>{

     @Override
     public Result convert(ResponseBody value) throws IOException {
       RawResult rawResult = new Gson().fromJson(value.string(), RawResult.class);
       Result result = new Result();
       result.body = rawResult.content;
       result.code = rawResult.err;
       result.msg = rawResult.message;
       return result;
     }
   }

   static class RawResult{
     int err;
     String content;
     String message;
   }

1.3.2 Retrofit原理剖析

这里涉及到了动态代理,参考Java基础面试题整理。

public <T> T create(final Class<T> service) {
     Utils.validateServiceInterface(service);
     if (validateEagerly) {
       eagerlyValidateMethods(service);
     }
     //这里返回一个 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);
             }
             //DefaultMethod 是 Java 8 的概念,是定义在 interface 当中的有实现的方法
             if (platform.isDefaultMethod(method)) {
               return platform.invokeDefaultMethod(method, service, proxy, args);
             }
             //每一个接口最终实例化成一个 ServiceMethod,并且会缓存
             ServiceMethod serviceMethod = loadServiceMethod(method);

             //由此可见 Retrofit 与 OkHttp 完全耦合,不可分割
             OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
             //下面这一句当中会发起请求,并解析服务端返回的结果
             return serviceMethod.callAdapter.adapt(okHttpCall);
           }
         });
   }

任意方法最终都会 实际上调用的是这里的 InvocationHandler.invoke 方法~~

1.3.2.1 OkHttpCall

实际上每一个 OkHttpCall 都对应于一个请求,它主要完成最基础的网络请求,而我们在接口的返回中看到的 Call 默认情况下就是 OkHttpCall 了,如果我们添加了自定义的 callAdapter,那么它就会将 OkHttp 适配成我们需要的返回值,并返回给我们。

call接口定义:

 public interface Call<T> extends Cloneable {
   //同步发起请求
   Response<T> execute() throws IOException;
   //异步发起请求,结果通过回调返回
   void enqueue(Callback<T> callback);
   boolean isExecuted();
   void cancel();
   boolean isCanceled();
   Call<T> clone();
   //返回原始请求
   Request request();
 }
1.3.2.2 OkHttpCall.execute
@Override public Response<T> execute() throws IOException {
     //这个 call 是真正的 OkHttp 的 call,本质上 OkHttpCall 只是对它做了一层封装
     okhttp3.Call call;

     synchronized (this) {
       //处理重复执行的逻辑
       if (executed) throw new IllegalStateException("Already executed.");
       executed = true;

       if (creationFailure != null) {
         if (creationFailure instanceof IOException) {
           throw (IOException) creationFailure;
         } else {
           throw (RuntimeException) creationFailure;
         }
       }

       call = rawCall;
       if (call == null) {
         try {
           call = rawCall = createRawCall();
         } catch (IOException | RuntimeException e) {
           creationFailure = e;
           throw e;
         }
       }
     }

     if (canceled) {
       call.cancel();
     }
     //发起请求,并解析结果
     return parseResponse(call.execute());
   }

  • OkHttpCall 其实也是封装了 okhttp3.Call,在这个方法中,我们通过 okhttp3.Call发起了请求。

  • parseResponse 主要完成了由 okhttp3.Response 向 retrofit.Response 的转换,同时也处理了对原始返回的解析:

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
     ResponseBody rawBody = rawResponse.body();

     //略掉一些代码
     try {
       //在这里完成了原始 Response 的解析,T 就是我们想要的结果,比如 GitHubService.listRepos 的 List<Repo>
       T body = serviceMethod.toResponse(catchingBody);
       return Response.success(body, rawResponse);
     } catch (RuntimeException e) {
       // If the underlying source threw an exception, propagate that rather than indicating it was
       // a runtime exception.
       catchingBody.throwIfCaught();
       throw e;
     }
   }

至此,我们就拿到了我们想要的数据~~

1.3.2.3 结果适配,你是不是想用 RxJava?

前面我们已经提到过 CallAdapter 的事儿,默认情况下,它并不会对 OkHttpCall 实例做任何处理:

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
   static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();

   @Override
   public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
     ... 毫不留情的省略一些代码 ...
     return new CallAdapter<Call<?>>() {
       ... 省略一些代码 ...

       @Override public <R> Call<R> adapt(Call<R> call) {
         //看这里,直接把传入的 call 返回了
         return call;
       }
     };
   }
 }

现在的需求是,我想要接入 RxJava,让接口的返回结果改为 Observable:

addCallAdapterFactory(RxJavaCallAdapterFactory.create())

1.3.3 Mock Server

参考文献

1.4 Glide

2、性能优化

2.1 卡顿优化

相对于其他类型的性能指标,卡顿是能直接让用户产生视觉反馈的现象,比如App反应滞后于用户的操作,在严重的情况下会出现ANR。关乎用户体验的大事,是很容易遭到用户吐槽的(真理真理)。因此,开发人员平时写代码时必须要时刻提醒自己不要落入卡顿的陷阱之中。

2.1.1 卡顿原因

UI线程是基于queue中的message事件驱动的,事件 -> 执行 -> 下一个事件…,另一方面由于Android的帧率是60fps,也就是每16ms就会触发一次UI刷新,如果某个message的处理时间 > 16ms,就会导致接收到VSYNC信号的时候无法完成本次刷新操作,产生掉帧现象。

从本质上来讲,我们必须让UI线程的任何事件在16ms之内解决战斗

基于此,可能会导致卡顿的原因有三大类:

  • 1)事件本身太耗时。
  • 2)事件本身并不耗时,但需要等待别的地方返回耗时。
  • 3)UI线程本身已经拿不到CPU资源来执行事件。
2.1.1.1 耗时事件

就是把一些耗时业务逻辑直接写在了UI线程中,比如计算密集型的复杂计算,庞大的MD5计算,非对称RSA解密等。一般情况下,开发人员都不会犯这种错误,因为能够直接意识到计算量很大,本身就有警醒的作用。

2.1.1.2 耗时等待
  • 网络I/O 同步请求这种如果是在用以前比较老的网络库,比如URLConnection这种就需要开发人员自己来开启新的线程。开发者可能忘记开启子线程,又同时做了同步请求等待,导致卡顿的发生。但是现代网络库比如Okhttp,Retrofit已经帮我们准备好了线程池,一般不会再遇到

  • 磁盘I/O 文件,数据库一般的文件和数据库操作,大家可能都会自觉的在子线程中操作。但是值得一提的是SharedPreference的存储和读取,根据sp的设计,创建的时候会开启子线程把整个文件全部加载进内存,加载完毕再通知主线程,如果读取尚未结束,此时想获取某个key的值,主线程就必须等待加载完毕为止
    因此,如果你的sp文件比较大,那么会带来几个严重问题:
    a)第一次从sp中获取值的时候,有可能阻塞主线程,使界面卡顿、掉帧。
    b)解析sp的时候会产生大量的临时对象,导致频繁GC,引起界面卡顿。
    c)这些key和value会永远存在于内存之中,不会被释放,占用大量内存。所以千万不要把庞大的key/value存在sp中,比如把复杂的json当value。
    另外对于sp的存储,commit是同步操作,要在子线程中使用。而apply虽然是在子线程执行的,但是无节制地apply也会造成卡顿,原因是每次有系统消息发生的时候(handleStopActivity,handlePauseActivity)都会去检查已经提交的apply写操作是否完成,如果没有完成则阻塞主线程。

2.1.2 分析

对于卡顿的分析手段,有很多工具可以使用,下面介绍几种。

1)TraceView相比之下,TraceView是分析卡顿的神兵利器,它不仅能看出每个方法消耗的时间、发生次数,并且可以进行排序,直接从最耗时的方法开始处优化。

2)ANR-WatchDog 其原理简单来说就是开启一个子线程,设置tick = interval,然后每隔一个interval(可设置)就往UI线程queue中扔一个runnable,若UI线程没卡顿,则interval时间内会取出此runnable执行,即重置tick,那么下一个interval循环时根据检测此tick是否被重置来判断是否有卡顿发生。如果有,则打印此时的各个线程运行时的stack trace(可设置只打印主线程),以帮助定位。

3)AndroidPerformanceMonitor 是国人开发的一个检测卡顿的开源库,原名是BlockCanary,可以设置卡顿检测时间,debug模式下检测到的卡顿可以通知展示(基本和LeakCanary一样),这个在开发自测时很有用。

利用系统在loop()方法里取出message前后进行了log打印这一特点,来重写Printer的println(String)方法,根据message处理前后的时间差,来判断是否发生了卡顿。

public static void loop() {
    ...

    for (;;) {
        ...

        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        ...
    }
}

而且这个工具在卡顿发生时,收集的信息还比较丰富,包括基本信息,耗时信息,CPU信息,堆栈信息等。

4)ANR trace.txt而对于ANR,每当测试跑monkey一晚下来,ANR必是log的重点关注对象,若存在ANR,测试肯定会开jira贴上log给开发解决。对于trace.txt的分析,有几个基本的点是需要重点关注的:

  • a)具体的call stack指向的具体代码,是否是卡顿发生的原因。
  • b)是否有lock相关的关键字,代表可能发生死锁。
  • c)是否有iowait字样,是否在UI线程发生了网络或者磁盘I/O。
  • d)CPU使用率是否很高,很高表示要么自身有计算密集型任务发生,要么在其他地方有抢占CPU资源的任务。很低说明非耗时计算导致,可怀疑死锁和I/O耗时等待。

2.2 内存优化

Android 内存优化总结&实践

2.3 耗电优化

2.4 apk大小优化

2.5 io优化