Android rxJava 错误处理与改造

Posted

技术标签:

【中文标题】Android rxJava 错误处理与改造【英文标题】:Android rxJava Error handling with retrofit 【发布时间】:2015-03-06 07:51:39 【问题描述】:

我使用的是较新的 RX java where 而不是

Observable.create(new Observable.OnSubscribeFunc<T>() ...);

这是使用的:(由于弃用)

Observable.create(new Observable.OnSubscribe<T>() ...);

(这可能很重要,因为大多数示例、教程、explonation 都使用旧的......)

好吧,让我们看看我的问题。我有一个 Java 类,来自它的相关部分:

private interface ApiManagerService 

    @FormUrlEncoded
    @POST("/login")
    User getUser(@Field("username") String userName, @Field("password") String password);




private static RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint(HOST)
    .setLogLevel(RestAdapter.LogLevel.FULL)
    .build();

private static ApiManagerService apiManager = restAdapter.create(ApiManagerService.class);

public static Subscription login(final String userName, final String password, Observer<User> observer) 
    return Observable.create(new Observable.OnSubscribe<User>() 
        @Override
        public void call(Subscriber<? super User> subscriber) 
            try 

                User user = apiManager.getUser(userName, password);

                subscriber.onNext(user);
                subscriber.onCompleted();

             catch (RetrofitError e) 
                subscriber.onError(e);
             catch (Throwable e) 
                subscriber.onError(e);
            
        
    

    ).subscribeOn(Schedulers.io())
     .retry(3)
     .observeOn(androidSchedulers.mainThread())
     .subscribe(observer);

如果一切正常,这段代码几乎可以完美运行。但是如果我犯了一个故意的错误,比如我关闭了 WiFi.. 而不是改造得到 "UnKnownHostException"... 因为它应该发生在 try catch 块中的改造调用 (getUser) 中。但不是将错误处理到 onError(Throwable t) --> 我可以处理的地方,它只是让应用程序崩溃。所以就像错误永远不会到达 catch 块一样。 奇怪的是 HTTP 错误(如 404、401 等)被捕获,由 onError(...) 获取,一切都很好。

从 .retry(3) 开始,在崩溃之前一切都进行了 3 次,但没有一个进入 catch 子句。

编辑 1

LogCat 输出:

    01-08 16:19:31.576  15285-16162/asd.bdef.gh D/Retrofit﹕ ---- ERROR https://testapi.com/api/login
    01-08 16:19:31.606  15285-16162/asd.bdef.gh D/Retrofit﹕ java.net.UnknownHostException: Unable to resolve host "testapi.com": No address associated with hostname
                at java.net.InetAddress.lookupHostByName(InetAddress.java:394)
                at java.net.InetAddress.getAllByNameImpl(InetAddress.java:236)
                at java.net.InetAddress.getAllByName(InetAddress.java:214)
                at com.squareup.okhttp.internal.Network$1.resolveInetAddresses(Network.java:29)
                at com.squareup.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:259)
                at com.squareup.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:233)
                at com.squareup.okhttp.internal.http.RouteSelector.nextUnconnected(RouteSelector.java:159)
                at com.squareup.okhttp.internal.http.RouteSelector.next(RouteSelector.java:133)
                at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:314)
                at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:237)
                at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:423)
                at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:105)
                at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:239)
                at com.squareup.okhttp.internal.huc.DelegatingHttpsURLConnection.getOutputStream(DelegatingHttpsURLConnection.java:218)
                at com.squareup.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:25)
                at retrofit.client.UrlConnectionClient.prepareRequest(UrlConnectionClient.java:68)
                at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:37)
                at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:321)
                at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
                at retrofit.RestAdapter$RestHandler$1.invoke(RestAdapter.java:265)
                at retrofit.RxSupport$2.run(RxSupport.java:55)
                at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:390)
                at java.util.concurrent.FutureTask.run(FutureTask.java:234)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
                at retrofit.Platform$Android$2$1.run(Platform.java:142)
                at java.lang.Thread.run(Thread.java:841)
    01-08 16:19:31.606  15285-16162/asd.bdef.gh D/Retrofit﹕ ---- END ERROR
    01-08 16:19:31.977  15285-15285/asd.bdef.gh D/AndroidRuntime﹕ Shutting down VM
    01-08 16:19:31.977  15285-15285/asd.bdef.gh W/dalvikvm﹕ threadid=1: thread exiting with uncaught exception (group=0x41c9d8b0)
    01-08 16:19:31.977  15285-15285/asd.bdef.gh E/AndroidRuntime﹕ FATAL EXCEPTION: main

