Estoy haciendo llamadas de red usando Retrofit2 + RxJava + RxAndroid.

He intentado con diferentes Schedulers del mismo modo io() y newThread() . Soy consciente de las consecuencias del uso de newThread(), pero la aplicación sigue cerrando la causa de OOM.

Escenario:

  • Lista de filtros (65 elementos después del filtrado)
  • Descargar gzip desde URL
  • escribir bytes y almacenar en el reino

La llamada a la red anterior se produce 65 veces.

Código:

/*form version download method*/
public void startFormDownload(List<Form> form, ApiClient apiClient) {
    Observable.fromIterable(form)
            .concatMapIterable(Form::getFormVersions) // get form version list from single form object
            .doOnSubscribe(disposable -> AppLogger.i(tag, "Form Versions download is subscribed")) // subscribe process to rx
            .filter(this::checkFormVersionToDownloadOrNot) // only get those versions who needed to downloaded
            .doOnNext(formVersion -> { // get formVersion object

                AppLogger.i(tag, "download this form ---------> " + formVersion.getFormUrl());
                AppLogger.i(tag, formVersion.getFormUrl());
                String constructURL = formVersion.getFormUrl();

                /* download form gzip process starts from here */
                apiClient.getJsonByFormURL(constructURL)
                        .subscribeOn(Schedulers.newThread())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new SingleObserver<Response<ResponseBody>>() {
                            @Override
                            public void onSubscribe(Disposable disposable) {
                                AppLogger.i(tag, "gzip download ->" + "subscribed");
                            }

                            @Override
                            public void onSuccess(Response<ResponseBody> responseBodyResponse) {
                                AppLogger.i(tag, "gzip downloaded " + " success");
                                AppLogger.i(tag, "gzip downloaded ->" + responseBodyResponse.toString());

                                if (responseBodyResponse.code() == 401) {
                                    //not authorized
                                    return;
                                }

                                ByteArrayInputStream bais = null;
                                if (responseBodyResponse.body() != null)
                                    try {
                                        bais = new ByteArrayInputStream(responseBodyResponse.body().bytes());

                                        GZIPInputStream gzis = new GZIPInputStream(bais);
                                        InputStreamReader reader = new InputStreamReader(gzis);
                                        BufferedReader in = new BufferedReader(reader);

                                        String readed;
                                        StringBuilder stringBuilder = new StringBuilder();
                                        while ((readed = in.readLine()) != null) {
                                            stringBuilder.append(readed);
                                        }

                                        baseRealm = Realm.getDefaultInstance();
                                        formVersion.setJsonString(stringBuilder.toString());
                                        baseRealm.executeTransaction(realm ->
                                                baseRealm.copyToRealmOrUpdate(formVersion));



                                    } catch (IOException e) {
                                        AppLogger.e(tag, "gzip extract exception->" + e.getLocalizedMessage(), e);
                                    }
                            }

                            @Override
                            public void onError(Throwable throwable) {
                                AppLogger.e(tag, "gzip failed->" + throwable.getMessage(), throwable);
                            }
                        });


            })
            .doOnTerminate(() -> AppLogger.i(tag, "Form Versions terminated"))
            .doOnError(Throwable::printStackTrace)
            .subscribe(
                    formVersion -> AppLogger.i(tag, "Form Versions download completed"),
                    throwable -> AppLogger.e(tag, throwable.getMessage(), throwable)
            );
}

Puede haber un caso de InputStream para causar OOM, pero no estoy seguro de qué se puede hacer aquí.

PD. OOM ocurre solo algunas veces, no siempre.

Informe del accidente

