Android WebView -> Display WebArchive - android

Android's WebView has this saveWebArchive method since API level 11: http://developer.android.com/.
It can save entire websites as webarchives, which is great! But how do I get the downloaded contents back into a webview? I tried
webview.loadUrl(Uri.fromFile(mywebarchivefile));
But that only displays xml on the screen.

Update Feb. 21, 2014
My answer posted below does not apply to web archive files saved under Android 4.4 KitKat and newer. The saveWebArchive() method of WebView under Android 4.4 "KitKat" (and probably newer versions too) does not save the web archive in XML code that this reader code posted below. Instead it saves pages in MHT (MHTML) format. It is easy to read back the .mht files - just use:
webView.loadUrl("file:///my_dir/mySavedWebPage.mht");
That's all, much easier than the previous method, and compatible with other platforms.
Previously posted
I needed it myself, and everywhere I searched, there were unanswered questions like this. So I had to work it out myself. Below is my little WebArchiveReader class and sample code on how to use it. Please note that despite the Android docs declaring that shouldInterceptRequest() was added to WebViewClient in API11 (Honeycomb), this code works and was tested successfully in Android emulators down to API8 (Froyo). Below is all the code that's needed, I also uploaded the full project to GitHub repository at https://github.com/gregko/WebArchiveReader
File WebArchiveReader.java:
package com.hyperionics.war_test;
import android.util.Base64;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.w3c.dom.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
public abstract class WebArchiveReader {
private Document myDoc = null;
private static boolean myLoadingArchive = false;
private WebView myWebView = null;
private ArrayList<String> urlList = new ArrayList<String>();
private ArrayList<Element> urlNodes = new ArrayList<Element>();
abstract void onFinished(WebView webView);
public boolean readWebArchive(InputStream is) {
DocumentBuilderFactory builderFactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
myDoc = null;
try {
builder = builderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
try {
myDoc = builder.parse(is);
NodeList nl = myDoc.getElementsByTagName("url");
for (int i = 0; i < nl.getLength(); i++) {
Node nd = nl.item(i);
if(nd instanceof Element) {
Element el = (Element) nd;
// siblings of el (url) are: mimeType, textEncoding, frameName, data
NodeList nodes = el.getChildNodes();
for (int j = 0; j < nodes.getLength(); j++) {
Node node = nodes.item(j);
if (node instanceof Text) {
String dt = ((Text)node).getData();
byte[] b = Base64.decode(dt, Base64.DEFAULT);
dt = new String(b);
urlList.add(dt);
urlNodes.add((Element) el.getParentNode());
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
myDoc = null;
}
return myDoc != null;
}
private byte [] getElBytes(Element el, String childName) {
try {
Node kid = el.getFirstChild();
while (kid != null) {
if (childName.equals(kid.getNodeName())) {
Node nn = kid.getFirstChild();
if (nn instanceof Text) {
String dt = ((Text)nn).getData();
return Base64.decode(dt, Base64.DEFAULT);
}
}
kid = kid.getNextSibling();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public boolean loadToWebView(WebView v) {
myWebView = v;
v.setWebViewClient(new WebClient());
WebSettings webSettings = v.getSettings();
webSettings.setDefaultTextEncodingName("UTF-8");
myLoadingArchive = true;
try {
// Find the first ArchiveResource in myDoc, should be <ArchiveResource>
Element ar = (Element) myDoc.getDocumentElement().getFirstChild().getFirstChild();
byte b[] = getElBytes(ar, "data");
// Find out the web page charset encoding
String charset = null;
String topHtml = new String(b).toLowerCase();
int n1 = topHtml.indexOf("<meta http-equiv=\"content-type\"");
if (n1 > -1) {
int n2 = topHtml.indexOf('>', n1);
if (n2 > -1) {
String tag = topHtml.substring(n1, n2);
n1 = tag.indexOf("charset");
if (n1 > -1) {
tag = tag.substring(n1);
n1 = tag.indexOf('=');
if (n1 > -1) {
tag = tag.substring(n1+1);
tag = tag.trim();
n1 = tag.indexOf('\"');
if (n1 < 0)
n1 = tag.indexOf('\'');
if (n1 > -1) {
charset = tag.substring(0, n1).trim();
}
}
}
}
}
if (charset != null)
topHtml = new String(b, charset);
else
topHtml = new String(b);
String baseUrl = new String(getElBytes(ar, "url"));
v.loadDataWithBaseURL(baseUrl, topHtml, "text/html", "UTF-8", null);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
private class WebClient extends WebViewClient {
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if (!myLoadingArchive)
return null;
int n = urlList.indexOf(url);
if (n < 0)
return null;
Element parentEl = urlNodes.get(n);
byte [] b = getElBytes(parentEl, "mimeType");
String mimeType = b == null ? "text/html" : new String(b);
b = getElBytes(parentEl, "textEncoding");
String encoding = b == null ? "UTF-8" : new String(b);
b = getElBytes(parentEl, "data");
return new WebResourceResponse(mimeType, encoding, new ByteArrayInputStream(b));
}
#Override
public void onPageFinished(WebView view, String url)
{
// our WebClient is no longer needed in view
view.setWebViewClient(null);
myLoadingArchive = false;
onFinished(myWebView);
}
}
}
Here is how to use this class, sample MyActivity.java class:
package com.hyperionics.war_test;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import java.io.IOException;
import java.io.InputStream;
public class MyActivity extends Activity {
// Sample WebViewClient in case it was needed...
// See continueWhenLoaded() sample function for the best place to set it on our webView
private class MyWebClient extends WebViewClient {
#Override
public void onPageFinished(WebView view, String url)
{
Lt.d("Web page loaded: " + url);
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WebView webView = (WebView) findViewById(R.id.webView);
try {
InputStream is = getAssets().open("TestHtmlArchive.xml");
WebArchiveReader wr = new WebArchiveReader() {
void onFinished(WebView v) {
// we are notified here when the page is fully loaded.
continueWhenLoaded(v);
}
};
// To read from a file instead of an asset, use:
// FileInputStream is = new FileInputStream(fileName);
if (wr.readWebArchive(is)) {
wr.loadToWebView(webView);
}
} catch (IOException e) {
e.printStackTrace();
}
}
void continueWhenLoaded(WebView webView) {
Lt.d("Page from WebArchive fully loaded.");
// If you need to set your own WebViewClient, do it here,
// after the WebArchive was fully loaded:
webView.setWebViewClient(new MyWebClient());
// Any other code we need to execute after loading a page from a WebArchive...
}
}
To make things complete, here is my little Lt.java class for debug output:
package com.hyperionics.war_test;
import android.util.Log;
public class Lt {
private static String myTag = "war_test";
private Lt() {}
static void setTag(String tag) { myTag = tag; }
public static void d(String msg) {
// Uncomment line below to turn on debug output
Log.d(myTag, msg == null ? "(null)" : msg);
}
public static void df(String msg) {
// Forced output, do not comment out - for exceptions etc.
Log.d(myTag, msg == null ? "(null)" : msg);
}
}
Hope this is helpful.
Update July 19, 2013
Some web pages don't have meta tag specifying text encoding, and then the code we show above does not display the characters correctly. In the GitHub version of this code I now added charset detection algorithm, which guesses the encoding in such cases. Again, see https://github.com/gregko/WebArchiveReader
Greg

I've found an undocumented way of reading saved webarchive. Just do:
String raw_data = (read the mywebarchivefile as a string)
and then call
webview.loadDataWithBaseURL(mywebarchivefile, raw_data, "application/x-webarchive-xml", "UTF-8", null);
The reference:
http://androidxref.com/4.0.4/xref/external/webkit/Source/WebCore/loader/archive/ArchiveFactory.cpp
Available from Android 3.0, api level 11.

Related

Displaying xml float data

I am trying to create an earthquake watcher app but I can't seem to get the coordinates and other sections from the XML URL to show on my activity when I load the project I know that they are of type float. I have tried different methods and I have no errors on the console so it must be something with the way that I am calling it??
I have added some output and images
package ja.example.mpd1starterearth;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
ListView lvRss;
ArrayList<String> titles;
ArrayList<String> links;
ArrayList<Double> lat;
ArrayList<Double> lon;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lvRss = (ListView) findViewById(R.id.lvRss);
titles = new ArrayList<String>();
links = new ArrayList<String>();
lat = new ArrayList<Double>();
lon = new ArrayList<Double>();
lvRss.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Uri uri = Uri.parse(links.get(position));
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
});
new ProcessInBackground().execute();
}
public InputStream getInputStream(URL url)
{
try
{
//openConnection() returns instance that represents a connection to the remote object referred to by the URL
//getInputStream() returns a stream that reads from the open connection
return url.openConnection().getInputStream();
}
catch (IOException e)
{
return null;
}
}
public class ProcessInBackground extends AsyncTask<Integer, Void, Exception>
{
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
Exception exception = null;
#Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog.setMessage("Busy loading rss feed...please wait...");
progressDialog.show();
}
#Override
protected Exception doInBackground(Integer... params) {
try
{
URL url = new URL("http://quakes.bgs.ac.uk/feeds/MhSeismology.xml");
//creates new instance of PullParserFactory that can be used to create XML pull parsers
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
//Specifies whether the parser produced by this factory will provide support
//for XML namespaces
factory.setNamespaceAware(false);
//creates a new instance of a XML pull parser using the currently configured
//factory features
XmlPullParser xpp = factory.newPullParser();
// We will get the XML from an input stream
xpp.setInput(getInputStream(url), "UTF_8");
/* We will parse the XML content looking for the "<title>" tag which appears inside the "<item>" tag.
* We should take into consideration that the rss feed name is also enclosed in a "<title>" tag.
* Every feed begins with these lines: "<channel><title>Feed_Name</title> etc."
* We should skip the "<title>" tag which is a child of "<channel>" tag,
* and take into consideration only the "<title>" tag which is a child of the "<item>" tag
*
* In order to achieve this, we will make use of a boolean variable called "insideItem".
*/
boolean insideItem = false;
// Returns the type of current event: START_TAG, END_TAG, START_DOCUMENT, END_DOCUMENT etc..
int eventType = xpp.getEventType(); //loop control variable
while (eventType != XmlPullParser.END_DOCUMENT)
{
//if we are at a START_TAG (opening tag)
if (eventType == XmlPullParser.START_TAG)
{
//if the tag is called "item"
if (xpp.getName().equalsIgnoreCase("item"))
{
insideItem = true;
}
//if the tag is called "title"
else if (xpp.getName().equalsIgnoreCase("title"))
{
if (insideItem)
{
// extract the text between <title> and </title>
titles.add(xpp.nextText());
}
}
//if the tag is called "link"
else if (xpp.getName().equalsIgnoreCase("link"))
{
if (insideItem)
{
// extract the text between <link> and </link>
links.add(xpp.nextText());
}
}
else if(xpp.getName().equalsIgnoreCase("geo:lat")){
if(insideItem){
//extract the text between <geo:lat> and </geo:lat>
lat.add(Double.valueOf(xpp.nextText()));
}
}
else if(xpp.getName().equalsIgnoreCase("geo:long")){
if(insideItem) {
//extract the text between <geo:lat> and </geo:lat>
lon.add(Double.valueOf(xpp.nextText()));;
} }
}
//if we are at an END_TAG and the END_TAG is called "item"
else if (eventType == XmlPullParser.END_TAG && xpp.getName().equalsIgnoreCase("item"))
{
insideItem = false;
}
eventType = xpp.next(); //move to next element
}
}
catch (MalformedURLException e)
{
exception = e;
}
catch (XmlPullParserException e)
{
exception = e;
}
catch (IOException e)
{
exception = e;
}
return exception;
}
#Override
protected void onPostExecute(Exception s) {
super.onPostExecute(s);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, titles);
lvRss.setAdapter(adapter);
progressDialog.dismiss();
}
}
}
Your code to parse data from the URL is totally correct. You do not see all data all the activity because this line of code.
ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, titles);
You pass titles arraylist to the adapter, that why you only see all title xml value in the activity.
Solution: The simple way is format the data which connect from 4 array list titles, links, lat, lon.
/**
* This method will format data from titles, links, lat, lon arraylist.
*/
private List<String> formatDataBeforeDisplayOnListView(){
List<String> list = new ArrayList<>();
StringBuilder sb = new StringBuilder();
int size = titles.size();
for (int i = 0; i < size; ++i) {
String title = titles.get(i);
String link = links.get(i);
Double geoLat = lat.get(i);
Double getLon = lon.get(i);
sb.append("title: ").append(title).append("\n")
.append("link: ").append(link).append("\n")
.append("geo-lat: ").append(geoLat).append("\n")
.append("geo-lon: ").append(getLon);
list.add(sb.toString());
}
return list;
}
Then change your code to
// Comment-out this line
// ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, titles);
List<String> items = formatDataBeforeDisplayOnListView();
ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, items);
Note: If you want to display each item listview in a better design/UI then you should write a custom adapter class instead of ArrayAdapter.
Updated: Based on Jase's comment
First, declare a new class named Item
public class Item {
private String title;
private String link;
private Double lat;
private Double lon;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public Double getLat() {
return lat;
}
public void setLat(Double lat) {
this.lat = lat;
}
public Double getLon() {
return lon;
}
public void setLon(Double lon) {
this.lon = lon;
}
#Override
public String toString() {
return (new StringBuilder()).append("title: ").append(title).append("\n")
.append("link: ").append(link).append("\n")
.append("geo-lat: ").append(lat).append("\n")
.append("geo-lon: ").append(lon).toString();
}
}
Then, change your activity code to
public class MainActivity extends AppCompatActivity {
ListView lvRss;
ArrayList<Item> mItems = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lvRss = (ListView) findViewById(R.id.lvRss);
lvRss.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO: Process clicked item here
Item item = (Item) parent.getItemAtPosition(position);
Uri uri = Uri.parse(item.getLink());
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
});
new ProcessInBackground().execute();
}
public InputStream getInputStream(URL url) {
try {
//openConnection() returns instance that represents a connection to the remote object referred to by the URL
//getInputStream() returns a stream that reads from the open connection
return url.openConnection().getInputStream();
} catch (IOException e) {
return null;
}
}
public class ProcessInBackground extends AsyncTask<Integer, Void, Exception> {
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
Exception exception = null;
#Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog.setMessage("Busy loading rss feed...please wait...");
progressDialog.show();
}
#Override
protected Exception doInBackground(Integer... params) {
try {
URL url = new URL("http://quakes.bgs.ac.uk/feeds/MhSeismology.xml");
//creates new instance of PullParserFactory that can be used to create XML pull parsers
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
//Specifies whether the parser produced by this factory will provide support
//for XML namespaces
factory.setNamespaceAware(false);
//creates a new instance of a XML pull parser using the currently configured
//factory features
XmlPullParser xpp = factory.newPullParser();
// We will get the XML from an input stream
xpp.setInput(getInputStream(url), "UTF_8");
/* We will parse the XML content looking for the "<title>" tag which appears inside the "<item>" tag.
* We should take into consideration that the rss feed name is also enclosed in a "<title>" tag.
* Every feed begins with these lines: "<channel><title>Feed_Name</title> etc."
* We should skip the "<title>" tag which is a child of "<channel>" tag,
* and take into consideration only the "<title>" tag which is a child of the "<item>" tag
*
* In order to achieve this, we will make use of a boolean variable called "insideItem".
*/
boolean insideItem = false;
// Returns the type of current event: START_TAG, END_TAG, START_DOCUMENT, END_DOCUMENT etc..
int eventType = xpp.getEventType(); //loop control variable
Item item = null;
while (eventType != XmlPullParser.END_DOCUMENT) {
//if we are at a START_TAG (opening tag)
if (eventType == XmlPullParser.START_TAG) {
//if the tag is called "item"
if (xpp.getName().equalsIgnoreCase("item")) {
insideItem = true;
item = new Item();
}
//if the tag is called "title"
else if (xpp.getName().equalsIgnoreCase("title")) {
if (insideItem) {
// extract the text between <title> and </title>
item.setTitle(xpp.nextText());
}
}
//if the tag is called "link"
else if (xpp.getName().equalsIgnoreCase("link")) {
if (insideItem) {
// extract the text between <link> and </link>
item.setLink(xpp.nextText());
}
} else if (xpp.getName().equalsIgnoreCase("geo:lat")) {
if (insideItem) {
//extract the text between <geo:lat> and </geo:lat>
item.setLat(Double.valueOf(xpp.nextText()));
}
} else if (xpp.getName().equalsIgnoreCase("geo:long")) {
if (insideItem) {
//extract the text between <geo:lat> and </geo:lat>
item.setLon(Double.valueOf(xpp.nextText()));
}
}
}
//if we are at an END_TAG and the END_TAG is called "item"
else if (eventType == XmlPullParser.END_TAG && xpp.getName().equalsIgnoreCase("item")) {
insideItem = false;
mItems.add(item);
}
eventType = xpp.next(); //move to next element
}
} catch (MalformedURLException e) {
exception = e;
} catch (XmlPullParserException e) {
exception = e;
} catch (IOException e) {
exception = e;
}
return exception;
}
#Override
protected void onPostExecute(Exception s) {
super.onPostExecute(s);
ArrayAdapter<Item> adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, mItems);
lvRss.setAdapter(adapter);
progressDialog.dismiss();
}
}
}
You are comparing tag name with space in it thus equalsIgnoreCase() will always return false as geo:lang will not be equal to geo :lat.
Just test this code.
String s = "geo:lang";
System.out.println(""+s.equalsIgnoreCase("geo :long"));
You need to remove space in between o and : for both cases eg.equalsIgnoreCase("geo:long")

