75

我正在切换到 Retrofit 并尝试了解将其与异步回调一起使用的适当架构。

例如我有一个界面:

interface RESTService{
    @GET("/api/getusername")
    void getUserName(@Query("user_id") String userId, 
                     Callback<Response> callback);
}

我从主要活动中运行它:

RestAdapter restAdapter = new RestAdapter.Builder()
        .setServer("WEBSITE_URL")     
        .build();
RESTService api = restAdapter.create(RESTService.class);
api.getUserName(userId, new Callback<Response> {...});

然后用户旋转设备,我有新创建的活动......这里发生了什么?我怎样才能得到对新活动的响应(我假设后台的 api 调用将比第一个活动的生命周期长)。也许我必须使用回调的静态实例或什么?请告诉我正确的方法...

4

7 回答 7

42

使用奥托。有很多样本可以混合 otto 和改造,例如https://github.com/pat-dalberg/ImageNom/blob/master/src/com/dalberg/android/imagenom/async/FlickrClient.java

或阅读这篇文章http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html 它回答了几乎所有问题

于 2014-04-15T20:10:31.060 回答
34

对于潜在的长时间运行的服务器调用,我使用AsyncTaskLoader。对我来说,Loaders 的主要优势是活动生命周期处理。仅当您的活动对用户可见时才会调用onLoadFinished 。加载器也在活动/片段和方向更改之间共享。

所以我创建了一个 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;

}

在你的活动中,你像这样初始化这个加载器:

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) {}
    });

如果您想多次请求数据,请使用restartLoader而不是initLoader

于 2014-03-14T09:21:15.233 回答
22

我一直在我的 Android 应用程序上使用一种 MVP (ModelViewPresenter) 实现。对于 Retrofit 请求,我将 Activity 调用为各自的 Presenter,而 Presenter 又会发出 Retrofit Request 并作为参数,我发送一个带有自定义侦听器的回调(由演示者实现)。当回调到达onSuccessonFailure方法时,我调用侦听器各自的方法,它调用演示者,然后调用活动方法:P

现在,如果屏幕被转动,当我的 Activity 重新创建时,它会将自己附加到 Presenter。这是使用 Android 应用程序的自定义实现实现的,它保留演示者的实例,并使用映射来根据 Activity 的类恢复正确的演示者。

我不知道这是否是最好的方法,也许@pareshgoel 的答案更好,但它一直对我有用。

例子:

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);
    }

}

在演示者的某处实现侦听器,并在被覆盖的方法上调用演示者的方法,该方法将调用 Activity。并在演示者的任何地方调用以初始化所有内容:P

Request rsqt = restAdapter.create(Request.class);
rsqt.get(new RequestCallback<YourExpectedObject>(listener));
于 2014-03-14T06:45:25.610 回答
10

首先,您的活动在此处泄漏,因为以下行: api.getUserName(userId, new Callback {...}) 创建了一个匿名回调类,该类对您的 MainActivity 具有强引用。在调用 Callback 之前旋转设备时,不会对 MainActivity 进行垃圾回收。根据您在 Callback.call() 中执行的操作,您的应用程序可能会产生未定义的行为。

处理这种情况的一般思路是:

  1. 永远不要创建非静态内部类(或问题中提到的匿名类)。
  2. 而是创建一个静态类,将 WeakReference<> 保存到 Activity/Fragment。

以上只是防止泄漏。它仍然不能帮助您将 Retrofit 调用回您的 Activity。

现在,即使在配置更改后,要将结果返回到您的组件(在您的情况下为 Activity),您可能希望使用附加到您的 Activity 的无头保留片段,这会调用 Retrofit。在此处阅读有关保留片段的更多信息 - http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)

总体思路是,Fragment 在配置更改时自动将自身附加到 Activity。

于 2014-03-08T09:44:36.103 回答
5

我强烈建议您观看 Google I/O 上提供的此视频

它讨论了如何通过将 REST 请求委托给服务(几乎从不被杀死)来创建 REST 请求。请求完成后,它会立即存储到 Android 的内置数据库中,因此当您的 Activity 准备好时,数据立即可用。

使用这种方法,您永远不必担心活动的生命周期,并且您的请求以更加解耦的方式处理。

该视频没有专门讨论改造,但您可以轻松地针对这种范式进行改造。

于 2015-08-04T15:47:55.673 回答
2

使用Robospice

应用程序中需要数据的所有组件都向 spice 服务注册。该服务负责将您的请求发送到服务器(如果您愿意,可以通过改造)。当响应返回时,所有注册的组件都会收到通知。如果其中一个不再可用(例如由于轮换而被踢的活动),则不会通知。

好处:一个不会丢失的请求,无论您是否旋转设备,打开新的对话框/片段等......

于 2015-07-16T20:42:40.237 回答
2

使用 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();
            }
        }
    }
    }
于 2017-01-21T00:18:06.020 回答