Created
November 25, 2011 14:56
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<resources> | |
<declare-styleable name="ImageViewExtended"> | |
<attr name="href" format="string"/> | |
</declare-styleable> | |
</resources> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
* | |
* <com.example.android.ImageViewExtended/> | |
* | |
* 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); | |
} | |
}); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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