给定的api地址不是真实的,但真实的是可达的。我刚刚关闭了 WiFi 来测试错误处理。

还有一个用例:如果我添加到 observable .onExceptionResumeNext([2nd observable]) 中,它会进入第二个 observable,并且它不会崩溃。但这不是问题的解决方案。

编辑 2

ApiManager.login(userName, pass, new Observer<User>() 
    @Override
    public void onCompleted()  

    @Override
    public void onError(Throwable e) 
        DialogManager.showBasicErrorDialog(getApplicationContext(), e.getLocalizedMessage());
        logger.showLog("Login Not ok");
        e.printStackTrace();
    

    @Override
    public void onNext(User user) 
        logger.showLog("login ok, user: " + user.getName().toString());
        ...
    

); 

编辑 3

FATAL EXCEPTION: main
rx.exceptions.OnErrorFailedException: Error occurred when trying to propagate error to Observer.onError
        at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:175)
        at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:97)
        at rx.internal.operators.NotificationLite.accept(NotificationLite.java:144)

...

Caused by: java.lang.NullPointerException: Attempt to read from field 'rx.functions.Action0 rx.schedulers.TrampolineScheduler$TimedAction.action' on a null object reference
        at rx.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.enqueue(TrampolineScheduler.java:85)

提前感谢您的帮助。

【问题讨论】:

您能发布您的堆栈跟踪/logcat 输出吗?调用代码是什么样的(观察者定义)? 它已经被编辑了..但实际上它并不太相关,因为错误是故意的。 ... 看起来retry(long count) 不会传播错误 - 所以看起来所有这些代码都按预期/如记录的那样工作,并且在此之后发生崩溃。 如果我注释掉那些我在 onError() 中使用 Throwable e 做任何事情的行,那么一切正常。它必须是 DialogManager 类,所以可能是另一个错误.. logcat 没有在正确的位置显示.. 同意 - 您粘贴的 logcat 输出肯定是 Retrofit 日志,那里有开始/结束行。 【参考方案1】:

您不必使用 Retrofit 构建自己的 Observable,因为 Retrofit 可以直接返回 Observable:

http://square.github.io/retrofit/

Retrofit 还集成了 RxJava 以支持具有返回类型的方法 rx.Observable

@GET("/user/id/photo") 可观察的 getUserPhoto(@Path("id") int id);

(您不必自己处理错误)

您可以发布崩溃的堆栈跟踪吗?我和你一样认为,你的应用程序不应该崩溃。

【讨论】:

谢谢,我刚刚学到了一些很好的东西,我修改了代码,就像你建议的那样,删除了我所有的 Observable,并修改了: Observable getUser(@Field("username") String userName, @Field("password") 字符串密码);但问题仍然存在。错误未处理,应用程序崩溃。我认为这不是改造的问题,而是 RX。并添加了堆栈跟踪。 顺便说一句,如果我想在将对象处理给 Observable 之前对它做一些事情怎么办? --> 像这里一样,在 login(username, pass) --> 我想将“凭据”保存到用户对象中,最好只写一次,而不是应用程序中的所有登录位置。所以在这里这个 Observable 可以像超级函数一样运行,然后告诉 LoginScreen 发生了什么事。【参考方案2】:

从 RxJava 1.0 开始,您可能会遇到问题:

TrampolineScheduler NullPointerException

【讨论】:

我用的是rxjava的android模块,现在jCenter上最新的是0.20.7依赖:compile 'com.netflix.rxjava:rxjava-android:0.20.7' 新版本的RxAndroid有一个不同的标识符——编译'io.reactivex:rxandroid:0.24.0'。您还需要将 RxJava 核心模块升级到 1.0。 这将是我认为的问题。 @AdamS 我不确定您的意思是“您还需要将 RxJava 核心模块升级到 1.0。”顺便感谢您的回答。 我正在使用 RxJava 1.1.0 和 RxAndroid 1.1.0 并面临同样的问题!

以上是关于Android rxJava 错误处理与改造的主要内容,如果未能解决你的问题,请参考以下文章

使用改造对 mvvm 中的 Rxjava 进行错误处理

Android :RxJava学习笔记之 错误处理

Android Retrofit+RxJava 优雅的处理服务器返回异常错误

从retrofit2和rxjava中的错误获取url

Android 12 (S) 出现 RxJava 或 Retrofit PendingIntent 错误

改造返回错误 307,在 android 中调用 web 服务