2015-06-03 11 views
6

Realm.getInstance(context) restituirà raramente un'istanza di dominio già chiusa. Com'è possibile?Android: Realm.getInstance (contesto) restituisce un'istanza di dominio già chiusa

Sto usando Reame con RxJava, per https://realm.io/news/using-realm-with-rxjava/

In particolare, questo metodo genera un IllegalStateException: This Realm instance has already been closed, making it unusable.

@Override 
    public void call(final Subscriber<? super RealmList<T>> subscriber) { 
     final Realm realm = Realm.getInstance(context); 
     subscriber.add(Subscriptions.create(new Action0() { 
      @Override 
      public void call() { 
       try { 
        realm.close(); 
       } catch (RealmException ex) { 
        subscriber.onError(ex); 
       } 
      } 
     })); 

     RealmList<T> object; 
     realm.beginTransaction(); //THROWS EXCEPTION 

     //... 
} 

Se commento fuori la questione realm.close();, nessun problema. Anche se penso che questo porterà a una perdita di memoria nativa, quindi.

La mia ipotesi migliore sul motivo per cui questo si sta verificando è che sono state fatte più chiamate a questo metodo e se questo metodo chiama perfettamente la linea, un'istanza di dominio già chiusa può essere recuperata?

MODIFICA: utilizzando Schedulers.io(), vengono visualizzati molti avvisi Calling close() on a Realm that is already closed. La mia ipotesi è che in qualche modo dopo aver finito di usare il thread .io(), l'istanza del reame si chiude automaticamente. Non sono sicuro del perché questo potrebbe accadere però.

EDIT2: Passando all'utilizzo di Schedulers.newThread() anziché Schedulers.io() per i miei osservabili, il problema si è interrotto. Ma vedo un sacco di avvisi Remember to call close() on all Realm instances. Sono abbastanza sicuro che li sto chiudendo, quindi sono molto confuso su questo.

EDIT3: passando all'utilizzo di AndroidSchedulers.mainThread(), nessun errore. Tranne che le chiamate del mio reame sono eseguite sul thread principale, il che è un brutto male. La mia ipotesi sul perché questo non causa alcun avvertimento è perché il realm ora vive sul thread principale, che è anche il punto in cui viene chiamato realm.close() (tramite il rx.subscriber).

EDIT4: Ecco la logica per la mia chiamata osservabile nel regno.

@Override 
public Observable<List<ImageArticleCategoryEntity>> getArticleBuckets() { 

    return RealmObservable.list(context, GET_ARTICLE_BUCKETS) 
      .filter(FILTER_OUT_NULL_OR_EMPTY_LIST) 
      .switchIfEmpty(refreshAndSaveAndLoadNewDataFromDb) 
      .map(CONVERT_FROM_REALMLIST_TO_IMAGE_ARTICLE_ENTITYLIST); 

} 

