【问题标题】:Best practice to implement Retrofit callback to recreated activity?对重新创建的活动实施改造回调的最佳实践?
【发布时间】:2014-03-15 17:46:43
【问题描述】:

我正在切换到 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 调用执行的时间比第一个活动的生命周期长)。也许我必须使用回调的静态实例或什么?请告诉我正确的方法...

【问题讨论】:

    标签: android web-services rest retrofit


    【解决方案1】:

    【讨论】:

    • 感谢您提供另一种方式! Otto 和 RoboSpice 之间的全球差异是什么?两者都使用侦听器、POJO、请求类...当活动暂停时如何取消 Web 请求?
    • 我没用过机器人香料。我用方形的东西。如果您的活动暂停,您将在 onPause 方法中删除对 Bus 的订阅。所以什么都不会被破坏。
    • 这绝不是对 OP 问题“我如何才能获得对新活动的响应”的答案。博客文章明确指出“如果一个活动发起了一个 API 调用,然后被销毁或后台处理,我们仍然会发布结果响应事件,但没有人会在那里收听......我们总是在任何时候重新查询数据我们的活动已恢复”
    • @Gabor OP 正在询问设备旋转。在这种情况下,活动重新订阅并获得响应。如果为时已晚,另一端可能有一个生产者可以缓存响应。最后,重新查询数据问题应该通过saveInstanceState处理。
    • 您需要 setRetainInstanceObservable.cache 来实现这一点。这是我写的一个例子,几乎复制了一个 RxAndroid 示例:github.com/ber4444/u2020-v2/blob/master/Application/src/main/…
    【解决方案2】:

    对于可能长时间运行的服务器调用,我使用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

    【讨论】:

    • 我根据您的想法创建了一个完整的示例:github.com/rciovati/retrofit-loaders-example
    • 观看 Google 工程师的 Google IO 2013 Volley 演示,加载程序对网络请求不利。
    • @JBeckton:查看 Volley 演示文稿的 transcript,我没有看到任何提到 Loader 框架(只有 Volley 的“图像加载器”)...
    • @Benjamin 为什么不使用ServiceIntentService 而不是AsyncTask
    【解决方案3】:

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

    【讨论】:

    • 对我来说类似于观察者。只需使用 RxJava
    • 回调很难看。您可以改用 RxJava(RxAndroid) 或 Otto(EventBus)。
    • 我一直想要这样的东西
    • 你好@LeoFarage你能用改造2解释一下这个结构吗
    • 你好@pks。我已经使用 MVP 实施了改造 2。你可以在这里查看并查看。 github.com/receme/Retrofit2WithMVP你好LeoFarage,谢谢你的回答。这对我帮助很大。
    【解决方案4】:

    首先,您的活动在这里泄漏,因为这一行: 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。

    【讨论】:

    • 我读到了上下文泄漏和how to avoid them。我总是可以在 Retrofit 中使用同步调用,而无需在 AsyncTask 中进行回调,设置/获取活动,检查它是否不为空,然后才做一些工作,但是我如何才能通过 Retrofit 的回调正确地使用use async 方式来做到这一点或我不能?
    【解决方案5】:

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

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

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

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

    【讨论】:

    • 这是 5 年前的事了,我的猜测是从那以后技术有所改进,使用服务可能不是目前最好的方式。
    【解决方案6】:

    使用Robospice

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

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

    【讨论】:

    • Robospice 现已弃用
    【解决方案7】:

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

    【讨论】:

    • 为什么我们应该在活动销毁时取消请求?
    • @CoolMind 请求没有完成,所以我们在 onStart() / onResume() 中再次启动它。我没有添加那部分,对不起。我们在这里没有使用 IntentServices,所以不要“一劳永逸”。此外,也不要使用无头片段或 AsyncTaskLoaders。
    • 你说得对,Activity 的生命周期很难管理,所以有一些架构技巧,比如保存/恢复数据的 MVP/MVVM,也许是加载器。
    • 您应该找到除取消通话之外的其他方法。由于设备轮换,您无法真正取消每个 API 调用。
    猜你喜欢
    • 2015-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-23
    • 1970-01-01
    • 1970-01-01
    • 2010-09-20
    • 2011-08-17
    相关资源
    最近更新 更多