Skip to content

Instantly share code, notes, and snippets.

@gipi
Created November 25, 2011 14:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gipi/1393709 to your computer and use it in GitHub Desktop.
Save gipi/1393709 to your computer and use it in GitHub Desktop.
Android: class to download remote resources and implemente Observer pattern and a simple extension of ImageView that use it.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ImageViewExtended">
<attr name="href" format="string"/>
</declare-styleable>
</resources>
package com.example.android;
import android.content.Context;
import android.widget.ImageView;
import android.util.AttributeSet;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.animation.AnimationUtils;
import android.view.animation.Animation;
import java.net.URL;
import java.io.File;
import java.net.URLEncoder;
/**
* Declare in the layout this class like
*
* &lt;com.example.android.ImageViewExtended/&gt;
*
* In order to use the "href" attribute you have to declare its namespace like this
* xmlns:ms="http://schemas.android.com/apk/res/com.example.android"
*
*/
public class ImageViewExtended extends ImageView implements RemoteResourcesDownloader.RemoteResourcesObserver {
private static final String TAG = "ImageViewExtended";
private Context mContext;
private String mHref;
private Animation mAnimation;
public ImageViewExtended(Context context) {
this(context, null, 0);
}
public ImageViewExtended(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init(context, attrs, 0);
}
public ImageViewExtended(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init(context, attrs, defStyle);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ImageViewExtended, defStyle, 0);
// this is useless for now since we have only one attribute
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
//android.util.Log.i(TAG, "N=" + N);
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.ImageViewExtended_href:
//android.util.Log.i(TAG, "ImageViewExtended_href");
setImageHref(a.getString(attr));
break;
default:
android.util.Log.i(TAG, "default");
}
}
a.recycle();
}
private void startSpinner() {
mAnimation = AnimationUtils.loadAnimation(mContext, R.anim.clockwise_rotation);
setImageResource(R.drawable.spinner_black_48);
startAnimation(mAnimation);
}
private void stopSpinner() {
mAnimation.cancel();
mAnimation.reset();
}
private String getLocalFilePath(String filename, boolean inSDCard) {
// http://stackoverflow.com/questions/3082325/my-cache-folder-in-android-is-null-do-i-have-to-create-it
String cacheDirName;
// get (if exists) the SD Card mount path and create (if doesn't exists) the directory
if (inSDCard && android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
cacheDirName = android.os.Environment.getExternalStorageDirectory() + "/myApp/";
File cacheDir = new File(cacheDirName);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
} else {
cacheDirName = mContext.getCacheDir().toString();
}
// "/data/com.example.android.myApp/cache/" + "meow.png"
String localFileName = cacheDirName + filename;
return localFileName;
}
private void setImageBroken() {
setImageResource(R.drawable.broken_image);
}
/**
* This sets the href attribute and downloads the image if necessary.
*
* This implementations get the URL, find the last path component and look
* in the local filesystem if a file exists with the same name; if not call
* the RemoteResourcesDownloader and set a default image.
* TODO: if mHref was already set with different value notify the downloader
* to remove this instance from the observer for the old resource.
*/
public void setImageHref(String href) {
mHref = href;
// imageURL = "http://www.dominio.com/miao/bau/meow.png"
final URL imageURL;
try {
imageURL = new URL(href);
} catch(java.net.MalformedURLException e) {
/*
* If the href is malformed what to do? we simply set a broken image
* and return.
*/
e.printStackTrace();
setImageBroken();
return;
}
// ["miao", "bau", "meow.png"]
String[] components = imageURL.getFile().split("/");
// "meow.png"
String filename = "";
try {
filename = URLEncoder.encode(components[components.length - 1], "UTF-8");
} catch (java.io.UnsupportedEncodingException e) {
filename = components[components.length - 1];
}
String localFileName = getLocalFilePath(filename, false);
final File localFile = new File(localFileName);
if (!localFile.exists()) {
startSpinner();
RemoteResourcesDownloader.getInstance().askForResource(this, imageURL, localFile);
} else {
setBitmapFromFilename(localFileName);
}
}
protected void setBitmapFromFilename(String localFileName) {
Bitmap b = BitmapFactory.decodeFile(localFileName);
super.setImageBitmap(b);
}
/**
* When the downloader has finished this instance will be advised and
* recall setImageHref() so to find the file locally and load the Bitmap.
*/
public void notifyEndDownload() {
/*
* This will be called from a thread different from the UI thread and can cause
* crash of application. Run instead in the UIThread using post().
*/
post(new Runnable() {
public void run() {
stopSpinner();
setImageHref(mHref);
}
});
}
}
package com.example.android;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.net.HttpURLConnection;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.File;
/**
* This class downloads a resource from remote server and then
* notify some registered observers.
*
* The resource is downloaded in the filesystem with the same name.
*
* EXAMPLE OF USAGE
* <pre>
* RemoteResourcesDownloader.getInstance().askForResource(this,
* new URL("http://www.example.com/random/resource"),
* new File("/data/com.example.android/cache/"));
* </pre>
*
* TODO: make the request grouped by some parameter. (imagine that a bunch of
* images need to be donwloaded in order to show sponsors on a map and that we want
* to refresh the map only when all the images are retrieved).
* TODO: POST requests handling.
* TODO: Timeout of connection.
* TODO: return something like an InputStream for situation with streaming data (imagine
* the observer needs to parse a JSON without saving on the filesystem).
*/
public class RemoteResourcesDownloader {
private static RemoteResourcesDownloader instance = null;
private RemoteResourcesDownloader() {
}
public interface RemoteResourcesObserver {
public void notifyEndDownload();
}
/**
* This variable contains the pair URL-observers.
*/
private HashMap<URL, List<RemoteResourcesObserver>> mObservers = new HashMap();
/**
* This class only downloads the resource in a background thread.
*/
private class DownloadTask extends Thread {
private URL mURL;
private File mDestination;
public DownloadTask(URL url, File destination) {
mDestination = destination;
mURL = url;
}
@Override
public void run() {
/**
* If some error happens what to do? is it possible to atomize the write of the file.
*/
try {
HttpURLConnection conn = (HttpURLConnection)mURL.openConnection();
InputStream is = conn.getInputStream();
FileOutputStream f = new FileOutputStream(mDestination);
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) > 0) {
f.write(buffer, 0, len);
}
} catch(Exception e) {
e.printStackTrace();
}
notifyObserver(mURL);
}
}
public void notifyObserver(URL url) {
List<RemoteResourcesObserver> observers = mObservers.get(url);
for (RemoteResourcesObserver o : observers) {
o.notifyEndDownload();
}
mObservers.remove(url);
}
synchronized public void askForResource(RemoteResourcesObserver object, URL url, File destination) {
List<RemoteResourcesObserver> observers = new ArrayList();
if (!mObservers.containsKey(url)) {
new DownloadTask(url, destination).start();
} else {
observers = mObservers.get(url);
}
observers.add(object);
mObservers.put(url, observers);
}
static public RemoteResourcesDownloader getInstance() {
if (instance == null) {
instance = new RemoteResourcesDownloader();
}
return instance;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment