- dependencies {
- //retrofit + gson + rxjava
- implementation 'com.squareup.retrofit2:retrofit:2.9.0'
- implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
- implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
- }
- interface ApiService {
-
- @GET("getUserData")
- fun getUserData1(): Call
- }
-
- fun main1() {
- val retrofit = Retrofit.Builder()
- .baseUrl("http://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
- .build()
- val service = retrofit.create(ApiService::class.java)
- val call: Call
= service.getUserData1() - call.enqueue(object : Callback
{ - override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
- val userBean = response.body()?.string()
- println("userBean: $userBean")
- }
-
- override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
- println("onFailure: $t")
- }
- })
- }
Retrofit 是建立在 OkHttp 之上的一个网络请求封装库,内部依靠 okhttp 来完成实际网络请求。Retrofit 在使用上很简洁, API 通过 interface 来声明。我只需要通过 interface 来声明 API路径、请求方式、请求参数、返回值类型等各个配置项。
可以看到,getUserData() 方法的请求结果是一个 json 格式的字符串,其返回值类型被定义为 Call
API 返回值 Json 转换 :
- interface ApiService {
-
- @GET("getUserData")
- fun getUserData2(): Call
- }
-
- data class UserBean(val status: Int, val msg: String, val data: Data)
-
- data class Data(val userName: String, val userAge: Long)
-
- fun main2() {
- val retrofit = Retrofit.Builder()
- .baseUrl("http://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
- .addConverterFactory(GsonConverterFactory.create())
- .build()
- val service = retrofit.create(ApiService::class.java)
- val call: Call
= service.getUserData2() - call.enqueue(object : Callback
{ - override fun onResponse(call: Call<UserBean>, response: Response<UserBean>) {
- val userBean = response.body()
- println("userBean: $userBean")
- }
-
- override fun onFailure(call: Call<UserBean>, t: Throwable) {
- println("onFailure: $t")
- }
- })
- }
adapter-rxjava2 转换返回值为被观察者
- interface ApiService {
-
- @GET("getUserData")
- fun getUserData3(): Observable
- }
-
- data class UserBean(val status: Int, val msg: String, val data: Data)
-
- data class Data(val userName: String, val userAge: Long)
-
- fun main3() {
- val retrofit = Retrofit.Builder()
- .baseUrl("http://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
- .addConverterFactory(GsonConverterFactory.create())
- .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
- .build()
- val service = retrofit.create(ApiService::class.java)
- val call: Observable
= service.getUserData3() - call.subscribe({ user ->
- println("userBean: $user")
- }, { t ->
- println("onFailure: $t")
- })
- }
- @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
- public
T create(final Class 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];
-
- @Override
- public @Nullable 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);
- }
- args = args != null ? args : emptyArgs;
- return platform.isDefaultMethod(method)
- ? platform.invokeDefaultMethod(method, service, proxy, args)
- : loadServiceMethod(method).invoke(args);
- }
- });
- }
- 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;
- }
- abstract class ServiceMethod<T> {
- static
ServiceMethod 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);
- }
- abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
-
-
- static
HttpServiceMethod 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) {
- Type[] parameterTypes = method.getGenericParameterTypes();
- Type responseType =
- Utils.getParameterLowerBound(
- 0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
- if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
- // Unwrap the actual body type from Response
. - responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
- continuationWantsResponse = true;
- } else {
- // TODO figure out if type is nullable or not
- // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
- // Find the entry for method
- // Determine if return type is nullable or not
- }
-
- adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
- annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
- } else {
- adapterType = method.getGenericReturnType();
- }
-
- CallAdapter
callAdapter = - createCallAdapter(retrofit, method, adapterType, annotations);
- Type responseType = callAdapter.responseType();
- if (responseType == okhttp3.Response.class) {
- throw methodError(
- method,
- "'"
- + getRawType(responseType).getName()
- + "' is not a valid response body type. Did you mean ResponseBody?");
- }
- if (responseType == Response.class) {
- throw methodError(method, "Response must include generic type (e.g., Response
)" ); - }
- // TODO support Unit for Kotlin?
- if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
- throw methodError(method, "HEAD method must use Void as response type.");
- }
-
- Converter
responseConverter = - createResponseConverter(retrofit, method, responseType);
-
- okhttp3.Call.Factory callFactory = retrofit.callFactory;
- if (!isKotlinSuspendFunction) {
- return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
- } else if (continuationWantsResponse) {
- //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
- return (HttpServiceMethod
) - new SuspendForResponse<>(
- requestFactory,
- callFactory,
- responseConverter,
- (CallAdapter
>) callAdapter); - } else {
- //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
- return (HttpServiceMethod
) - new SuspendForBody<>(
- requestFactory,
- callFactory,
- responseConverter,
- (CallAdapter
>) callAdapter, - continuationBodyNullable);
- }
- }
-
-
- @Override
- final @Nullable ReturnT invoke(Object[] args) {
- Call
call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter); - return adapt(call, args);
- }
- }
OkHttpCall 是实际发起 okHttp 请求的地方。当我们调用 fun getUserData(): Call
- final class OkHttpCall<T> implements Call<T> {
- private final RequestFactory requestFactory;
- private final Object[] args;
- private final okhttp3.Call.Factory callFactory;
- private final Converter
responseConverter; -
- private volatile boolean canceled;
-
- @GuardedBy("this")
- private @Nullable okhttp3.Call rawCall;
-
- @GuardedBy("this") // Either a RuntimeException, non-fatal Error, or IOException.
- private @Nullable Throwable creationFailure;
-
- @GuardedBy("this")
- private boolean executed;
-
- OkHttpCall(
- RequestFactory requestFactory,
- Object[] args,
- okhttp3.Call.Factory callFactory,
- Converter
responseConverter) { - this.requestFactory = requestFactory;
- this.args = args;
- this.callFactory = callFactory;
- this.responseConverter = responseConverter;
- }
-
- @SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.
- @Override
- public OkHttpCall
clone() { - return new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
- }
-
- @Override
- public synchronized Request request() {
- try {
- return getRawCall().request();
- } catch (IOException e) {
- throw new RuntimeException("Unable to create request.", e);
- }
- }
-
- @Override
- public synchronized Timeout timeout() {
- try {
- return getRawCall().timeout();
- } catch (IOException e) {
- throw new RuntimeException("Unable to create call.", e);
- }
- }
-
- /**
- * Returns the raw call, initializing it if necessary. Throws if initializing the raw call throws,
- * or has thrown in previous attempts to create it.
- */
- @GuardedBy("this")
- private okhttp3.Call getRawCall() throws IOException {
- okhttp3.Call call = rawCall;
- if (call != null) return call;
-
- // Re-throw previous failures if this isn't the first attempt.
- if (creationFailure != null) {
- if (creationFailure instanceof IOException) {
- throw (IOException) creationFailure;
- } else if (creationFailure instanceof RuntimeException) {
- throw (RuntimeException) creationFailure;
- } else {
- throw (Error) creationFailure;
- }
- }
-
- // Create and remember either the success or the failure.
- try {
- return rawCall = createRawCall();
- } catch (RuntimeException | Error | IOException e) {
- throwIfFatal(e); // Do not assign a fatal error to creationFailure.
- creationFailure = e;
- throw e;
- }
- }
-
- @Override
- public void enqueue(final Callback
callback) { - Objects.requireNonNull(callback, "callback == null");
-
- 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 {
- call = rawCall = createRawCall();
- } catch (Throwable t) {
- throwIfFatal(t);
- failure = creationFailure = t;
- }
- }
- }
-
- if (failure != null) {
- callback.onFailure(this, failure);
- return;
- }
-
- if (canceled) {
- call.cancel();
- }
-
- call.enqueue(
- new okhttp3.Callback() {
- @Override
- public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
- Response
response; - try {
- response = parseResponse(rawResponse);
- } catch (Throwable e) {
- throwIfFatal(e);
- callFailure(e);
- return;
- }
-
- try {
- callback.onResponse(OkHttpCall.this, response);
- } catch (Throwable t) {
- throwIfFatal(t);
- t.printStackTrace(); // TODO this is not great
- }
- }
-
- @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) {
- throwIfFatal(t);
- t.printStackTrace(); // TODO this is not great
- }
- }
- });
- }
-
- @Override
- public synchronized boolean isExecuted() {
- return executed;
- }
-
- @Override
- public Response
execute() throws IOException { - okhttp3.Call call;
-
- synchronized (this) {
- if (executed) throw new IllegalStateException("Already executed.");
- executed = true;
-
- call = getRawCall();
- }
-
- if (canceled) {
- call.cancel();
- }
-
- return parseResponse(call.execute());
- }
- }
- 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;
- private final @Nullable String relativeUrl;
- private final @Nullable Headers headers;
- private final @Nullable MediaType contentType;
- private final boolean hasBody;
- private final boolean isFormEncoded;
- private final boolean isMultipart;
- private final ParameterHandler>[] parameterHandlers;
- final boolean isKotlinSuspendFunction;
-
- RequestFactory(Builder builder) {
- method = builder.method;
- baseUrl = builder.retrofit.baseUrl;
- httpMethod = builder.httpMethod;
- relativeUrl = builder.relativeUrl;
- headers = builder.headers;
- contentType = builder.contentType;
- hasBody = builder.hasBody;
- isFormEncoded = builder.isFormEncoded;
- isMultipart = builder.isMultipart;
- parameterHandlers = builder.parameterHandlers;
- isKotlinSuspendFunction = builder.isKotlinSuspendFunction;
- }
-
- okhttp3.Request create(Object[] args) throws IOException {
- @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
- ParameterHandler
-
- int argumentCount = args.length;
- if (argumentCount != handlers.length) {
- throw new IllegalArgumentException(
- "Argument count ("
- + argumentCount
- + ") doesn't match expected count ("
- + handlers.length
- + ")");
- }
-
- RequestBuilder requestBuilder =
- new RequestBuilder(
- httpMethod,
- baseUrl,
- relativeUrl,
- headers,
- contentType,
- hasBody,
- isFormEncoded,
- isMultipart);
-
- if (isKotlinSuspendFunction) {
- // The Continuation is the last parameter and the handlers array contains null at that index.
- argumentCount--;
- }
-
- List
argumentList = new ArrayList<>(argumentCount); - for (int p = 0; p < argumentCount; p++) {
- argumentList.add(args[p]);
- handlers[p].apply(requestBuilder, args[p]);
- }
-
- return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();
- }
-
- /**
- * Inspects the annotations on an interface method to construct a reusable service method. This
- * requires potentially-expensive reflection so it is best to build each service method only once
- * and reuse it. Builders cannot be reused.
- */
- static final class Builder {
- // Upper and lower characters, digits, underscores, and hyphens, starting with a character.
- private static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
- private static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
- private static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);
-
- final Retrofit retrofit;
- final Method method;
- final Annotation[] methodAnnotations;
- final Annotation[][] parameterAnnotationsArray;
- final Type[] parameterTypes;
-
- boolean gotField;
- boolean gotPart;
- boolean gotBody;
- boolean gotPath;
- boolean gotQuery;
- boolean gotQueryName;
- boolean gotQueryMap;
- boolean gotUrl;
- @Nullable String httpMethod;
- boolean hasBody;
- boolean isFormEncoded;
- boolean isMultipart;
- @Nullable String relativeUrl;
- @Nullable Headers headers;
- @Nullable MediaType contentType;
- @Nullable Set
relativeUrlParamNames; - @Nullable ParameterHandler>[] parameterHandlers;
- boolean isKotlinSuspendFunction;
-
- Builder(Retrofit retrofit, Method method) {
- this.retrofit = retrofit;
- this.method = method;
- this.methodAnnotations = method.getAnnotations();
- this.parameterTypes = method.getGenericParameterTypes();
- this.parameterAnnotationsArray = method.getParameterAnnotations();
- }
-
- RequestFactory build() {
- for (Annotation annotation : methodAnnotations) {
- parseMethodAnnotation(annotation);
- }
-
- if (httpMethod == null) {
- throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");
- }
-
- if (!hasBody) {
- if (isMultipart) {
- throw methodError(
- method,
- "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
- }
- if (isFormEncoded) {
- throw methodError(
- method,
- "FormUrlEncoded can only be specified on HTTP methods with "
- + "request body (e.g., @POST).");
- }
- }
-
- int 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);
- }
-
- if (relativeUrl == null && !gotUrl) {
- throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod);
- }
- if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
- throw methodError(method, "Non-body HTTP method cannot contain @Body.");
- }
- if (isFormEncoded && !gotField) {
- throw methodError(method, "Form-encoded method must contain at least one @Field.");
- }
- if (isMultipart && !gotPart) {
- throw methodError(method, "Multipart method must contain at least one @Part.");
- }
-
- return new RequestFactory(this);
- }
- }
参考: