2015-04-15 10 views
18

Ich erhalte eine Liste der installierten Apps auf dem Gerät. Es ist ein kostspieliger Vorgang, also verwende ich Rx dafür:Wie behandelt man onError in RxJava (Android) richtig?

Observable<List> observable = Observable.create(subscriber -> { 
     List result = getUserApps(); 

     subscriber.onNext(result); 
     subscriber.onError(new Throwable()); 
     subscriber.onCompleted(); 
    }); 

    observable 
      .map(s -> { 
       ArrayList<String> list = new ArrayList<>(); 
       ArrayList<Application> applist = new ArrayList<>(); 
       for (Application p : (ArrayList<Application>) s) { 
        list.add(p.getAppName()); 
        applist.add(p); 
       } 
       return applist; 
      }) 
      .subscribeOn(Schedulers.newThread()) 
      .observeOn(AndroidSchedulers.mainThread()) 
      .doOnError(throwable -> L.e(TAG, "Throwable " + throwable.getMessage())) 
      .subscribe(s -> createListView(s, view)); 

jedoch mein Problem mit Handhabungsfehlern ist. Normalerweise startet der Benutzer diesen Bildschirm, wartet auf das Laden von Apps, wählt den besten aus und geht zur nächsten Seite. Wenn der Benutzer die Benutzeroberfläche jedoch schnell ändert, stürzt die App mit NullPointer ab.

Okay, also habe ich diese onError implementiert. Doch es immer noch nicht funktioniert, und mit über usecase wirft es mir dies:

04-15 18:12:42.530 22388-22388/pl.digitalvirgo.safemob E/AndroidRuntime﹕ FATAL EXCEPTION: main 
     java.lang.IllegalStateException: Exception thrown on Scheduler.Worker thread. Add `onError` handling. 
       at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:52) 
       at android.os.Handler.handleCallback(Handler.java:730) 
       at android.os.Handler.dispatchMessage(Handler.java:92) 
       at android.os.Looper.loop(Looper.java:176) 
       at android.app.ActivityThread.main(ActivityThread.java:5419) 
       at java.lang.reflect.Method.invokeNative(Native Method) 
       at java.lang.reflect.Method.invoke(Method.java:525) 
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1046) 
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:862) 
       at dalvik.system.NativeStart.main(Native Method) 
     Caused by: rx.exceptions.OnErrorNotImplementedException 
       at rx.Observable$31.onError(Observable.java:7134) 
       at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:154) 
       at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:111) 
       at rx.internal.operators.OperatorDoOnEach$1.onError(OperatorDoOnEach.java:70) 
       at rx.internal.operators.NotificationLite.accept(NotificationLite.java:147) 
       at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.pollQueue(OperatorObserveOn.java:177) 
       at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.access$000(OperatorObserveOn.java:65) 
       at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber$2.call(OperatorObserveOn.java:153) 
       at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:47) 
                at android.os.Handler.handleCallback(Handler.java:730) 
                at android.os.Handler.dispatchMessage(Handler.java:92) 
                at android.os.Looper.loop(Looper.java:176) 
                at android.app.ActivityThread.main(ActivityThread.java:5419) 
                at java.lang.reflect.Method.invokeNative(Native Method) 
                at java.lang.reflect.Method.invoke(Method.java:525) 
                at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1046) 
                at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:862) 
                at dalvik.system.NativeStart.main(Native Method) 
     Caused by: java.lang.Throwable 
       at pl.digitalvirgo.safemob.fragments.wizard.ApplicationsFragment.lambda$getAppList$25(ApplicationsFragment.java:267) 
       at pl.digitalvirgo.safemob.fragments.wizard.ApplicationsFragment.access$lambda$2(ApplicationsFragment.java) 
       at pl.digitalvirgo.safemob.fragments.wizard.ApplicationsFragment$$Lambda$3.call(Unknown Source) 
       at rx.Observable$1.call(Observable.java:145) 
       at rx.Observable$1.call(Observable.java:137) 
       at rx.Observable.unsafeSubscribe(Observable.java:7304) 
       at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62) 
       at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:47) 
       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:390) 
       at java.util.concurrent.FutureTask.run(FutureTask.java:234) 
       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:153) 
       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:267) 
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080) 
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573) 
       at java.lang.Thread.run(Thread.java:841) 

Wie soll ich richtig dieses Problem umgehen?

+0

Haben Sie das Problem behoben? – User9527

+0

ist "Anwendung" ein benutzerdefiniertes Modell? – Lobato

Antwort

12

Meine Meinung ist: Sie sind wahrscheinlich Action1 in

