Friday, August 1, 2014

How to get html content from android webview

There are 2 different ways to get html content from android webview.

1) Using Javascript
In this approach, url will be loaded by webview itself. Means each and every url will be invoked in the webview context. so webview will manage cookies and http session automatically.

2) Using Java only to get html content from webview.
In this approach, webview will not be responsible for fetching the content from remote servers. We have to handle invocation of urls and have to fetch html content using code and then we can perform whatever operations we require on content before setting it in the webview.
So maintaining cookies and http session will be our responsibility.
In the example snippet below for this approach will show a workaround for maintaining http session.


1) Using Javascript

First you have to configure javascript interface for webview, using below method

webview.addJavascriptInterface(new MyJavaScriptInterface(this), "HtmlViewer");

Here 1st parameter is the MyJavaScriptInterface class, that you have to write as below.
and 2nd parameter is the name of the javascript object you will use to call the method you write (showHTML).

class MyJavaScriptInterface 
{
 private Context ctx;

        MyJavaScriptInterface(Context ctx) 
 {
            this.ctx = ctx;
        }

       @JavascriptInterface
       public void showHTML(String html) 
       {
           //code to use html content here
 handlerForJavascriptInterface.post(new Runnable() {
   @Override
   public void run() 
   {
    Toast toast = Toast.makeText(this, "Page has been loaded in webview. html content :"+html_, Toast.LENGTH_LONG);
    toast.show();
   }});
        }
}

Note that annotation ‘ @JavascriptInterface’ on the showHTML method. it is mandatory to work this propertly for android >=4.2.
Another thing to keep in mind is that, from showHTML method, you will not able to access other components of your activity directly. You have to post runnable objects to a handler. and write all the logic to access activity’s components inside the run method of runnable.

Now everyting is setup. You just have to call showHTML method explicitly when the page loading is finished into the webview.
For this you have to implement WebviewClient which will be notified by android once page is loaded into the webview, and from that you can invoke showHTML method using javascript and pass the html content loaded into webview.
This can be achieved using below code.

webview.setWebViewClient(new WebViewClient() 
 {
  @Override
  public void onPageFinished(WebView view, String url) 
  {
   webview.loadUrl("javascript:window.HtmlViewer.showHTML" +
    "('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');");
  }
        });

Below is the complete activity code you can refer to if anything is missed in above code snippet.

public class TestActivity extends Activity
{
    Handler handlerForJavascriptInterface = new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.webview);
        final WebView webview = (WebView) findViewById(R.id.browser);
        webview.getSettings().setJavaScriptEnabled(true);
        webview.addJavascriptInterface(new MyJavaScriptInterface(this), "HtmlViewer");
        webview.setWebViewClient(new WebViewClient()
        {
            @Override
            public void onPageFinished(WebView view, String url)
            {
                webview.loadUrl("javascript:window.HtmlViewer.showHTML" +
                "('&lt;html&gt;'+document.getElementsByTagName('html')[0].innerHTML+'&lt;/html&gt;');");
            }
        }
        );
        webview.loadUrl("http://android-in-action.com/index.php?post/" +
        "Common-errors-and-bugs-and-how-to-solve-avoid-them");
    }
    class MyJavaScriptInterface
    {
        private Context ctx;
        MyJavaScriptInterface(Context ctx)
        {
            this.ctx = ctx;
        }
        public void showHTML(String html)
        {
            final html_ = html;
            handlerForJavascriptInterface.post(new Runnable()
            {
                @Override
                public void run()
                {
                    Toast toast = Toast.makeText(this, "Page has been loaded in webview. html content :"+html_, Toast.LENGTH_LONG);
                    toast.show();
                }
            }
            );
        }
    }
}

If you are using proguard while exporting your apps then following properties must be included in proguard-project.txt (proguard config file) .

-keepattributes JavascriptInterface
-keep public class com.mypackage.MyClass$MyJavaScriptInterface
-keep public class * implements com.mypackage.MyClass$MyJavaScriptInterface
-keepclassmembers class com.mypackage.MyClass$MyJavaScriptInterface { 
    <methods>; 
}


2) Using Java only to get html content from webview.

In this approach, you have to handle page loading events (page loading started, page loading finished) of webview based on your requirements and from there you have to use httpclient api to actually hit the url and get the content.
this way you will have the access to the html content returned by remote server and you can modify the content as per your needs before/after it is rendered into the webview.

Below is the complete code for this.

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;

/**
 * getting html content from webview using java only. not using javascript for
 * that.
 */
