栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

腾讯面试官:说一下Android网络知识和框架?,深入解析Android-AutoLayout

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

腾讯面试官:说一下Android网络知识和框架?,深入解析Android-AutoLayout

final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;//代理选择器
final cookieJar cookieJar;//cookie
final @Nullable
Cache cache;//cache 缓存
final @Nullable
InternalCache internalCache;//内部缓存
final SocketFactory socketFactory;//socket 工厂
final @Nullable
SSLSocketFactory sslSocketFactory;//安全套层socket工厂 用于https
final @Nullable
CertificateChainCleaner certificateChainCleaner;//验证确认响应书,适用HTTPS 请求连接的主机名
final HostnameVerifier hostnameVerifier;//主机名字确认
final CertificatePinner certificatePinner;//证书链
final Authenticator proxyAuthenticator;//代理身份验证
final Authenticator authenticator;//本地省份验证
final ConnectionPool connectionPool;//链接池 复用连接
final Dns dns; //域名
final boolean followSslRedirects;//安全套接层重定向
final boolean followRedirects;//本地重定向
final boolean retryOnConnectionFailure;//重试连接失败
final int connectTimeout;//连接超时
final int readTimeout;//读取超时
final int writeTimeout;//写入超时

这里OkHttpClient的构造采用了建造者模式。

2.2 OkHttpClient 请求网络

String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();

Response response = client.newCall(request).execute();
return response.body().string();
}

定义一个请求Request,Request中包含客户请求的参数:url、method、headers、requestBody和tag,也采用了建造者模式。
实际请求网络的是Call接口,OkHttp实现了Call.Factory接口,其真正的实现类是RealCall,OkHttp将真正的请求交给了RealCall,RealCall实现了Call中方法完成请求。这里采用了简单工厂模式。

interface Factory {
Call newCall(Request request);
}

@Override
public

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整资料开源分享

Call newCall(Request request) {
return RealCall.newRealCall(this, request, false );
}

RealCall中主要方法有

  • 同步请求:client.newCall(request).execute
  • 异步请求:client.newCall(request).enqueue(常用)

异步请求 RealCall.enqueue()

RealCall.java

@Override public void enqueue(Callback responseCallback) {
//TODO 不能重复执行
synchronized (this) {
if (executed) throw new IllegalStateException(“Already Executed”);
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
//TODO 交给 dispatcher调度器 进行调度
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

  1. synchronized (this) 确保每个call只能被执行一次不能重复执行
  2. Dispatcher 调度器 将 Call 加入队列,并通过线程池执行 Call
    利用dispatcher调度器,来进行实际的执行client.dispatcher().enqueue(new AsyncCall(responseCallback));,在上面的OkHttpClient.Builder可以看出 已经初始化了Dispatcher。
    Dispatcher的属性和方法

//TODO 同时能进行的最大请求数
private int maxRequests = 64;
//TODO 同时请求的相同HOST的最大个数 SCHEME / HOST [ “:” PORT ] [ PATH [ “?” QUERY ]]
//TODO 如 https://restapi.amap.com restapi.amap.com - host
private int maxRequestsPerHost = 5;

private final Deque readyAsyncCalls = new ArrayDeque<>();


private final Deque runningAsyncCalls = new ArrayDeque<>();

Dispatcher管理两个异步请求队列,可对多个并发网络请求进行处理。
Dispatcher.enqueue方法用于执行异步请求,实现如下

//TODO 执行异步请求
synchronized void enqueue(AsyncCall call) {
//TODO 同时请求不能超过并发数(64,可配置调度器调整)
//TODO okhttp会使用共享主机即 地址相同的会共享socket
//TODO 同一个host最多允许5条线程通知执行请求
if (runningAsyncCalls.size() < maxRequests &&
runningCallsForHost(call) < maxRequestsPerHost) {
//TODO 加入运行队列 并交给线程池执行
runningAsyncCalls.add(call);
//TODO AsyncCall 是一个runnable,放到线程池中去执行,查看其execute实现
executorService().execute(call);
} else {
//TODO 加入等候队列
readyAsyncCalls.add(call);
}
}

可见Dispatcher将Call加入队列中(若同时请求数未超过最大值,则加入运行队列,放到线程池中执行;否则加入等待队列),然后通过线程池执行call。
executorService() 本质上是一个线程池执行方法,用于创建一个线程池

public synchronized ExecutorService executorService() {
if (executorService == null) {
//TODO 线程池的相关概念 需要理解
//TODO 核心线程 最大线程 非核心线程闲置60秒回收 任务队列
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue(), Util.threadFactory(“OkHttp Dispatcher”,
false));
}
return executorService;
}

  1. 在 ConnectionPool 线程池中执行请求 AsyncCall
    异步请求中Call实际上为AsyncCall,继承自NamedRunnable

final class AsyncCall extends NamedRunnable

NamedRunnable

public abstract class NamedRunnable implements Runnable {
protected final String name;

public NamedRunnable(String format, Object… args) {
this.name = Util.format(format, args);
}

@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}

protected abstract void execute();
}

