Android 对重新创建的活动实施改造回调的最佳实践?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21811999/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best practice to implement Retrofit callback to recreated activity?
提问by lordmegamax
I'm switching to Retrofit and trying to understand proper architecture for using it with async callbacks.
我正在切换到 Retrofit 并试图了解将其与异步回调一起使用的正确架构。
For example I have an interface:
例如我有一个界面:
interface RESTService{
@GET("/api/getusername")
void getUserName(@Query("user_id") String userId,
Callback<Response> callback);
}
And I run this from main activity:
我从主要活动中运行它:
RestAdapter restAdapter = new RestAdapter.Builder()
.setServer("WEBSITE_URL")
.build();
RESTService api = restAdapter.create(RESTService.class);
api.getUserName(userId, new Callback<Response> {...});
Then user rotates the device and I have newly created activity... What was happen here? How can I get response to the new activity (I assume that api call in background will execute longer than first activity life). Maybe I must use static instance of callback or what? Please show me the right way...
然后用户旋转设备,我有新创建的活动......这里发生了什么?我怎样才能得到对新活动的响应(我假设后台的 api 调用将执行比第一个活动寿命更长的时间)。也许我必须使用回调的静态实例或什么?请告诉我正确的方法...
回答by avgx
Use otto. There are a lot of samples to mix otto and retrofit, for example https://github.com/pat-dalberg/ImageNom/blob/master/src/com/dalberg/android/imagenom/async/FlickrClient.java
使用奥托。有很多样本可以混合otto和retrofit,例如https://github.com/pat-dalberg/ImageNom/blob/master/src/com/dalberg/android/imagenom/async/FlickrClient.java
Or read this post http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.htmlIt answers on almost all questions
或者阅读这篇文章http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html它几乎回答了所有问题
回答by Benjamin
For potential long running server calls i use an AsyncTaskLoader. For me, the main advantage of Loaders are the activity-lifecycle handling. onLoadFinishedis only called if your activity is visible to the user. Loaders are also shared between activity/fragment and orientation changes.
对于潜在的长时间运行的服务器调用,我使用AsyncTaskLoader。对我来说,Loaders 的主要优点是活动生命周期处理。只有当您的活动对用户可见时,才会调用onLoadFinished。加载器也在活动/片段和方向更改之间共享。
So i created an ApiLoader which uses retrofits synchronous calls in loadInBackground.
所以,我创建了使用改造中同步调用的ApiLoader loadInBackground。
abstract public class ApiLoader<Type> extends AsyncTaskLoader<ApiResponse<Type>> {
protected ApiService service;
protected ApiResponse<Type> response;
public ApiLoader(Context context) {
super(context);
Vibes app = (Vibes) context.getApplicationContext();
service = app.getApiService();
}
@Override
public ApiResponse<Type> loadInBackground() {
ApiResponse<Type> localResponse = new ApiResponse<Type>();
try {
localResponse.setResult(callServerInBackground(service));
} catch(Exception e) {
localResponse.setError(e);
}
response = localResponse;
return response;
}
@Override
protected void onStartLoading() {
super.onStartLoading();
if(response != null) {
deliverResult(response);
}
if(takeContentChanged() || response == null) {
forceLoad();
}
}
@Override
protected void onReset() {
super.onReset();
response = null;
}
abstract protected Type callServerInBackground(SecondLevelApiService api) throws Exception;
}
In your activity you init this loader like this:
在您的活动中,您可以像这样初始化这个加载器:
getSupportLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks<ApiResponse<DAO>>() {
@Override
public Loader<ApiResponse<DAO>> onCreateLoader(int id, Bundle args) {
spbProgress.setVisibility(View.VISIBLE);
return new ApiLoader<DAO>(getApplicationContext()) {
@Override
protected DAO callServerInBackground(ApiService api) throws Exception {
return api.requestDAO();
}
};
}
@Override
public void onLoadFinished(Loader<ApiResponse<DAO>> loader, ApiResponse<DAO> data) {
if (!data.hasError()) {
DAO dao = data.getResult();
//handle data
} else {
Exception error = data.getError();
//handle error
}
}
@Override
public void onLoaderReset(Loader<ApiResponse<DAO>> loader) {}
});
If you want to request data multiple times use restartLoaderinstead of initLoader.
如果您想多次请求数据,请使用restartLoader而不是initLoader。
回答by LeoFarage
I've been using a kind of MVP (ModelViewPresenter) implementation on my Android apps. For the Retrofit request I made the Activity calls it's respective Presenter, which in turn makes the Retrofit Request and as a parameter I send a Callback with a custom Listener attached to it (implemented by the presenter). When the Callback reach onSuccess
or onFailure
methods I call the Listener's respective methods, which calls the Presenter and then the Activity methods :P
我一直在我的 Android 应用程序上使用一种 MVP (ModelViewPresenter) 实现。对于 Retrofit 请求,我进行了 Activity 调用,它是相应的 Presenter,它依次进行 Retrofit 请求,并且作为参数,我发送了一个带有附加到它的自定义侦听器的回调(由演示者实现)。当回调到达onSuccess
或onFailure
方法时,我调用侦听器的相应方法,该方法调用演示者,然后调用活动方法:P
Now in case the screen is turned, when my Activity is re-created it attaches itself to the Presenter. This is made using a custom implementation of Android's Application, where it keeps the presenters' instance, and using a map for recovering the correct presenter according to the Activity's class.
现在,如果屏幕被转动,当我的 Activity 重新创建时,它会将自身附加到演示者。这是使用 Android 应用程序的自定义实现实现的,它保留演示者的实例,并使用映射根据 Activity 的类恢复正确的演示者。
I dunno if it's the best way, perhaps @pareshgoel answer is better, but it has been working for me :D
我不知道这是否是最好的方法,也许@pareshgoel 的答案更好,但它一直对我有用:D
Examples:
例子:
public abstract interface RequestListener<T> {
void onSuccess(T response);
void onFailure(RetrofitError error);
}
...
...
public class RequestCallback<T> implements Callback<T> {
protected RequestListener<T> listener;
public RequestCallback(RequestListener<T> listener){
this.listener = listener;
}
@Override
public void failure(RetrofitError arg0){
this.listener.onFailure(arg0);
}
@Override
public void success(T arg0, Response arg1){
this.listener.onSuccess(arg0);
}
}
Implement the listener somewhere on the presenter, and on the overrode methods call a presenter's method that will make the call to the Activity. And call wherever you want on the presenter to init everything :P
在演示者的某处实现侦听器,并在覆盖方法上调用演示者的方法,该方法将对活动进行调用。并在演示者的任何地方调用以初始化所有内容:P
Request rsqt = restAdapter.create(Request.class);
rsqt.get(new RequestCallback<YourExpectedObject>(listener));
Hope it helps you.
希望对你有帮助。
回答by pareshgoel
Firstly, your activity leaks here because this line: api.getUserName(userId, new Callback {...}) creates an anonymous Callback class that holds a strong reference to you MainActivity. When the device is rotated before the Callback is called, then the MainActivity will not be garbage collected. Depending on what you do in the Callback.call(), your app may yield undefined behaviour.
首先,您的活动在这里泄漏,因为这一行: api.getUserName(userId, new Callback {...}) 创建了一个匿名回调类,该类拥有对您的 MainActivity 的强引用。如果在调用 Callback 之前旋转设备,则 MainActivity 将不会被垃圾收集。根据您在 Callback.call() 中执行的操作,您的应用程序可能会产生未定义的行为。
The general idea to handle such scenarios is:
处理这种情况的一般想法是:
- Never create a non-static inner class (or an anonymous class as mentioned in the problem).
- Instead create a static class that holds a WeakReference<> to the Activity/Fragment.
- 永远不要创建非静态内部类(或问题中提到的匿名类)。
- 而是创建一个静态类,其中包含指向 Activity/Fragment 的 WeakReference<>。
The above just prevents Leaks. It still does not help you get the Retrofit call back to your Activity.
以上只是防止泄漏。它仍然无法帮助您将 Retrofit 调用返回到您的 Activity。
Now, to get the results back to your component (Activity in your case) even after configuration change, you may want to use a headless retained fragment attached to your Activity, which makes the call to Retrofit. Read more here about Retained fragment - http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)
现在,即使在配置更改后,为了将结果返回到您的组件(在您的情况下为 Activity),您可能需要使用附加到您的 Activity 的无头保留片段,这会调用 Retrofit。在此处阅读有关保留片段的更多信息 - http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)
The general idea is that the Fragment automatically attaches itself to the Activity on configuration change.
总体思路是,在配置更改时,Fragment 会自动将自身附加到 Activity。
回答by Martin Konecny
I highly recommend you watch this video given at Google I/O.
我强烈建议您观看 Google I/O 上提供的此视频。
It talks about how to create REST requests by delegating them to a service (which is almost never killed). When the request is completed it is immediately stored into Android's built-in database so the data is immediately available when your Activity is ready.
它讨论了如何通过将 REST 请求委托给服务(几乎从未被终止)来创建它们。请求完成后,它会立即存储到 Android 的内置数据库中,因此当您的 Activity 准备就绪时,数据立即可用。
With this approach, you never have to worry about the lifecycle of the activity and your requests are handled in a much more decoupled way.
使用这种方法,您永远不必担心 Activity 的生命周期,并且您的请求将以更加解耦的方式处理。
The video doesn't specifically talk about retrofit, but you can easily adapt retrofit for this paradigm.
该视频并未专门讨论改造,但您可以轻松地针对此范例进行改造。
回答by John61590
Using Retrofit2 to handle orientation change. I was asked this in a job interview and was rejected for not knowing it at the time but here it is now.
使用 Retrofit2 处理方向变化。我在求职面试中被问到这个问题,但因为当时不知道而被拒绝,但现在就是这样。
public class TestActivity extends AppCompatActivity {
Call<Object> mCall;
@Override
public void onDestroy() {
super.onDestroy();
if (mCall != null) {
if (mCall.isExecuted()) {
//An attempt will be made to cancel in-flight calls, and
// if the call has not yet been executed it never will be.
mCall.cancel();
}
}
}
}
回答by stoefln
Use Robospice
All components in your app which require data, register with the spice service. The service takes care of sending your request to the server (via retrofit if you want). When the response comes back, all components which registered get notified. If there is one of them not available any more (like an activity which got kicked because of rotation), it's just not notified.
您的应用程序中需要数据的所有组件都向 spice 服务注册。该服务负责将您的请求发送到服务器(如果需要,可以通过改造)。当响应返回时,所有注册的组件都会收到通知。如果其中一个不再可用(例如由于轮换而被踢的活动),则不会通知它。
Benefit: One single request which does not get lost, no matter whether you rotate your device, open new dialogs/fragments etc...
好处:一个不会丢失的请求,无论您旋转设备、打开新对话框/片段等...