参考文档:
12W字;2022最新Android11位大厂面试专题(一) - 掘金
Retrofit 版本号:2.9.0
Retrofit简单来说,就是对OkHttp上层进行了封装,已达到用户方便使用管理网络请求的目的。
Retrofit内部有着简捷、优雅的结构设计。当然对于新手来说,Retrofit也有着不宜理解和上手的问题。不过瑕不掩瑜,Retrofit仍然是一款优秀的网络请求框架。
这边文章除了进行源码分析,还有个人对Retrofit的一些浅见和感悟。仅供读者参考。
一.使用方法
早期网络请求方式,一般都是设计一个OKHttpUtil之类的工具类,实现对Okhttp请求的封装,入参传入url、请求参数param,回调函数callback。这样的请求方式对新用户来说是非常易于上手和容易理解的,但为什么还要使用Retrofit呢,它到底能给我们带来什么呢?
先看看使用方法吧:
使用示例中的接口来自玩Android - wanandroid.com - 每日推荐优质文章
1.添加Retrofit依赖
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// retrofit gson转换器
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
2.创建一个接口
用于管理不同的网络请求
这里第一个接口返回的类型为Call<T>,此处的Call为Retrofit中的接口,并非Okhttp中的Call(虽然他们接口中定义的方法很类似),如果不特别说明,后面源码分析中的也是这个Call。
interface MainService {@GET("article/list/{index}/json")fun paperList(@Path("index") index: Int): Call<MainPaperList>@GET("hotkey/json")suspend fun hotkey(): HotKey@POST("hotkey/json")suspend fun hotkey2(@Body req: TestReq): HotKey
}
3.创建Retrofit并创建接口实现的实例
创建Retrofit,完成配置。
这里的接口实现使用Java动态代理方式。
// 1.Retrofit创建
val retrofit = Retrofit.Builder().baseUrl("https://www.wanandroid.com/").addConverterFactory(GsonConverterFactory.create()).build()// 2.创建网络请求接口示例
val service = retrofit.create(MainService::class.java)
4.发起请求
这里给出异步请求的示例,默认情况下回调函数会在主线程运行。
动态代理实现中,对调用接口方法进行解析,包括接口参数、返回值和注解,根据这些配置项使用OKHttp进行网络请求。
这里使用反射方式进行解析,可能会稍有耗时,但解析完成后会放在缓存中,再次请求时从缓存中取用。
fun queryPaperList() {service.paperList(0).enqueue(object : Callback<MainPaperList> {override fun onResponse(call: Call<MainPaperList>,response: Response<MainPaperList>) {// 请求成功}override fun onFailure(call: Call<MainPaperList>, t: Throwable) {// 请求异常}})
}
5.推荐个IDEA插件
在IDEA插件搜索框搜索Retrofit就可以出来。
1.RestfulTool Retrofit
这个插件可以浏览并快速定位到自己定义的Retrofit接口。免费使用。
2.Retrofit Assistant
这个插件功能强大,方便用户快速创建和浏览网络请求的定义的接口,有30天免费试用期,之后使用需要付费。
可以参考插件作者的这篇文章了解:Retrofit的好基友-RetrofitAssistant插件 - 掘金
二.为什么好用
Retrofit有以下优点,分开来解读
1.写法简洁
用接口进行网络请求管理对于初学者来说确实比较怪异,有一定学习曲线,需要花时间来熟悉用法和实现原理。但这样的写法非常简洁方便,声明式的方式定义和处理网络请求可以减少了样板代码的编写。
2.主流支持
同时Retrofit内部实现了对RxJava和协程(2.6.0版本之后)的支持,以及对Json、xml数据格式的支持。这些支持方式免去了使用者去费劲编写转换代码。
3.可扩展
如果需要对请求数据和返回数据做一些转换或者特殊处理,Retrofit同样提供了入口允许用户进行一些特殊处理,比如设置自己的okhttpClient;如需对请求数据和返回数据做一些转换或者特殊处理,可以设置自定义的converterFactory;还可以配置callAdapterFactory,实现特殊的请求方式。
Retrofit存在着大量的设计模式,这些设计模式实现了代码的解耦课可拓展性,这里配置中出现converterFactory和callAdapterFactory都是工厂方法模式的使用。有一些经验的开发者觉得阅读起来会觉得很舒服,而初学者则会有一定的阅读困难。不过很多设计模式都会有这样的问题。设计模式已经广泛应用在各个开源组件的代码中,学习和使用设计模式是提高代码理解能力的必备功课吧。
三.结构总览
Retrofit经营着一家高档餐厅,餐厅里共有五个部门。第一个部门是主厨部门,一个主厨只负责一道菜的指挥和监督工作。还有四个部门,分别归四个管家管理,每个管家负责对小厨娘进行招募,每个部门的小厨娘只负责做菜的其中一个环节。
当一道菜品需要制作时,会组建一个厨师团队进行菜品制作的部署工作,这个团队安排一个主厨负责,还有几个小厨娘供主厨指挥,在部署工作进行中还会招募一些其他帮手。
几个个小厨娘各司其职,互相配合,在主厨指挥下,完成任务,厨师负责最后摆盘,供客人食用。
四个管家:callFactory、converterFactories、callAdapterFactories、callbackExecutor
主厨团队:serviceMethodCache
请记住这四个管家,他们将在后面源码中频繁出现。
四.源码分析
源码中关键地方都做了中文注释
1.人员预备:Retrofit和Retrofit.Build
这里主要做基础的建设工作。
Retrofit使用建造者模式创建:
val retrofit = Retrofit.Builder()....build()
先看看Retrofit的内部的成员变量和构造方法:
public final class Retrofit {// 接口缓存。一个接口请求的方法为一个Method,对应一个ServiceMethod,ServiceMethod内部有着网络请求相关配置private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();// OkHttp的接口,创建Okhttp请求(Call)的工厂接口,只有一个实现类:OkHttpClientfinal okhttp3.Call.Factory callFactory;// 网络请求基地址,即host地址final HttpUrl baseUrl;// 将请求或者返回结果进行转换的工厂类列表,比如Retrofit提供的GsonConverterFactory提供了将出入参和Gson进行转换的Converterfinal List<Converter.Factory> converterFactories;// callAdapter工厂集合,CallAdapter就是Call的转换器(Retrofit中的Call),callAdapterFactory会根据自己的能力返回合适的CallAdapterfinal List<CallAdapter.Factory> callAdapterFactories;// Call请求的回调方法运行的executor,默认Retrofit定义的MainThreadExecutor,即运行在主线程的Executorfinal @Nullable Executor callbackExecutor;// 字面意思就是提前验证,如果是true的话,当retrofit.create创建接口实现类时,就进行接口的解析和初始化,生成相应的ServiceMethod,放入缓存map中final boolean validateEagerly;Retrofit(okhttp3.Call.Factory callFactory,HttpUrl baseUrl,List<Converter.Factory> converterFactories,List<CallAdapter.Factory> callAdapterFactories,@Nullable Executor callbackExecutor,boolean validateEagerly) {this.callFactory = callFactory;this.baseUrl = baseUrl;this.converterFactories = converterFactories; // Copy+unmodifiable at call site.this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.this.callbackExecutor = callbackExecutor;this.validateEagerly = validateEagerly;}
}
Retrofit.Builder中的成员变量除了多了一个Platform,其他与Retrofit的那些一样,Builder里的方法也是对这些值得设置。
这里的Platform要重点说明一下
Platform提供了对Android和Java平台的支持,也就是说Retrofit不仅可以用在Android开发,还可以用于后端Java开发。
Platform具体提供了以下内容:
1.默认的CallAdapterFactory。通过defaultCallAdapterFactories方法,其中有DefaultCallAdapterFactory(将异步请求结果的回调使用callbackExecutor运行)、CompletableFutureCallAdapterFactory.INSTANCE(将Call转换为CompletableFuture)。
2.默认的ConverterFactory。通过defaultConverterFactories方法,只有OptionalConverterFactory.INSTANCE(将结果转换成Optional)。
3.默认的callbackExecutor。通过defaultCallbackExecutor,这里只有Android平台会提供一个MainThreadExecutor。
我们只分析Android平台。可以认为,Platform提供了Builder进行初始化时四个管家其中三个的默认配置,即converterFactories、callAdapterFactories、callbackExecutor的默认配置(CompletableFutureCallAdapterFactory和OptionalConverterFactory基本上是用不上,此处忽略)。
看看Builder的build方法,是如何完成Retrofit的创建工作:
public Retrofit build() {if (baseUrl == null) {throw new IllegalStateException("Base URL required.");}okhttp3.Call.Factory callFactory = this.callFactory;if (callFactory == null) {callFactory = new OkHttpClient();}Executor callbackExecutor = this.callbackExecutor;if (callbackExecutor == null) {callbackExecutor = platform.defaultCallbackExecutor();}// Make a defensive copy of the adapters and add the default Call adapter.List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));// 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 that consume all types.converterFactories.add(new BuiltInConverters());converterFactories.addAll(this.converterFactories);converterFactories.addAll(platform.defaultConverterFactories());return new Retrofit(callFactory,baseUrl,unmodifiableList(converterFactories),unmodifiableList(callAdapterFactories),callbackExecutor,validateEagerly);}
build方法非常清晰,将Platform提供的用户的配置和Platform提供的默认支持加入,完成Retrofit的创建。
如果没有设置callFactory,那么久创建一个OkHttpClient对象。BuiltInConverters提供了对接口返回值为Void、Unit的支持,以及对注解为Streaming的支持。
2.报菜员就位:Retrofit.create
这一部分是接口实例化工作。
这里分析下面这行代码的源码
val service = retrofit.create(MainService::class.java)
public <T> T create(final Class<T> service) {// 如果validateEagerly为true,这里直接对service进行解析和初始化,否则将在第一次运行时进行初始化validateServiceInterface(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];@Overridepublic @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable {// 如果是Object的方法,则直接调用这个方法。这里限制只能是接口// If the method is a method from Object then defer to normal invocation.if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);}// 如果是接口默认方法,则调用默认方法,如果不是则调用loadServiceMethod(method).invokeargs = args != null ? args : emptyArgs;return platform.isDefaultMethod(method)? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);}});}
源码中使用了Java提供的动态代理的方式对接口进行实例化。
这里简单讲一下,当用户使用service这个对象调用接口方法时,会被分发到InvocationHandler,并调用起invoke方法,传入这个方法的method和入参args。
Method是Java反射包中的一个类,通过method,我们可以轻松获取到这个方法的注解和入参,由此便能实现将接口转换成网络请求。这是Retrofit核心,理解了这里,再使用Retrofit,便会觉得豁然开朗。
loadServiceMethod(method).invoke(args)这句是重点,分开来分析,先loadServiceMethod,再分析invoke(ServiceMethod的方法)。
3.分配主厨:loadServiceMethod
这一步正是Retrofit偷梁换柱的动作,将注解(菜单)转化成各种对象(菜品),并分配主厨上场。
ServiceMethod<?> loadServiceMethod(Method method) {// 先从缓存中查找ServiceMethod<?> result = serviceMethodCache.get(method);if (result != null) return result;synchronized (serviceMethodCache) {result = serviceMethodCache.get(method);if (result == null) {result = ServiceMethod.parseAnnotations(this, method);serviceMethodCache.put(method, result);}}return result;}
先从缓存中查找,如果没有便通过ServiceMethod.parseAnnotations(this, method)获取到ServiceMethod,放入缓存,并返回。
abstract class ServiceMethod<T> {static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {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.");}return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);}abstract @Nullable T invoke(Object[] args);
}
首先创建了requestFactory,将requestFactory传递给厨师HttpServiceMethod,然后返回。
有请主厨HttpServiceMethod闪亮登场,只见他左边张小龙(requestFactory),右边赵小虎(responseConverter),前方展小昭(callFactory)。
private final RequestFactory requestFactory;
private final okhttp3.Call.Factory callFactory;
private final Converter<ResponseBody, ResponseT> responseConverter;HttpServiceMethod(RequestFactory requestFactory,okhttp3.Call.Factory callFactory,Converter<ResponseBody, ResponseT> responseConverter) {this.requestFactory = requestFactory;this.callFactory = callFactory;this.responseConverter = responseConverter;
}
在这里,requestFactory成为了主厨的帮手。
下面看看RequestFactory有什么能力来帮助主厨。
先来看看RequestFactory.parseAnnotations方法和RequestFactory有哪些成员变量:
final class RequestFactory {static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {return new Builder(retrofit, method).build();}private final Method method;private final HttpUrl baseUrl;final String httpMethod;// 相对url,例如注解@GET后面的值private final @Nullable String relativeUrl;// 注解Headers解析,格式为 Name: Valueprivate final @Nullable Headers headers;// 注解Header中Content-Type的值private final @Nullable MediaType contentType;// 是否有bodyprivate final boolean hasBody;// 是否是FormEncodedprivate final boolean isFormEncoded;// 是否是Multipartprivate final boolean isMultipart;// 方法的每个入参都有一个对应的parameterHandlersprivate final ParameterHandler<?>[] parameterHandlers;// 是否是kotlin的suspend方法final boolean isKotlinSuspendFunction;// 省略以下内容
}
RequestFactory依旧是工厂,但不是抽象工厂,这个工厂是Request工厂,就是创建用来创建okhttp3.Request的工厂。除了创建Request,这里对方法的注解、入参、入参注解进行了解析。
Builder(Retrofit retrofit, Method method) {this.retrofit = retrofit;this.method = method;// 获取方法注解,返回值为Annotation[]this.methodAnnotations = method.getAnnotations();// 获取方法参数类型,返回值为Type[]this.parameterTypes = method.getGenericParameterTypes();// 获取参数中的注解,返回值为Annotation[][]this.parameterAnnotationsArray = method.getParameterAnnotations();}
这里的parseAnnotations,依旧是建造者模式来创建。
Builder.build方法对RequestFactory进行创建:
RequestFactory build() {// 解析方法注解,例如GET、POST、HTTP、Multipart、FormUrlEncoded等等for (Annotation annotation : methodAnnotations) {parseMethodAnnotation(annotation);}// 略过错误检查代码...// 解析每个入参和其注解,生成对应的ParameterHandlerint parameterCount = parameterAnnotationsArray.length;parameterHandlers = new ParameterHandler<?>[parameterCount];for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {parameterHandlers[p] =parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);}// 略过错误检查代码...return new RequestFactory(this);}
说一下这里的ParameterHandler,下方是ParameterHandler的源码,这是是一个用于处理请求参数的抽象类,内部只有一个抽象方法apply。在后面创建Request请求时(RequestBuilder),按顺序执行ParameterHandler列表中每个ParameterHandler的apply方法,对RequestBuilder进行处理。这又是一个巧妙使用设计模式的地方,这里用到责任链模式和策略模式。
abstract class ParameterHandler<T> {abstract void apply(RequestBuilder builder, @Nullable T value) throws IOException;// 忽略其他
}
责任链模式:ParameterHandler的实现类可以形成一个责任链,每个处理器负责处理特定类型的参数,如果当前处理器无法处理参数,则将参数传递给下一个处理器。这样可以很好地组织和管理参数的处理流程。
策略模式:ParameterHandler可以根据不同的参数类型采用不同的处理策略,每个实现类都代表一种具体的处理策略,通过接口的多态性可以灵活地切换处理策略。
这种设计模式使得Retrofit可以动态地处理各种类型的请求参数,并且方便扩展和维护。
RequestFactory源码中可以看到久违的Retrofit的注解,这里开始完成对注解和参数的解析。如果对这些注解处理感兴趣,可以看这个类。
我们再来分析分析HttpServiceMethod的parseAnnotations方法。
HttpServiceMethod的parseAnnotations方法,从名字可以看出,仍然是对注解的解析
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;boolean continuationWantsResponse = false;boolean continuationBodyNullable = false;Annotation[] annotations = method.getAnnotations();Type adapterType;if (isKotlinSuspendFunction) {// ...// 暂时不分析对kotlin挂起函数的支持} else {adapterType = method.getGenericReturnType();}// 从callAdapterFactories中获取到合适的CallAdapter,对Call进行转换CallAdapter<ResponseT, ReturnT> callAdapter =createCallAdapter(retrofit, method, adapterType, annotations);Type responseType = callAdapter.responseType();// 忽略部分检查错误的代码// 从converterFactories中获取到合适的responseConverterConverter<ResponseBody, ResponseT> responseConverter =createResponseConverter(retrofit, method, responseType);okhttp3.Call.Factory callFactory = retrofit.callFactory;if (!isKotlinSuspendFunction) {return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);} else {// ...// 暂时不分析对kotlin挂起函数的支持}
}
暂时不分析kotlin挂起函数,先看普通函数的处理。拿到requestFactory、合适的callAdapter、合适的responseConverter,组成了新的CallAdapted,CallAdapted是HttpServiceMethod的一个子类。
static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {private final CallAdapter<ResponseT, ReturnT> callAdapter;CallAdapted(RequestFactory requestFactory,okhttp3.Call.Factory callFactory,Converter<ResponseBody, ResponseT> responseConverter,CallAdapter<ResponseT, ReturnT> callAdapter) {super(requestFactory, callFactory, responseConverter);this.callAdapter = callAdapter;}@Overrideprotected ReturnT adapt(Call<ResponseT> call, Object[] args) {return callAdapter.adapt(call);}}
主厨CallAdapted上场了。
loadServiceMethod方法,最终得到的就是这个CallAdapted,它也有着自己的四个厨娘的调用权:requestFactory(功能:对请求的解析和创建)、callFactory(默认就是OkhttpClient)、responseConverter和callAdapter。
4.工作部署:invoke
HttpServiceMethod.invoke一调用,主厨开始进行最后的部署工作,并分配了一个部署指挥官。主厨将三个帮手的指挥权传递给部署指挥官,交由他完成下一步工作。
@Override
final @Nullable ReturnT invoke(Object[] args) {Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);return adapt(call, args);
}
invoke方法只有两行,创建一个OkHttpCall对象,执行adapt(call, args)。
先要说明一点,这里的OkHttpCall是Retrofit包中的一个类,并不是OkHttp包中的,但这个类封装了对OKHttp中Call请求的创建和处理。
看看OkHttpCall的构造函数和它的成员变量,有requestFactory、callFactory,、responseConverter,通过这几个,它的能力便可以想象得到。
再回到adapter方法的分析,loadServiceMethod返回的CallAdapted对adapt进行了重写,返回了callAdapter.adapt(call)。
来看看默认的CallAdapterFactory和其提供的Adapter:
final class DefaultCallAdapterFactory extends CallAdapter.Factory {@Nullableprivate final Executor callbackExecutor;DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {this.callbackExecutor = callbackExecutor;}@Nullablepublic CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {if (getRawType(returnType) != Call.class) {return null;} else if (!(returnType instanceof ParameterizedType)) {throw new IllegalArgumentException("Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");} else {// 如果没有使用SkipCallbackExecutor注解,那就返回ExecutorCallbackCallfinal Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType)returnType);final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class) ? null : this.callbackExecutor;return new CallAdapter<Object, Call<?>>() {public Type responseType() {return responseType;}public Call<Object> adapt(Call<Object> call) {return (Call)(executor == null ? call : new ExecutorCallbackCall(executor, call));}};}}static final class ExecutorCallbackCall<T> implements Call<T> {final Executor callbackExecutor;final Call<T> delegate;ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {this.callbackExecutor = callbackExecutor;this.delegate = delegate;}public void enqueue(final Callback<T> callback) {Objects.requireNonNull(callback, "callback == null");this.delegate.enqueue(new Callback<T>() {public void onResponse(Call<T> call, Response<T> response) {ExecutorCallbackCall.this.callbackExecutor.execute(() -> {if (ExecutorCallbackCall.this.delegate.isCanceled()) {callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));} else {callback.onResponse(ExecutorCallbackCall.this, response);}});}public void onFailure(Call<T> call, Throwable t) {ExecutorCallbackCall.this.callbackExecutor.execute(() -> {callback.onFailure(ExecutorCallbackCall.this, t);});}});}public boolean isExecuted() {return this.delegate.isExecuted();}public Response<T> execute() throws IOException {return this.delegate.execute();}public void cancel() {this.delegate.cancel();}public boolean isCanceled() {return this.delegate.isCanceled();}public Call<T> clone() {return new ExecutorCallbackCall(this.callbackExecutor, this.delegate.clone());}public Request request() {return this.delegate.request();}public Timeout timeout() {return this.delegate.timeout();}}
}
这里返回了ExecutorCallbackCall,对传入的OkHttpCall对象做了一层简单的封装,enqueue方法将回调函数运行在主线程中。可以认为这里使用了代理模式。
这里简单理解为部署完成,最后部署指挥官便是ExecutorCallbackCall。
5.做菜了:enqueue
当用户要菜时(发起请求时),部署指挥官开始指挥工作,稍后便将美食制作完成:
service.paperList(0)
.enqueue(object : Callback<MainPaperList> {override fun onResponse(call: Call<MainPaperList>,response: Response<MainPaperList>) {// 请求成功}override fun onFailure(call: Call<MainPaperList>, t: Throwable) {// 请求异常}
})
部署指挥官的工作就是指挥,底层工作仍是别人执行:
分析便止于此。
我想以容易理解的方式进行源码解读,但是我给出的一个餐厅模型对于源码中部分逻辑仍然难以适配,因此只做入门理解使用。
因为能力有限,以上分析会有部分理解错误之处,希望各位提出。
五.总结
Retrofit作为一个轻量的网络请求封装库,却有着丰富和优雅的设计。这是非常值得大家分析学习的。
在分析源码的过程中,下面几点会更好地帮助到你:
1.理解类名
优秀的框架通过类名便可以看出这个类功能的一二。
比如XXFactory,一般都是工厂模式,对应会有一个create方法区创建一个XX。
XXAdapter和XXConvert,名字更是直接表明是一个适配器或者转换类。
2.看看类的成员变量,判断类的能力
面向对象的一大特征便是封装。类的成员变量,便是类所封装和承载的信息。对于不熟悉的类,不妨先看看他的成员变量有哪些,看看他有什么能力。
3.阅读注释
有时候看注释也会不理解,但不妨碍注释给我的启示作用。
推荐一个免费的IDEA插件Translation,助你轻松应对英文注释。