Here's the code I have written for communication between the Android app and the backend server. You may want to encrypt your byte data.
Note that in the following code, I do not depend on the Content-Length to get the data. I use a 4096 byte buffer to read data until there are no data. I used to depend on the Content-Length to retrieve data from the data. However, when I was testing using Nexus S on Android SDK 2.3, the content length was null. After some investigation, I found that the server was sending gzip data and it wipes out the content length variable.
Here's the http connection code:
public static String getHttpWithResponse(String urlStr, byte[] data) {
String responseFromServer = null;
HttpURLConnection con = null;
try {
URL url = new URL(urlStr);
con = (HttpURLConnection) url.openConnection();
con.setReadTimeout(50000);
con.setConnectTimeout(20000);
con.setRequestProperty("Connection", "Keep-Alive");
con.setInstanceFollowRedirects(true);
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
con.setRequestMethod("GET");
con.setDoInput(true);
if (data != null) {
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "*****************************************";
con.setRequestMethod("POST");
con.setDoOutput(true);
con.setRequestProperty("Content-Type",
"multipart/form-data;boundary=" + boundary);
con.setRequestProperty("Content-Disposition",
"multipart/form-data");
OutputStream o = con.getOutputStream();
o.write(data, 0, data.length);
o.close();
} else {
con.setInstanceFollowRedirects(true);
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
con.setRequestMethod("GET");
}
if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new Exception("Response is not empty");
}
InputStream i = con.getInputStream();
ByteArrayOutputStream bs = new ByteArrayOutputStream();
byte[] bytesToRead = new byte[4096];
int actuallyRead = 0;
while (true) {
int currentlyRead = i.read(bytesToRead, 0,
bytesToRead.length);
if (currentlyRead <= 0)
break;
bs.write(bytesToRead, 0, currentlyRead);
actuallyRead += currentlyRead;
}
bytesToRead = null;
i.close();
if (actuallyRead < 0) {
throw new Exception("Nothing Read");
}
bytesToRead = bs.toByteArray();
responseFromServer = new String(bytesToRead, "UTF-8");
} catch (Exception e) {
responseFromServer = null;
} finally {
if (con != null) {
con.disconnect();
}
}
return responseFromServer;
}
Wednesday, May 4, 2011
Tuesday, May 3, 2011
How to dynamically change TextView font color in Android
The following is an easy way to change the font color dynamically in activities.
Activity Code:
String msg = "Hello World!";
TextView tv = (TextView) findViewById(R.id.tv);
txt.setText(Html.fromHtml("<font color='green'>" + msg + "</font>"));
Activity Code:
String msg = "Hello World!";
TextView tv = (TextView) findViewById(R.id.tv);
txt.setText(Html.fromHtml("<font color='green'>" + msg + "</font>"));
XML layout Code:
<TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
Monday, May 2, 2011
HttpConnection with AsyncTask and Loading Dialog
One of the common tasks in Android is to make an Http connection to retrieve some information for the app to use. In a lot of areas, internet speed is slow; and we should always assume a slow connection. If we do a synchronous Http connection (calling from the UI thread), we will block user activity if the http response does not return immediately. When users click on anywhere on the screen, there would be no response because the app is waiting for the Http connection to return on the UI thread. Users may assume the app is frozen. In addition, the ANR dialog box may show up.
The solution to this is to sponsor another a background thread to do the Http connection, without blocking the UI thread. We can achieve this by extending the AsyncTask. It also makes sense to put a cancel-able loading dialog box to show that the task is running. We want it to be cancel-able because we do not want to block the UI thread.
The following shows an example for download an image:
private class LoadImageTask extends
AsyncTask<String, Integer, Bitmap> {
private Activity mActivity;
private ProgressDialog mDialog;
public LoadImageTask(CustomActivity activity) {
mActivity = activity;
}
protected void onPreExecute() {
if (mActivity != null && mActivity.getIsActive()) {
mDialog = new ProgressDialog(mActivity);
mDialog.setMessage("Loading...");
mDialog.setCancelable(true);
OnCancelListener ocl = new OnCancelListener() {
public void onCancel(DialogInterface arg0) {
LoadImageTask.this.cancel(true);
}
};
mDialog.setOnCancelListener(ocl);
mDialog.show();
}
}
@Override
protected Bitmap doInBackground(String... m) {
if(m != null && m.size() > 0) {
Bitmap image = HttpConnect.downloadImage(m[0]);
}
return image;
}
@Override
protected void onProgressUpdate(Integer... progress) {
}
@Override
protected void onPostExecute(Bitmap image) {
if (mActivity != null && mActivity.getIsActive() && mDialog != null) { mDialog.dismiss();
}
if(im != null && image != null) {
iv.setImageBitmap(image); // iv is the ImageView object
}
}
}
Notes:
CustomActivity extends Activity. It has a private variable called mActive is keep track of if the activity is active. This is needed to keep track of if the activity is still on. When an AsyncTask returns, it is easy for a user to be ten activities ahead of the activity that started the AsyncTask. In this case, when the dialog dismisses, you may get "java.lang.IllegalArgumentException: View not attached to window manager".
The following is the implementation of the CustomActivity. To use it, you will want your activities to extend it.
public class CustomActivity extends Activity {
protected boolean mActive = false;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActive = true;
}
protected void onPause() {
super.onPause();
mActive = false;
}
public boolean getIsActive() {
return mActive;
}
...
}
To use the class, you will call the following in your Activity:
new LoadImageTask(this).execute(YOUR_IMAGE_URL);
The solution to this is to sponsor another a background thread to do the Http connection, without blocking the UI thread. We can achieve this by extending the AsyncTask. It also makes sense to put a cancel-able loading dialog box to show that the task is running. We want it to be cancel-able because we do not want to block the UI thread.
The following shows an example for download an image:
private class LoadImageTask extends
AsyncTask<String, Integer, Bitmap> {
private Activity mActivity;
private ProgressDialog mDialog;
public LoadImageTask(CustomActivity activity) {
mActivity = activity;
}
protected void onPreExecute() {
if (mActivity != null && mActivity.getIsActive()) {
mDialog = new ProgressDialog(mActivity);
mDialog.setMessage("Loading...");
mDialog.setCancelable(true);
OnCancelListener ocl = new OnCancelListener() {
public void onCancel(DialogInterface arg0) {
LoadImageTask.this.cancel(true);
}
};
mDialog.setOnCancelListener(ocl);
mDialog.show();
}
}
@Override
protected Bitmap doInBackground(String... m) {
if(m != null && m.size() > 0) {
Bitmap image = HttpConnect.downloadImage(m[0]);
}
return image;
}
@Override
protected void onProgressUpdate(Integer... progress) {
}
@Override
protected void onPostExecute(Bitmap image) {
if (mActivity != null && mActivity.getIsActive() && mDialog != null) { mDialog.dismiss();
}
if(im != null && image != null) {
iv.setImageBitmap(image); // iv is the ImageView object
}
}
}
Notes:
CustomActivity extends Activity. It has a private variable called mActive is keep track of if the activity is active. This is needed to keep track of if the activity is still on. When an AsyncTask returns, it is easy for a user to be ten activities ahead of the activity that started the AsyncTask. In this case, when the dialog dismisses, you may get "java.lang.IllegalArgumentException: View not attached to window manager".
The following is the implementation of the CustomActivity. To use it, you will want your activities to extend it.
public class CustomActivity extends Activity {
protected boolean mActive = false;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActive = true;
}
protected void onPause() {
super.onPause();
mActive = false;
}
public boolean getIsActive() {
return mActive;
}
...
}
To use the class, you will call the following in your Activity:
new LoadImageTask(this).execute(YOUR_IMAGE_URL);
Wednesday, March 16, 2011
How to Build an Image Cache Manager in Android
In mobile phone networks, http connections are expensive and time-consuming. Fetching images is a very common job that is in almost every application. Other than asynchronous loading, it is better to cache the images for future reuse.
Reasons for caching images:
Concept
We will store the in-memory images in a HashMap that maps a filename to an ImageCacheObject. The ImageCacheObject will have the bitmap image and a the lastRetrieved date (the date when the image was last retrieved). We will have an ImageCacheManager that deletes the images that were not used in a period of time from the HashMap. This ImageCacheManager will run on a timer thread.
Implementation
Let's assume you already have the following functions:
We will go through the implementation of these image cache components
empty() is for clearing out the whole ImageCache HashMap, while expireOldCache is for removing the images that were not retrieved in one min (60000ms). You may want to make this time interval dynamic. A user that browses slowly will populate the ImageCache much slower than a user who browses quickly; in this case, you will want a longer timeout before each image is deleted. You may also want to consider memory use as well.
Reasons for caching images:
- avoid http-fetching of the same images; faster loading of the activity
- better memory managing (if you are showing same images in a list)
Concept
We will store the in-memory images in a HashMap that maps a filename to an ImageCacheObject. The ImageCacheObject will have the bitmap image and a the lastRetrieved date (the date when the image was last retrieved). We will have an ImageCacheManager that deletes the images that were not used in a period of time from the HashMap. This ImageCacheManager will run on a timer thread.
Implementation
Let's assume you already have the following functions:
- fetchImage(String fileUrl, Activity a, boolean useImageCache) - the activity is used for retrieval of the Application object
We will go through the implementation of these image cache components
- ImageCacheObject - holds the image and the lastRetrieved date (when the image was last used)
- ImageCache - a subclass for HashMap that maps the filename (key) to the image
- ImageCacheManager - timer for deleting images that are not being used
- CustomApplication - extends android.app.Application and manages the ImageCacheObject to expire the older images; you should name this to your application's name
ImageCacheObject
There are only the bitmap and lastRetrieved date in this object. This is used by the ImageCache Hashmap.
package com.custom.android;
import java.util.Date;
import android.graphics.Bitmap;
public class ImageCacheObject {
public Bitmap bm;
public Date lastRetrieved;
public ImageCacheObject(Bitmap bm) {
lastRetrieved = new Date();
this.bm = bm;
}
public void touch() {
lastRetrieved = new Date();
}
public Bitmap getBitmap() {
touch();
return bm;
}
}
ImageCache
empty() is for clearing out the whole ImageCache HashMap, while expireOldCache is for removing the images that were not retrieved in one min (60000ms). You may want to make this time interval dynamic. A user that browses slowly will populate the ImageCache much slower than a user who browses quickly; in this case, you will want a longer timeout before each image is deleted. You may also want to consider memory use as well.
package com.custom.android;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import android.graphics.Bitmap;
import android.util.Log;
public class ImageCache extends HashMap<String, ImageCacheObject> {
public void addImage(String filename, Bitmap image) {
synchronized (this) {
if (!this.containsKey(filename))
this.put(filename, new ImageCacheObject(image));
}
}
public Bitmap getImage(String filename) {
synchronized (this) {
if (this.size() > 0) {
ImageCacheObject o = this.get(filename);
return (o != null) ? o.getBitmap() : null;
}
}
return null;
}
public void empty() {
empty(false);
}
public void empty(boolean doTimeCheck) {
Date d = new Date();
synchronized (this) {
if (this.size() > 0) {
try {
ArrayList<String> list = new ArrayList<String>();
for (Iterator i = this.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, ImageCacheObject> pair = (Map.Entry<String, ImageCacheObject>) i
.next();
ImageCacheObject o = pair.getValue();
if (!doTimeCheck || d.getTime() - o.lastRetrieved.getTime() > 60000) {
o.bm = null;
pair.setValue(null);
list.add(pair.getKey());
}
}
for(int i = 0; i < list.size(); i++) {
this.remove(list.get(i));
}
} catch (Exception e) {
Log.v("ImageCache", "ImageCache: Exception in function empty()");
}
}
}
}
public void expireOldCache() {
empty(true);
}
}
ImageCacheManager
This is used for periodically clearing out the ImageCache hashmap. isRunning prevents multiple TimerTask from running at the same time.
package com.custom.android;
import java.util.TimerTask;
public class ImageCacheManager extends TimerTask {
private boolean isRunning = false;
public void run() {
if (isRunning)
return;
synchronized (this) {
isRunning = true;
try {
CustomApplication.mApp.expireOldCache();
} catch (Exception e) {
} catch (Throwable t) {
}
isRunning = false;
}
}
}
package com.custom.android;
import java.util.TimerTask;
public class ImageCacheManager extends TimerTask {
private boolean isRunning = false;
public void run() {
if (isRunning)
return;
synchronized (this) {
isRunning = true;
try {
CustomApplication.mApp.expireOldCache();
} catch (Exception e) {
} catch (Throwable t) {
}
isRunning = false;
}
}
}
CustomApplication
This is where any activities can access the ImageCache hashmap.
package com.custom.android;
package com.custom.android;
import java.util.Timer;
import android.app.Activity;
import android.app.Application;
import android.graphics.Bitmap;
public class CustomApplication extends Application {
public static CustomApplication mApp = null;
private ImageCache mImageCache = new ImageCache();
static private Timer cacheTimer = new Timer();
@Override
public void onCreate() {
mApp = this;
cacheTimer.schedule(new ImageCacheManager(), 0, 30000);
super.onCreate();
}
@Override
public void onTerminate() {
cacheTimer.cancel();
cacheTimer = null;
super.onTerminate();
}
public static CustomApplication getCustomApplication(Activity a) {
Application app = a.getApplication();
return (CustomApplication) app;
}
public Bitmap getImage(String filename) {
return mImageCache.getImage(filename);
}
public void addImage(String filename, Bitmap image) {
mImageCache.addImage(filename, image);
}
public void expireOldCache() {
mImageCache.expireOldCache();
}
}
FetchImage
This will be your utility function for fetching images from a http connection. I provided a sample fetch function below. The red-colored text are places where you want to put the code. The function will try to get the image from the ImageCache if it exists. If not, it will add the image to the cache.
public static Bitmap fetchImage(String fileUrl, Activity a, boolean useImageCache) {
public static Bitmap fetchImage(String fileUrl, Activity a, boolean useImageCache) {
if (fileUrl == null || fileUrl.equals(""))
return null;
Bitmap bm = null;
if (a != null) {
CustomApplication pa = CustomApplication.getCustomApplication(a);
bm = pa.getImage(fileUrl);
if (bm != null) {
return bm;
}
}
Bitmap image = null;
URL myFileUrl = null;
HttpURLConnection conn = null;
try {
myFileUrl = new URL(fileUrl);
} catch (MalformedURLException e) {
return null;
}
try {
CustomApplication pa = CustomApplication .getCustomApplication(a);
conn = (HttpURLConnection) myFileUrl.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
image = BitmapFactory.decodeStream(is);
conn.disconnect();
conn = null;
if (image != null) {
if (useImageCache && image != null) {
CustomApplication.getCustomApplication(a).addImage(fileUrl, image);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null)
conn.disconnect();
}
return image;
}
Note:
When dealing with a lot high quality images, you can get a OutOfMemoryError. Be sure to deal with memory allocation when the images are no longer used. Use image.recycle() or call System.gc() explicitly to free memory.
Note:
When dealing with a lot high quality images, you can get a OutOfMemoryError. Be sure to deal with memory allocation when the images are no longer used. Use image.recycle() or call System.gc() explicitly to free memory.
Subscribe to:
Posts (Atom)