public void loadArticleImages() { 
    articleRepo.getArticleBuckets() 
      .subscribeOn(RealmThread.get()) 
      .observeOn(AndroidSchedulers.mainThread()) 
      .subscribe(new Subscriber<List<ImageArticleCategoryEntity>>() { 
       @Override 
       public void onCompleted() { 
        Timber.v("Loading article images complete!"); 
        if (view != null) 
         view.hideLoadingAnimation(); 
       } 

       @Override 
       public void onError(Throwable e) { 
        Timber.e("Error loading article images", e); 
        Log.e("tag", "Error loading article images", e); 
        if (view != null) { 
         view.hideLoadingAnimation(); 
         view.showLoadingErrorToast(); 
        } 
       } 

       @Override 
       public void onNext(List<ImageArticleCategoryEntity> integerImageArticleCategoryEntityHashMap) { 
        if (view != null) 
         view.loadArticleImages(integerImageArticleCategoryEntityHashMap); 
       } 
      }); 
+0

sei sicuro al 100% che 'Realm.getInstance (context)' restituisca effettivamente un'istanza chiusa? Penso che dovresti probabilmente associare il Realm al conteggio delle attività in un servizio, o al ciclo di vita di un'attività, o al ciclo di vita di un frammento senza testa conservato. – EpicPandaForce

+0

Non eseguo operazioni IO sul thread principale, che sembra essere ciò che si suggerisce, poiché le istanze di autenticazione non possono essere passate attraverso i thread. Sì, sono sicuro al 100% che ciò accada. Ho provato un blocco try/catch che ha rilevato 'IllegalStateException'. Nel catch, ho ottenuto un nuovo 'realm' tramite' realm.getInstance (context) 'e ho chiamato' realm.beginTransaction() 'immediatamente dopo. Ha gettato di nuovo l'eccezione. – ZakTaccardi

+0

oh, va bene. non è possibile condividere il dominio attraverso il thread. – EpicPandaForce

risposta

1

Diamo semplificare come il ciclo di vita dell'istanza Realm è gestito. Se lo gestiamo nel contesto del ciclo di ripresa/pausa di uno Activity o Fragment, possiamo controllare e interrompere più facilmente il lavoro che potrebbe consumare l'istanza Realm. Gli strumenti di RxAndroid aiutano molto in questo.

Quindi, ho intenzione di assumere per questo esempio questo sta accadendo da un Activity, ma un approccio molto simile potrebbe essere utilizzato da un Fragment, o estratto in classi di helper con solo un suggerimento più impianti idraulici.

Se si associa il tuo Observable al ciclo di vita del Activity o Fragment usando RxAndroid, che sembra si sta già utilizzando per mainThread(), si dovrebbe essere in grado di gestire l'istanza Realm facilmente all'interno di un ciclo di vita. Sto anche usando RxAsyncUtil per Async.toAsync, che semplifica la creazione originale dell'istanza Realm.

Ho usato Rx molto più del Reame (anche se mi sono divertito un po '), quindi perdonami se alcune delle mie API non sono perfette. Ho anche abbreviato le cose in lambda in stile java8 solo per facilità di scrittura e lettura. Se non stai usando qualcosa come Retrolambda, dovrebbe comunque essere abbastanza facile convertirlo.

class MyActivity extends Activity { 
    private final CompositeSubscriptions realmSubscriptions = new CompositeSubscription(); 

    private AsyncSubject<Realm> realm; 

    @Override 
    protected void onResume() { 
    super.onResume(); 
    realm = AsyncSubject.create(); 
    realmSubscriptions.add(getRealmInstance()); 

    // This could actually happen anytime between onResume and onPause. 
    realmSubscriptions.add(loadArticlesImages()); 
    } 

    private <T> Observable<T> bindToRealm(final Observable<T> observable) { 
    // Utility to bind to the activity lifecycle, observe on the main thread 
    // (implicit in the bindActivity call), and do work on the Realm thread. 
    return AppObservable.bindActivity(this, observable.subscribeOn(RealmThread.get())); 
    } 

    private Observable<List<ImageArticleCategoryEntity>> getArticleBuckets(
     final Realm realm) { 
    /* As is, except the realm instance should be passed to RealmObservable.list instead 
     of the context. */ 
    } 

    private Subscription getRealmInstance() { 
    // Grab the realm instance on the realm thread, while caching it in our AsyncSubject. 
    return bindtoRealm(Async.toAsync(() -> Realm.getInstance(MyActivity.this))) 
     .subscribe(realm); 
    } 

    private Subscription loadArticleImages() { 
     // Using the flatMap lets us defer this execution until the 
     // Realm instance comes back from being created on the Realm thread. 
     // Since it is in an AsyncSubject, it will be available nearly 
     // immediately once it has been created, and is cached for any future 
     // subscribers. 
     return bindToRealm(realm.flatMap((realm) -> 
      articleRepo.getArticleBuckets(realm).subscribeOn(RealmThread.get()))) 
       .subscribe(
        (next) -> { 
        if (view != null) { 
         view.loadArticleImages(next); 
        } 
        }, 
        (error) -> { 
        Timber.e("Error loading article images", e); 
        Log.e("tag", "Error loading article images", e); 
        if (view != null) { 
         view.hideLoadingAnimation(); 
         view.showLoadingErrorToast(); 
        } 
        }, 
        // onCompleted 
       () -> { 
        Timber.v("Loading article images complete!"); 
        if (view != null) view.hideLoadingAnimation(); 
        }); 
    } 

    @Override 
    protected void onPause() { 
    // Stop any work which we added that involves the Realm instance. 
    realmSubscriptions.clear(); 

    // Clean up the AsyncObservable. If it has a Realm instance, close it. 
    if (realm.getValue() != null) { 
     realm.getValue().close(); 
    } 
    realm.dispose(); 
    realm = null; 

    super.onPause(); 
    } 
} 

Si dovrebbe essere in grado di estrarre facilmente questo al di fuori di un'attività, se necessario, e solo passare un Attività/istanza Frammento per il legame del ciclo di vita, così come la Observable<Realm> che in questo caso sarebbe la AsyncSubject. Se ci sono ancora condizioni di gara dovute all'abbonamento, potresti provare a aggiungere .unsubscribeOn(AndroidSchedulers.mainThread()) o anche a .unsubscribeOn(Schedulers.immediate()) (non sono sicuro quale sarebbe il migliore in questo scenario) a bindToRealm per assicurarti che l'annullamento dell'iscrizione avvenga quando lo vuoi , prima che l'istanza Realm sia chiusa.

Problemi correlati