java.lang.OutOfMemoryError: Could not allocate JNI Env
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:730)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:941)
at java.util.concurrent.ThreadPoolExecutor.ensurePrestart(ThreadPoolExecutor.java:1582)
at java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:313)
at java.util.concurrent.ScheduledThreadPoolExecutor.schedule(ScheduledThreadPoolExecutor.java:550)
at java.util.concurrent.ScheduledThreadPoolExecutor.submit(ScheduledThreadPoolExecutor.java:651)
at io.reactivex.internal.schedulers.NewThreadWorker.scheduleActual(NewThreadWorker.java:146)
at io.reactivex.internal.schedulers.NewThreadWorker.schedule(NewThreadWorker.java:51)
at io.reactivex.Scheduler.scheduleDirect(Scheduler.java:135)
at io.reactivex.Scheduler.scheduleDirect(Scheduler.java:111)
at io.reactivex.internal.operators.single.SingleSubscribeOn.subscribeActual(SingleSubscribeOn.java:37)
at io.reactivex.Single.subscribe(Single.java:2779)
at io.reactivex.internal.operators.single.SingleObserveOn.subscribeActual(SingleObserveOn.java:35)
at io.reactivex.Single.subscribe(Single.java:2779)
at com.visualogyx.app.fragments.VisualogyxBaseFragment.lambda$startFormDownload$10$VisualogyxBaseFragment(VisualogyxBaseFragment.java:623)
at com.visualogyx.app.fragments.VisualogyxBaseFragment$$Lambda$9.accept(Unknown Source)
at io.reactivex.internal.operators.observable.ObservableDoOnEach$DoOnEachObserver.onNext(ObservableDoOnEach.java:95)
at io.reactivex.internal.operators.observable.ObservableFilter$FilterObserver.onNext(ObservableFilter.java:52)
at io.reactivex.internal.observers.DisposableLambdaObserver.onNext(DisposableLambdaObserver.java:58)
at io.reactivex.internal.operators.observable.ObservableFlattenIterable$FlattenIterableObserver.onNext(ObservableFlattenIterable.java:111)
at io.reactivex.internal.operators.observable.ObservableFromIterable$FromIterableDisposable.run(ObservableFromIterable.java:98)
at io.reactivex.internal.operators.observable.ObservableFromIterable.subscribeActual(ObservableFromIterable.java:58)
at io.reactivex.Observable.subscribe(Observable.java:10910)
at io.reactivex.internal.operators.observable.ObservableFlattenIterable.subscribeActual(ObservableFlattenIterable.java:44)
at io.reactivex.Observable.subscribe(Observable.java:10910)
at io.reactivex.internal.operators.observable.ObservableDoOnLifecycle.subscribeActual(ObservableDoOnLifecycle.java:33)
at io.reactivex.Observable.subscribe(Observable.java:10910)
at io.reactivex.internal.operators.observable.ObservableFilter.subscribeActual(ObservableFilter.java:30)
at io.reactivex.Observable.subscribe(Observable.java:10910)
at io.reactivex.internal.operators.observable.ObservableDoOnEach.subscribeActual(ObservableDoOnEach.java:42)
at io.reactivex.Observable.subscribe(Observable.java:10910)
at io.reactivex.internal.operators.observable.ObservableDoOnEach.subscribeActual(ObservableDoOnEach.java:42)
at io.reactivex.Observable.subscribe(Observable.java:10910)
at io.reactivex.internal.operators.observable.ObservableDoOnEach.subscribeActual(ObservableDoOnEach.java:42)
at io.reactivex.Observable.subscribe(Observable.java:10910)
at io.reactivex.Observable.subscribe(Observable.java:10896)
at io.reactivex.Observable.subscribe(Observable.java:10825)
at com.visualogyx.app.fragments.VisualogyxBaseFragment.startFormDownload(VisualogyxBaseFragment.java:683)
at com.visualogyx.app.fragments.VisualogyxBaseFragment.updateJobAndDownloadCheckListForm(VisualogyxBaseFragment.java:239)
at com.visualogyx.app.fragments.VisualogyxBaseFragment.lambda$null$1$VisualogyxBaseFragment(VisualogyxBaseFragment.java:221)
at com.visualogyx.app.fragments.VisualogyxBaseFragment$$Lambda$15.test(Unknown Source)
at io.reactivex.internal.operators.observable.ObservableAllSingle$AllObserver.onNext(ObservableAllSingle.java:69)
at io.reactivex.internal.operators.observable.ObservableFromIterable$FromIterableDisposable.run(ObservableFromIterable.java:98)
at io.reactivex.internal.operators.observable.ObservableFromIterable.subscribeActual(ObservableFromIterable.java:58)
at io.reactivex.Observable.subscribe(Observable.java:10910)
at io.reactivex.internal.operators.observable.ObservableAllSingle.subscribeActual(ObservableAllSingle.java:34)
at io.reactivex.Single.subscribe(Single.java:2779)
at io.reactivex.Single.subscribe(Single.java:2765)
at io.reactivex.Single.subscribe(Single.java:2686)
at com.visualogyx.app.fragments.VisualogyxBaseFragment.lambda$storeAndRetrieveListFromDB$2$VisualogyxBaseFragment(VisualogyxBaseFragment.java:226)
at com.visualogyx.app.fragments.VisualogyxBaseFragment$$Lambda$0.execute(Unknown Source)
at io.realm.Realm.executeTransaction(Realm.java:1394)
at com.visualogyx.app.fragments.VisualogyxBaseFragment.storeAndRetrieveListFromDB(VisualogyxBaseFragment.java:200)
at com.visualogyx.app.fragments.HomeFragment$1.onSuccess(HomeFragment.java:59)
at com.visualogyx.app.fragments.HomeFragment$1.onSuccess(HomeFragment.java:54)
at com.visualogyx.app.controller.ApiCallback.onResponse(ApiCallback.java:36)
at com.visualogyx.app.controller.ApiClient$11.onResponse(ApiClient.java:264)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:70)
at android.os.Handler.handleCallback(Handler.java:836)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:203)
at android.app.ActivityThread.main(ActivityThread.java:6293)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1065)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:926)
0
Aks4125 23 ene. 2018 a las 08:35

