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


73

我将改用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调用的执行时间将比第一个活动寿命长)。也许我必须使用静态的回调实例还是什么?请告诉我正确的方法...

Answers:


42

感谢另一种方式!Otto和RoboSpice之间的全局差异是什么?两者都使用侦听器,POJO,请求类...暂停活动时如何取消Web请求?
lordmegamax 2014年

我没有用过机器人糖膏。我用方形的东西。如果您的活动暂停,则可以使用onPause方法删除对Bus的订阅。所以什么都不会坏。
avgx

14
这绝不是OP的问题的答案,即“我如何获得对新活动的回应”。博客文章明确指出:“如果某个活动启动了API调用,然后被销毁或后台运行,我们仍然会发布结果响应事件,但是没有人可以听。……我们总是在任何时候重新查询数据我们的活动已恢复”
Gabor 2014年

@Gabor OP正在询问设备旋转情况。在这种情况下,活动将重新订阅并获得响应。如果为时已晚,另一端可以有一个生产者可以缓存响应。最后,应通过saveInstanceState处理重新查询数据问题。
黑色

您将需要setRetainInstance并且Observable.cache为此。这是我写的一个示例,几乎复制了一个RxAndroid示例:github.com/ber4444/u2020-v2/blob/master/Application/src/main/…–
Gabor,

34

对于可能长时间运行的服务器调用,我使用AsyncTaskLoader。对我来说,加载程序的主要优点是活动生命周期处理。仅当您的活动对用户可见时才调用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


5
我已经根据您的想法创建了一个完整的示例:github.com/rciovati/retrofit-loaders-example
rciovati 2014年

观看Google工程师在Google IO 2013 Volley上的演讲,加载程序对网络请求不利。
JBeckton

@JBeckton:在看Volley演示文稿的笔录时,我没有看到关于Loader框架的任何提及(仅是Volley的“图像加载器”)……
corsair992,2015年

@Benjamin为什么不使用ServiceIntentService代替AsyncTask
mahdi pishguy

22

我一直在Android应用程序上使用一种MVP(ModelViewPresenter)实现。对于Retrofit请求,我将Activity调用为相应的Presenter,这又使Retrofit Request成为参数,并且我发送了一个回调,并附加了一个自定义侦听器(由Presenter实现)。当回调达到onSuccessonFailure调用方法时,我调用侦听器的相应方法,该方法调用Presenter,然后调用Activity方法:P

现在,如果屏幕已打开,则当重新创建我的活动时,它将自身附加到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);
    }

}

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

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

希望对您有帮助。


5
对我而言,这类似于观察员。只需使用RxJava
Dimitri

2
回调很丑陋。您可以改用RxJava(RxAndroid)或Otto(EventBus)。
Chris.Zou 2015年

1
我一直想这样的事情
Jolson Da Costa

您好@LeoFarage您能否通过改造2解释这种结构
Pravesh

您好@pks。我已经用MVP实现了Retrofit2。您可以在这里查看并查看。github.com/receme/Retrofit2WithMVP 您好LeoFarage,谢谢您的回答。这对我帮助很大。
塔赫米德·莫扎法(Mah。Tahmid Mozaffar)

10

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

处理此类情况的总体思路是:

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

以上只是防止泄漏。它仍然不能帮助您将“改装”电话重新回到“活动”。

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

通常的想法是,片段会在配置更改时自动将其自身附加到活动。


1
我了解了上下文泄漏以及如何避免它们。我总是可以在Retrofit中使用同步调用而无需在AsyncTask中进行回调,设置/获取活动,检查它是否不为null并仅做一些工作,但是我该如何正确地使用Retrofit中的回调与异步方式来做到这一点呢?不能吗
lordmegamax 2014年

5

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

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

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

该视频没有专门讨论改造,但是您可以轻松地将改造应用于此范例。


5
这是5年前,我的猜测是此后技术已得到改进,使用服务可能不是当前的最佳方法。
JBeckton 2015年

2

使用Robospice

您的应用程序中需要数据的所有组件都需要在香料服务中注册。该服务负责将您的请求发送到服务器(如果需要,可以通过翻新)。当响应返回时,所有注册的组件都会得到通知。如果其中有一个不再可用(例如由于轮换而被取消的活动),则不会被通知。

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


Robospice现在已弃用
AbdelHady

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

3
您为什么认为我们应该在活动销毁时取消请求?
CoolMind

@CoolMind请求尚未完成,因此我们在onStart()/ onResume()中再次启动它。我没有添加那部分,对不起。我们在这里没有使用IntentServices,所以不要“一劳永逸”。另外,也不要使用无头片段或AsyncTaskLoaders。
约翰61590 '17

没错,活动的生命周期很难管理,因此存在一些架构上的技巧,例如MVP / MVVM可以保存/恢复数据,也许是装载程序。
CoolMind

除了取消呼叫之外,您还应该找到其他方法。由于设备轮换,您无法真正取消每个API调用。
最好的艺术家,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.