xamarin android webview no longer retrieves page served locally with httplistener

We have a xamarin android app that contains a httplistener that serves up html, js, css and json calls, to a single page app running in a full screen webview. As of Xamarin 3.5 onwards the webview fails to retrieve this localhost address which we run on port 31316. Prior to this release this was functioning correctly.
From what i can see, the httplistener seems healthy, as we can call it with the WebRequest library correctly, which leads me tb believe that something has changed to the webview.
Any assistance would be greatly appreciated.
Below is a sample app demonstrating the behaviour:
using System;
using System.Net;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Webkit;
using Android.Widget;
using System.IO;
using System.Text;
namespace HttpListenerTest
{
[Activity (Label = "HttpListenerTest", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
StartService(new Intent(this, typeof(HttpListenerTestService)));
var webView = (WebView)this.FindViewById<WebView>(Resource.Id.webview);
webView.Settings.AllowFileAccess = true;
webView.Settings.BlockNetworkLoads = false;
webView.LoadUrl("http://localhost:31316/");
//webView.LoadData (Activities.html, "text/html", "UTF-8");
var button = FindViewById<Button>(Resource.Id.button1);
button.Click += delegate
{
AlertDialog.Builder alert1;
alert1 = new AlertDialog.Builder(this);
try
{
if(!Activities.httpListener.IsListening)
{
alert1.SetMessage("Listener has stopped listening");
alert1.Show();
return;
}
string url = "http://localhost:31316/" + DateTime.Now.ToString("O");
var request = (HttpWebRequest)WebRequest.Create(new Uri(url));
request.Method = "GET";
string responseString = "";
using (WebResponse response = request.GetResponse())
{
StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
responseString = reader.ReadToEnd();
response.Close();
reader.Close();
}
alert1.SetMessage(responseString);
}
catch (Exception e)
{
alert1.SetMessage(e.Message + " " + e.StackTrace);
}
alert1.Show();
};
}
}
[Service(Label = "HttpListenerTest Service")]
public class HttpListenerTestService : Service
{
public override IBinder OnBind(Intent intent)
{
return null;
}
public override void OnCreate()
{
base.OnCreate();
Activities.InitRest();
}
public override void OnDestroy()
{
}
public override void OnStart(Intent intent, int startId)
{
base.OnStart(intent, startId);
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
}
}
public static class Activities
{
public static HttpListener httpListener { get; set; }
public static Thread RestThread { get; set; }
public const string html = "<html>hello world</html>";
public static void InitRest()
{
RestThread = new Thread(StartRest) { Name = "Rest Service" };
RestThread.Start();
}
private static void StartRest()
{
httpListener = new HttpListener { IgnoreWriteExceptions = true };
httpListener.Prefixes.Add(#"http://localhost:31316/");
httpListener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
httpListener.Start();
httpListener.BeginGetContext(HandleRequest, httpListener);
}
public static void HandleRequest(IAsyncResult result)
{
Console.WriteLine ("==========================HandleRequest==========================");
var context = httpListener.EndGetContext(result);
var unescapedUrl = Uri.UnescapeDataString(context.Request.RawUrl);
var bytes = new byte[html.Length * sizeof(char)];
Buffer.BlockCopy(html.ToCharArray(), 0, bytes, 0, bytes.Length);
context.Response.ContentLength64 = bytes.Length;
context.Response.OutputStream.Write(bytes, 0, bytes.Length);
context.Response.ContentType = "text/html";
//context.Response.ContentEncoding = "UTF-8";
context.Response.OutputStream.Close();
context.Response.OutputStream.Dispose();
context.Response.StatusCode = 200;
httpListener.BeginGetContext(HandleRequest, httpListener);
}
}
}

Activity restarted when i press home button in android

I am afraid that my activity is restarted when i open it through TaskManager....
and my DefaultHttpClient object treated as fresh one..so here i am loosing the
session.
I tried by overriding the onSaveInstanceState() method..but no use..
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); // the UI component values are saved here.
}
How i can get rid of this one...
when you press button Home, activity will pause and resume when reopen.
you shout put code to onCreate().
see activity lifecryde:
You could subclass Android Applications: You can init the HttpClient there and hold the reference.
Look here
Than you can access from activity your Application object with activity.getApplication()
If your session works with cookies than you may need a persistent cookie storeage (like database or shared preferences):
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.http.client.CookieStore;
import org.apache.http.cookie.Cookie;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
/**
* A persistent cookie store which implements the Apache HttpClient
* {#link CookieStore} interface. Cookies are stored and will persist on the
* user's device between application sessions since they are serialized and
* stored in {#link SharedPreferences}.
* <p>
*/
public class PersistentCookieStore implements CookieStore {
private static final String COOKIE_PREFS = "CookiePrefsFile";
private static final String COOKIE_NAME_STORE = "names";
private static final String COOKIE_NAME_PREFIX = "cookie_";
private final ConcurrentHashMap<String, Cookie> cookies;
private final SharedPreferences cookiePrefs;
/**
* Construct a persistent cookie store.
*/
public PersistentCookieStore(Context context) {
cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
cookies = new ConcurrentHashMap<String, Cookie>();
// Load any previously stored cookies into the store
String storedCookieNames = cookiePrefs.getString(COOKIE_NAME_STORE,
null);
if (storedCookieNames != null) {
String[] cookieNames = TextUtils.split(storedCookieNames, ",");
for (String name : cookieNames) {
String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX
+ name, null);
if (encodedCookie != null) {
Cookie decodedCookie = decodeCookie(encodedCookie);
if (decodedCookie != null) {
cookies.put(name, decodedCookie);
}
}
}
// Clear out expired cookies
clearExpired(new Date());
}
}
#Override
public synchronized void addCookie(Cookie cookie) {
String name = cookie.getName() + cookie.getDomain();
// Save cookie into local store, or remove if expired
if (!cookie.isExpired(new Date())) {
cookies.put(name, cookie);
} else {
cookies.remove(name);
}
// Save cookie into persistent store
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.putString(COOKIE_NAME_STORE,
TextUtils.join(",", cookies.keySet()));
prefsWriter.putString(COOKIE_NAME_PREFIX + name,
encodeCookie(new SerializableCookie(cookie)));
prefsWriter.commit();
}
#Override
public synchronized void clear() {
// Clear cookies from persistent store
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
for (String name : cookies.keySet()) {
prefsWriter.remove(COOKIE_NAME_PREFIX + name);
}
prefsWriter.remove(COOKIE_NAME_STORE);
prefsWriter.commit();
// Clear cookies from local store
cookies.clear();
}
#Override
public synchronized boolean clearExpired(Date date) {
boolean clearedAny = false;
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
for (ConcurrentHashMap.Entry<String, Cookie> entry : cookies.entrySet()) {
String name = entry.getKey();
Cookie cookie = entry.getValue();
if (cookie.isExpired(date)) {
// Clear cookies from local store
cookies.remove(name);
// Clear cookies from persistent store
prefsWriter.remove(COOKIE_NAME_PREFIX + name);
// We've cleared at least one
clearedAny = true;
}
}
// Update names in persistent store
if (clearedAny) {
prefsWriter.putString(COOKIE_NAME_STORE,
TextUtils.join(",", cookies.keySet()));
}
prefsWriter.commit();
return clearedAny;
}
#Override
public synchronized List<Cookie> getCookies() {
return new CopyOnWriteArrayList<Cookie>(cookies.values());
}
//
// Cookie serialization/deserialization
//
protected synchronized String encodeCookie(SerializableCookie cookie) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream = new ObjectOutputStream(os);
outputStream.writeObject(cookie);
} catch (Exception e) {
return null;
}
return byteArrayToHexString(os.toByteArray());
}
protected synchronized Cookie decodeCookie(String cookieStr) {
byte[] bytes = hexStringToByteArray(cookieStr);
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
Cookie cookie = null;
try {
ObjectInputStream ois = new ObjectInputStream(is);
cookie = ((SerializableCookie) ois.readObject()).getCookie();
} catch (Exception e) {
e.printStackTrace();
}
return cookie;
}
// Using some super basic byte array <-> hex conversions so we don't have
// to rely on any large Base64 libraries. Can be overridden if you like!
protected synchronized String byteArrayToHexString(byte[] b) {
StringBuffer sb = new StringBuffer(b.length * 2);
for (byte element : b) {
int v = element & 0xff;
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v));
}
return sb.toString().toUpperCase();
}
protected synchronized byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
.digit(s.charAt(i + 1), 16));
}
return data;
}
}
Do something like this:
httpClient.setCookieStoreage(new PersistentCookieStore(this)) in your application subclass where you init the httpclient
You are probably seeing a nasty, long-standing Android bug that causes the symptoms you are describing. Have a look at my answer here: https://stackoverflow.com/a/16447508/769265
You probably have to add following code inside the onCreate() event of launcher Activity.
if (!isTaskRoot()) {
final Intent intent = getIntent();
final String action = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
finish();//Launcher Activity is not the root. So,finish it instead of launching
return;
}
}
android:launchMode="singleInstance"
android:alwaysRetainTaskState="true"
Try adding this two attributes to your Activity in manifest, this will make sure newIntent is called when activity is resumed from background.

