前言
虽说2022年只剩下整整2个月了,但是今年的局势有点不太好,导致很多小伙伴吐槽找工作好难啊。
今天来分享一下关于百度会问那些技术点,提前做好公开事半功倍。😁
关注公众号:初一十五a
解锁 《Android十三大板块文档》,让学习更贴近未来实战。已形成PDF版
内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载应用大合集,从零开始一起学
4.性能优化大合集,告别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高级工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对工作需求
10.Android基础篇大合集,根基稳固高楼平地起
11.Flutter番外篇:面试+项目实战+电子书
12.高级Android组件化强化实战
13.十二模块之补充部分:其他Android十一大知识体系
整理不易,关注一下吧。开始进入正题,ღ( ´・ᴗ・` ) 🤔
一丶百度篇
1.关于OKhttp和Retrofit
Retrofit
①基本使用流程
定义HTTP API,用于描述请求
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
创建Retrofit并生成API的实现
(注意: 方法上面的注解表示请求的接口部分,返回类型是请求的返回值类型,方法的参数即是请求的参数)
// 1.Retrofit构建过程
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
// 2.创建网络请求接口类实例过程
GitHubService service = retrofit.create(GitHubService.class);
调用API方法,生成Call,执行请求
// 3.生成并执行请求过程
Call<List<Repo>> repos = service.listRepos("octocat");
repos.execute() or repos.enqueue()
Retrofit的基本使用流程很简洁,但是简洁并不代表简单,Retrofit为了实现这种简洁的使用流程,内部使用了优秀的架构设计和大量的设计模式,在分析过Retrofit最新版的源码和大量优秀的Retrofit源码分析文章后发现,要想真正理解Retrofit内部的核心源码流程和设计思想,首先,需要对这九大设计模式有一定的了解,如下:
-
Retrofit构建过程 建造者模式、工厂方法模式
-
创建网络请求接口实例过程 外观模式、代理模式、单例模式、策略模式、装饰模式(建造者模式)
-
生成并执行请求过程 适配器模式(代理模式、装饰模式)
其次,需要对OKHttp源码有一定的了解。让我们按以上流程去深入Retrofit源码内部,领悟它带给我们的设计之美。
②Retrofit构建过程
Retrofit核心对象解析
首先Retrofit中有一个全局变量非常关键,在V2.5之前的版本,使用的是LinkedHashMap(),它是一个网络请求配置对象,是由网络请求接口中方法注解进行解析后得到的。
public final class Retrofit {
// 网络请求配置对象,存储网络请求相关的配置,如网络请求的方法、数据转换器、网络请求适配器、网络请求工厂、基地址等
private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
Retrofit使用了建造者模式通过内部类Builder类建立一个Retrofit实例,如下:
public static final class Builder {
// 平台类型对象(Platform -> Android)
private final Platform platform;
// 网络请求工厂,默认使用OkHttpCall(工厂方法模式)
private @Nullable okhttp3.Call.Factory callFactory;
// 网络请求的url地址
private @Nullable HttpUrl baseUrl;
// 数据转换器工厂的集合
private final List<Converter.Factory> converterFactories = new ArrayList<>();
// 网络请求适配器工厂的集合,默认是ExecutorCallAdapterFactory
private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
// 回调方法执行器,在 Android 上默认是封装了 handler 的 MainThreadExecutor, 默认作用是:切换线程(子线程 -> 主线程)
private @Nullable Executor callbackExecutor;
// 一个开关,为true则会缓存创建的ServiceMethod
private boolean validateEagerly;
Builder内部构造
下面看看Builder内部构造做了什么。
public static final class Builder {
...
Builder(Platform platform) {
this.platform = platform;
}
public Builder() {
this(Platform.get());
}
...
}
class Platform {
private static final Platform PLATFORM = findPlatform();
static Platform get() {
return PLATFORM;
}
private static Platform findPlatform() {
try {
// 使用JVM加载类的方式判断是否是Android平台
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
// 同时支持Java平台
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}
static class Android extends Platform {
...
@Override public Executor defaultCallbackExecutor() {
//切换线程(子线程 -> 主线程)
return new MainThreadExecutor();
}
// 创建默认的网络请求适配器工厂,如果是Android7.0或Java8上,则使
// 用了并发包中的CompletableFuture保证了回调的同步
// 在Retrofit中提供了四种CallAdapterFactory(策略模式):
// ExecutorCallAdapterFactory(默认)、GuavaCallAdapterFactory、
// va8CallAdapterFactory、RxJavaCallAdapterFactory
@Override List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
@Nullable Executor callbackExecutor) {
if (callbackExecutor == null) throw new AssertionError();
ExecutorCallAdapterFactory executorFactory = new ExecutorCallAdapterFactory(callbackExecutor);
return Build.VERSION.SDK_INT >= 24
? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
: singletonList(executorFactory);
}
...
@Override List<? extends Converter.Factory> defaultConverterFactories() {
return Build.VERSION.SDK_INT >= 24
? singletonList(OptionalConverterFactory.INSTANCE)
: Collections.<Converter.Factory>emptyList();
}
...
static class MainThreadExecutor implements Executor {
// 获取Android 主线程的Handler
private final Handler handler = new Handler(Looper.getMainLooper());
@Override public void execute(Runnable r) {
// 在UI线程对网络请求返回数据处理
handler.post(r);
}
}
}
可以看到,在Builder内部构造时设置了默认Platform、callAdapterFactories和callbackExecutor。
添加baseUrl
很简单,就是将String类型的url转换为OkHttp的HttpUrl过程如下:
/**
* Set the API base URL.
*
* @see #baseUrl(HttpUrl)
*/
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
return baseUrl(HttpUrl.get(baseUrl));
}
public Builder baseUrl(HttpUrl baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
List<String> pathSegments = baseUrl.pathSegments();
if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
}
this.baseUrl = baseUrl;
return this;
}
添加GsonConverterFactory
首先,看到GsonConverterFactory.creat()的源码。
public final class GsonConverterFactory extends Converter.Factory {
public static GsonConverterFactory create() {
return create(new Gson());
}
public static GsonConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new GsonConverterFactory(gson);
}
private final Gson gson;
// 创建了一个含有Gson对象实例的GsonConverterFactory
private GsonConverterFactory(Gson gson) {
this.gson = gson;
}
然后,看看addConverterFactory()方法内部。
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory null"));
return this;
}
可知,这一步是将一个含有Gson对象实例的GsonConverterFactory放入到了数据转换器工厂converterFactories里。
build过程
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
// 默认使用okhttp
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
// Android默认的callbackExecutor
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the defaultCall adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
// 添加默认适配器工厂在集合尾部
callAdapterFactories.addAll(platform.defaultCallAdapterFactorisca llbackExecutor));
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories = new ArrayList<>(
1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
// Add the built-in converter factory first. This prevents overriding its behavior but also
// ensures correct behavior when using converters thatconsumeall types.
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories();
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
可以看到,最终我们在Builder类中看到的6大核心对象都已经配置到Retrofit对象中了
③创建网络请求接口实例过程
retrofit.create()使用了外观模式和代理模式创建了网络请求的接口实例,我们分析下create方法。
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
// 判断是否需要提前缓存ServiceMethod对象
eagerlyValidateMethods(service);
}
// 使用动态代理拿到请求接口所有注解配置后,创建网络请求接口实例
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override public Object invoke(Object proxy, Method method, @Nullable 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);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
private void eagerlyValidateMethods(Class<?> service) {
Platform platform = Platform.get();
for (Method method : service.getDeclaredMethods()) {
if (!platform.isDefaultMethod(method)) {
loadServiceMethod(method);
}
}
}
继续看看loadServiceMethod的内部流程
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
// 解析注解配置得到了ServiceMethod
result = ServiceMethod.parseAnnotations(this, method);
// 可以看到,最终加入到ConcurrentHashMap缓存中
serviceMethodCache.put(method, result);
}
}
return result;
}
abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// 通过RequestFactory解析注解配置(工厂模式、内部使用了建造者模式)
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(method,
"Method return type must not include a type variable or wildcard: %s", returnType);
}
if (returnType == void.class) {
throw methodError(method, "Service methods cannot return void.");
}
// 最终是通过HttpServiceMethod构建的请求方法
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
abstract T invoke(Object[] args);
}
请求构造核心流程
根据RequestFactory#Builder构造方法和parseAnnotations方法的源码,可知的它的作用就是用来解析注解配置的。
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
// 获取网络请求接口方法里的注释
this.methodAnnotations = method.getAnnotations();
// 获取网络请求接口方法里的参数类型
this.parameterTypes = method.getGenericParameterTypes();
// 获取网络请求接口方法里的注解内容
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
接着看HttpServiceMethod.parseAnnotations()的内部流程。
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
//1.根据网络请求接口方法的返回值和注解类型,
// 从Retrofit对象中获取对应的网络请求适配器
CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit,method);
// 得到响应类型
Type responseType = callAdapter.responseType();
...
//2.根据网络请求接口方法的返回值和注解类型从Retrofit对象中获取对应的数据转换器
Converter<ResponseBody, ResponseT>responseConverter =
createResponseConverter(retrofit,method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
return newHttpServiceMethod<>(requestFactory, callFactory, callAdapter,responseConverter);
}
createCallAdapter(retrofit, method)
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
Retrofit retrofit, Method method) {
// 获取网络请求接口里方法的返回值类型
Type returnType = method.getGenericReturnType();
// 获取网络请求接口接口里的注解
Annotation[] annotations = method.getAnnotations();
try {
//noinspection unchecked
return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create call adapter for %s", returnType);
}
}
public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
...
int start = callAdapterFactories.indexOf(skipPast) + 1;
// 遍历 CallAdapter.Factory 集合寻找合适的工厂
for (int i = start, count = callAdapterFactories.size(); i <count; i++) {
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
}
createResponseConverter(Retrofit retrofit, Method method, Type responseType)
private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter(
Retrofit retrofit, Method method, Type responseType) {
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.responseBodyConverter(responseType,annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create converter for%s", responseType);
}
}
public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
return nextResponseBodyConverter(null, type, annotations);
}
public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
...
int start = converterFactories.indexOf(skipPast) + 1;
// 遍历 Converter.Factory 集合并寻找合适的工厂, 这里是GsonResponseBodyConverter
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<ResponseBody, T>) converter;
}
}
执行HttpServiceMethod的invoke方法
@Override ReturnT invoke(Object[] args) {
return callAdapter.adapt(
new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
}
最终在adapt中创建了一个ExecutorCallbackCall对象,它是一个装饰者,而在它内部真正去执行网络请求的还是OkHttpCall。
④创建网络请求接口类实例并执行请求过程
service.listRepos()
1、Call<List<Repo>> repos = service.listRepos("octocat");
service对象是动态代理对象Proxy.newProxyInstance(),当调用getCall()时会被 它拦截,然后调用自身的InvocationHandler#invoke(),得到最终的Call对象。
同步执行流程 repos.execute()
@Override public Response<T> execute() throws IOException {
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 if (creationFailure instanceof RuntimeException) {
throw (RuntimeException) creationFailure;
} else {
throw (Error) creationFailure;
}
}
call = rawCall;
if (call == null) {
try {
// 创建一个OkHttp的Request对象请求
call = rawCall = createRawCall();
} catch (IOException | RuntimeException | Error e) {
throwIfFatal(e); // Do not assign a fatal error to creationFailure.
creationFailure = e;
throw e;
}
}
}
if (canceled) {
call.cancel();
}
// 调用OkHttpCall的execute()发送网络请求(同步),
// 并解析网络请求返回的数据
return parseResponse(call.execute());
}
private okhttp3.Call createRawCall() throws IOException {
// 创建 一个okhttp3.Request
okhttp3.Call call =
callFactory.newCall(requestFactory.create(args));
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the body's source (the only stateful object) so we can pass the response along.
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
// 根据响应返回的状态码进行处理
int code = rawResponse.code();
if (code < 200 || code >= 300) {
try {
// Buffer the entire body to avoid future I/O.
ResponseBody bufferedBody = Utils.buffer(rawBody);
return Response.error(bufferedBody, rawResponse);
} finally {
rawBody.close();
}
}
if (code == 204 || code == 205) {
rawBody.close();
return Response.success(null, rawResponse);
}
ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
try {
// 将响应体转为Java对象
T body = responseConverter.convert(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;
}
}
异步请求流程 reponse.enqueque
@Override
public void enqueue(final Callback<T> callback) {
// 使用静态代理 delegate进行异步请求
delegate.enqueue(new Callback<T>() {
@Override
public void onResponse(Call<T> call, finalResponse<T>response) {
// 线程切换,在主线程显示结果
callbackExecutor.execute(new Runnable() {
@Override
public void run() {
if (delegate.isCanceled()) {
callback.onFailure(ExecutorCallbackCall.this, newIOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this,respons);
}
}
});
}
@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
callback.onFailure(ExecutorCallbackCall.this, t);
}
});
}
});
}
看看 delegate.enqueue 内部流程。
@Override
public void enqueue(final Callback<T> callback) {
okhttp3.Call call;
Throwable failure;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
try {
// 创建OkHttp的Request对象,再封装成OkHttp.call
// 方法同发送同步请求,此处上面已分析
call = rawCall = createRawCall();
} catch (Throwable t) {
failure = creationFailure = t;
}
}
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null");
okhttp3.Call call;
Throwable failure;
...
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
// 此处上面已分析
response = parseResponse(rawResponse);
} catch (Throwable e) {
throwIfFatal(e);
callFailure(e);
return;
}
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
@Override public void onFailure(okhttp3.Call call, IOException e) {
callFailure(e);
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
⑤Retrofit源码流程图
建议大家自己主动配合着Retrofit最新版的源码一步步去彻底地认识它,只有这样,你才能看到它真实的内心,附上一张Retrofit源码流程图,要注意的是,这是V2.5之前版本的流程,但是,在看完上面的源码分析后,我们知道,主体流程是没有变化的。
从本质上来说,Retrofit虽然只是一个RESTful 的HTTP 网络请求框架的封装库。但是,它内部通过 大量的设计模式 封装了 OkHttp,让使用者感到它非常简洁、易懂。它内部主要是用动态代理的方式,动态将网络请求接口的注解解析成HTTP请求,最后执行请求的过程。
OKhttp
①OKHttp网络框架
OKHttp内部的大致请求流程图如下所示:
如下为使用OKHttp进行Get请求的步骤:
//1.新建OKHttpClient客户端
OkHttpClient client = new OkHttpClient();
//新建一个Request对象
Request request = new Request.Builder()
.url(url)
.build();
//2.Response为OKHttp中的响应
Response response = client.newCall(request).execute();
新建OKHttpClient客户端
OkHttpClient client = new OkHttpClient();
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
....
}
可以看到,OkHttpClient使用了建造者模式,Builder里面的可配置参数如下:
public static final class Builder {
Dispatcher dispatcher;// 分发器
@Nullable Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;// 传输层版本和连接协议
final List<Interceptor> interceptors = new ArrayList<>();// 拦截器
final List<Interceptor> networkInterceptors = new ArrayList<>();
EventListener.Factory eventListenerFactory;
ProxySelector proxySelector;
CookieJar cookieJar;
@Nullable Cache cache;
@Nullable InternalCache internalCache;// 内部缓存
SocketFactory socketFactory;
@Nullable SSLSocketFactory sslSocketFactory;// 安全套接层socket 工厂,用于HTTPS
@Nullable CertificateChainCleaner certificateChainCleaner;// 验证确认响应证书 适用 HTTPS 请求连接的主机名。
HostnameVerifier hostnameVerifier;// 验证确认响应证书 适用 HTTPS 请求连接的主机名。
CertificatePinner certificatePinner;// 证书锁定,使用CertificatePinner来约束哪些认证机构被信任。
Authenticator proxyAuthenticator;// 代理身份验证
Authenticator authenticator;// 身份验证
ConnectionPool connectionPool;// 连接池
Dns dns;
boolean followSslRedirects; // 安全套接层重定向
boolean followRedirects;// 本地重定向
boolean retryOnConnectionFailure;// 重试连接失败
int callTimeout;
int connectTimeout;
int readTimeout;
int writeTimeout;
int pingInterval;
// 这里是默认配置的构建参数
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
...
}
// 这里传入自己配置的构建参数
Builder(OkHttpClient okHttpClient) {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
this.protocols = okHttpClient.protocols;
this.connectionSpecs = okHttpClient.connectionSpecs;
this.interceptors.addAll(okHttpClient.interceptors);
this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
...
}
同步请求流程
Response response = client.newCall(request).execute();
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@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;
}
@Override public Response execute() throws IOException {
synchronized (this) {
// 每个Call只能执行一次
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
// 通知dispatcher已经进入执行状态
client.dispatcher().executed(this);
// 通过一系列的拦截器请求处理和响应处理得到最终的返回结果
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
// 通知 dispatcher 自己已经执行完毕
client.dispatcher().finished(this);
}
}
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
// 在配置 OkHttpClient 时设置的 interceptors;
interceptors.addAll(client.interceptors());
// 负责失败重试以及重定向
interceptors.add(retryAndFollowUpInterceptor);
// 请求时,对必要的Header进行一些添加,接收响应时,移除必要的Header
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// 负责读取缓存直接返回、更新缓存
interceptors.add(new CacheInterceptor(client.internalCache()));
// 负责和服务器建立连接
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
// 配置 OkHttpClient 时设置的 networkInterceptors
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);
}
// StreamAllocation 对象,它相当于一个管理类,维护了服务器连接、并发流
// 和请求之间的关系,该类还会初始化一个 Socket 连接对象,获取输入/输出流对象。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
...
// Call the next interceptor in the chain.
// 实例化下一个拦截器对应的RealIterceptorChain对象
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
// 得到当前的拦截器
Interceptor interceptor = interceptors.get(index);
// 调用当前拦截器的intercept()方法,并将下一个拦截器的RealIterceptorChain对象传递下去,最后得到响应
Response response = interceptor.intercept(next);
...
return response;
}
异步请求流程
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
...
}
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
// 正在准备中的异步请求队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
// 运行中的异步请求
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
// 同步请求
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
// Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
// them on the executor service. Must not be called with synchronization because executing calls
// can call into user code.
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
// 如果其中的runningAsynCalls不满,且call占用的host小于最大数量,则将call加入到runningAsyncCalls中执行,
// 同时利用线程池执行call;否者将call加入到readyAsyncCalls中。
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
最后,我们在看看AsynCall的代码。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
/**
* Attempt to enqueue this async call on {@code executorService}. This will attempt to clean up
* if the executor has been shut down by reporting the call as failed.
*/
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
@Override protected void execute() {
boolean signalledCallback = false;
timeout.enter();
try {
// 跟同步执行一样,最后都会调用到这里
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) {
e = timeoutExit(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 {
client.dispatcher().finished(this);
}
}
}
从上面的源码可以知道,拦截链的处理OKHttp帮我们默认做了五步拦截处理,其中RetryAndFollowUpInterceptor、BridgeInterceptor、CallServerInterceptor内部的源码很简洁易懂,此处不再多说。
网络请求缓存处理之CacheInterceptor
@Override public Response intercept(Chain chain) throws IOException {
// 根据request得到cache中缓存的response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// request判断缓存的策略,是否要使用了网络,缓存或两者都使用
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
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.
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();
}
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
// 调用下一个拦截器,决定从网络上来得到response
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());
}
}
// If we have a cache response too, then we're doing a conditional get.
// 如果本地已经存在cacheResponse,那么让它和网络得到的networkResponse做比较,决定是否来更新缓存的cacheResponse
if (cacheResponse != null) {
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());
}
}
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.
// 缓存未经缓存过的response
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;
}
缓存拦截器会根据请求的信息和缓存的响应的信息来判断是否存在缓存可用,如果有可以使用的缓存,那么就返回该缓存给用户,否则就继续使用责任链模式来从服务器中获取响应。当获取到响应的时候,又会把响应缓存到磁盘上面。
ConnectInterceptor之连接池
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// HttpCodec是对 HTTP 协议操作的抽象,有两个实现:Http1Codec和Http2Codec,顾名思义,它们分别对应 HTTP/1.1 和 HTTP/2 版本的实现。在这个方法的内部实现连接池的复用处理
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
// Returns a connection to host a new stream. This // prefers the existing connection if it exists,
// then the pool, finally building a new connection.
// 调用 streamAllocation 的 newStream() 方法的时候,最终会经过一系列
// 的判断到达 StreamAllocation 中的 findConnection() 方法
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
...
// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new streams.
// 尝试使用已分配的连接,已经分配的连接可能已经被限制创建新的流
releasedConnection = this.connection;
// 释放当前连接的资源,如果该连接已经被限制创建新的流,就返回一个Socket以关闭连接
toClose = releaseIfNoNewStreams();
if (this.connection != null) {
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
// 如果该连接从未被标记为获得,不要标记为发布状态,reportedAcquired 通过 acquire() 方法修改
releasedConnection = null;
}
if (result == null) {
// Attempt to get a connection from the pool.
// 尝试供连接池中获取一个连接
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
// 关闭连接
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
// 如果已经从连接池中获取到了一个连接,就将其返回
return result;
}
// If we need a route selection, make one. This is a blocking operation.
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
// 根据一系列的 IP地址从连接池中获取一个链接
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size;i++) {
Route route = routes.get(i);
// 从连接池中获取一个连接
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
// 在连接池中如果没有该连接,则创建一个新的连接,并将其分配,这样我们就可以在握手之前进行终端
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}
// If we found a pooled connection on the 2nd time around, we're done.
if (foundPooledConnection) {
// 如果我们在第二次的时候发现了一个池连接,那么我们就将其返回
eventListener.connectionAcquired(call, result);
return result;
}
// Do TCP + TLS handshakes. This is a blocking operation.
// 进行 TCP 和 TLS 握手
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
// 将该连接放进连接池中
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
// 如果同时创建了另一个到同一地址的多路复用连接,释放这个连接并获取那个连接
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
从以上的源码分析可知:
- 判断当前的连接是否可以使用:流是否已经被关闭,并且已经被限制创建新的流;
- 如果当前的连接无法使用,就从连接池中获取一个连接;
- 连接池中也没有发现可用的连接,创建一个新的连接,并进行握手,然后将其放到连接池中。
在从连接池中获取一个连接的时候,使用了 Internal 的 get() 方法。Internal 有一个静态的实例,会在 OkHttpClient 的静态代码快中被初始化。我们会在 Internal 的 get() 中调用连接池的 get() 方法来得到一个连接。并且,从中我们明白了连接复用的一个好处就是省去了进行 TCP 和 TLS 握手的一个过程。因为建立连接本身也是需要消耗一些时间的,连接被复用之后可以提升我们网络访问的效率。
接下来详细分析下ConnectionPool是如何实现连接管理的。
OkHttp 的缓存管理分成两个步骤,一边当我们创建了一个新的连接的时候,我们要把它放进缓存里面;另一边,我们还要来对缓存进行清理。在 ConnectionPool 中,当我们向连接池中缓存一个连接的时候,只要调用双端队列的 add() 方法,将其加入到双端队列即可,而清理连接缓存的操作则交给线程池来定时执行。
private final Deque<RealConnection> connections = new ArrayDeque<>();
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
// 使用线程池执行清理任务
executor.execute(cleanupRunnable);
}
// 将新建的连接插入到双端队列中
connections.add(connection);
}
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
// 内部调用 cleanup() 方法来清理无效的连接
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
};
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
// Find either a connection to evict, or the time that the next eviction is due.
synchronized (this) {
// 遍历所有的连接
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// If the connection is in use, keep searching.
// 遍历所有的连接
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
// 如果找到了一个可以被清理的连接,会尝试去寻找闲置时间最久的连接来释放
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
// maxIdleConnections 表示最大允许的闲置的连接的数量,keepAliveDurationNs表示连接允许存活的最长的时间。
// 默认空闲连接最大数目为5个,keepalive 时间最长为5分钟。
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
// 该连接的时长超出了最大的活跃时长或者闲置的连接数量超出了最大允许的范围,直接移除
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
// 闲置的连接的数量大于0,停顿指定的时间(等会儿会将其清理掉,现在还不是时候)
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
// 所有的连接都在使用中,5分钟后再清理
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
// 没有连接
cleanupRunning = false;
return -1;
}
}
从以上的源码分析可知,首先会对缓存中的连接进行遍历,以寻找一个闲置时间最长的连接,然后根据该连接的闲置时长和最大允许的连接数量等参数来决定是否应该清理该连接。同时注意上面的方法的返回值是一个时间,如果闲置时间最长的连接仍然需要一段时间才能被清理的时候,会返回这段时间的时间差,然后会在这段时间之后再次对连接池进行清理。
经过上面对OKHttp内部工作机制的一系列分析,相信你已经对OKHttp已经有了一个比较深入的了解了。首先,我们会在请求的时候初始化一个Call的实例,然后执行它的execute()方法或enqueue()方法,内部最后都会执行到getResponseWithInterceptorChain()方法,这个方法里面通过拦截器组成的责任链,依次经过用户自定义普通拦截器、重试拦截器、桥接拦截器、缓存拦截器、连接拦截器和用户自定义网络拦截器以及访问服务器拦截器等拦截处理过程,来获取到一个响应并交给用户。
其中,除了OKHttp的内部请求流程这点之外,缓存和连接这两部分内容也是两个很重要的点,相信经过讲解,大家对这三部分重点内容已经有了自己的理解。
2.View的绘制流程
Android 中 Activity 是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当 Activity 启动时,我们会通过 setContentView 方法来设置一个内容视图,这个内容视图就是用户看到的界面。那么 View 和 activity 是如何关联在一起的呢 ?
①Android的UI层级绘制体系
上图是View与Activity之间的关系,先介绍一下上面这张图
- PhoneWindow:每个Activity都会创建一个Window用来承载View的显示,Window是一个抽象类,PhoneWindow是Window的唯一实现类,该类中包含一个DecorView。
- DecorView:最顶层的View,该View继承自 FrameLayout,它的内部包含两部分,一部分是ActionBar ,另一部分ContentView,
- ContentView:我们 setContentView() 中传入的布局,就在该View中加载显示
- ViewRootImpl:视图层次结构的顶部。一个 Window 对应着一个 ViewRootImpl 和 一个DecorView,通过该实例对DecorView进行控制,最终通过执行ViewRootImpl的performTraversals()开启整个View树的绘制,
②View的加载流程
- 当调用 Activity 的setContentView 方法后会调用PhoneWindow 类的setContentView方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
- PhoneWindow类的setContentView方法中最终会生成一个DecorView对象
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//在这里生成一个DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
}
private void installDecor() {
mForceDecorInstall = false;
//mDecor 为DecorView
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
...
}
protected DecorView generateDecor(int featureId) {
...
// 在这里直接 new 了一个DecorView
return new DecorView(context, featureId, this, getAttributes());
}
- DecorView容器中包含根布局,根布局中包含一个id为content的FrameLayout布局,Activity加载布局的xml最后通过LayoutInflater将xml文件中的内容解析成View层级体系,最后填加到id为content的FrameLayout布局中。
protected ViewGroup generateLayout(DecorView decor) {
//做一些窗体样式的判断
...
//给窗体进行装饰
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
//加载系统布局 判断到底是加载那个布局
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
}
...
mDecor.startChanging();
//将加载到的基础布局添加到mDecor中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//通过系统的content的资源ID去进行实例化这个控件
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
}
到此,Actvity的绘制完成
③View的视图绘制流程剖析
- DecorView被加载到Window中 在ActivityThread的 handleResumeActivity() 方法中通过WindowManager将DecorView加载到Window中,通过ActivityThread中一下代码可以得到应征
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
//在此处执行Activity的onResume方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
//获取window对象
r.window = r.activity.getWindow();
//获取DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取WindowManager,在这里getWindowManager()实质上获取的是ViewManager的子类对象WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
//获取ViewRootImpl对象
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
//在这里WindowManager将DecorView添加到PhoneWindow中
wm.addView(decor, l);
}
}
总结:在ActivityThread的handleResumeActivity方法中WindowManager将DecorView添加到PhoneWindow中,addView()方法执行时将视图添加的动作交给了ViewRootImpl处理,最后在ViewRootImpl的performTraversals中开始View树的绘制
④ViewRootImpl的performTraversals()方法完成具体的视图绘制流程
private void performTraversals() {
if (!mStopped || mReportNextDraw) {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// Ask host how big it wants to be
//View绘制:开始测量 View的测量时递归逐层测量,由父布局与子布局共同确认子View的测量模式,在子布局测量完毕时确认副布局的宽高,
//在此方法执行完毕后才可获取到View的宽高,否侧获取的宽高都为0
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
if (didLayout) {
//开始摆放,该方法是ViewGroup中的方法,例如 LinerLayout...
performLayout(lp, mWidth, mHeight);
}
if (!cancelDraw && !newSurface) {
//开始绘制,执行View的onDraw()方法
performDraw();
}
}
下面开始对performMeasure(),performLayout(),performDraw()进行解析
- performMeasure()
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
通过以上这段代码,我们可以看到两个重要的参数 childWidthMeasureSpec,childHeightMeasureSpec,这两个Int类型的参数包含了View的测量模式和宽高信息,因此在onMeasure()方法中我们可以通过该参数获取到测量模式,和宽高信息,我们在onMeasue中设置宽高信息也是通过MeasureSpec设置,
*/
public static class MeasureSpec {
//int类型占4个字节,其中高2位表示尺寸测量模式,低30位表示具体的宽高信息
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
//如下所示是MeasureSpec中的三种模式:UNSPECIFIED、EXACTLY、AT_MOST
//UNSPECIFIED:未指定模式,父容器不限制View的大小,一般用于系统内部的测量
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
//AT_MOST:最大模式,对应于在xml文件中指定控件大小为wrap_content属性,子View的最终大小是父View指定的大小值,并且子View的大小不能大于这个值
public static final int EXACTLY = 1 << MODE_SHIFT;
//EXACTLY :精确模式,对应于在xml文件中指定控件为match_parent属性或者是具体的数值,父容器测量出View所需的具体大小
public static final int AT_MOST = 2 << MODE_SHIFT;
//获取测量模式
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
//获取宽高信息
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
...
}
performMeasure()会继续调用mView.measure()方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
//根据原有宽高计算获取不同模式下的具体宽高值
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
...
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
//在该方法中子控件完成具体的测量
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
...
}
从上述代码片段中可以看到执行到了onMeasure()方法,如果该控件为View的话,测量到此结束,如果是ViewGroup的话,会继续循环获取所有子View,调用子View的measure方法,下面以LinearLayout为例,继续看
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
LinearLayout通过不同的摆放布局执行不同的测量方法,以measureVertical为例,向下看
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
//获取子View的个数
final int count = getVirtualChildCount();
...
//循环获取所有子View
for (int i = 0; i < count; ++i) {
//获取子View
final View child = getVirtualChildAt(i);
//调用子View的measure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
....
}
至此,View的测量流程结束
⑤View的layout流程分析
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
// 在此处调用mView的layout()摆放开始
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
/*
*@param l view 左边缘相对于父布局左边缘距离
*@param t view 上边缘相对于父布局上边缘位置
*@param r view 右边缘相对于父布局左边缘距离
*@param b view 下边缘相对于父布局上边缘距离
*/
public void layout(int l, int t, int r, int b) {
...
//记录 view 原始位置
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//调用 setFrame 方法 设置新的 mLeft、mTop、mBottom、mRight 值,
//设置 View 本身四个顶点位置
//并返回 changed 用于判断 view 布局是否改变
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//第二步,如果 view 位置改变那么调用 onLayout 方法设置子 view 位置
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//开始调用 onLayout 在此处根据子View的宽高及相关规则进行摆放
onLayout(changed, l, t, r, b);
...
}
}
}
}
⑥View的Draw流程分析
private void performDraw() {
...
//调用draw方法
draw(fullRedrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
...
//View的绘制流程调用的 drawSoftware() 该方法
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
...
//初始化画布
canvas = mSurface.lockCanvas(dirty);
...
//开始调用ViewGroup 和 View的draw方法
mView.draw(canvas);
...
}
public void draw(Canvas canvas) {
drawBackground(canvas);
//ViewGroup 默认是不会调用OnDraw方法的
if (!dirtyOpaque) onDraw(canvas);
//这个方法主要是ViewGroup循环调用 drawChild()进行对子View的绘制
dispatchDraw(canvas);
}
protected void onDraw(Canvas canvas) {
}
View的onDraw方法只是一个模版,具体实现方式,交由我们这些开发者去进行实现
至此,View的绘制流程完毕
- requestLayout重新绘制视图 子View调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量、布局、绘制。
- invalidate在UI线程中重新绘制视图
当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
- postInvalidate在非UI线程中重新绘制视图
这个方法与invalidate方法的作用是一样的,都是使View树重绘,但两者的使用条件不同,postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。
3.讲一下触摸的传递机制
①触摸时间的几种类型
- ACTION_DOWN
- ACTION_MOVE
- ACTION_UP
很简单也就是你手指的按下 移动 松开的这三个核心的操作!
②触摸事件的三个核心方法
1,分发 Dispatch 也就是对应着 dispatchTouchEvent 方法。
一般来说,这个方法表明当前的这个视图是处理这个事件还是继续分发这个事件,当这个方法的返回值为True的时候表明,事件已经被消费了,不会再继续的往下传递了。但是这个视图是ViewGroup或者它的子类,则会调用onInterceptTouchEvent来判断是否继续分发该事件。
2,拦截 Intercept
事件拦截对应着onInterceptTouchEvent方法,这个方法只在ViewGroup及其子类中存在,在activity和view中是不存在这类方法的。
3 消费 Consume
事件消费对应着onTouchEvent这个方法,一般事件的逻辑就是在这里面进行处理的。
总结一下: activity中有 dispatchTouchEvent 和 onTouchEvent这两个方法 viewGroup和它的子类 拥有dispatchTouchEvent,onInterceptTouch和onTouchEvent这个三个方法 view只有dispatchTouchEvent和onTouchEvent两个方法。
③View事件的传递机制
//activity 部分代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"dispatchTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG,"dispatchTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"onTouchEvent Action_DOWN");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG,"onTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (v.getId()){
case R.id.textView:
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"MyTextView onTouch Action_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"MyTextView onTouch ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"MyTextView onTouch ACTION_UP");
break;
default:
break;
}
default:
break;
}
return false;
}
//MyTextView部分代码
//分配事件的方法,在这里决定是否要分配事件
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_BUTTON_PRESS:
Log.e(TAG,"dispatchTouchEvent ACTION_BUTTON_PRESS");
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"dispatchTouchEvent ACTION_DOWN");
case MotionEvent.ACTION_UP:
Log.e(TAG,"dispatchTouchEvent ACTION_UP");
case MotionEvent.ACTION_CANCEL:
Log.e(TAG,"dispatchTouchEvent ACTION_CANCEL");
default:
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// 如果点击
// performClick();
Log.e(TAG,"onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG,"onTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.onTouchEvent(event);
}
从打印出来的Log来看,点击事件的 第一步是走Activity的dispatchTouchEvent的ACTION_DOWN->再进入MyTextView的dispatchTouchEvent 第二步是走Activity的onTouchEvent的onTouch ACTION_DOWN-再进入MyTextView的onTouch ACTION_DOWN 第二步是走Activity的onTouchEvent的onTouch ACTION_MOVE-再进入MyTextView的onTouch ACTION_MOVE 这很明显的阐述了点击事件和触摸事件的传递顺序
1,通过上面的图我们可以分析到,触摸事件的传递流程是从 dispatchTouchEvent开发的,如果不进行人为干预,(也就是默认返回父类的同名函数),则事件将会依照嵌套层次从外层向内层传递,达到最内的View层View, 就有它的onTouchEvent方法进处理。该方法如果能消费该事件,则返回true,如果处理不了,则返回false,这时候事件就会向外层传递,并有外层的View的onTouchEvent方法进行处理。 2,如果事件在向内层传递的过程中由于人为干预的原因,事件处理函数返回true,则会导致事件提前被消费掉,内层View将不会受到这个事件。 3,View控件的事件出发顺序是先执行onTouch方法,最后才执行onClick方法的,如果onTouch返回True,则事件不会继续传递,最后也不会调用onClick方法,如果onTouch返回为False,则事件会继续传递下去。
④ViewGroup 事件的传递机制
ViewGroup是作为View控件的容器,我们常见的ViewGroup有:LinnerLayout,Relativeyout, ListView, ScrollView.这里我们来实现一个例子。
//在上面的基础上我们自定义了RelativeRelayout这个相对布局
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.RelativeLayout;
public class MyRelativeLayout extends RelativeLayout {
private static final String TAG = "MyRelayoutLayout";
public MyRelativeLayout(Context context) {
super(context);
}
public MyRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"MyRelativeLayout dispatchTouch ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyRelaytiveLayout dispatchTouch ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG,"MyRelativeLayout dispatchTouch ACTION_UP");
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
/**
*这个方法是ViewGroup特有的方法
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MyRelativeLayout onInterceptTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyRelativeLayout onInterceptTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MyRelativeLayout onInterceptTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MyRelativeLayout onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyRelativeLayout onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MyRelativeLayout onTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}
这里我么进使用自己的相对布局
<?xml version="1.0" encoding="utf-8"?>
<com.example.administrator.testactivity.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.administrator.testactivity.MyTextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.181"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.15" />
<Button
android:id="@+id/button"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="44dp"
android:layout_marginTop="8dp"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textView"
app:layout_constraintTop_toTopOf="parent" />
</com.example.administrator.testactivity.MyRelativeLayout>
触摸屏幕上的TextView,我们可以看到打印出来的Log信息如下
其实细心的读者可以发现,中间也就是多了一道门而已也就是RelativeLayout ,这个我们做个ViewGroup的事件传递的总结:
ViewGroup通过onInterceptTouch方法对时间进行拦截,如果该方法返回True,则事件不会继续传递给子View,如果返回false或者是super.onInterceptTouch,则会继续传递给子View。
在子View中对事件进行消费后ViewGroup将接受不到任何事件, 那么当我这里插一句嘴,当我们再开发嵌套View的时候,比如说是在ScrollView中嵌套了一个ListView,那么我们是否可以采取这种方法,当ListView接收了触摸事件后就不会继续向下传递,那么就不会出现滑动冲突这样的问题了。
4.自定义View主要重写哪个方法
A.View()
B.onDraw()
C.onCreate()
D.ViewGroup()
答案: onDraw()
5.讲一下策略模式和状态模式的各自的特点和区别
策略模式一般用于单个算法,而状态模式的每个状态子类中需要包含所有原来的语境类(Context)中的所有方法的具体实现。
区别:
状态模式将各个状态所对应的操作分离开来,即对于不同的状态,由不同的子类实现具体操作,不同状态的切换由子类实现,当发现传入参数不是自己这个状态所对应的参数,则自己给Context类切换状态;而策略模式是直接依赖注入到Context类的参数进行选择策略,不存在切换状态的操作。
联系:
状态模式和策略模式都是为具有多种可能情形设计的模式,把不同的处理情形抽象为一个相同的接口,符合对扩展开放,对修改封闭的原则。
还有就是,策略模式更具有一般性一些,在实践中,可以用策略模式来封装几乎任何类型的规则,只要在分析过程中听到需要在不同实践应用不同的业务规则,就可以考虑使用策略模式处理,在这点上策略模式是包含状态模式的功能的,策略模式是一个重要的设计模式。
6.说一下线程安全,线程同步的原子性,可见性和有序性
①定义:什么是线程安全性
当多个线程访问某个类时,不管运行时环境采用 何种调度方式 或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类就是线程安全的。
②线程安全性的三个体现
- 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作(Atomic、CAS算法、synchronized、Lock)
- 可见性:一个主内存的线程如果进行了修改,可以及时被其他线程观察到(synchronized、volatile)
- 有序性:如果两个线程不能从 happens-before原则 观察出来,那么就不能观察他们的有序性,虚拟机可以随意的对他们进行重排序,导致其观察观察结果杂乱无序(happens-before原则)
③线程安全性:原子性
3.1、原子性 — Atomic包
在Java jdk中里面提供了很多Atomic类
- AtomicXXX:CAS、Unsafe.compareAndSwapInt
- AtomicLong、LongAdder
- AtomicReference、AtomicReferenceFieldUpdater
- AtomicStampReference:CAS的ABA问题
由于CAS原语的直接操作与计算机底层的联系很大,CAS原语有三个参数,内存地址、期望值、新值。我们在Java中一般不去直接写CAS相关的代码,JDK为我们封装在AtomicXXX中,因此,我们直接使用就可以了。
我们在 java.util.concurrent.atomic 目录中可以看到我们这些类,包下提供了AtomicBoolean、AtomicLong、AtomicInteger三种原子更新基本类型和一个比较好玩的类AtomicReference,这些类都有一个共同点,都支持CAS,以AtomicInteger为重点讲解。
3.1.1、AtomicInteger
AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减
以下是AtomicIntege基本包括的方法:
public final int getAndSet(int newValue) //给AtomicInteger设置newValue并返回加oldValue
public final boolean compareAndSet(int expect, int update) //如果输入的值和期望值相等就set并返回true/false
public final int getAndIncrement() //对AtomicInteger原子的加1并返回当前自增前的value
public final int getAndDecrement() //对AtomicInteger原子的减1并返回自减之前的的value
public final int getAndAdd(int delta) //对AtomicInteger原子的加上delta值并返加之前的value
public final int incrementAndGet() //对AtomicInteger原子的加1并返回加1后的值
public final int decrementAndGet() //对AtomicInteger原子的减1并返回减1后的值
public final int addAndGet(int delta) //给AtomicInteger原子的加上指定的delta值并返回加后的值
示例:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class AtomicIntegerExample {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
//获取线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//定义信号量
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count.get());
}
private static void add() {
count.incrementAndGet();
}
}
这里我们使用请求总数为:5000,同时执行的并发线程数为:200,我们最终需要得到结果为:5000,这个执行结果才算正确。
查看返回结果:
13:43:26.473 [main] INFO com.mmall.concurrency.example.atomic.AtomicIntegerExample - count:5000
最后结果是 5000表示是线程安全的。
我们来看看 AtomicInteger底层代码中到底为我们做了什么?首先我们来看 AtomicInteger.incrementAndGet()方法
public class AtomicInteger extends Number implements java.io.Serializable{
/**
* 对AtomicInteger原子的加1并返回加1后的值
* @return 更新的值
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
AtomicInteger调用了java底层的 unsafe.getAndAddInt()方法,这里是实现CAS 的关键。
incrementAndGet()是将自增后的值返回,还有一个方法getAndIncrement()是将自增前的值返回,分别对应++i和i++操作。同样的decrementAndGet()和getAndDecrement()则对--i和i--操作。
Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于 Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作 能力方面起了很大的作用。Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。 过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。通常我们最好也不 要使用Unsafe类,除非有明确的目的,并且也要对它有深入的了解才行。
再来看 Unsafe.getAndAddInt()方法
/*
* 其中getIntVolatile和compareAndSwapInt都是native方法
* getIntVolatile是获取当前的期望值
* compareAndSwapInt就是我们平时说的CAS(compare and swap),通过比较如果内存区的值没有改变,那么就用新值直接给该内存区赋值
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public native int getIntVolatile(Object var1, long var2);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
我们可以看到getAndAddInt(Object var1, long var2, int var4),传进来的第一个参数是当前的一个对象,也就是我们的:count.incrementAndGet(),在getAndAddInt()中,var1就是count,var2就是当前的值,比如当前循环中count值为 2,var4为每次递增1
其次getAndAddInt()方法中涉及到的两个方法调用都定义为native,即java底层实现的本地方法
getIntVolatile():获取保存当前对象count的主存地址的引用(注意不是对象的值,是引用)。compareAndSwapInt():比较当前对象的值和底层该对象的值是否相等,如果相等,则将当前对象值加1,如果不相等,则重新去获取底层该对象的值,这个方法的实现就是CPU的CAS(compare and swap)操作。
我们知道volatile具有一致性的特征,但是它不具备原子性,为什么AtomicInteger却同时具备一致性和原子性,原来在AtomicInteger源码中实现了这样一串代码:private volatile int value;,在AtomicInteger内部实现就使用了volatile关键字,这就是为什么执行CAS操作的时候,从底层获取的数据就是最新的数据:
如果当前要保存的值和内存中最新的值不相等的话,说明在这个过程中被其他线程修改了,只能获取更新当前值为最新值,再那这个当前值再去和重新去内存获取的最新值比较,直到二者相等的时候,才完成+1的过程.
使用AtomicInteger的好处在于,它不同于sychronized关键字或lock用锁的形式来实现原子性,加锁会影响性能,而是采用循环比较的形式来提高性能。
3.1.2、AtomicLong
AtomicLong是作用是对长整形进行原子操作,依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用死循环不断地cas到特定的值,从而达到更新数据的目的。
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;
@Slf4j
public class AtomicLongExample {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static AtomicLong count = new AtomicLong(0);
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count.get());
}
private static void add() {
count.incrementAndGet();
// count.getAndIncrement();
}
}
执行结果:
14:59:38.978 [main] INFO com.mmall.concurrency.example.atomic.AtomicLongExample - count:5000
最后结果是 5000表示是线程安全的。
3.1.3、AtomicBoolean
AtomicBoolean位于java.util.concurrent.atomic包下,是java提供给的可以保证数据的原子性操作的一个类
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
@Slf4j
public class AtomicBooleanExample {
private static AtomicBoolean isHappened = new AtomicBoolean(false);
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
test();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("isHappened:{}", isHappened.get());
}
private static void test() {
if (isHappened.compareAndSet(false, true)) {
log.info("execute");
}
}
}
返回结果:
15:04:54.954 [pool-1-thread-2] INFO com.mmall.concurrency.example.atomic.AtomicBooleanExample - execute
15:04:54.971 [main] INFO com.mmall.concurrency.example.atomic.AtomicBooleanExample - isHappened:true
这里我们发现log.info("execute");,在代码中只执行了一次,并且isHappened:true的值为true,这是为啥呢?
这里是因为当程序第一次compareAndSet()的时候,使得isHappend变为了true,因为原子性的关系,没有其他线程进行干扰,通过使用AtomicBoolean,我们使某段代码只执行一次。
3.1.4、AtomicReference
AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,底层采用的是compareAndSwapInt实现CAS,比较的是数值是否相等,而AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。
多个线程之间的操作无论采用何种执行时序或交替方式,都要保证不变性条件不被破坏,要保持状态的一致性,就需要在单个原子操作中更新相关的状态变量。
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
public class AtomicReferenceExample {
private static AtomicReference<Integer> count = new AtomicReference<>(0);
public static void main(String[] args) {
count.compareAndSet(0, 2);
count.compareAndSet(0, 1);
count.compareAndSet(1, 3);
count.compareAndSet(2, 4);
count.compareAndSet(3, 5);
log.info("count:{}", count.get());
}
}
大家觉得我们输出的结果会是多少?
返回结果:
15:26:59.680 [main] INFO com.mmall.concurrency.example.atomic.AtomicReferenceExample - count:4
为什么是4呢? 首先我们 要说的是public final boolean compareAndSet(V expect, V update) 这个方法,这个方法主要的作用是通过比对两个对象,然后更新为新的对象,这里的比对两个对象,比对的方式不是equals而是==,意味着比对的是内存的中地址。
1、首先我们创建count的初始化为0 2、在main方法中 count.compareAndSet(0, 2);,判断count为0时赋值为2 3、在count.compareAndSet(0, 1); 和 count.compareAndSet(1, 3);判断count是否为1或者0,因为上一步我们已经赋值为2了,所以判断不成立 4、在count.compareAndSet(2, 4);判断count是否为2,等式成立 5、最好输出结果为4
count.compareAndSet(0, 2); //count=0?赋值 2,判断成立,此时count=0,更新后为2
count.compareAndSet(0, 1); //count=0?赋值 1,判断不成立,此时count=2
count.compareAndSet(1, 3); //count=1?赋值 3,判断不成立,此时count=2
count.compareAndSet(2, 4); //count=2?赋值 4,判断成立,此时count=2,更新后count=4
count.compareAndSet(3, 5); //count=3?赋值 5,判断不成立,此时count=4
所以我们输出结果为:4
3.1.5、CAS中ABA问题的解决
CAS并非完美的,它会导致ABA问题,例如:当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有被其他线程访问过。在某些场景下这样是存在错误风险的。比如在链表中。 如何解决这个ABA问题呢,大多数情况下乐观锁的实现都会通过引入一个版本号标记这个对象,每次修改版本号都会变话,比如使用时间戳作为版本号,这样就可以很好的解决ABA问题。 在JDK中提供了AtomicStampedReference类来解决这个问题,这个类维护了一个int类型的标记stamp,每次更新数据的时候顺带更新一下stamp。
3.2、原子性 — synchronized
synchronized是一种同步锁,通过锁实现原子操作。 1、修饰代码块:大括号括起来的代码,作用于调用的对象 2、修饰方法:整个方法,作用于调用的对象 3、修饰静态方法:整个静态方法,作用于所有对象 4、修饰类:括号括起来的部分,作用于所有对象
3.3、原子性 — 对比
- Atomic:竞争激烈时能维持常态,比Lock性能好, 只能同步一个值
- synchronized:不可中断锁,适合竞争不激烈,可读性好的情况
- Lock:可中断锁,多样化同步,竞争激烈时能维持常态
④线程安全性:可见性
简介:一个线程对主内存的修改可以及时被其他线程观察到
导致共享变量在线程间不可见的原因: 1.线程交叉执行 2.重新排序结合线程交叉执行 3.共享变量更新后的值没有在工作内存中与主内存间及时更新
4.1 可见性 — syncronized
JMM关于syncronized的两条规定:
- 线程解锁前,必须把共享变量的最新值刷新到主内存中
- 线程加锁时,将清空工作内存中共享变量的值,从而使得使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁是同一把锁) 由于
syncronized可以保证原子性及可见性,变量只要被syncronized修饰,就可以放心的使用
4.2 可见性 — volatile
通过加入内存屏障和禁止重排序优化来实现可见性。 具体实现过程:
- 对
volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存 - 对
volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量
volatile不能保证操作的原子性,也就是不能保证线程安全性, 如果需要使用volatile必须满足以下两个条件:
- 对变量的写操作不依赖与变量当前的值。
- 该变量没有包含在具有其他变量的不变的式子中。
所以volatile修饰的变量适合作为状态标记量。
注:以下图片为资料中获取,如有雷同,纯属巧合
示例:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Slf4j
public class VolatileExample {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static volatile int count = 0;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
count++;
}
}
返回结果:
16:12:01.404 [main] INFO com.mmall.concurrency.example.count.VolatileExample4 - count:4986
通过执行代码我们可以发现,返回结果并不是我们想看到的5000,说明这个是线程不安全的类
主要是因为当我们执行conut++时分成了三步: 1、取出当前内存count值,这时count值时最新的 2、+1操作 3、重新写回主存
例如:有两个线程同时在执行count++,两个内存都执行了第一步,比如当前count值为99,它们都读到了这个count值,然后两个线程分别执行了+1,并写回主存,这样就丢掉了一次+1的操作。
⑤线程安全性:有序性
- 在JMM中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
- 通过
volatile、synchronized、lock保证有序性
5.1 happens-before原则
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
- 锁定规则:一个
unLock操作先行发生于后面对同一个锁的Lock()操作,也就是说只有先解锁才能对下面的线程进行加锁 - volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作A先行发生与操作B,而操作B先行发生于操作C,则操作A先行发生于操作C
- 线程启动规则:
Thread对象的start()方法先行发生于此线程的每一个动作,一个线程只有执行了start()方法后才能做其他的操作 - 线程终端规则:对线程
interrupt()方法的调用先行发生与被中断线程的代码检测到中断事件的发生(只有执行了interrupt()方法才可以检测到中断事件的发生) - 线程终结规则:线程中所有操作都先行发生于线程的终止检测,我们可以通过
Thread.join()方法结束,Thread.isAlive()的返回值手段检测到线程已经终止执行 - 对象终结规则:一个对象的初始化完成先行发生于他的
finalize()方法的开始
⑥线程安全性:总结
- 原子性:
Atomic包、CAS算法、synchronized、Lock原子性做了互斥方法,同一个线程只能有一个进行操作` - 可见性:
synchronized、volatile一个主内存的线程如果进行了修改,可以及时被其他线程观察到,介绍了volatile如何被观察到的` - 有序性:
happens-before原则happens-before原则,观察结果,如果两个线程不能偶从happens-before原则观察出来,那么就不能观察他们的有序性,虚拟机可以随意的对他们进行重排序`
7.讲一下双重检查锁中的 volatile 关键字的作用
① volatile关键字 的作用
volatile变量会利用内存屏障来防止指令重排。- 修改
volatile变量时会强制将修改后的数据刷新到主内存中。 volatile变量修改后,其他线程引用到的该值将会失效,再次使用此变量时,需要到主内存重新获取。
②new 并不是一个原子操作,可分为三步:
- 分配内存空间
- 调用构造器方法,执行初始化
- 将内存地址的引用赋值给变量。其中2、3在指令重排是颠倒顺序为 1>3>2
③总结
在没有 volatile 关键字修饰的情况下;
当 线程A 第一次获取锁开始 new 对象时,发生了 指令重排;
cup执行了 new 的 第三步 变量获取了地址的引用,即变量不是 null;
此时 线程B 刚好进入方法开始执行 DCL 的第一个判断,发现变量不是空便开始访问此变量;
但变量内部并未进行初始化操作。可能导致程序报错。
这也就解释了书中的注释:volatile 确保当 uniqueInstance变量 被初始化成实例时,多个线程能够正确的处理该 uniqueInstance变量
8.讲一下jvm的堆栈
①概述
JAVA在程序运行时,在内存中划分5片空间进行数据的存储。分别是:
1:寄存器
2:本地方法区
3:方法区
4:栈
5:堆。
堆
可以把堆理解为一家餐厅,里面有200张桌子,也就是说最多容纳200桌客人就餐,来一批客人就为他们安排一些桌子,如果某天来的客人特别多,超过200桌了,哪就不能在接待超出的客人了。
当然,进来吃饭的客人不能是同时的,有的早有的晚,先吃好的客人,老板会安排给他们结账走人,然后空出来的桌子又能接待新的客人。
这里,堆就是餐桌,最大容量200桌就是堆内存的大小,老板就相当于GC(垃圾回收)给客人安排桌子就相当于Java创建对象的时候分配堆内存,结账就相当于GC回收对象占用的空间。
栈
接着把栈比作一座废井,这口井多年不用已经没水了,主人现在把它作为存储酿酒的地方,存酒的时候就用绳子勾着酒坛子慢慢放下去,后面在存酒就一坛一坛的堆着放上去,取酒的时候就先取最上面的坛子。
②堆内存
什么是堆内存?
堆内存是Java内存中的一种,它的作用是用于存储Java中的对象和数组,当我们new一个对象或者创建一个数组的时候,就会在堆内存中开辟一段空间给它,用于存放。
堆内存的特点是什么?
- 堆起始可以类似的看做是管道,或者是平时去排队买票的情况差不多,所以堆内存的特点就是:先进先出,后进后出,也就是你先排队好,你先买票
- 堆可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,但缺点是,由于要在运行时动态分配内存,存取速度较慢。
new对象在堆中如何分配
由Java虚拟机的自动垃圾回收器来管理
③栈内存
什么是栈内存?
栈内存是Java的另一种内存,主要是用来执行程序的,比如:基本类型的变量和对象的引用变量栈内存的特点
- 栈内存就好像是一个矿泉水瓶,往里面放东西,那么先放入的沉入底部,所以它的特点是:先进后出,后进先出
- 存取速度比堆要快,仅次于寄存器,栈数据可以共享,但缺点是,存在栈中的数据大小与生存必须是确定的,缺乏灵活性。
栈内存分配机制:
栈内存可以称为一级缓存,由垃圾回收器自动回收。
数据共享:
例子:
int a = 3;
int b = 3;
④栈和堆的区别
JVM是基于堆栈的虚拟机,JVM为新创建的线程都分配一个堆栈,也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
差异:
- 堆内存用来存放有new创建的对象和数组
- 栈内存用来存放方法或者局部变量等
- 堆是先进先出,后进后出
- 栈是先进后出,后进先出
- 共享性不同(栈内存是线程私有的,堆内存是所有线程共有的)
9.二叉树的层次遍历
本文主要介绍了二叉树的按层遍历。并且分别用如下三种方式实现:
- 哈希表结合LinkedList
- 使用系统自带的LinkedList
- 自定义队列
以上方法只是空间复杂度有所差异,时间复杂度上都是一样的。
①示例二叉树
这个二叉树按层次遍历的结果就是 1->2->3->4->5->6->7->8->9->10->11->12->13
②数据结构
public static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
③流程
整个过程最核心的地方就是需要记录当前层什么时候遍历完毕以及当前弹出的节点在第几层。
方法1中,使用哈希表来存每个节点当前所在的层,头节点默认在第0层,且将头节点首先入队列,然后在弹出的过程中,将弹出节点的子节点放入哈希表,且把层数设置为当前节点的层数+1,同时把子节点放入队列,然后进行同样的队列弹出操作,直到队列空。
方法1的完整代码如下
public static List<List<Integer>> levelOrder(TreeNode head) {
if (head == null) {
return new ArrayList<>();
}
List<List<Integer>> ans = new ArrayList<>();
// 记录某个节点在第几层
Map<TreeNode, Integer> map = new HashMap<>();
Queue<TreeNode> queue = new LinkedList<>();
// 当前是第几层
int curLevel = 0;
TreeNode cur = head;
queue.offer(cur);
map.put(cur, curLevel);
List<Integer> levelRecords = new ArrayList<>();
while (!queue.isEmpty()) {
TreeNode c = queue.poll();
int level = map.get(c);
if (c.left != null) {
queue.offer(c.left);
map.put(c.left, level + 1);
}
if (c.right != null) {
queue.offer(c.right);
map.put(c.right, level + 1);
}
if (curLevel == level) {
levelRecords.add(c.val);
} else {
ans.add(levelRecords);
levelRecords = new ArrayList<>();
levelRecords.add(c.val);
curLevel = level;
}
}
// 记得要存最后一层的数据
ans.add(levelRecords);
return ans;
}
方法2省略了一个哈希表,使用了两个变量来判断层数的变化,分别是:
// 遍历到的当前层的最后一个位置
TreeNode curEnd;
// 下一层的最后一个位置
TreeNode nextEnd;
在队列每次弹出元素的时候,设置nextEnd变量,同时,如果弹出的元素等于curEnd,说明已经到当前层的结尾了,就可以收集这一层的答案了。
方法2的完整代码如下
public static List<List<Integer>> levelOrder2(TreeNode head) {
if (head == null) {
return new ArrayList<>();
}
List<List<Integer>> ans = new ArrayList<>();
List<Integer> levelRecords = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
TreeNode curEnd = head;
TreeNode nextEnd = null;
queue.offer(curEnd);
while (!queue.isEmpty()) {
TreeNode c = queue.poll();
levelRecords.add(c.val);
if (c.left != null) {
queue.offer(c.left);
// 弹出的时候,设置nextEnd
nextEnd = c.left;
}
if (c.right != null) {
queue.offer(c.right);
// 弹出的时候,设置nextEnd
nextEnd = c.right;
}
if (c == curEnd) {
// 即将要来到新的一层了
curEnd = nextEnd;
ans.add(levelRecords);
levelRecords = new ArrayList<>();
}
}
return ans;
}
方法3只是把方法2中的链表和队列换成自己实现的链表和队列结构,大思路上和方法2一样,我们可以自己实现一个链表和队列,实现最简单的poll和offer方法即可,自定义的链表如下:
// 自定义链表
public static class MyNode {
public TreeNode data;
public MyNode next;
public MyNode(TreeNode node) {
data = node;
}
}
// 自定义队列
public static class MyQueue {
public MyNode front;
public MyNode end;
public int size;
public MyQueue() {
front = null;
end = null;
}
public void offer(MyNode c) {
size++;
if (front == null) {
front = c;
} else {
end.next = c;
}
end = c;
}
public boolean isEmpty() {
return size == 0;
}
public MyNode poll() {
size--;
MyNode ans = front;
front = front.next;
ans.next = null;
return ans;
}
}
然后把方法2中的Java自带的LinkedList换成我们自己实现的链表和队列,完整代码如下
public static List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
if (root == null) {
return ans;
}
MyNode head = new MyNode(root);
MyQueue queue = new MyQueue();
queue.offer(head);
MyNode curEnd = head;
MyNode nextEnd = null;
List<Integer> item = new ArrayList<>();
MyNode t;
while (!queue.isEmpty()) {
MyNode c = queue.poll();
if (c.data.left != null) {
t = new MyNode(c.data.left);
queue.offer(t);
nextEnd = t;
}
if (c.data.right != null) {
t = new MyNode(c.data.right);
queue.offer(t);
nextEnd = t;
}
item.add(c.data.val);
if (curEnd.data == c.data) {
ans.add(item);
item = new ArrayList<>();
curEnd = nextEnd;
}
}
return ans;
}
10.自定义Navigator切换fragment
①使用场景:
使用Navigation完成fragment间的跳转操作
②问题描述:
Navigation 实现 fragment 间的跳转用的是replace()方法,此方法会移除原来的fragment,再添加新的fragment,所以回到上一个fragment时就需要重新走一遍生命周期流程,重新加载数据。
③解决方案:
分析 NavController类 中的navigate 源码
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
...
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName()); 根据节点名称生成不同的navigator
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras); 调用navigator 中的 navigate方法
...
}
getNavigator 源码如下
private final HashMap<String, Navigator<? extends NavDestination>> mNavigators =
new HashMap<>();
public <T extends Navigator<?>> T getNavigator(@NonNull String name) {
if (!validateName(name)) {
throw new IllegalArgumentException("navigator name cannot be an empty string");
}
Navigator<? extends NavDestination> navigator = mNavigators.get(name); // 根据传入的node获取不同的navigator
if (navigator == null) {
throw new IllegalStateException("Could not find Navigator with name \"" + name
+ "\". You must call NavController.addNavigator() for each navigation type.");
}
return (T) navigator;
}
因此,想要调用自定义navigate()方法就需要 自定义一个Navigator类,同时改变fragment节点名称,将fragment节点名称与Navigator类作为key,value 添加到 HashMap类型的mNavigators中。
④自定义Navigator
@Navigator.Name("custom_fragment") // 节点名称定义为custom_fragment,作为 mNavigatorProvider 变量的 key
class CustomNavigator( //作为 mNavigatorProvider 变量的 value
private val context: Context,
private val manager: FragmentManager,
private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {
override fun navigate( // 重写 navigate 方法
destination: Destination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
): NavDestination? {
val tag = destination.id.toString() // 跳转目的地
val transaction = manager.beginTransaction() // 开启fragment事务
val currentFragment = manager.primaryNavigationFragment // navigation 顶层fragment
if (currentFragment != null) {
transaction.hide(currentFragment) // 隐藏当前fragment
}
var fragment = manager.findFragmentByTag(tag) // 找到目的地fragment
if (fragment == null) { // fragment未被初始化
val className = destination.className
fragment = manager.fragmentFactory.instantiate(context.classLoader, className) // 实例化 fragment
transaction.add(containerId, fragment, tag) // 将碎片添加到容器中
} else { // fragment 已经初始化过了
transaction.show(fragment) // 显示fragment
}
transaction.setPrimaryNavigationFragment(fragment) //将fragment 设置为顶层fragment
transaction.setReorderingAllowed(true)
transaction.commitNow() // 提交事务
return destination // 返回目的地,用于监听
}
}
⑤修改节点名称
因为节点名称定义为custom_fragment,所以修改为<custom_fragment>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/my_nav"
app:startDestination="@id/home_dest"
tools:ignore="UnusedNavigation">
<custom_fragment
android:id="@+id/home_dest"
android:name="com.cl.androidstudy.ui.home.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" />
<custom_fragment
android:id="@+id/system_dest"
android:name="com.cl.navicationtest.SystemFragment"
android:label="fragment_system"
tools:layout="@layout/fragment_system" />
<custom_fragment
android:id="@+id/square_dest"
android:name="com.cl.navicationtest.SquareFragment"
android:label="fragment_square"
tools:layout="@layout/fragment_square" />
<custom_fragment
android:id="@+id/me_dest"
android:name="com.cl.androidstudy.ui.me.MeFragment"
android:label="fragment_me"
tools:layout="@layout/fragment_me" />
</navigation>
⑥逻辑代码
val navController = Navigation.findNavController(this, R.id.fragment) // 创建navController
val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment)!!
val navigator = CustomNavigator(
this,
navHostFragment.childFragmentManager,
R.id.fragment
)// 生成自定义Navigator对象
navController.navigatorProvider.addNavigator("custom_fragment", navigator) // 添加 key, value
navController.setGraph(R.navigation.my_nav) // 要在 CustomNavigator 类被加载之后添加graph,不然找不到 custom_fragment节点
11.Navigation使用和源码剖析
12.volatile的作用
字数限制写不下啦
关注公众号:初一十五a
解锁 《Android十三大板块文档》,让学习更贴近未来实战。已形成PDF版
内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载应用大合集,从零开始一起学
4.性能优化大合集,告别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高级工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对工作需求
10.Android基础篇大合集,根基稳固高楼平地起
11.Flutter番外篇:面试+项目实战+电子书
12.高级Android组件化强化实战
13.十二模块之补充部分:其他Android十一大知识体系
整理不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