Retrofit回调在主线程上

16

使用以下调用:

@GET("/user/{id}/data")
void getUserData(@Path("id") int id, Callback<Data> cb);

回调函数应该在主线程上执行(如果不使用RxJava)。我的问题是:

  1. 解析发生在哪里(假设我正在使用XML转换器来处理响应)。这是在主线程还是其他线程上执行?这取决于转换器的实现吗?
  2. 如果我需要包含一些(繁重的)验证规则/业务规则,我是否需要在callable内部启动一个新线程?或者在回调方法中完成是否可以?

我正在寻找一种从Web服务获取数据并避免自己进行线程管理(或使用其他方法如IntentService等)的方法,但担心使用RxJava(因为它具有实验性支持)。是否有另一种建议的方法来解决这个问题?


2
如果我必须包含一些(沉重的)验证规则/业务规则,我需要在callable内部生成一个新线程吗?在这种情况下,你不应该使用Callback。删除它,让getUserData()返回 Data,并在后台线程上调用getUserData(),例如一个AsyncTaskdoInBackground()。在那里,您可以在更新主应用程序线程上的UI之前完成其余工作。 - CommonsWare
@CommonsWare;如果我移除Callback,Retrofit会发起同步的Web服务调用,这将导致Activity抛出NetworkOnMainthreadException异常。我还有另一个选择,就是在Activity中开启一个新线程来进行Retrofit Web服务调用,但正如我所提到的,我想避免在代码中使用线程管理。我只是想知道是否有办法让Retrofit帮助我处理请求和响应,而无需使用线程/服务/处理程序/异步任务。谢谢! - dev
2
如果我移除回调函数,Retrofit 将会发起同步的 Web 服务调用,并且我将从活动中得到 NetworkOnMainthreadException 异常。如果您读了我的评论,我写道“在后台线程上调用 getUserData()”。我正在避免在我的代码中进行线程管理。在我看来,这不是一种有效的开发方法。使用第三方库来处理涉及线程的特定问题是可以的;说自己拒绝处理线程是不切实际的。 - CommonsWare
@CommonsWare 哦,我明白了。我实际上正在查看更复杂的库,比如Robospice(我知道它不能与Retrofit相比),但是Robospice让我变得更加苛刻(因为它似乎不依赖于我编写线程逻辑)。如果我从回调方法中生成线程来处理响应(而不是根本不使用回调),你认为有什么问题吗?最坏的情况下,我想避免在活动中使用线程,但可以(并且确实)在其他层中使用它,例如用于业务验证的“模型”中。 - dev
你觉得如果我从回调方法中生成线程来处理响应(而不是根本不使用回调),有什么问题吗?如果那不是你需要进行工作的线程,那么告诉Retrofit在主应用程序线程上回调你是毫无意义的。 - CommonsWare
1个回答

22

解析过程发生在哪里(假设我正在使用XML转换器处理响应)。这是主线程还是另一个线程?它取决于转换器的实现吗?

无论您使用哪种转换器,始终在后台线程中进行解析。

如果我必须包含一些(繁重的)验证规则/业务规则,我需要在callable内部生成一个新线程吗?或者将其放在回调方法中是否可以?

这相当主观,并且有许多解决方法。默认情况下,Callback 将在主线程上执行。

您可以通过为RestAdapter.Builder提供自定义Executor来更改调用回调的线程。但是,这将影响由该RestAdapter构建的所有服务,这可能不是您想要的。

如果您要完成的工作可以与更新UI并行进行(例如,轻量级缓存),则从Callback中生成另一个线程(或排队到执行程序)是没有问题的。

如果昂贵的操作必须在通知UI之前完成,则最好将方法切换为同步方法(无需回调),并自己进行线程处理。这样,您可以在HTTP调用之前和之后进行昂贵的操作(文件I/O、缓存、转换、验证等)。

我们目前使用RxJava(Retrofit具有实验支持)来完成您要求的操作:

interface Foo {
  @GET("/")
  Observable<Foo> getFoo(String bar);
}

foo.getFoo()
  .mapMany(new ExpensiveOperationFunction())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(new Observer<TransformedFoo>() { .. });

非常感谢!我正在提供一个执行器来在不同的池中运行所有回调。我使用EventBus在完成后将其发布回主线程。现在一切看起来都很好,但我很好奇为什么需要在setExecutor()方法(RestAdapter.Builder的方法)中传递2个执行器 - 如果我对默认值满意,我可以为httpExecutor传递null吗?如果我将相同的执行器传递给httpExecutor和callbackExecutor会怎样? - dev
2
根据文档,将第二个(回调)执行器设置为null将在运行HTTP请求的同一线程上调用回调。您不能将HTTP执行器设置为null。 - Jake Wharton
无论您使用哪种转换器,始终存在后台线程。这对于retrofit2也是正确的吗? - gaara87
@gaara87 是的,转换器是在OkHttp的工作线程上调用的:https://github.com/square/retrofit/blob/47cc621c7736cace218be341f9b480add64c9083/retrofit/src/main/java/retrofit2/OkHttpCall.java#L106 - Eric Cochran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接