因此 AsyncCall 其实就是一个 Runnable,线程池实际上就是执行了execute()。
AsyncCall的execute()

final class AsyncCall extends NamedRunnable {
@Override protected void execute() {
boolean signalledCallback = false;
try {
//TODO 责任链模式
//TODO 拦截器链 执行请求
Response response = getResponseWithInterceptorChain();
//回调结果
if (retryAndFollowUpInterceptor.isCanceled()) {
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!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//TODO 移除队列
client.dispatcher().finished(this);
}
}
}

从上述代码可以看出真正执行请求的是getResponseWithInterceptorChain(); 然后通过回调将Response返回给用户。

  1. 通过拦截器链 RealInterceptorChain 通过 责任链模式 真正执行网络请求
    真正的执行网络请求和返回响应结果:getResponseWithInterceptorChain()

//TODO 核心代码 开始真正的执行网络请求
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
//TODO 责任链
List interceptors = new ArrayList<>();
//TODO 在配置okhttpClient 时设置的intercept 由用户自己设置
interceptors.addAll(client.interceptors());
//TODO 负责处理失败后的重试与重定向
interceptors.add(retryAndFollowUpInterceptor);
//TODO 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息
//TODO 从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//TODO 处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响应在有效期内)返回缓存响应
//TODO 设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改)
//TODO 可配置用户自己设置的缓存拦截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//TODO 连接服务器 负责和服务器建立连接 这里才是真正的请求网络
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//TODO 配置okhttpClient 时设置的networkInterceptors
//TODO 返回观察单个网络请求和响应的不可变拦截器列表。
interceptors.addAll(client.networkInterceptors());
}
//TODO 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据
//TODO 进行http请求报文的封装与请求报文的解析
interceptors.add(new CallServerInterceptor(forWebSocket));

//TODO 创建责任链
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());

//TODO 执行责任链
return chain.proceed(originalRequest);
}

一共执行6种拦截器,一种是用户自定义拦截器client.interceptors(),另外五种是OkHttp自带拦截器:

  • RetryAndFollowUpInterceptor,重试那些失败或者redirect的请求。
  • BridgeInterceptor,请求之前对响应头做了一些检查,并添加一些头,然后在请求之后对响应做一些处理(gzip解压or设置cookie)。
  • CacheInterceptor,根据用户是否有设置cache,如果有的话,则从用户的cache中获取当前请求的缓存。
  • ConnectInterceptor,复用连接池中的连接,如果没有就与服务器建立新的socket连接。
  • CallServerInterceptor,负责发送请求和获取响应。

从上述代码中,可以看出都实现了Interceptor接口,这是okhttp最核心的部分,采用责任链的模式来使每个功能分开,每个Interceptor自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑。

责任链模式是OkHttp中最核心的设计模式。

我们着重分析一下,okhttp的设计实现,如何通过责任链来进行传递返回数据的。上述代码中可以看出interceptors,是传递到了RealInterceptorChain该类实现了Interceptor.Chain,并且执行了chain.proceed(originalRequest)。核心代码就是chain.proceed() 通过该方法进行责任链的执行。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
//TODO 获取下一个拦截链,即链中的拦截器集合index+1
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//TODO 执行当前的拦截器-如果在配置okhttpClient,时没有设置intercept默认是先执行:retryAndFollowUpInterceptor 拦截器
Interceptor interceptor = interceptors.get(index);
//TODO 执行下一个拦截器
Response response = interceptor.intercept(next);
return response;
}

从上述代码,我们可以知道,获取下一个(index+1)RealInterceptorChain 责任链,然后 执行当前(index)责任链interceptors.get(index),最后返回下一个责任链的Response。
其实就是按责任链顺序递归执行了拦截器
这样设计的一个好处就是,责任链中每个拦截器都会执行chain.proceed()方法之前的代码,等责任链最后一个拦截器执行完毕后会返回最终的响应数据,而chain.proceed() 方法会得到最终的响应数据,这时就会执行每个拦截器的chain.proceed()方法之后的代码,其实就是对响应数据的一些操作。执行过程如下图:

CacheInterceptor 缓存拦截器就是很好的证明,我们来通过CacheInterceptor 缓存拦截器来进行分析,大家就会明白了。
CacheInterceptor 的实现如下:
首先我们先分析上部分代码当没有网络的情况下是如何处理获取缓存的。

@Override public Response intercept(Chain chain) throws IOException
{
//TODO 获取request对应缓存的Response 如果用户没有配置缓存拦截器 cacheCandidate == null
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;

//TODO 执行响应缓存策略
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//TODO 如果networkRequest == null 则说明不使用网络请求
Request networkRequest = strategy.networkRequest;
//TODO 获取缓存中(CacheStrategy)的Response
Response cacheResponse = strategy.cacheResponse;

if (cache != null) {
cache.trackResponse(strategy);
}
//TODO 缓存无效 关闭资源
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn’t applicable. Close it.
}

// If we’re forbidden from using the network and the cache is insufficient, fail.
//TODO networkRequest == null 不实用网路请求 且没有缓存 cacheResponse == null 返回失败
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message(“Unsatisfiable Request (only-if-cached)”)
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}

//TODO 不使用网络请求 且存在缓存 直接返回响应
// If we don’t need the network, we’re done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
}

上述的代码,主要做了几件事:
如果用户自己配置了缓存拦截器,cacheCandidate = cache.Response 获取用户自己存储的Response,否则 cacheCandidate = null;同时从CacheStrategy 获取cacheResponse 和 networkRequest
如果cacheCandidate != null 而 cacheResponse == null 说明缓存无效清除cacheCandidate缓存。
如果networkRequest == null 说明没有网络,cacheResponse == null 没有缓存,返回失败的信息,责任链此时也就终止,不会在往下继续执行。
如果networkRequest == null 说明没有网络,cacheResponse != null 有缓存,返回缓存的信息,责任链此时也就终止,不会在往下继续执行。
上部分代码,其实就是没有网络的时候的处理。
那么下部分代码肯定是,有网络的时候处理

//TODO 执行下一个拦截器
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we’re crashing on I/O or otherwise, don’t leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}

//TODO 网络请求 回来 更新缓存
// If we have a cache response too, then we’re doing a conditional get.
//TODO 如果存在缓存 更新
if (cacheResponse != null) {
//TODO 304响应码 自从上次请求后,请求需要响应的内容未发生改变
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();

// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//TODO 缓存Response
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();

if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}

if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}

return response;
}

