turtleboy
turtleboy

Reputation: 7574

how to update webview from AsyncTask

I have an app that gets an xml file from the network, parses it to retreive paths to webpages of a site i have to cache.eg

/services/orthopaedics.php

. I can successfully download the paths of all the pages that need caching. At the moment i do this in an AsyncTask. The results of the webservice call(paths to pages) are store in a contentsValues in the doInBackground method of the AsyncTask. I then iterate through this contentsvalues in the onPostExecute() method where i can one by one load the webpages in a webview's loadUrl() method. this webview is made invisible as it is only used for caching purposes.

The proble is that as the invisible webview is loading another webview that the user can see and is to display the index page of the site it slow in loading. A white screen appears untill the invisible webview has stopped loading the cache. On a phoe it takes around 4 seconds which IS acceptable, but on a android tablet it takes around 30 seconds.

I know that calls to webview must be on the UI thread and that is why i load the webpages(invisible webview) in the onPostExecute method. Would it be possible to somehow load the pages into the cache in the doInBacground method, but place the webview.loadUrl() inside a runOnUiThread(new Runnable)?

I'll give a simplified sample of what i mean below.

private class AsyncCacheSite extends AsyncTask<String, ContentValues, ContentValues>{

        ProgressDialog progressDialog;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Log.e(TAG, "about to load main page");
            webView.loadUrl(mobileWebsiteUrl, getHeaders());

            progressDialog = new ProgressDialog(MainActivity.this);
            progressDialog.setTitle("Connecting to Server");
            progressDialog.setMessage("Caching website for offline use...");
            progressDialog.setIndeterminate(true);
            progressDialog.show();
        }

        @Override
        protected ContentValues doInBackground(String... params) {
            ContentValues cv = null;

            try {
                //call the service to get the xml file containing paths
                cv = ws.checkForUpdates();


            } catch (Exception e) {
               e.printStackTrace();
            }

//iterate through the xml file geting the paths
.......
.......
.......
runOnUiThread(new Runnable() {

                            @Override
                            public void run() {
                                // TODO Auto-generated method stub
loadUrlIntoCache(page);

                            }
                        })


            return null; 

        }



        @Override
        protected void onPostExecute(ContentValues result) {
            super.onPostExecute(result);



            progressDialog.dismiss();


    }//end of postExecute

 }//end of asyncTask



public void loadUrlIntoCache(String pageUrl){

        WebView wv2 = new WebView(MainActivity.this);

        wv2.getSettings().setSupportZoom(true);
        wv2.getSettings().setBuiltInZoomControls(true);
        wv2.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
        wv2.setScrollbarFadingEnabled(true);
        wv2.getSettings().setLoadsImagesAutomatically(true);
        wv2.getSettings().setDomStorageEnabled(true);
        wv2.getSettings().setAppCacheEnabled(true);
        // Set cache size to 8 mb by default. should be more than enough
        wv2.getSettings().setAppCacheMaxSize(1024*1024*32);
        // This next one is crazy. It's the DEFAULT location for your app's cache
        // But it didn't work for me without this line.
        // UPDATE: no hardcoded path. Thanks to Kevin Hawkins
        String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();

        wv2.getSettings().setAppCachePath(appCachePath);
        wv2.getSettings().setAllowFileAccess(true);
        wv2.getSettings().setJavaScriptEnabled(true);
       // wv2.setWebViewClient(new WebViewClient());

////////////////////////////////////////////////////////////////////////////////////////
/////the webviewclient below is the code you gave me to overcome the redirecting issue//
////////////////////////////////////////////////////////////////////////////////////////
webView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url){
// do your handling codes here, which url is the requested url
// probably you need to open that url rather than redirect:
view.loadUrl(url);
return false; // then it is not handled by default action
}
});

        wv2.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
        wv2.setVisibility(View.GONE); 

        wv2.loadUrl(mobileWebsiteUrl + pageUrl, getHeaders());
        Log.e(TAG, "loading " + mobileWebsiteUrl + pageUrl);



    }

Upvotes: 1

Views: 5449

Answers (3)

HarryQ
HarryQ

Reputation: 1423

The WebView needs to be called from the UI thread. If you happen have access to the main looper, you can create, set and load the page through a UI thread handler.

    //Code for creating an instance of Webview inside AsyncTask
    WebView wv = null;
    final Object lock = new Object();
    final int TIMEOUT_MS = 3000;
    AtomicReference<WebView> holder = new AtomicReference<>(); //use to pass the created webview back to worker thread.
    Handler mainHandler = new Handler(Looper.getMainLooper());
    try {
        synchronized (lock) {
            while (wv == null) {
                mainHandler.post(() -> {
                    synchronized (lock) {
                        WebView localWebView = new WebView(context);
                        setWebViewCache(context, localWebView); //configure the cache to your desired location.
                        holder.set(localWebView); //use this line to pass out the created instance from the UI thread back to your async task.
                        lock.notify();
                    }
                });
                lock.wait(TIMEOUT_MS);
                wv = holder.get();
            }
        }
    } catch (InterruptedException ie) {
        Log.e(TAG, "creating prefetch webview times out");
    }

Next you want to load the Webview with the URL you want inside your AsyncTask.

  synchronized (lock) {
            mainHandler.post(() -> {
                wv.setWebViewClient(new WebViewClient() {
                    @Override
                    public void onPageFinished(WebView view, String url) {
                        super.onPageFinished(view, url);
                        //override with behavior you want after load finishes, make sure to notify the workerthread to proceed.
                        synchronized (lock) {
                            lock.notify();
                        }
                    }
                });
                wv.loadData(html, mimeType, encoding); //here load the url you want
            });
            lock.wait(TIMEOUT_MS);
        }

Hopefully my code can help.

Upvotes: 0

Mariano Zorrilla
Mariano Zorrilla

Reputation: 7686

Sadly you can't "load" a URL from an AsyncTask. All the "WebView" methods must be called from the main thread.

All you could do is to update the progress dialog in a second thread. Check this answer Webview with asynctask on Android.

Upvotes: 2

Guillaume Alouege
Guillaume Alouege

Reputation: 790

I think the only solution is to load your data with an http request in doInBackground then use loadData in publishProgress.

Upvotes: 3

Related Questions