安卓开发大军浩浩荡荡,经过近十年的发展,Android技术优化日异月新,如今Android 11.0 已经发布,Android系统性能也已经非常流畅,可以在体验上完全媲美iOS。
但是,到了各大厂商手里,改源码、自定义系统,使得Android原生系统变得鱼龙混杂,然后到了不同层次的开发工程师手里,因为技术水平的参差不齐,即使很多手机在跑分软件性能非常高,打开应用依然存在卡顿现象。
另外,随着产品内容迭代,功能越来越复杂,UI页面也越来越丰富,也成为流畅运行的一种阻碍。综上所述,对APP进行性能优化已成为开发者该有的一种综合素质,也是开发者能够完成高质量应用程序作品的保证。
为此,我把阿里、腾讯、字节跳动、美团等公司Android性能优化实战整合成了一个PDF文档,由于内容过多的,我先介绍实战部分,之后再介绍理论部分。
public final class Request {
final HttpUrl url; //网络请求路径
final String method; //get、post…
final Headers headers;//请求头
final @Nullable RequestBody body;//请求体
/**
你可以通过tags来同时取消多个请求。
当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。
之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。.
*/
final Map<Class<?>, Object> tags;
…
…
…
}
这个估计很多人都清楚,如果对请求头请求体等不清楚的,可以看下以前我们这个系列的文章:[Android技能树 — 网络小结(3)之HTTP/HTTPS](()
2.3 Call相关
我们可以看到我们生成的Request实例,会传给OkHttpClient实例的newÇall方法:
Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
call.execute();或者 call.enqueue(…);
我们Request和OkHttpClient大致都了解过了,我们来具体看下newCall执行了什么和Call的具体内容。
Call类代码:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
RealCall类代码:
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
我们可以看到,最后获取到的是RealCall的实例,同时把我们各种参数都配置好的OkHttpClient和Request都传入了。
所以后面call.execute()/call.enqueue()
都是执行的RealCall的相对应的方法。但目前位置我们上面的图已经讲解好了,我这里再贴一次:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vzblsa8m-1649662193340)(https://user-gold-cdn.xitu.io/2018/11/4/166dea0866cbbb48?imageView2/0/w/1280/h/960/ignore-error/1)]
恭喜你,下次别人考你Okhttp前面的相关参数配置方面的代码你已经都理解了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YMPPMmsB-1649662193341)(https://user-gold-cdn.xitu.io/2018/11/13/1670b21751eafe73?imageView2/0/w/1280/h/960/ignore-error/1)]
3.请求分发Dispatcher
我们继续看我们的流程图下面的内容:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xsvX0cbL-1649662193342)(https://user-gold-cdn.xitu.io/2018/11/13/1670bdac1f65f61d?imageView2/0/w/1280/h/960/ignore-error/1)]
3.1 Dispatcher 同步操作
我们先来讲同步执行:
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException(“Already Executed”);
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
//‘1. 执行了dispatcher的executed方法’
client.dispatcher().executed(this);
//‘2. 调用了getResponseWithInterceptorChain方法’
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException(“Canceled”);
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
//‘3. 最后一定会执行dispatcher的finished方法’
client.dispatcher().finished(this);
}
}
我们一步步来具体看,第一步看Dispatcher类中的executed方法了:
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
可以看到把我们的RealCall加入到了一个同步线程runningSyncCalls
中,然后中间调用了getResponseWithInterceptorChain
方法*(这个第二个操作我们会放在后面很具体的讲解),我们既然加入到了一个同步线程中,肯定用完了要移除,然后第三步finished方法会做处理:
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private void finished(Deque calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//‘if语句里面我们可以看到这里把我们的队列中移除了call对象’
if (!calls.remove(call)) throw new AssertionError(“Call wasn’t in-flight!”);
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
3.2 Dispatcher 异步操作
我们先来看RealCall
里面的enqueue
代码:
@Override public void enqueue(Callback responseCallback) {
//‘1. 这里有个同步锁的抛异常操作’
synchronized (this) {
if (executed) throw new IllegalStateException(“Already Executed”);
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
//‘2. 调用Dispatcher里面的enqueue方法’
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
我们一步步来看,第一个同步锁抛异常的操作,我们知道一个Call应对一个网络请求,加入你这么写是错误的:
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}
@Override
public void onResponse(Call call, Response response) throws IOException {}
});
//‘同一个call对象再次发起请求’
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}
@Override
public void onResponse(Call call, Response response) throws IOException {}
});
同一个Call对象,同时请求了二次。这时候就会进入我们的同步锁判断,只要一个执行过了,里面 executed会为true
,也就会抛出异常。
我们再来看第二步操作:
我们知道异步请求,肯定会代表很多请求都在各自的线程中去执行,那么我们在不看OkHttp源码前,让你去实现,你怎么实现,是不是第一个反应是使用线程池。
Java/Android线程池框架的结构主要包括3个部分
1.任务:包括被执行任务需要实现的接口类:Runnable 或 Callable
2.任务的执行器:包括任务执行机制的核心接口类Executor,以及继承自Executor的EexcutorService接口。
3.执行器的创建者,工厂类Executors
具体可以参考:[Android 线程池框架、Executor、ThreadPoolExecutor详解](()
client.dispatcher().enqueue(new AsyncCall(responseCallback));
,不再是像同步操作一样,直接把RealCall传入,而是传入一个AsyncCall
对象。没错,按照我们上面提到的线程池架构,任务是使用Runnable 或 Callable接口
,我们查看AsyncCall的代码:
final class AsyncCall extends NamedRunnable {
…
…
}
public abstract class NamedRunnable implements Runnable {
…
…
}
果然如我们预计,是使用了Runnable接口。
client.dispatcher().enqueue(new AsyncCall(responseCallback));
,不再是像同步操作一样,直接把RealCall传入,而是传入一个AsyncCall
对象。
调用Dispatcher里面的enqueue方法:
synchronized void enqueue(AsyncCall call) {
//‘1. 判断当前异步队列里面的数量是否小于最大值,当前请求数是否小于最大值’
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//‘2. 如果没有大于最大值,则将call加入到异步请求队列中’
runningAsyncCalls.add(call);
//‘3. 并且运行call的任务’
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
我么直接看第三步,按照我们上面提到过的Java/Android线程池框架的结构主要包括3个部分,可以看到执行我们的Runnable对象的,说明他是一个任务执行器,也就是Executor的继承类。说明executorService()返回了一个Executor的实现类,我们点进去查看:
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue(), Util.threadFactory(“OkHttp Dispatcher”, false));
}
return executorService;
}
果然创建一个可缓存线程池,线程池的最大长度无限制,但如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
那我们知道是线程池执行了Runnable的任务,那我们只要具体看我们的okhttp的Runnable到底执行了什么即可:
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super(“OkHttp %s”, redactedUrl());
this.responseCallback = responseCallback;
}
…
…
…
@Override protected void execute() {
boolean signalledCallback = false;
try {
//‘1. 我们可以发现最后线程池执行的任务就是getResponseWithInterceptorChain方法’
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 {
//‘2. 最后再从Dispatcher里面的异步队列中移除’
client.dispatcher().finished(this);
}
}
}
我们发现不管是异步还是同步,都是一样的三部曲:1.加入到Dispatcher里面的同步(或异步)队列,2.执行getResponseWithInterceptorChain方法,3.从Dispatcher里面的同步(或异步)队列移除。(只不过同步操作是直接运行了getResponseWithInterceptorChain方法,而异步是通过线程池执行Runnable再去执行getResponseWithInterceptorChain方法)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L719gYlI-1649662193342)(https://user-gold-cdn.xitu.io/2018/11/13/1670b21751eafe73?imageView2/0/w/1280/h/960/ignore-error/1)]
4 Okhttp拦截
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r1P801ZQ-1649662193343)(https://user-gold-cdn.xitu.io/2018/11/13/1670bd9cd984df72?imageView2/0/w/1280/h/960/ignore-error/1)]
我们在前面已经知道了不管是异步请求还是同步请求,都会去执行 RealCall
的getResponseWithInterceptorChain
操作:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
//‘1. 创建一个拦截器List’
List interceptors = new ArrayList<>();
//‘2. 添加用户自己创建的应用拦截器’
interceptors.addAll(client.interceptors());
//‘3. 添加重试与重定向拦截器’
interceptors.add(retryAndFollowUpInterceptor);
//‘4. 添加内容拦截器’
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//‘4. 添加缓存拦截器’
interceptors.add(new CacheInterceptor(client.internalCache()));
/‘5. 添加连接拦截器’
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//‘6. 添加用户自己创建的网络拦截器’
interceptors.addAll(client.networkInterceptors());
}
//‘7. 添加请求服务拦截器’
interceptors.add(new CallServerInterceptor(forWebSocket));
//‘8.把这些拦截器们一起封装在一个拦截器链条上面(RealInterceptorChain)’
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//‘9.然后执行链条的proceed方法’
return chain.proceed(originalRequest);
}
我们先不管具体的拦截器的功能,我们先来看整体的执行方式,所以我们直接来看拦截器链条的工作模式:
public final class RealInterceptorChain implements Interceptor.Chain {
//‘我们刚才建立的放拦截器的队列’
private final List interceptors;
//‘当前执行的第几个拦截器序号’
private final int index;
…
…
…
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
…
…
…
//‘实例化了一个新的RealInterceptorChain对象,并且传入相同的拦截器List,只不过传入的index值+1’
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//‘获取当前index对应的拦截器里面的具体的某个拦截器,
Interceptor interceptor = interceptors.get(index);
//然后执行拦截器的intercept方法,同时传入新的RealInterceptorChain对象(主要的区别在于index+1了)’
Response response = interceptor.intercept(next);
…
…
…
return response;
}
}
我们可以看到在RealInterceptorChain类的proceed的方法里面,又去实例化了一个RealInterceptorChain类。很多人可能看着比较绕,没关系,我们举个例子简单说下就可以了:
我的写法还是按照它的写法,写了二个Interceptor,一个用来填充地址AddAddressInterceptor,一个中来填充电话AddTelephoneInterceptor,然后也建立一个拦截链条InterceptorChain。这样我只需要传进去一个字符串,然后会自动按照每个拦截器的功能,自动帮我填充了地址和电话号码。
Interceptor只负责处理自己的业务功能,比如我们这里是填充地址和手机号码,然后自己的任务结束就会调用拦截器链条,执行链条接下去的任务,其他跟Interceptor无关。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pDWnsiWo-1649662193343)(https://user-gold-cdn.xitu.io/2018/11/12/16705c7c8eb7393b?imageView2/0/w/1280/h/960/ignore-error/1)]
我们来看我们的拦截器和拦截器链条:
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》开源
Android优秀开源项目:
- ali1024.coding.net/public/P7/Android/git
学习福利
【Android 详细知识点思维脑图(技能树)】
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
段就好。
虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
[外链图片转存中…(img-t3w4Mi28-1649662193344)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。