Best practice to pull content from the web?
I'm new to developing Android applications, and have only a little experience with Java in school. I was redirected to StackOverflow from the Google groups page when I was looking for the Android Beginners group. I have a question about what is best practice to pull content from a web source and parse it.
Firstly, I would eventually like to have my application threaded (by use of Handler?), however, my issue now is that the class I have created (Server) to connect and fetch content often fails to retrieve the content, which causes my JSON parser class (JSONParser) to fail, and my View to display nothing. After navigating to the previous Activity, and attempting to call the connect(), fetch(), and parse() methods on the same remote URI, it will work.
Why does this (sometimes retrieve the remote data) happen sometimes, but not always? What is the best practice, including the use of ProgressDialog and the internal Handler class, to make my application seamless to the user. Is this the best place to ask this question? Thanks for your help
Here is the code I'm using now.
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import org.apache.http.util.ByteArrayBuffer;
import android.util.Log;
public class Server {
public static final String HTTP_PROTOCOL = "https://";
public static final String EXAMPLE_NET_DOMAIN = "example.domain.net/";
private final String API_KEY = "1234567890";
private static final String API_ENDPOINT = "api.js?";
public final String FORMAT = "json";
public String API_VERSION;
public String METHOD;
public String OPTIONAL_ARGUMENTS;
public String json;
public URL jURL;
public URLConnection jConnection;
public BufferedReader jIn;
public InputStream is = null;
/**
* @param aPIVERSION
* @param mETHOD
* @param oPTIONALARGUMENTS
*/
public Server(String aPIVERSION, String mETHOD, String oPTIONALARGUMENTS) {
super();
API_VERSION = aPIVERSION;
METHOD = mETHOD;
OPTIONAL_ARGUMENTS = oPTIONALARGUMENTS;
connect();
Log.i("DEBUG:","connect();");
}
/**
* @param aPIVERSION
* @param mETHOD
*/
public Server(String aPIVERSION, String mETHOD) {
super();
API_VERSION = aPIVERSION;
METHOD = mETHOD;
OPTIONAL_ARGUMENTS = "";
connect();
}
/**
* @param aPIVERSION
* @param mETHOD
*/
public void connect(){
try {
jURL = new URL(HTTP_PROTOCOL
+ EXAMPLE_NET_DOMAIN
+ API_ENDPOINT
+ "api=" + this.API_VERSION
+ "&method=" + this.METHOD
+ "&format=" + FORMAT
+ "&apikey=" + API_KEY
+ this.OPTIONAL_ARGUMENTS);
jConnection = jURL.openConnection();
} catch (IOException e) {
Log.e("USER: ", "Error in server connection.");
Log.e("DEBUG:", "Error in server connection");
}
Log.i("USER: ", "Connection success!");
}
public String fetch() {
try {
is = jConnection.getInputStream();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.e("DEBUG:", "fetch-1() error");
}
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayBuffer baf = new ByteArrayBuffer(50);
int current = 0;
try {
while((current = bis.read()) != -1){
baf.append((byte)current);
}
Log.i("DEBUG:",new String(baf.toByteArray()));
} catch (IOException e) {
// TODO Auto-generated catch block
Log.e("DEBUG:","fetch() ERROR!");
}
/* Convert the Bytes read to a String. */
Log.i("DEBUG:",new String(baf.toByteArray()));
return new String(baf.toByteArray());
}
/* Returns a string containing a concise, human-readable description of jURL
*
*/
public String showUrl() {
return jURL.toExternalForm();
}
}
and the code from my ListActivity
/* Called when the activity is starting. This is where most initialization should go: calling setContentView(int) to inflate the activity's UI, using findViewById(int) to programmatically interact with widgets in the UI, calling managedQuery(android.net.Uri, String[], String, String[], String) to retrieve cursors for data being displayed, etc.
* @see android.app.Activity#onCreate()
*/
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.i("LIFECYCLE: ", "RS.class onCreate()");
Server serverConnection = new Server(API_VERSION, METHOD, OPTIONAL_ARGUMENTS);
json = serverConnection.fetch();
JSONParser jParser = new JSONParser(json);
groupData = jParser.parseJsonForRecentShowList();
SimpleAdapter adapter = new SimpleAdapter( this, groupData, android.R.layout.simple_list_item_2, new String[] { "venue","showdate"},
new int[]{ android.R.id.text2, android.R.id.text1 } );
setListAdapter( adapter );
registerForContextMenu(getListView());
}
Here is my JSON Parser class
/**
*
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
/**
* @author
*
*/
public class JSONParser {
public List<Map<String, String>> groupData = new ArrayList<Map<String, String>>();
private String jString;
private Map<String, String> group;
private JSONArray jArray;
private String setlistData;
public String notesString = "";
public String[] splitSetsArray;
public String[] splitEncoreArray;
public String[] extraWork;
public String[] notesArray;
public String pVenue_text;
public String pCity_text;
public String pState_text;
public String pCountry_text;
public String pDate_text;
public String pSet1_text;
public String pSet2_text;
public String pSet3_text;
public String pEncore1_text;
public String pEncore2_text;
public String pNotes_text;
public int totalNumberOfSets;
public int totalNumberOfEncores;
public int totalNumberOfNotes;
public JSONObject jObject;
public JSONParser(String json) {
// TODO Auto-generated constructor stub
jString = json;
}
/**
* @return
*/
public List<Map<String, String>> parseJsonForRecentShowList(){
try {
jArray = new JSONArray(jString);
for(int i=0;i<jArray.length();i++) {
jObject = jArray.getJSONObject(i);
group = new HashMap<String, String>();
group.put("showdate", jObject.getString("showdate"));
group.put("venue", jObject.getString("venue"));
groupData.add(group);
}
} catch (JSONException e) {
Log.e("DEBUG: ", "JSON Parse error!");
}
return groupData;
}
/**
*
*/
public void parseJsonForSetlistData(){
if(jString != null){
try {
jArray = new JSONArray(jString);
jObject = jArray.getJSONObject(0);
pVenue_text = jObject.getString("venue") ;
pCity_text = jObject.getString("city") + ", " ;
pState_text = jObject.getString("state") + ", " ;
pCountry_text = jObject.getString("country") ;
pDate_text = jObject.getString("nicedate") ;
setlistData = nohtml(jObject.getString("setlistdata"));
splitSetsArray = setlistData.split("Set..:.");
totalNumberOfSets = splitSetsArray.length-1;
String[] splitEncoreArray = splitSetsArray[splitSetsArray.length-1].split("Encore:.");
totalNumberOfEncores = splitEncoreArray.length-1;
splitSetsArray[splitSetsArray.length-1] = splitEncoreArray[0];
splitEncoreArray[0] = "";
extraW开发者_如何学Cork = splitEncoreArray[splitEncoreArray.length-1].split("\\[1\\]");
notesArray = extraWork[extraWork.length-1].split("\\[.\\]");
totalNumberOfNotes = notesArray.length-1;
splitEncoreArray[splitEncoreArray.length-1] = extraWork[0];
//notesArray[0] = "";
for(int i=0;i<notesArray.length;i++){
int number = i+1;
notesString += "["+number+"] "+notesArray[i] + "\n";
}
if(totalNumberOfSets != 0) {
pSet1_text = "Set 1: " + splitSetsArray[1];
if (totalNumberOfSets > 1){
pSet2_text = "Set 2: " + splitSetsArray[2];
} else {
pSet2_text = "";
}
if (totalNumberOfSets > 2){
pSet3_text = "Set 3: " + splitSetsArray[3];
} else {
pSet3_text = "";
}
}
pEncore1_text = "Encore: " + splitEncoreArray[1];
if (totalNumberOfEncores > 1) {
pEncore2_text = "Encore 2: " + splitEncoreArray[2];
} else {
pEncore2_text = "";
}
pNotes_text = notesString;
Log.e("DEBUG: ", "JSON Parsed!");
} catch (JSONException e) {
Log.e("ERROR:","caught JSON Exception at parseForSetlistData()");
}
} else {
Log.e("ERROR:", "jString = null");
pVenue_text = "I'm Sorry, the Setlist Data could not be retrieved from server. Please try again.";
}
}
public void parseJsonForReviews(){
try {
jArray = new JSONArray(jString);
for(int i=0;i<jArray.length();i++){
jObject = jArray.getJSONObject(i);
group = new HashMap<String, String>();
group.put("author", jObject.getString("author"));
group.put("review", jObject.getString("review"));
groupData.add(group);
}
} catch (JSONException e) {
Log.e("DEBUG: ", "JSON Reviews parse error!");
}
}
public List<Map<String, String>> parseJsonForYears(){
try {
jArray = new JSONArray(jString);
for(int i=0;i<jArray.length();i++){
JSONObject jObject = jArray.getJSONObject(i);
group = new HashMap<String, String>();
group.put("showdate", jObject.getString("showdate"));
group.put("venue", jObject.getString("venue"));
groupData.add(group);
}
} catch (JSONException e) {
Log.e("DEBUG: ", "JSON Years parse error!");
}
Collections.reverse(groupData);
return groupData;
}
public String nohtml(String json){
return json.toString().replaceAll("\\<.*?>", "");
}
}
Well, without any error logs it's difficult to guess what might be your issue.
However, you might have better luck if you used the AndroidHttpClient for your connection. It does a good job of recovering/retrying.
Also, regarding multi-threading, AsyncTask is a good starting place.
My ShortcutLink application, available on github, might give you a heads up on how to use these.
EDIT
You might try converting the InputStream directly to a String using the following code from java2s.com.
public static String convertStreamToString(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
is.close();
return sb.toString();
}
You might have an easier time using the Apache HTTP modules (included in Android), which can get you the response of an HTTP GET in 3 lines:
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet("http://stackoverflow.com/a/b/c?param=value");
String content = client.execute(get, new BasicResponseHandler());
You could then feed content
directly into the JSON parser:
JSONObject json = new JSONObject(content);
Generally, you could wrap this operation up inside an AsyncTask
, showing/hiding a ProgressDialog from its onPreExecute
and onPostExecute
methods respectively, finally calling a Handler to return control back to your Activity when it's done.
For tasks like that I would say you definitely want to go with the AsyncTask.
Using this, you would implement the doInBackground()
function to download and parse your data.
As for progress, it is not as easy to get the status of a simple text page such as your example. I would recommend having a "busy" image that is displayed and hidden in the onPreExecute()
and onPostExecute()
funciton respectively.
Additionally, anything you want to display when all is said and done can be implemented in the onPostExecute()
as well. This will get called from the main UI thread, so it will be safe to directly modify the View
you have displayed and want to update.
To get most of what you are looking for I would do something like below.
View v = ... ; // the view you want to update with stuff
new AsyncTask<String, Void, String>()
{
@Override
protected Void doInBackground(String... urls)
{
String ret = "" ; // Doesn't have to be a String
try{
JSONObject jo = new JSONObject(SimpleHttpClient.getPage(urls[0])) ;
// parse everything you want into ret
ret = jo.getString("xxx") ;
}
catch(Exception e){
ret = "Error: Could not get band data: "+e ;
}
return ret ;
}
public void onPreExecute()
{
// display some busy spinny thing
}
public void onPostExecute(String s)
{
// hide some busy spinny thing
// do what you want to the view!
v.setText(s) ;
}
}.execute("Your url here") ;
As for the JSONObject jo = new JSONObject(SimpleHttpClient.getPage(url));
line,
I would say @Dave's answer definitely covers everything and is probably the easiest/more corrent(/maybe more efficient) solution.
As an alternative, the class below provides a very simple interface (catch Exceptions when OK to ignore) while giving the user the ability to dive deeper when needed (get InputStream or Reader if applicable for efficiency).
SimpleHttpClient.java:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
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.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
public class SimpleHttpClient
{
private static final int BUFSIZE = 2048 ;
/**
* Returns an HttpEntity of the page specified by the given URL.
*
* @param url URL of the page to retrieve.
* @return HttpEntity representing the text of the page.
* @throws ClientProtocolException
* @throws IOException
*/
private static HttpEntity getEntity(String url) throws ClientProtocolException, IOException
{
HttpClient client = new DefaultHttpClient() ;
HttpUriRequest request = new HttpGet(url) ;
HttpResponse response = client.execute(request) ;
return response.getEntity() ;
}
/**
* Send request to given URL and disregard response.
*
* @param url URL of the page to request.
* @throws ClientProtocolException
* @throws IOException
*/
public static void send(String url) throws ClientProtocolException, IOException
{
getEntity(url) ;
}
/**
* Send request to given URL and disregard response.
*
* @param url URL of the page to request.
* @return Return true if no exceptions, false if exceptions occurred.
*/
public static boolean trySend(String url)
{
boolean ret = true ;
try {
send(url) ;
}
catch (Exception e) { ret = false ; }
return ret ;
}
/**
* Returns an InputStream of the page specified by the given URL.
*
* @param url URL of the page to retrieve.
* @return InputStream representing the text of the page.
* @throws ClientProtocolException
* @throws IOException
*/
public static InputStream getStream(String url) throws ClientProtocolException, IOException
{
return getEntity(url).getContent() ;
}
/**
* Returns an BufferedReader of the page specified by the given URL.
*
* @param url URL of the page to retrieve.
* @return BufferedReader representing the text of the page.
* @throws ClientProtocolException
* @throws IOException
*/
public static BufferedReader getReader(String url) throws ClientProtocolException, IOException
{
HttpEntity he = getEntity(url) ;
int size = (int)he.getContentLength() ;
size = (BUFSIZE > size && size > 0) ? size+32 : BUFSIZE ;
return new BufferedReader(new InputStreamReader(he.getContent()), size) ;
}
/**
* Return the page specified by the given URL as a String.
*
* @param url URL of the page to retrieve.
* @return String representing the text of the page.
* @throws ClientProtocolException
* @throws IOException
*/
public static String getPage(String url) throws ClientProtocolException, IOException
{
StringBuffer sb = new StringBuffer("") ;
String line = "" ;
String nl = System.getProperty("line.separator") ;
String page = "" ;
BufferedReader in = null;
try {
in = getReader(url) ;
while((line = in.readLine()) != null) {
sb.append(line + nl) ;
}
page = sb.toString() ;
}
finally {
if( in != null ) try { in.close() ; } catch (Exception e) {} ;
}
return page ;
}
/**
* Same as getPage, returns the given URL as a String, but masks any Exceptions and
* returns null instead.
*
* @param url URL of the page to retrieve.
* @return String representing the text of the page, or null if there is an error.
*/
public static String tryPage(String url)
{
String tmp = null ;
try {
tmp = getPage(url) ;
}
catch (Exception e) {} ;
return tmp ;
}
}
精彩评论