Skip to content

Commit

Permalink
added image manager and updated readme accordingly
Browse files Browse the repository at this point in the history
  • Loading branch information
ajselvig committed Mar 5, 2012
1 parent 9d04347 commit 0d4a027
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 16 deletions.
15 changes: 15 additions & 0 deletions README.textile
Expand Up @@ -8,6 +8,17 @@ It seems like the standard Java library for RSS/Atom is "Rome":http://rometools.

Because there's already a de facto standard implementation for regular Java projects, I decided to focus TinyRss specifically on Android development. Therefore, it's an Android library project, not a regular jar. It's integrated tightly with Android API's and is specifically designed to show an RSS feed in an Android ListView (although you don't have to use this functionality).

h2. Features

* Fast SAX-based parser for RSS 2.0 feeds
* Object-Oriented model to store feed and item data
* Default ListView implementation for displaying feeds, including:
** Title
** Publication Date
** Description
** Image
* Asynchronously download and cache item images

h1. Integration

The following instructions are for integrating TinyRss into an existing Android application. They assume you're using Eclipse with ADT.
Expand Down Expand Up @@ -42,6 +53,9 @@ The following table shows the methods that can be overridden on a subclass of Fe
| int | getItemLayoutId() | The resource id of the layout used to render each feed item | R.layout.feed_list_item |
| boolean | isImageVisible() | Whether or not to display an image in each feed item | false |
| boolean | isDateVisible() | Whether or not to display the item pub date under the title | false |
| FeedImageSource | getImageSource() | Specifies where to get the image urls from the feed | FeedImageSource.Media |

Note: Currently, feed images can only be pulled from the media:content tag by returning FeedImageSource.Media from getImageSource(). Other possible image sources will be added soon.

h2. Resource Overrides

Expand All @@ -55,6 +69,7 @@ The following table shows the Android resources that be be overridden to control
| @style/FeedItemTitle | Style for the TextView that renders the feed item title |
| @style/FeedItemDate | Style for the TextView that renders the feed item date |
| @style/FeedItemDescription | Style for the TextView that renders the feed item description |
| @style/FeedItemImage | Style for the ImageView that renders the feed item image |
| @drawable/feed_list_background | The background drawable for @layout/feed_list_item. By default, a vertical linear gradient |
| @color/feed_bg_top | The top color of the default background gradient for @drawable/feed_list_background |
| @color/feed_bg_bottom | The bottom color of the default background gradient for @drawable/feed_list_background |
Expand Down
7 changes: 4 additions & 3 deletions TinyRssDemo/gen/com/tinymission/rss/R.java
Expand Up @@ -46,13 +46,14 @@ public static final class string {
public static final class style {
public static final int FeedItemDate=0x7f050003;
public static final int FeedItemDescription=0x7f050004;
public static final int FeedItemImage=0x7f050005;
public static final int FeedItemTitle=0x7f050002;
/** Feed List
*/
public static final int FeedList=0x7f050000;
public static final int FeedListItem=0x7f050001;
public static final int HomeButton=0x7f050006;
public static final int HomeLabel=0x7f050007;
public static final int TinyTheme=0x7f050005;
public static final int HomeButton=0x7f050007;
public static final int HomeLabel=0x7f050008;
public static final int TinyTheme=0x7f050006;
}
}
7 changes: 4 additions & 3 deletions TinyRssDemo/gen/com/tinymission/rssdemo/R.java
Expand Up @@ -46,13 +46,14 @@ public static final class string {
public static final class style {
public static final int FeedItemDate=0x7f050003;
public static final int FeedItemDescription=0x7f050004;
public static final int FeedItemImage=0x7f050005;
public static final int FeedItemTitle=0x7f050002;
/** Feed List
*/
public static final int FeedList=0x7f050000;
public static final int FeedListItem=0x7f050001;
public static final int HomeButton=0x7f050006;
public static final int HomeLabel=0x7f050007;
public static final int TinyTheme=0x7f050005;
public static final int HomeButton=0x7f050007;
public static final int HomeLabel=0x7f050008;
public static final int TinyTheme=0x7f050006;
}
}
1 change: 1 addition & 0 deletions TinyRssLib/gen/com/tinymission/rss/R.java
Expand Up @@ -33,6 +33,7 @@ public static final class layout {
public static final class style {
public static int FeedItemDate=0x7f050003;
public static int FeedItemDescription=0x7f050004;
public static int FeedItemImage=0x7f050005;
public static int FeedItemTitle=0x7f050002;
/** Feed List
*/
Expand Down
4 changes: 1 addition & 3 deletions TinyRssLib/res/layout/feed_list_item.xml
Expand Up @@ -6,9 +6,7 @@
android:padding="8dp" >

<ImageView android:id="@+id/feed_item_image"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:visibility="gone"
style="@style/FeedItemImage"
/>

<TextView android:id="@+id/feed_item_title"
Expand Down
6 changes: 6 additions & 0 deletions TinyRssLib/res/values/styles.xml
Expand Up @@ -41,5 +41,11 @@
<item name="android:maxLines">2</item>
<item name="android:ellipsize">end</item>
</style>
<style name="FeedItemImage" parent="@android:style/Widget">
<item name="android:layout_width">80dp</item>
<item name="android:layout_height">80dp</item>
<item name="android:scaleType">fitCenter</item>
<item name="android:paddingRight">12dp</item>
</style>

</resources>
17 changes: 16 additions & 1 deletion TinyRssLib/src/com/tinymission/rss/Feed.java
Expand Up @@ -6,7 +6,6 @@




/** Encapsulation of a collection rss items.
*
*/
Expand All @@ -24,6 +23,7 @@ public class Feed extends FeedEntity {

private String _language;

private ImageManager _imageManager = new ImageManager();

/**
* @return The title of the feed
Expand Down Expand Up @@ -135,6 +135,21 @@ public String getLanguage() {
public void setLanguage(String language) {
this._language = language;
}


/**
* @return The feed's image manager
*/
public ImageManager getImageManager() {
return _imageManager;
}

/**
* @param The feed's image manager
*/
public void setImageManager(ImageManager imageManager) {
this._imageManager = imageManager;
}


private ArrayList<Item> _items = new ArrayList<Item>();
Expand Down
24 changes: 18 additions & 6 deletions TinyRssLib/src/com/tinymission/rss/FeedActivity.java
Expand Up @@ -54,6 +54,12 @@ public boolean isDateVisible() {
return false;
}

/** Subclasses can override to specify where to obtain the feed item images.
* @return a value specifying the source of the feed item images (default Media)
*/
public FeedImageSource getImageSource() {
return FeedImageSource.Media;
}

private SimpleDateFormat _dateformat;

Expand Down Expand Up @@ -216,13 +222,19 @@ public View getView(int position, View convertView, ViewGroup parent) {

ImageView imageView = (ImageView)view.findViewById(R.id.feed_item_image);
if (imageView != null) {
if (isImageVisible()) {
//imageView.setLayoutParams(new LayoutParams(getIconWidth(), LayoutParams.WRAP_CONTENT));
FeedImageSource imageSource = getImageSource();
if (isImageVisible() && imageSource != FeedImageSource.None) {
imageView.setVisibility(View.VISIBLE);
MediaContent mc = item.getMediaContent();
if (mc != null) {
Log.v("Feed Adapter", "trying to use image url: " + mc.getUrl());
imageView.setImageURI(Uri.parse(mc.getUrl()));
switch (imageSource) {
case Media:
MediaContent mc = item.getMediaContent();
imageView.setImageBitmap(null);
if (mc != null) {
_feed.getImageManager().download(mc.getUrl(), imageView);
}
break;
default:
Log.w(getClass().getName(), "Don't know how to get an image from source type: " + imageSource.toString());
}
}
else {
Expand Down
10 changes: 10 additions & 0 deletions TinyRssLib/src/com/tinymission/rss/FeedImageSource.java
@@ -0,0 +1,10 @@
package com.tinymission.rss;

/** Describes the source of the feed item images.
*/
public enum FeedImageSource {
None,
Media
};


195 changes: 195 additions & 0 deletions TinyRssLib/src/com/tinymission/rss/ImageManager.java
@@ -0,0 +1,195 @@
package com.tinymission.rss;

import java.io.*;
import java.util.HashMap;

import org.apache.http.*;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.graphics.*;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.*;


/** Retrieves and stores images for the feed.
*
*/
public class ImageManager {

public static final String LOG_TAG = "ImageManager";

private HashMap<String, Bitmap> _bitmapCache;
private HashMap<Integer, DownloaderTask> _tasks;

public ImageManager() {
_bitmapCache = new HashMap<String, Bitmap>();
_tasks = new HashMap<Integer, ImageManager.DownloaderTask>();
}


public void download(String url, ImageView imageView) {
// cancel any existing downloads
cancelDownloaderTask(imageView);

// try the cache first
if (tryLoadCachedBitmap(url, imageView))
return;

// download the image
DownloaderTask task = new DownloaderTask(imageView);
task.execute(url);
_tasks.put(imageView.getId(), task);
}


private DownloaderTask getDownloaderTask(ImageView imageView) {
return _tasks.get(imageView.getId());
}


private void cancelDownloaderTask(ImageView imageView) {
if (_tasks.containsKey(imageView.getId())) {
DownloaderTask task = _tasks.get(imageView.getId());
task.cancel(true);
}
}

private void removeDownloaderTask(ImageView imageView) {
_tasks.remove(imageView.getId());
}

private Bitmap downloadBitmap(String url) {

// AndroidHttpClient is not allowed to be used from the main thread
final DefaultHttpClient client = new DefaultHttpClient();
final HttpGet getRequest = new HttpGet(url);

try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w("ImageDownloader", "Error " + statusCode +
" while retrieving bitmap from " + url);
return null;
}

final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
try {
inputStream = entity.getContent();
// return BitmapFactory.decodeStream(inputStream);
// Bug on slow connections, fixed in future release.
return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
} finally {
if (inputStream != null) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (IOException e) {
getRequest.abort();
Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
} catch (IllegalStateException e) {
getRequest.abort();
Log.w(LOG_TAG, "Incorrect URL: " + url);
} catch (Exception e) {
getRequest.abort();
Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
} finally {

}
return null;
}

/*
* An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
*/
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int b = read();
if (b < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}


/** Tries to load a bitmap associated with the given url from the cache into the image view.
* @param url the url of the image
* @param imageView the ImageView to load the image into
* @return true if the bitmap was found in the cache
*/
public boolean tryLoadCachedBitmap(String url, ImageView imageView) {
if (_bitmapCache.containsKey(url)) {
imageView.setImageBitmap(_bitmapCache.get(url));
Log.v(LOG_TAG, "Loaded cached bitmap for url: " + url);
return true;
}
return false;
}

/** Adds the given bitmap to the cache associated with the url.
* @param url
* @param bitmap
*/
public void addBitmapToCache(String url, Bitmap bitmap) {
_bitmapCache.put(url, bitmap);
}

/**
* The actual AsyncTask that will asynchronously download the image.
*/
class DownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private final ImageView _imageView;

public DownloaderTask(ImageView imageView) {
_imageView = imageView;
}

/**
* Actual download method.
*/
@Override
protected Bitmap doInBackground(String... params) {
url = params[0];
return downloadBitmap(url);
}

/**
* Once the image is downloaded, associates it to the imageView
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
addBitmapToCache(url, bitmap);
if (_imageView != null) {
DownloaderTask bitmapDownloaderTask = getDownloaderTask(_imageView);
// Change bitmap only if this process is still associated with it
if (this == bitmapDownloaderTask) {
_imageView.setImageBitmap(bitmap);
removeDownloaderTask(_imageView);
Log.v(LOG_TAG, "Downloaded bitmap for url: " + url);
}
}
}
}

}

0 comments on commit 0d4a027

Please sign in to comment.