public class WebviewTestActivity1 extends Activity
{
 @Override
 protected void onCreate(Bundle savedInstanceState)
 {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_webview_test_activity1);

  final WebView webview = (WebView) findViewById(R.id.webview1);
  webview.getSettings().setJavaScriptEnabled(true);

  WebViewClient webViewClient = new WebViewClient()
  {
   /**
    * this method will be invoked when page started loading into the
    * webview. so you can use thid method if you want to modifiy the
    * content before it is rendered.
    */
   @Override
   public void onPageStarted(WebView view, String url, Bitmap favicon)
   {
    super.onPageStarted(view, url, favicon);
    String htmlContent = getRemoteContent(url);
    webview.loadDataWithBaseURL(url, htmlContent, null, "utf-8", url);
   }

   /**
    * this method will be invoked when page loading is finished into
    * the webview.
    */
   @Override
   public void onPageFinished(WebView view, String url)
   {
    // TODO Auto-generated method stub
    super.onPageFinished(view, url);
     // similar code as above method will go here .
   }
  };
  
  webview.setWebViewClient(webViewClient);
 }

 /**
  * this method will hit the remote url and get the content. this content
  * will be set in the webview.
  */
 private String getRemoteContent(String url)
 {
  HttpGet pageGet = new HttpGet(url);
  HttpClient client = new DefaultHttpClient();

  ResponseHandler<String> handler = new ResponseHandler<String>()
  {
   public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException
   {
    HttpEntity entity = response.getEntity();
    String html;

    if (entity != null)
    {
     html = EntityUtils.toString(entity);
     return html;
    }
    else
    {
     return null;
    }
   }
  };

  String pageHTML = null;
  try
  {
        pageHTML = client.execute(pageGet, handler);
        //if you want to manage http sessions then you have to add localContext as a third argument to this method and have uncomment below line to sync cookies.
         //syncCookies();
  }
  catch (Exception e)
  {
   e.printStackTrace();
  }

  // you can filter your html content here if you wish before displaying
  // in webview

  return pageHTML;
 }
}

As stated above, this approach will not maintain cookies and http sessions.

So if you don't have requirement to maintain http session, then above code snippet is sufficient. Otherwise you have to implement additional code to achieve session management.
This is how you can achieve it.

First thing is you have to make your httpclient instance global. Means, you have to use same http client for executing each and every request.
Also you have to create global HttpContext and CookieStore objects, which will be used for executing http requests.

You can use below method to initialize the httpclient.

private static DefaultHttpClient httpClient = null; 
private static void initHttpClient()
{
 httpClient = new DefaultHttpClient();
 ClientConnectionManager cm = httpClient.getConnectionManager();
 SchemeRegistry sr = cm.getSchemeRegistry();

 sr.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
 sr.register(new Scheme("https", TrustAllSSLSocketFactory.getSocketFactory(), 443));

 cookieStore = new BasicCookieStore();
 localContext = new BasicHttpContext();
 localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
}

Now you have to execute each and every request in localContext, which is using cookieStore internally.
And after invoking the request urls, you have to call below method to sync the cookies with webview.

private void syncCookies()
{  
 Cookie sessionInfo;
 List<Cookie> cookies = client.getCookieStore().getCookies();
 Log.d(TAG, "cookies = " + cookies);

 if (!cookies.isEmpty())
 {
  WebView webView = (WebView) findViewById(R.id.webView1);
  CookieSyncManager.createInstance(webView.getContext());
  CookieManager cookieManager = CookieManager.getInstance();

  for (Cookie cookie : cookies)
  {
   sessionInfo = cookie;
   String cookieString = sessionInfo.getName() + "=" + sessionInfo.getValue() + "; domain=" + sessionInfo.getDomain();
   Log.d(TAG, "cookieString = " + cookieString);
   cookieManager.setCookie("<your_host_address_here>", cookieString);
   CookieSyncManager.getInstance().sync();
  }
 }
}

Thats it! Feel free to add your feedback/comments if there are any.



3 comments:

  1. I'm no expert, but I believe you just made an excellent point. You certainly fully understand what youre speaking about, and
    I can truly get behind that. content://com.android.browser.home/index

    ReplyDelete
  2. Positive site, where did u come up with the information on this posting?I have read a few of the articles on your website now, and I really like your style.
    Thanks a million and please keep up the effective work.https://1asiaqq.net/

    ReplyDelete

Creating and Deploying Java Web Application on AWS using Elastic Beanstalk

This tutorial is for creating simple java web application using eclipse and then deploying it on AWS cloud. Video tutorial for creating/de...