对重新创建的活动实施Retrofit回调的最佳实践?

时间:2014-02-16 13:47:04

标签: android web-services rest retrofit

我正在转向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调用后台将比第一个活动生命执行更长时间)。也许我必须使用静态回调实例或者什么?请以正确的方式告诉我......

9 个答案:

答案 0 :(得分:42)

答案 1 :(得分: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

答案 2 :(得分:21)

我一直在Android应用上使用一种MVP(ModelViewPresenter)实现。对于Retrofit请求,我使Activity调用它的各自的Presenter,它反过来生成Retrofit Request,并作为参数我发送一个附加了自定义Listener的Callback(由演示者实现)。当回调达到onSuccessonFailure方法时,我会调用监听器的相应方法,这些方法调用Presenter,然后调用Activity方法:P

现在,如果屏幕被转动,当重新创建我的Activity时,它会将自己附加到Presenter。这是通过使用Android应用程序的自定义实现来实现的,它可以让演示者保持这种状态。实例,并使用地图根据活动的类恢复正确的演示者。

我不知道如果这是最好的方式,也许@pareshgoel答案更好,但它一直在为我工作:D

示例:

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

}

在演示者的某处实现侦听器,并在overrode方法上调用演示者的方法来调用Activity。并在演示者的任何地方打电话来启动所有内容:P

Request rsqt = restAdapter.create(Request.class);
rsqt.get(new RequestCallback<YourExpectedObject>(listener));

希望它对你有所帮助。

答案 3 :(得分:10)

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

处理此类情况的一般想法是:

  1. 永远不要创建非静态内部类(或问题中提到的匿名类)。
  2. 而是创建一个包含WeakReference的静态类&lt;&gt;到活动/片段。
  3. 以上只是防止泄漏。它仍然无法帮助您将Retrofit回调给您的活动。

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

    一般的想法是Fragment会自动将自身附加到Activity上的配置更改。

答案 4 :(得分:5)

我强烈推荐你watch this video given at Google I/O

它讨论了如何通过将它们委托给服务(几乎从未被杀死)来创建REST请求。请求完成后,会立即存储到Android的内置数据库中,以便在您的活动准备就绪后立即获得数据。

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

该视频并未专门讨论改造,但您可以轻松地根据此范例调整改造。

答案 5 :(得分:3)

使用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();
            }
        }
    }
    }

答案 6 :(得分:2)

使用Robospice

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

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

答案 7 :(得分:2)

当我是一名改装新手时,我从这些网站学到了最好的方法

Consuming api with retrofit

retrofit tutorial

答案 8 :(得分:-37)

这是此类解决方案的典型限制,默认情况下Android会在用户旋转设备时创建新活动。

但是,这种情况有一个简单的解决方法,配置方向监听器。完成此操作后,Android将不会创建新活动。

    <activity
        ...
        android:configChanges="orientation" />

修复它的其他方法是使用其他框架(如Robospice)进行改造。