.subscribe(s -> createListView(s, view)); 

mit Sie müssen es mit Subscriber oder Beobachter zu ersetzen, die onError abstrakte Methode hat. Diese Methode wird aufgerufen von subscriber.onError(new Throwable());

EDIT: So würde ich es machen. Bei näherem Hinsehen denke ich, das Hauptproblem in Ihrem Code ist der frühe Teil, wo Sie subscriber.onError aufrufen, auch wenn es keinen Fehler gibt. Sie brauchen wahrscheinlich map entweder nicht, weil Sie Daten ohne Manipulation technisch unverändert weitergeben. Aber ich habe es für den Fall gelassen, dass es später gebraucht wird. Hier

 Observable.create(new Observable.OnSubscribe<Application>() { 
     @Override 
     public void call(Subscriber<? super Application> subscriber) { 
      List result = getUserApps(); 
      if (result != null){ 
       for (Application app : result){ 
        subscriber.onNext(app); 
       } 
       subscriber.onComplete(); 
      }else{ 
       subscriber.onError(new IOException("no permission/no internet/etc")); 
       //or if this is a try catch event you can pass the exception 
      }  
     } 
    }) 
    .subscribeOn(Schedulers.io())//the thread *observer* runs in 
    .observeOn(AndroidSchedulers.mainThread())//the thread *subscriber* runs in 
    .map(new Func1<Application, String>() { 

     // Mapping methods are where data are manipulated. 
     // You can simply skip this and 
     //do the same thing in Subscriber implementation 
     @Override 
     public String call(Application application) { 
      return application.getName(); 
     } 
    }).subscribe(new Subscriber<String>() { 
     @Override 
     public void onCompleted() { 
      Toast.makeText(context, "completed", Toast.LENGTH_SHORT).show(); 
      //because subscriber runs in main UI thread it's ok to do UI stuff 
      //raise Toast, play sound, etc 
     } 

     @Override 
     public void onError(Throwable e) { 
      Log.e("getAppsError", e.getMessage()); 
      //raise Toast, play sound, etc 
     } 

     @Override 
     public void onNext(String s) { 
      listAdapter.add(s); 
     } 
    }); 
+1

Könnten Sie eine Art von Beispiel veröffentlichen? Ich bin etwas neu in der reaktiven Programmierung, so dass die meisten meiner Implementierungen mehr wie ein "hit and miss" sind;) –

+0

Ich habe meine Antwort bearbeitet um ein Beispiel hinzuzufügen. – inmyth

+0

Ist es mit Lambda-Ausdrücken machbar? –

4

ist der Neuling Antwort (weil ich bin neu in javarx und schließlich dieses Problem beheben):

Hier ist Ihre Implementierung:

Observable.create(new Observable.OnSubscribe<RegionItem>() { 
       @Override 
       public void call(Subscriber<? super RegionItem> subscriber) { 
        subscriber.onError(new Exception("TADA !")); 
       } 
      }) 
      .doOnNext(actionNext) 
      .doOnError(actionError) 
      .doOnCompleted(actionCompleted) 
      .subscribe(); 

In dieser früheren Implementierung, wenn ich abonnieren Ich trigger den Fehlerfluss ... und bekomme einen Absturz der Anwendung.

Das Problem ist, dass Sie den Fehler von der subscribe() -Aufruf verwalten müssen. Der "doOnError (...)" ist nur eine Art Helfer, der den Fehler klont und Ihnen einen neuen Platz gibt, um nach einem Fehler etwas zu tun. Aber es behandelt den Fehler nicht.

So müssen Sie Ihren Code mit, dass ändern:

Observable.create(new Observable.OnSubscribe<RegionItem>() { 
       @Override 
       public void call(Subscriber<? super RegionItem> subscriber) { 
        subscriber.onError(new Exception("TADA !")); 
       } 
      }) 
      .subscribe(actionNext, actionError, actionCompleted); 

nicht über die wirkliche Erklärung sicher, aber das ist, wie ich es beheben. Hoffe es wird helfen.

6

.doOnError() ist ein Operator und ist als solcher kein Teil der Subscriber.

Daher zählt eine .doOnError() nicht als eine implementierte onError().

Über die Frage in einem der Kommentare ist es natürlich möglich, Lambdas zu verwenden.

In diesem Fall einfach

ersetzen
.doOnError(throwable -> L.e(TAG, "Throwable " + throwable.getMessage())) 
.subscribe(s -> createListView(s, view)) 

mit

.subscribe(s -> createListView(s, view), 
    throwable -> L.e(TAG, "Throwable " + throwable.getMessage())) 
Verwandte Themen