Khu*_*hah 5 architecture performance android design-patterns retrofit
我想以这样一种方式设计 API 调用,以便从一个地方轻松处理成功和失败响应(而不是为所有 API 编写相同的调用函数代码)
以下是我想考虑的场景。
我目前的实现不满足上述要求。有没有办法使用 Retrofit 实现满足上述要求的 API 调用?请为我推荐一个好的设计。
这是我目前的实现:
接口接口.java
public interface ApiInterface {
// Get Devices
@GET("https://example-base-url.com" + "/devices")
Call<ResponseBody> getDevices(@Header("Authorization) String token);
// Other APIs......
}
Run Code Online (Sandbox Code Playgroud)
客户端程序
public class ApiClient {
private static Retrofit retrofitClient = null;
static Retrofit getClient(Context context) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), systemDefaultTrustManager())
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
retrofitClient = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
}
Run Code Online (Sandbox Code Playgroud)
接口管理器
public class ApiManager {
private static ApiManager apiManager;
public static ApiManager getInstance(Context context) {
if (apiManager == null) {
apiManager = new ApiManager(context);
}
return apiManager;
}
private ApiManager(Context context) {
this.context = context;
apiInterface = ApiClient.getClient(context).create(ApiInterface.class);
}
public void getDevices(ResponseListener listener) {
// API call and response handling
}
// Other API implementation
}
Run Code Online (Sandbox Code Playgroud)
更新 :
对于第一点,拦截器将有助于根据this全局处理 4xx、5xx 响应。但是拦截器将在 ApiClient 文件中并通知 UI 或 API 调用者组件,需要在回调中传递成功或失败结果我的意思是响应侦听器。我怎样才能做到这一点 ?任何的想法 ?
对于第三点,我对 Retrofit 知之甚少Authenticator。我认为这一点是合适的,但它需要同步调用才能使用刷新令牌获取新令牌。如何对 synchronous 进行异步调用?(注意:此调用不是改造调用)
通过在中心位置处理成功/失败响应,我假设您希望根据错误解析逻辑以及它如何为您的应用程序创建 UI 副作用来摆脱重复的样板。
我可能建议通过创建一个自定义抽象来让事情变得非常简单,Callback该抽象根据您的域逻辑调用您的 API 来判断成功/失败。
这是用例 (1) 的相当简单的实现:
abstract class CustomCallback<T> implements Callback<T> {
abstract void onSuccess(T response);
abstract void onFailure(Throwable throwable);
@Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.isSuccessful()) {
onSuccess(response.body());
} else {
onFailure(new HttpException(response));
}
}
@Override
public void onFailure(Call<T> call, Throwable t) {
onFailure(t);
}
}
Run Code Online (Sandbox Code Playgroud)
For use-case (2), to be able to cancel all enqueued calls upon a global event like logout you'd have to keep a reference to all such objects. Fortunately, Retrofit supports plugging in a custom call factory okhttp3.Call.Factory
You could use your implementation as a singleton to hold a collection of calls and in the event of a logout notify it to cancel all requests in-flight. Word of caution, do use weak references of such calls in the collection to avoid leaks/references to dead calls. (also you might want to brainstorm on the right collection to use or a periodic cleanup of weak references based on the transactions)
For use-case (3), Authenticator should work out fine since you've already figured out the usage there are 2 options -
Here's a sample implementation:
abstract class NetworkAuthenticator implements Authenticator {
private final SessionRepository sessionRepository;
public NetworkAuthenticator(SessionRepository repository) {
this.sessionRepository = repository;
}
public Request authenticate(@Nullable Route route, @NonNull Response response) {
String latestToken = getLatestToken(response);
// Refresh token failed, trigger a logout for the user
if (latestToken == null) {
logout();
return null;
}
return response
.request()
.newBuilder()
.header("AUTHORIZATION", latestToken)
.build();
}
private synchronized String getLatestToken(Response response) {
String currentToken = sessionRepository.getAccessToken();
// For a signed out user latest token would be empty
if (currentToken.isEmpty()) return null;
// If other calls received a 401 and landed here, pass them through with updated token
if (!getAuthToken(response.request()).equals(currentToken)) {
return currentToken;
} else {
return refreshToken();
}
}
private String getAuthToken(Request request) {
return request.header("AUTHORIZATION");
}
@Nullable
private String refreshToken() {
String result = null;
CountDownLatch countDownLatch = new CountDownLatch(1);
// Make async call to fetch token and update result in the callback
// Wait up to 10 seconds for the refresh token to succeed
try {
countDownLatch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
abstract void logout();
}
Run Code Online (Sandbox Code Playgroud)
I hope this helps with your network layer implementation