Periodic LogCat polling for updates and saving to SDcard

Goal
Collect periodic updates of the LogCat and save (append) those chunks of text to a file on the SDcard
Problem
The Log class doesn't provide updates since a specific time-stamp
Possible solution
My plan is to periodically run code that is similar to: http://www.helloandroid.com/tutorials/reading-logs-programatically
or https://stackoverflow.com/a/9039352/550471
However, with one notable difference: use the -v time parameter to ensure that each line is time-stamped.
After each time the LogCat data is collected, the app will store the time-stamp of the last Log entry. The next time the LogCat data is collected the app will search through the text to find the time-stamp and then save the chunk of data to sdcard that was added to the Log since the specified time-stamp.
Possible problem
If the LogCat data is collected at too short periods then the CPU is busy processing a lot of 'old' data.
If the Logcat data is collected at too long periods then some data could be missed.
Is there a better way ?
This is what I came up with - it works very well when it doesn't freeze up.
As you might know, Runtime.getRuntime().exec("") has a pretty good chance of causing an ANR in Android earlier than Jelly Bean. If someone has a solution to overcome the ANR, then please share.
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import android.os.Environment;
import android.util.Log;
/*
* For (compressed) buffer sizes, see: http://elinux.org/Android_Logging_System
* buffer:main = 64KB
* buffer:radio = 64KB
* buffer:system = 64KB
* buffer:event = 256KB
*
* NOTE: the 'command' must include "-d -v time" !!
* to switch buffers, use "-b <buffer>"
*/
public class LogCatReader {
// constants
private static final String CR = "\r\n";
private static final String END_OF_DATE_TIME = "): ";
private static final int DEFAULT_SEARCH_START_INDEX = 0;
// member variables
private StringBuilder mLog;
private LogThread mLogThread = null;
private String mLastLogReadToken = "";
private String mLogCommand = "";
private int mStringCapacity;
private File mFileTarget = null;
// constructor
public LogCatReader(String command, int capacity) {
mLogCommand = command;
mStringCapacity = capacity;
}
// returns complete logcat buffer
// note: takes about 1.5sec to finish
synchronized public StringBuilder getLogComplete() {
try {
// capacity should be about 25% bigger than buffer size since the
// buffer is compressed
mLog = new StringBuilder(mStringCapacity);
// command to capture log
Process process = Runtime.getRuntime().exec(mLogCommand);
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null) {
// append() is costly if capacity needs to be increased, be sure
// to reserve enough in the first place
mLog.append(line + CR);
}
} catch (IOException e) {
}
return mLog;
}
public String getLogUpdatesOnly() {
String strReturn = "";
StringBuilder sbLog = getLogComplete();
try {
int iStartindex = DEFAULT_SEARCH_START_INDEX;
// if there exists a token from a previous search then use that
if (mLastLogReadToken.length() > 0) {
iStartindex = sbLog.indexOf(mLastLogReadToken);
// if string not found then start at beginning
if (iStartindex == -1) {
// start search at beginning of log
iStartindex = DEFAULT_SEARCH_START_INDEX;
}
}
int iEndindex = sbLog.length();
// if token is found then move index to the next line
if (iStartindex > DEFAULT_SEARCH_START_INDEX) {
iStartindex = sbLog.indexOf(CR, iStartindex);
if (iStartindex != -1) {
iStartindex += CR.length();
} else {
// return an empty string
iStartindex = iEndindex;
}
}
// grab the data between the start and end indices
strReturn = sbLog.substring(iStartindex, iEndindex);
// grab date/time token for next search
iStartindex = sbLog.lastIndexOf(END_OF_DATE_TIME);
if (iStartindex != -1) {
iEndindex = iStartindex;
iStartindex = sbLog.lastIndexOf(CR, iEndindex);
iStartindex += CR.length();
if (iStartindex == -1) {
// read from beginning
iStartindex = 0;
}
mLastLogReadToken = sbLog.substring(iStartindex, iEndindex);
}
} catch (Exception e) {
strReturn = "";
}
return strReturn;
}
public void startPeriodicLogCatReader(int timePeriod, String logfilename) {
if (mLogThread == null) {
mLogThread = new LogThread(timePeriod, logfilename);
mLogThread.start();
}
}
public void stopPeriodicLogCatReader() {
if (mLogThread != null) {
mLogThread.interrupt();
mLogThread = null;
}
}
private class LogThread extends Thread {
private boolean mInterrupted;
private int mTimePeriod;// in seconds
private String mLogref;
private BufferedWriter mBuffWriter = null;
public boolean mPauseLogCollection = false;
// constructor: logfilename is optional - pass null to not use
public LogThread(int timePeriod, String logfilename) {
mTimePeriod = timePeriod;
if (logfilename != null) {
File fLogFolder = new File(
Environment.getExternalStorageDirectory() + "/logfiles");
if (fLogFolder.exists() == false) {
if (fLogFolder.mkdirs() == false) {
Log.e("LogCatReader",
"Could not create "
+ fLogFolder.getAbsolutePath());
}
}
mFileTarget = new File(
Environment.getExternalStorageDirectory() + "/logfiles",
logfilename);
if (mFileTarget.exists() == false) {
try {
// file doesn't yet exist - create a fresh one !
mFileTarget.createNewFile();
} catch (IOException e) {
e.printStackTrace();
mFileTarget = null;
}
}
}
}
#Override
public void interrupt() {
mInterrupted = true;
super.interrupt();
}
#Override
public void run() {
super.run();
// initialization
mInterrupted = false;
// set up storage
if (mFileTarget != null) {
try {
mBuffWriter = new BufferedWriter(new FileWriter(
mFileTarget, true), 10240);
} catch (IOException e) {
e.printStackTrace();
}
}
while ((mInterrupted == false) && (mBuffWriter != null)) {
if (mPauseLogCollection == false) {
// read log updates
mLogref = getLogUpdatesOnly();
// save log updates to file
try {
mBuffWriter.append(mLogref);
mBuffWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
if (!mInterrupted) {
try {
sleep(mTimePeriod * 1000);
} catch (InterruptedException e) {
}
}
}
if (mBuffWriter != null) {
try {
mBuffWriter.close();
mBuffWriter = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}// end of inner class
}// end of outer class
The procedure I used to find only the updates is to capture the date and time of the very last line of a logcat and use that as the search token next time around.
To use this class, the following is an example:
LogCatReader logcatPeriodicReader = new LogCatReader("logcat -b main -d -v time", 80 * 1024);//collect "main" buffer, exit after reading logcat
logcatPeriodicReader.startPeriodicLogReader(90, "log.txt");//read logcat every 90 secs

Jersey client in android with protocol buffer

I would like to create a Jersey client in Android using protocol buffer.
I am using the following libraries:
jersey-client-1.8.jar
jersey-core-1.8.jar
protobuf-java-2.4.0a.jar
The code I have written:
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.WebResource.Builder;
import com.sun.jersey.api.client.filter.ClientFilter;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
BaseRestClient client = BaseRestClient.create("", "");
HTTPBasicAuthFilter authenticationFilter =
new HTTPBasicAuthFilter(username, password);
client.addFilter(authenticationFilter);
..........
..........
WebResource webResourceGetMea = client.resource(url);
webResourceGetMea = webResourceGetMea.path("/accounts").path("/login");
ClientResponse responseGetMea = webResourceGetMea.type("application/x-protobuf").get(ClientResponse.class);
The above code is running successfully as Java main() application but when I am running it on Android the responseGetMea() object is null (last line of code).
I am using the "application/x-protobuf" because in such way it was defined on server side.
I have added the INTERNET permission in my Android application.
I also checked the URL from Android browser and when I click it, it prompts me to input the user name and the password(the expected behaviour).
I really appreciate any help.
Regards,
kalgik
An addition... When trying to POST with the version of Buscador at the previous link it complained with some errors. The new version of Buscador that works for POSTing is the following,
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import com.sun.jersey.spi.service.ServiceFinder;
import com.sun.jersey.spi.service.ServiceFinder.DefaultServiceIteratorProvider;
import com.sun.jersey.spi.service.ServiceFinder.ServiceIteratorProvider;
public class Buscador<T> extends ServiceIteratorProvider<T>
{
private static final HashMap<String, String[]> SERVICES = new HashMap<String, String[]>();
private static final String[] com_sun_jersey_spi_HeaderDelegateProvider = new String[] {
"com.sun.jersey.core.impl.provider.header.LocaleProvider",
"com.sun.jersey.core.impl.provider.header.EntityTagProvider",
"com.sun.jersey.core.impl.provider.header.MediaTypeProvider",
"com.sun.jersey.core.impl.provider.header.CacheControlProvider",
"com.sun.jersey.core.impl.provider.header.NewCookieProvider",
"com.sun.jersey.core.impl.provider.header.CookieProvider",
"com.sun.jersey.core.impl.provider.header.URIProvider",
"com.sun.jersey.core.impl.provider.header.DateProvider",
"com.sun.jersey.core.impl.provider.header.StringProvider"
};
private static final String[] com_sun_jersey_spi_inject_InjectableProvider = new String[] {
"com.sun.jersey.core.impl.provider.xml.SAXParserContextProvider",
"com.sun.jersey.core.impl.provider.xml.XMLStreamReaderContextProvider",
"com.sun.jersey.core.impl.provider.xml.DocumentBuilderFactoryProvider",
"com.sun.jersey.core.impl.provider.xml.TransformerFactoryProvider"
};
private static final String[] javax_ws_rs_ext_MessageBodyReader = new String[] {
"com.sun.jersey.core.impl.provider.entity.StringProvider",
"com.sun.jersey.core.impl.provider.entity.ByteArrayProvider",
"com.sun.jersey.core.impl.provider.entity.FileProvider",
"com.sun.jersey.core.impl.provider.entity.InputStreamProvider",
"com.sun.jersey.core.impl.provider.entity.DataSourceProvider",
"com.sun.jersey.core.impl.provider.entity.RenderedImageProvider",
"com.sun.jersey.core.impl.provider.entity.MimeMultipartProvider",
"com.sun.jersey.core.impl.provider.entity.FormProvider",
"com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider",
"com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$App",
"com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$Text",
"com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General",
"com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$App",
"com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$Text",
"com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General",
"com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$App",
"com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$Text",
"com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General",
"com.sun.jersey.core.impl.provider.entity.ReaderProvider",
"com.sun.jersey.core.impl.provider.entity.DocumentProvider",
"com.sun.jersey.core.impl.provider.entity.SourceProvider$StreamSourceReader",
"com.sun.jersey.core.impl.provider.entity.SourceProvider$SAXSourceReader",
"com.sun.jersey.core.impl.provider.entity.SourceProvider$DOMSourceReader",
"com.sun.jersey.core.impl.provider.entity.XMLRootObjectProvider$App",
"com.sun.jersey.core.impl.provider.entity.XMLRootObjectProvider$Text",
"com.sun.jersey.core.impl.provider.entity.XMLRootObjectProvider$General",
"com.sun.jersey.core.impl.provider.entity.EntityHolderReader"
};
private static final String[] javax_ws_rs_ext_MessageBodyWriter = new String[] {
"com.sun.jersey.core.impl.provider.entity.StringProvider",
"com.sun.jersey.core.impl.provider.entity.ByteArrayProvider",
"com.sun.jersey.core.impl.provider.entity.FileProvider",
"com.sun.jersey.core.impl.provider.entity.InputStreamProvider",
"com.sun.jersey.core.impl.provider.entity.DataSourceProvider",
"com.sun.jersey.core.impl.provider.entity.RenderedImageProvider",
"com.sun.jersey.core.impl.provider.entity.MimeMultipartProvider",
"com.sun.jersey.core.impl.provider.entity.FormProvider",
"com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider",
"com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$App",
"com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$Text",
"com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General",
"com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$App",
"com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$Text",
"com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General",
"com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$App",
"com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$Text",
"com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General",
"com.sun.jersey.core.impl.provider.entity.ReaderProvider",
"com.sun.jersey.core.impl.provider.entity.DocumentProvider",
"com.sun.jersey.core.impl.provider.entity.StreamingOutputProvider",
"com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter"
};
static
{
SERVICES.put("com.sun.jersey.spi.HeaderDelegateProvider",
com_sun_jersey_spi_HeaderDelegateProvider);
SERVICES.put("com.sun.jersey.spi.inject.InjectableProvider",
com_sun_jersey_spi_inject_InjectableProvider);
SERVICES.put("javax.ws.rs.ext.MessageBodyReader", javax_ws_rs_ext_MessageBodyReader);
SERVICES.put("javax.ws.rs.ext.MessageBodyWriter", javax_ws_rs_ext_MessageBodyWriter);
}
DefaultServiceIteratorProvider defaultServiceIteratorProvider = new ServiceFinder.DefaultServiceIteratorProvider();
#SuppressWarnings("unchecked")
#Override
public Iterator<Class<T>> createClassIterator(Class<T> service, String serviceName,
ClassLoader loader, boolean ignoreOnClassNotFound)
{
String[] classesNames = SERVICES.get(serviceName);
System.out.println("!!!!!!!!!!!! serviceName: " + serviceName + " !!!!!!!!!!!!!!!!!!!");
if(classesNames==null)
{
return defaultServiceIteratorProvider.createClassIterator(service, serviceName, loader, ignoreOnClassNotFound);
}
int length = classesNames.length;
ArrayList<Class<T>> classes = new ArrayList<Class<T>>(length);
for (int i = 0; i < length; i++)
{
try
{
classes.add((Class<T>) Class.forName(classesNames[i]));
} catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
//return null;
return classes.iterator();
}
#Override
public Iterator<T> createIterator(Class<T> service, String serviceName, ClassLoader loader,
boolean ignoreOnClassNotFound)
{
String[] classesNames = SERVICES.get(serviceName);
int length = classesNames.length;
ArrayList<T> classes = new ArrayList<T>(length);
for (int i = 0; i < length; i++)
{
try
{
classes.add(service.cast(Class.forName(classesNames[i]).newInstance()));
} catch (IllegalAccessException e)
{
e.printStackTrace();
} catch (InstantiationException e)
{
e.printStackTrace();
} catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
return classes.iterator();
}
}
I employed the solution offered at,
java.lang.NullPointerException on Android
as proposed by
Lucas Ventura, Aug 25, 2010; 9:15am
and it worked like a charm.
[EDIT]
Well, small comment/correction. Running in an HTC phone, caused the app to destroy/create when i minimised (not sure if this is normal) and re-opened. The solution proposed mandates that the Jersey client object and the ServiceFinder setting should occur in a static context. This should give a clue,
private static final BaseRestClient client;
static {
client = BaseRestClient.create("", ""); // just a helper class
ServiceFinder.setIteratorProvider(new Buscador());
}
Otherwise, the class loading fix complains in a rather strange way. Hope this helps someone...
Cheers!

Categories

Resources