Search code examples
androidandroid-webviewrx-java2headless-browserunsubscribe

RxJava unsubscribeOn with recursive call not in the right thread


I've built an headless webview in an Android application for scrape an URL from a webpage. Every time I retrieve an URL I may need to redoing the scraping in the webpage with this URL. I'm using RxJava to handle these operations concurrently, and I'm using the flatMap function to make a recursive call.

The problem is that I need to dispose the WebView in the mainThread, so I tried to add .unsubscribeOn(AndroidSchedulers.mainThread()) but it seems it doesn't work, and the dispose() method in HeadlessRequest is called in the last thread I called observeOn(Schedulers.computation()). What should I change to execute the dispose() method in the mainThread?

This is my code:

HeadlessRequest

public class HeadlessRequest  implements Disposable {
    ...

    private class HeadlessWebView extends WebView {
        ...
         private void destroyWebView() {
            this.removeAllViews();
            this.clearCache(false);
            this.loadUrl("about:blank");
            this.onPause();
            this.removeAllViews();
            this.destroy();
            this.isDisposed = true;
        }
    }

    @Override
    public void dispose() {
        // This doesn't print the mainThread id
        Log.d(TAG, "Disposing on thread " + Thread.currentThread().getId());
        this.webView.destroyWebView();
        this.webView = null;
    }

    @Override
    public boolean isDisposed() {
        return (this.webView == null || this.webView.isDisposed);
    } 
}

NetworkUtils

public static Single<Document> downloadPageHeadless(final String url, final int delay, final Context context) {
        return Single.create((SingleEmitter<Document> emitter) -> {
            try {
                emitter.setDisposable(new HeadlessRequest(url, USER_AGENT, delay, context, emitter::onSuccess, emitter::onError));
            } catch (Exception e) {
                emitter.onError(e);
            }
        }).unsubscribeOn(AndroidSchedulers.mainThread()) //  It MUST be executed on the mainThread
                .subscribeOn(AndroidSchedulers.mainThread());
    }

ServerService

private static Single<String> resolveRecursive(String url, Context context) {
        Server server = getServerInstance(url, context);
        if (server == null) {
            return Single.error(new UnsupportedOperationException("Server for " + url + " not supported"));
        } else if (server.isVideo()) {
            return server.resolve(url, context);  // This method return a Single with observeOn(Schedulers.computation())
        } else {
            return server.resolve(url, context) 
                    .observeOn(Schedulers.computation())
                    .flatMap(resolvedUrl -> resolveRecursive(resolvedUrl, context));
        }
    }

 public static Single<String> resolveURL(String url, Context context) {
        return resolveRecursive(url, context)
                .observeOn(AndroidSchedulers.mainThread());
    }

Solution

  • At the end I found another method to dispose the webview in the mainThread without RxJava. I've used the post method of the WebView.

    private void destroyWebView() {
               this.post(() -> {
                    this.removeAllViews();
                    this.clearCache(false);
                    this.loadUrl("about:blank");
                    this.onPause();
                    this.removeAllViews();
                    this.destroy();
                    this.isDisposed = true;
                });
            }