下部分代码主要做了这几件事:
执行下一个拦截器,也就是请求网络
责任链执行完毕后,会返回最终响应数据,如果缓存存在更新缓存,如果缓存不存在加入到缓存中去。
这样就体现出了,责任链这样实现的好处了,当责任链执行完毕,如果拦截器想要拿到最终的数据做其他的逻辑处理等,这样就不用在做其他的调用方法逻辑了,直接在当前的拦截器就可以拿到最终的数据。
这也是okhttp设计的最优雅最核心的功能。

  1. 执行调度器完成方法,移除队列
    AsyncCall的execute()中(见第3步)中finally 执行了client.dispatcher().finished(this); 通过调度器移除队列,并且判断是否存在等待队列,如果存在,检查执行队列是否达到最大值,如果没有将等待队列变为执行队列。这样也就确保了等待队列被执行。

private void finished(Deque calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//TODO calls 移除队列
if (!calls.remove(call)) throw new AssertionError(“Call wasn’t in-flight!”);
//TODO 检查是否为异步请求,检查等候的队列 readyAsyncCalls,如果存在等候队列,则将等候队列加入执行队列
if (promoteCalls) promoteCalls();
//TODO 运行队列的数量
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
//闲置调用
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}

private void promoteCalls() {
//TODO 检查 运行队列 与 等待队列
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

//TODO 将等待队列加入到运行队列中
for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
//TODO 相同host的请求没有达到最大,加入运行队列
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}

if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}

同步请求 RealCall.excute()

//TODO 同步执行请求 直接返回一个请求的结果
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException(“Already Executed”);
executed = true;
}
captureCallStackTrace();
//TODO 调用监听的开始方法
eventListener.callStart(this);
try {
//TODO 交给调度器去执行
client.dispatcher().executed(this);
//TODO 获取请求的返回数据
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException(“Canceled”);
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
//TODO 执行调度器的完成方法 移除队列
client.dispatcher().finished(this);
}
}

  1. synchronized (this) 避免重复执行
  2. Dispatcher 调度器 将 Call 加入同步执行队列

//TODO 调度器执行同步请求
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}

  1. 通过拦截器链 RealInterceptorChain 通过 责任链模式 真正执行网络请求
    getResponseWithInterceptorChain()最核心的代码,请求网络得到响应数据,返回给用户。
  2. 执行调度器完成方法,移除队列
    client.dispatcher().finished(this); 执行调度器的完成方法 移除队列
2.3 OkHttp 执行流程总结
  1. OkhttpClient 实现了Call.Fctory,负责为Request 创建 Call;
  2. RealCall 为Call的具体实现,其enqueue() 异步请求接口通过Dispatcher()调度器利用ExcutorService实现,而最终进行网络请求时和同步的execute()接口一致,都是通过 getResponseWithInterceptorChain() 函数实现
  3. getResponseWithInterceptorChain() 中利用 Interceptor 链条,责任链模式 分层实现缓存、透明压缩、网络 IO 等功能;最终将响应数据返回给用户。
2.4 OkHttp 设计模式总结
  1. 建造者模式
    创建者模式又叫建造者模式,是将一个复杂的对象的构建与它的表示分离,使
    得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。
    OkHttp中HttpClient、Request构造便是通过建造者模式
    OkHttpClient

mOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.retryonConnectionFailure(true)
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.build();

Request

Request request = new Request.Builder()
.url(url)
.build();

  1. 简单工厂模式
    okhttp 实现了Call.Factory接口

interface Factory {
Call newCall(Request request);
}

我们看一下okhttpClient 如何实现的Call接口,代码如下

@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false );
}

可以看出 真正的请求交给了 RealCall 类,并且RealCall 实现了Call方法,RealCall是真正的核心代码。

  1. 责任链模式
    责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
    在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
    OkHttp责任链验证
    (1)模拟拦截器接口

public interface Interceptor {
String interceptor(Chain chain);

interface Chain {
String request();

String proceed(String request);
}
}

(2)实现具体拦截器

public class BridgeInterceptor implements Interceptor {
@Override

题外话

我们见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

注意:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

interface Chain {
String request();

String proceed(String request);
}
}

(2)实现具体拦截器

public class BridgeInterceptor implements Interceptor {
@Override

题外话

我们见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

注意:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-URzHMual-1640330360323)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/679147.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号