3 respuestas

La mejor respuesta

Tuve que mover la operación relacionada con la transacción del reino en el bloque doFinally(). Hasta aquí todo bien.

Observable.fromIterable(form)
            .concatMapIterable(Form::getFormVersions) // get form version list from single form object
            .doOnSubscribe(disposable -> AppLogger.i(tag, "Form Versions download is subscribed")) // subscribe process to rx
            .filter(formVersion -> formVersion.getJsonString() == null) // only get those versions who is not downloaded yet
            .doOnNext(formVersion -> { // get formVersion object

                AppLogger.i(tag, "download this form ---------> " + formVersion.getFormUrl());
                AppLogger.i(tag, formVersion.getFormUrl());
                String constructURL = formVersion.getFormUrl();

                /* download form gzip process starts from here */
                apiClient.getJsonByFormURL(constructURL)
                        .subscribeOn(Schedulers.io())
                        .subscribe(new SingleObserver<Response<ResponseBody>>() {
                            @Override
                            public void onSubscribe(Disposable disposable) {
                                AppLogger.i(tag, "gzip download ->" + "subscribed");

                            }

                            @Override
                            public void onSuccess(Response<ResponseBody> responseBodyResponse) {
                                AppLogger.i(tag, "gzip downloaded " + " success");
                                AppLogger.i(tag, "gzip downloaded ->" + responseBodyResponse.toString());

                                if (responseBodyResponse.code() == 401) {
                                    //not authorized
                                    return;
                                }
                                // You should determine it based on response header.

                                if (responseBodyResponse.body() != null)
                                    try {
                                        gzipBody = null;

                                        gzis = responseBodyResponse.body().byteStream();
                                        ungzippedResponse = new GZIPInputStream(gzis);
                                        reader = new InputStreamReader(ungzippedResponse, charset);
                                        writer = new java.io.StringWriter();

                                        buffer = new char[10240];
                                        for (int length = 0; (length = reader.read(buffer)) > 0; ) {
                                            writer.write(buffer, 0, length);
                                        }
                                        gzipBody = writer.toString();

                                        formVersion.setJsonString(gzipBody);
                                        AppLogger.i(tag, "set json string ->" + gzipBody);

                                    } catch (IOException e) {
                                        AppLogger.e(tag, "gzip extract exception->" + e.getLocalizedMessage(), e);
                                    }
                            }

                            @Override
                            public void onError(Throwable throwable) {
                                AppLogger.e(tag, "gzip failed->" + throwable.getMessage(), throwable);
                            }
                        });


            })
            .doOnTerminate(() -> AppLogger.i(tag, "Form Versions terminated"))
            .doOnError(Throwable::printStackTrace)
            .doFinally(() -> {
                baseRealm = Realm.getDefaultInstance();
                baseRealm.executeTransaction(realm ->
                        realm.copyToRealmOrUpdate(form));
            })
            .subscribe(
                    formVersion -> AppLogger.i(tag, "Form Versions download completed"),
                    throwable -> AppLogger.e(tag, throwable.getMessage(), throwable)
            );
0
Aks4125 28 feb. 2018 a las 11:43

Puede usar bytestream en lugar de byte []

String body = null;
String charset = "UTF-8"; // You should determine it based on response header.

try (
    InputStream gzippedResponse = responseBodyResponse.body().byteStream();
    InputStream ungzippedResponse = new GZIPInputStream(gzippedResponse);
    Reader reader = new InputStreamReader(ungzippedResponse, charset);
    Writer writer = new StringWriter();
) {
    char[] buffer = new char[10240];
    for (int length = 0; (length = reader.read(buffer)) > 0;) {
        writer.write(buffer, 0, length);
    }
    body = writer.toString();
}
2
Krutik 23 ene. 2018 a las 05:58

Tal vez debería agregar un registro a una parte diferente de su código y averiguar la huella de la memoria, qué parte consume la memoria. O puede usar la herramienta de asignación de memoria de AndroidStuido para monitorear la asignación de memoria.

0
yu wang 23 ene. 2018 a las 05:53