├── .gitignore
├── LICENSE
├── README.md
├── UrlImageViewHelper
├── .classpath
├── .gitignore
├── .project
├── AndroidManifest.xml
├── UrlImageViewHelper.iml
├── build.gradle
├── build.xml
├── lint.xml
├── proguard-project.txt
├── proguard.cfg
├── project.properties
├── res
│ └── .gitignore
└── src
│ └── com
│ └── koushikdutta
│ └── urlimageviewhelper
│ ├── AssetUrlDownloader.java
│ ├── Constants.java
│ ├── ContactContentUrlDownloader.java
│ ├── ContentUrlDownloader.java
│ ├── DrawableCache.java
│ ├── FileUrlDownloader.java
│ ├── HttpUrlDownloader.java
│ ├── LruBitmapCache.java
│ ├── LruCache.java
│ ├── SoftReferenceHashTable.java
│ ├── UrlDownloader.java
│ ├── UrlImageViewCallback.java
│ └── UrlImageViewHelper.java
├── UrlImageViewHelperSample
├── .classpath
├── .project
├── AndroidManifest.xml
├── UrlImageViewHelperSample.iml
├── build.xml
├── default.properties
├── lint.xml
├── proguard-project.txt
├── proguard.cfg
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── icon.png
│ ├── drawable-ldpi
│ │ └── icon.png
│ ├── drawable-mdpi
│ │ └── icon.png
│ ├── drawable
│ │ ├── loading.png
│ │ └── transparent.png
│ ├── layout
│ │ ├── image.xml
│ │ ├── main.xml
│ │ └── row.xml
│ └── values
│ │ └── strings.xml
└── src
│ └── com
│ └── koushikdutta
│ └── urlimageviewhelper
│ └── sample
│ └── UrlImageViewHelperSample.java
├── helper.png
└── helper2.png
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | .settings
3 | local.properties
4 | gen
5 | target
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2012 Koushik Dutta
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # This library has been deprecated, use ion instead:
2 |
3 | http://github.com/koush/ion
4 |
5 | ## UrlImageViewHelper
6 | UrlImageViewHelper will fill an ImageView with an image that is found at a URL.
7 |
8 | ### Sample Project
9 |
10 | The sample will do a Google Image Search and load/show the results asynchronously.
11 |
12 | 
13 |
14 | ### Download
15 |
16 | Download [the latest JAR](http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.koushikdutta.urlimageviewhelper&a=urlimageviewhelper&v=LATEST
17 | ) or grab via Maven:
18 |
19 | ```xml
20 |
21 | com.koushikdutta.urlimageviewhelper
22 | urlimageviewhelper
23 | (insert latest version)
24 |
25 | ```
26 |
27 | ### Usage
28 |
29 | UrlImageViewHelper will automatically download and manage all the web images and ImageViews.
30 | Duplicate urls will not be loaded into memory twice. Bitmap memory is managed by using
31 | a weak reference hash table, so as soon as the image is no longer used by you,
32 | it will be garbage collected automatically.
33 |
34 | Usage is simple:
35 |
36 | ```java
37 | UrlImageViewHelper.setUrlDrawable(imageView, "http://example.com/image.png");
38 | ```
39 |
40 |
41 | Want a placeholder image while it is being downloaded?
42 |
43 | ```java
44 | UrlImageViewHelper.setUrlDrawable(imageView, "http://example.com/image.png", R.drawable.placeholder);
45 | ```
46 |
47 |
48 | Don't want to use a placeholder resource, but a drawable instead?
49 |
50 | ```java
51 | UrlImageViewHelper.setUrlDrawable(imageView, "http://example.com/image.png", drawable);
52 | ```
53 |
54 |
55 | What if you want to preload images for snazzy fast loading?
56 |
57 | ```java
58 | UrlImageViewHelper.loadUrlDrawable(context, "http://example.com/image.png");
59 | ```
60 |
61 |
62 | What if you only want to cache the images for a minute?
63 |
64 | ```java
65 | // Note that the 3rd argument "null" is an optional interstitial
66 | // placeholder image.
67 | UrlImageViewHelper.setUrlDrawable(imageView, "http://example.com/image.png", null, 60000);
68 | ```
69 |
70 | UrlImageViewHelper is pretty smart. It can even load the photo for an Android contact
71 | if given a Contact Content Provider URI.
72 |
73 | ```java
74 | UrlImageViewHelper.setUrlDrawable(imageView, "content://com.android.contacts/contacts/1115", R.drawable.dummy_contact_photo);
75 | ```
76 |
77 | ### FAQ
78 |
79 | **Does it work in list adapters when views are reused? (convertView)**
80 |
81 | Yes.
82 |
83 |
84 | ### Featured Implementations
85 |
86 | * [ROM Manager](https://play.google.com/store/apps/details?id=com.koushikdutta.rommanager&hl=en)
87 | * [Carbon](https://play.google.com/store/apps/details?id=com.koushikdutta.backup&hl=en)
88 | * Let me know if you use this library, so I can add it to the list!
89 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | .gradle
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | UrlImageViewHelper
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/UrlImageViewHelper.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | MANIFEST_FILE_PATH
9 | RESOURCES_DIR_PATH
10 | ASSETS_DIR_PATH
11 | NATIVE_LIBS_DIR_PATH
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | maven { url 'http://repo1.maven.org/maven2' }
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:0.4.+'
7 | }
8 | }
9 | apply plugin: 'android-library'
10 |
11 | dependencies {
12 | }
13 |
14 | android {
15 | sourceSets {
16 | main {
17 | manifest.srcFile 'AndroidManifest.xml'
18 |
19 | java {
20 | srcDir 'src/'
21 | }
22 | }
23 | }
24 |
25 | compileSdkVersion 17
26 | buildToolsVersion "17"
27 |
28 | defaultConfig {
29 | minSdkVersion 7
30 | targetSdkVersion 16
31 | }
32 | }
33 |
34 | // upload to maven task
35 | if (System.getenv().I_AM_KOUSH == 'true')
36 | apply from: 'https://raw.github.com/koush/mvn-repo/master/maven.gradle'
37 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
56 |
57 |
69 |
70 |
71 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class * extends android.app.backup.BackupAgentHelper
14 | -keep public class * extends android.preference.Preference
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | -keepclasseswithmembernames class * {
18 | native ;
19 | }
20 |
21 | -keepclasseswithmembernames class * {
22 | public (android.content.Context, android.util.AttributeSet);
23 | }
24 |
25 | -keepclasseswithmembernames class * {
26 | public (android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers enum * {
30 | public static **[] values();
31 | public static ** valueOf(java.lang.String);
32 | }
33 |
34 | -keep class * implements android.os.Parcelable {
35 | public static final android.os.Parcelable$Creator *;
36 | }
37 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | android.library=true
11 | # Project target.
12 | target=android-17
13 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/res/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | .settings
3 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/AssetUrlDownloader.java:
--------------------------------------------------------------------------------
1 |
2 | package com.koushikdutta.urlimageviewhelper;
3 |
4 | import java.io.InputStream;
5 |
6 | import android.content.Context;
7 | import android.os.AsyncTask;
8 |
9 | public class AssetUrlDownloader implements UrlDownloader {
10 | @Override
11 | public void download(final Context context, final String url, final String filename,
12 | final UrlDownloaderCallback callback, final Runnable completion) {
13 | final AsyncTask downloader = new AsyncTask() {
14 | @Override
15 | protected Void doInBackground(final Void... params) {
16 | try {
17 | String relativePath = url.replaceFirst("file:///android_asset/", "");
18 | InputStream is = context.getAssets().open(relativePath);
19 | callback.onDownloadComplete(AssetUrlDownloader.this, is, null);
20 | return null;
21 | }
22 | catch (final Throwable e) {
23 | e.printStackTrace();
24 | return null;
25 | }
26 | }
27 |
28 | @Override
29 | protected void onPostExecute(final Void result) {
30 | completion.run();
31 | }
32 | };
33 |
34 | UrlImageViewHelper.executeTask(downloader);
35 | }
36 |
37 | @Override
38 | public boolean allowCache() {
39 | return false;
40 | }
41 |
42 | @Override
43 | public boolean canDownloadUrl(String url) {
44 | return url.startsWith("file:///android_asset/");
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/Constants.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | public interface Constants {
4 |
5 | public static final String LOGTAG = "UrlImageViewHelper";
6 |
7 | public static final boolean LOG_ENABLED = false; //set to True to enable verbose logging
8 |
9 | //set here and not in Build to maintain proper backwards compatibility
10 | public static final int HONEYCOMB = 11;
11 | }
12 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/ContactContentUrlDownloader.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | import java.io.InputStream;
4 |
5 | import android.content.ContentResolver;
6 | import android.content.Context;
7 | import android.net.Uri;
8 | import android.os.AsyncTask;
9 | import android.provider.ContactsContract;
10 |
11 | public class ContactContentUrlDownloader implements UrlDownloader {
12 | @Override
13 | public void download(final Context context, final String url, final String filename, final UrlDownloaderCallback callback, final Runnable completion) {
14 | final AsyncTask downloader = new AsyncTask() {
15 | @Override
16 | protected Void doInBackground(final Void... params) {
17 | try {
18 | final ContentResolver cr = context.getContentResolver();
19 | InputStream is = ContactsContract.Contacts.openContactPhotoInputStream(cr, Uri.parse(url));
20 | callback.onDownloadComplete(ContactContentUrlDownloader.this, is, null);
21 | return null;
22 | }
23 | catch (final Throwable e) {
24 | e.printStackTrace();
25 | return null;
26 | }
27 | }
28 |
29 | @Override
30 | protected void onPostExecute(final Void result) {
31 | completion.run();
32 | }
33 | };
34 |
35 | UrlImageViewHelper.executeTask(downloader);
36 | }
37 |
38 | @Override
39 | public boolean allowCache() {
40 | return false;
41 | }
42 |
43 | @Override
44 | public boolean canDownloadUrl(String url) {
45 | return url.startsWith(ContactsContract.Contacts.CONTENT_URI.toString());
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/ContentUrlDownloader.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | import java.io.InputStream;
4 |
5 | import android.content.ContentResolver;
6 | import android.content.Context;
7 | import android.net.Uri;
8 | import android.os.AsyncTask;
9 |
10 | public class ContentUrlDownloader implements UrlDownloader {
11 | @Override
12 | public void download(final Context context, final String url, final String filename, final UrlDownloaderCallback callback, final Runnable completion) {
13 | final AsyncTask downloader = new AsyncTask() {
14 | @Override
15 | protected Void doInBackground(final Void... params) {
16 | try {
17 | final ContentResolver cr = context.getContentResolver();
18 | InputStream is = cr.openInputStream(Uri.parse(url));
19 | callback.onDownloadComplete(ContentUrlDownloader.this, is, null);
20 | return null;
21 | }
22 | catch (final Throwable e) {
23 | e.printStackTrace();
24 | return null;
25 | }
26 | }
27 |
28 | @Override
29 | protected void onPostExecute(final Void result) {
30 | completion.run();
31 | }
32 | };
33 |
34 | UrlImageViewHelper.executeTask(downloader);
35 | }
36 |
37 | @Override
38 | public boolean allowCache() {
39 | return false;
40 | }
41 |
42 | @Override
43 | public boolean canDownloadUrl(String url) {
44 | return url.startsWith(ContentResolver.SCHEME_CONTENT);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/DrawableCache.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | import android.graphics.drawable.Drawable;
4 |
5 | public final class DrawableCache extends SoftReferenceHashTable {
6 | private static DrawableCache mInstance = new DrawableCache();
7 |
8 | public static DrawableCache getInstance() {
9 | return mInstance;
10 | }
11 |
12 | private DrawableCache() {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/FileUrlDownloader.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | import java.io.File;
4 | import java.net.URI;
5 |
6 | import android.content.Context;
7 | import android.os.AsyncTask;
8 |
9 | public class FileUrlDownloader implements UrlDownloader {
10 | @Override
11 | public void download(final Context context, final String url, final String filename, final UrlDownloaderCallback callback, final Runnable completion) {
12 | final AsyncTask downloader = new AsyncTask() {
13 | @Override
14 | protected Void doInBackground(final Void... params) {
15 | try {
16 | callback.onDownloadComplete(FileUrlDownloader.this, null, new File(new URI(url)).getAbsolutePath());
17 | return null;
18 | }
19 | catch (final Throwable e) {
20 | e.printStackTrace();
21 | return null;
22 | }
23 | }
24 |
25 | @Override
26 | protected void onPostExecute(final Void result) {
27 | completion.run();
28 | }
29 | };
30 |
31 | UrlImageViewHelper.executeTask(downloader);
32 | }
33 |
34 | @Override
35 | public boolean allowCache() {
36 | return false;
37 | }
38 |
39 | @Override
40 | public boolean canDownloadUrl(String url) {
41 | return url.startsWith("file:/");
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/HttpUrlDownloader.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | import java.io.InputStream;
4 | import java.net.HttpURLConnection;
5 | import java.net.URL;
6 | import java.util.ArrayList;
7 |
8 | import org.apache.http.NameValuePair;
9 |
10 | import android.content.Context;
11 | import android.os.AsyncTask;
12 |
13 | import com.koushikdutta.urlimageviewhelper.UrlImageViewHelper.RequestPropertiesCallback;
14 |
15 | public class HttpUrlDownloader implements UrlDownloader {
16 | private RequestPropertiesCallback mRequestPropertiesCallback;
17 |
18 | public RequestPropertiesCallback getRequestPropertiesCallback() {
19 | return mRequestPropertiesCallback;
20 | }
21 |
22 | public void setRequestPropertiesCallback(final RequestPropertiesCallback callback) {
23 | mRequestPropertiesCallback = callback;
24 | }
25 |
26 |
27 | @Override
28 | public void download(final Context context, final String url, final String filename, final UrlDownloaderCallback callback, final Runnable completion) {
29 | final AsyncTask downloader = new AsyncTask() {
30 | @Override
31 | protected Void doInBackground(final Void... params) {
32 | try {
33 | InputStream is = null;
34 |
35 | String thisUrl = url;
36 | HttpURLConnection urlConnection;
37 | while (true) {
38 | final URL u = new URL(thisUrl);
39 | urlConnection = (HttpURLConnection)u.openConnection();
40 | urlConnection.setInstanceFollowRedirects(true);
41 |
42 | if (mRequestPropertiesCallback != null) {
43 | final ArrayList props = mRequestPropertiesCallback.getHeadersForRequest(context, url);
44 | if (props != null) {
45 | for (final NameValuePair pair: props) {
46 | urlConnection.addRequestProperty(pair.getName(), pair.getValue());
47 | }
48 | }
49 | }
50 |
51 | if (urlConnection.getResponseCode() != HttpURLConnection.HTTP_MOVED_TEMP && urlConnection.getResponseCode() != HttpURLConnection.HTTP_MOVED_PERM)
52 | break;
53 | thisUrl = urlConnection.getHeaderField("Location");
54 | }
55 |
56 | if (urlConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
57 | UrlImageViewHelper.clog("Response Code: " + urlConnection.getResponseCode());
58 | return null;
59 | }
60 | is = urlConnection.getInputStream();
61 | callback.onDownloadComplete(HttpUrlDownloader.this, is, null);
62 | return null;
63 | }
64 | catch (final Throwable e) {
65 | e.printStackTrace();
66 | return null;
67 | }
68 | }
69 |
70 | @Override
71 | protected void onPostExecute(final Void result) {
72 | completion.run();
73 | }
74 | };
75 |
76 | UrlImageViewHelper.executeTask(downloader);
77 | }
78 |
79 | @Override
80 | public boolean allowCache() {
81 | return true;
82 | }
83 |
84 | @Override
85 | public boolean canDownloadUrl(String url) {
86 | return url.startsWith("http");
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/LruBitmapCache.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | public class LruBitmapCache extends LruCache {
6 | public LruBitmapCache(int maxSize) {
7 | super(maxSize);
8 | }
9 |
10 | @Override
11 | protected int sizeOf(String key, Bitmap value) {
12 | return value.getRowBytes() * value.getHeight();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/LruCache.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.koushikdutta.urlimageviewhelper;
18 |
19 | import java.util.LinkedHashMap;
20 | import java.util.Map;
21 |
22 | /**
23 | * Static library version of {@link android.util.LruCache}. Used to write apps
24 | * that run on API levels prior to 12. When running on API level 12 or above,
25 | * this implementation is still used; it does not try to switch to the
26 | * framework's implementation. See the framework SDK documentation for a class
27 | * overview.
28 | */
29 | public class LruCache {
30 | private final LinkedHashMap map;
31 |
32 | /** Size of this cache in units. Not necessarily the number of elements. */
33 | private int size;
34 | private int maxSize;
35 |
36 | private int putCount;
37 | private int createCount;
38 | private int evictionCount;
39 | private int hitCount;
40 | private int missCount;
41 |
42 | /**
43 | * @param maxSize for caches that do not override {@link #sizeOf}, this is
44 | * the maximum number of entries in the cache. For all other caches,
45 | * this is the maximum sum of the sizes of the entries in this cache.
46 | */
47 | public LruCache(int maxSize) {
48 | if (maxSize <= 0) {
49 | throw new IllegalArgumentException("maxSize <= 0");
50 | }
51 | this.maxSize = maxSize;
52 | this.map = new LinkedHashMap(0, 0.75f, true);
53 | }
54 |
55 | /**
56 | * Returns the value for {@code key} if it exists in the cache or can be
57 | * created by {@code #create}. If a value was returned, it is moved to the
58 | * head of the queue. This returns null if a value is not cached and cannot
59 | * be created.
60 | */
61 | public final V get(K key) {
62 | if (key == null) {
63 | throw new NullPointerException("key == null");
64 | }
65 |
66 | V mapValue;
67 | synchronized (this) {
68 | mapValue = map.get(key);
69 | if (mapValue != null) {
70 | hitCount++;
71 | return mapValue;
72 | }
73 | missCount++;
74 | }
75 |
76 | /*
77 | * Attempt to create a value. This may take a long time, and the map
78 | * may be different when create() returns. If a conflicting value was
79 | * added to the map while create() was working, we leave that value in
80 | * the map and release the created value.
81 | */
82 |
83 | V createdValue = create(key);
84 | if (createdValue == null) {
85 | return null;
86 | }
87 |
88 | synchronized (this) {
89 | createCount++;
90 | mapValue = map.put(key, createdValue);
91 |
92 | if (mapValue != null) {
93 | // There was a conflict so undo that last put
94 | map.put(key, mapValue);
95 | } else {
96 | size += safeSizeOf(key, createdValue);
97 | }
98 | }
99 |
100 | if (mapValue != null) {
101 | entryRemoved(false, key, createdValue, mapValue);
102 | return mapValue;
103 | } else {
104 | trimToSize(maxSize);
105 | return createdValue;
106 | }
107 | }
108 |
109 | /**
110 | * Caches {@code value} for {@code key}. The value is moved to the head of
111 | * the queue.
112 | *
113 | * @return the previous value mapped by {@code key}.
114 | */
115 | public final V put(K key, V value) {
116 | if (key == null || value == null) {
117 | throw new NullPointerException("key == null || value == null");
118 | }
119 |
120 | V previous;
121 | synchronized (this) {
122 | putCount++;
123 | size += safeSizeOf(key, value);
124 | previous = map.put(key, value);
125 | if (previous != null) {
126 | size -= safeSizeOf(key, previous);
127 | }
128 | }
129 |
130 | if (previous != null) {
131 | entryRemoved(false, key, previous, value);
132 | }
133 |
134 | trimToSize(maxSize);
135 | return previous;
136 | }
137 |
138 | /**
139 | * @param maxSize the maximum size of the cache before returning. May be -1
140 | * to evict even 0-sized elements.
141 | */
142 | private void trimToSize(int maxSize) {
143 | while (true) {
144 | K key;
145 | V value;
146 | synchronized (this) {
147 | if (size < 0 || (map.isEmpty() && size != 0)) {
148 | throw new IllegalStateException(getClass().getName()
149 | + ".sizeOf() is reporting inconsistent results!");
150 | }
151 |
152 | if (size <= maxSize || map.isEmpty()) {
153 | break;
154 | }
155 |
156 | Map.Entry toEvict = map.entrySet().iterator().next();
157 | key = toEvict.getKey();
158 | value = toEvict.getValue();
159 | map.remove(key);
160 | size -= safeSizeOf(key, value);
161 | evictionCount++;
162 | }
163 |
164 | entryRemoved(true, key, value, null);
165 | }
166 | }
167 |
168 | /**
169 | * Removes the entry for {@code key} if it exists.
170 | *
171 | * @return the previous value mapped by {@code key}.
172 | */
173 | public final V remove(K key) {
174 | if (key == null) {
175 | throw new NullPointerException("key == null");
176 | }
177 |
178 | V previous;
179 | synchronized (this) {
180 | previous = map.remove(key);
181 | if (previous != null) {
182 | size -= safeSizeOf(key, previous);
183 | }
184 | }
185 |
186 | if (previous != null) {
187 | entryRemoved(false, key, previous, null);
188 | }
189 |
190 | return previous;
191 | }
192 |
193 | /**
194 | * Called for entries that have been evicted or removed. This method is
195 | * invoked when a value is evicted to make space, removed by a call to
196 | * {@link #remove}, or replaced by a call to {@link #put}. The default
197 | * implementation does nothing.
198 | *
199 | *
The method is called without synchronization: other threads may
200 | * access the cache while this method is executing.
201 | *
202 | * @param evicted true if the entry is being removed to make space, false
203 | * if the removal was caused by a {@link #put} or {@link #remove}.
204 | * @param newValue the new value for {@code key}, if it exists. If non-null,
205 | * this removal was caused by a {@link #put}. Otherwise it was caused by
206 | * an eviction or a {@link #remove}.
207 | */
208 | protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
209 |
210 | /**
211 | * Called after a cache miss to compute a value for the corresponding key.
212 | * Returns the computed value or null if no value can be computed. The
213 | * default implementation returns null.
214 | *
215 | *
The method is called without synchronization: other threads may
216 | * access the cache while this method is executing.
217 | *
218 | *
If a value for {@code key} exists in the cache when this method
219 | * returns, the created value will be released with {@link #entryRemoved}
220 | * and discarded. This can occur when multiple threads request the same key
221 | * at the same time (causing multiple values to be created), or when one
222 | * thread calls {@link #put} while another is creating a value for the same
223 | * key.
224 | */
225 | protected V create(K key) {
226 | return null;
227 | }
228 |
229 | private int safeSizeOf(K key, V value) {
230 | int result = sizeOf(key, value);
231 | if (result < 0) {
232 | throw new IllegalStateException("Negative size: " + key + "=" + value);
233 | }
234 | return result;
235 | }
236 |
237 | /**
238 | * Returns the size of the entry for {@code key} and {@code value} in
239 | * user-defined units. The default implementation returns 1 so that size
240 | * is the number of entries and max size is the maximum number of entries.
241 | *
242 | *
An entry's size must not change while it is in the cache.
243 | */
244 | protected int sizeOf(K key, V value) {
245 | return 1;
246 | }
247 |
248 | /**
249 | * Clear the cache, calling {@link #entryRemoved} on each removed entry.
250 | */
251 | public final void evictAll() {
252 | trimToSize(-1); // -1 will evict 0-sized elements
253 | }
254 |
255 | /**
256 | * For caches that do not override {@link #sizeOf}, this returns the number
257 | * of entries in the cache. For all other caches, this returns the sum of
258 | * the sizes of the entries in this cache.
259 | */
260 | public synchronized final int size() {
261 | return size;
262 | }
263 |
264 | /**
265 | * For caches that do not override {@link #sizeOf}, this returns the maximum
266 | * number of entries in the cache. For all other caches, this returns the
267 | * maximum sum of the sizes of the entries in this cache.
268 | */
269 | public synchronized final int maxSize() {
270 | return maxSize;
271 | }
272 |
273 | /**
274 | * Returns the number of times {@link #get} returned a value.
275 | */
276 | public synchronized final int hitCount() {
277 | return hitCount;
278 | }
279 |
280 | /**
281 | * Returns the number of times {@link #get} returned null or required a new
282 | * value to be created.
283 | */
284 | public synchronized final int missCount() {
285 | return missCount;
286 | }
287 |
288 | /**
289 | * Returns the number of times {@link #create(Object)} returned a value.
290 | */
291 | public synchronized final int createCount() {
292 | return createCount;
293 | }
294 |
295 | /**
296 | * Returns the number of times {@link #put} was called.
297 | */
298 | public synchronized final int putCount() {
299 | return putCount;
300 | }
301 |
302 | /**
303 | * Returns the number of values that have been evicted.
304 | */
305 | public synchronized final int evictionCount() {
306 | return evictionCount;
307 | }
308 |
309 | /**
310 | * Returns a copy of the current contents of the cache, ordered from least
311 | * recently accessed to most recently accessed.
312 | */
313 | public synchronized final Map snapshot() {
314 | return new LinkedHashMap(map);
315 | }
316 |
317 | @Override public synchronized final String toString() {
318 | int accesses = hitCount + missCount;
319 | int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
320 | return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
321 | maxSize, hitCount, missCount, hitPercent);
322 | }
323 | }
324 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/SoftReferenceHashTable.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | import java.lang.ref.SoftReference;
4 | import java.util.Hashtable;
5 |
6 | public class SoftReferenceHashTable {
7 | Hashtable> mTable = new Hashtable>();
8 |
9 | public V put(K key, V value) {
10 | SoftReference old = mTable.put(key, new SoftReference(value));
11 | if (old == null)
12 | return null;
13 | return old.get();
14 | }
15 |
16 | public V get(K key) {
17 | SoftReference val = mTable.get(key);
18 | if (val == null)
19 | return null;
20 | V ret = val.get();
21 | if (ret == null)
22 | mTable.remove(key);
23 | return ret;
24 | }
25 |
26 | public V remove(K k) {
27 | SoftReference v = mTable.remove(k);
28 | if (v == null)
29 | return null;
30 | return v.get();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/UrlDownloader.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | import java.io.InputStream;
4 |
5 | import android.content.Context;
6 |
7 | public interface UrlDownloader {
8 | public static interface UrlDownloaderCallback {
9 | public void onDownloadComplete(UrlDownloader downloader, InputStream in, String filename);
10 | }
11 |
12 | public void download(Context context, String url, String filename, UrlDownloaderCallback callback, Runnable completion);
13 | public boolean allowCache();
14 | public boolean canDownloadUrl(String url);
15 | }
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/UrlImageViewCallback.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | import android.graphics.Bitmap;
4 | import android.widget.ImageView;
5 |
6 | /**
7 | * Callback that is invoked with a success/failure after attempting to
8 | * load a drawable from an url.
9 | * Note: If an ImageView has multiple setUrlDrawable calls made on it, only the last callback
10 | * will be invoked. This scenario arises when using ListViews which recycle their views.
11 | * This is done to prevent callbacks from being erroneosly invoked on ImageViews that are no
12 | * longer interested in the url that was loaded.
13 | * To guarantee a callback is invoked, one can do the following:
14 | * First call loadUrlDrawable (with a callback), and then setUrlDrawable. Both loads just get queued into the same request,
15 | * so you don't need to worry about that being inefficient or that it is making two network calls.
16 | * @author koush
17 | *
18 | */
19 | public interface UrlImageViewCallback {
20 | /**
21 | *
22 | * @param imageView ImageView for the load request.
23 | * @param loadedBitmap The bitmap that was loaded by the request.
24 | * If the drawable failed to load, this will be null.
25 | * @param url The url that was loaded.
26 | * @param loadedFromCache This will indicate whether the load operation result came from cache, or was retrieved.
27 | */
28 | void onLoaded(ImageView imageView, Bitmap loadedBitmap, String url, boolean loadedFromCache);
29 | }
30 |
--------------------------------------------------------------------------------
/UrlImageViewHelper/src/com/koushikdutta/urlimageviewhelper/UrlImageViewHelper.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.ActivityManager;
5 | import android.content.Context;
6 | import android.content.res.AssetManager;
7 | import android.content.res.Resources;
8 | import android.graphics.Bitmap;
9 | import android.graphics.BitmapFactory;
10 | import android.graphics.BitmapFactory.Options;
11 | import android.graphics.drawable.BitmapDrawable;
12 | import android.graphics.drawable.Drawable;
13 | import android.os.AsyncTask;
14 | import android.os.Build;
15 | import android.os.Looper;
16 | import android.util.DisplayMetrics;
17 | import android.util.Log;
18 | import android.view.WindowManager;
19 | import android.widget.ImageView;
20 | import org.apache.http.NameValuePair;
21 |
22 | import java.io.*;
23 | import java.util.ArrayList;
24 | import java.util.HashSet;
25 | import java.util.Hashtable;
26 |
27 | public final class UrlImageViewHelper {
28 | static void clog(String format, Object... args) {
29 | String log;
30 | if (args.length == 0)
31 | log = format;
32 | else
33 | log = String.format(format, args);
34 | if (Constants.LOG_ENABLED)
35 | Log.i(Constants.LOGTAG, log);
36 | }
37 |
38 | public static int copyStream(final InputStream input, final OutputStream output) throws IOException {
39 | final byte[] stuff = new byte[8192];
40 | int read;
41 | int total = 0;
42 | while ((read = input.read(stuff)) != -1)
43 | {
44 | output.write(stuff, 0, read);
45 | total += read;
46 | }
47 | return total;
48 | }
49 |
50 | static Resources mResources;
51 | static DisplayMetrics mMetrics;
52 | private static void prepareResources(final Context context) {
53 | if (mMetrics != null) {
54 | return;
55 | }
56 | mMetrics = new DisplayMetrics();
57 | //final Activity act = (Activity)context;
58 | //act.getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
59 | ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
60 | .getDefaultDisplay().getMetrics(mMetrics);
61 | final AssetManager mgr = context.getAssets();
62 | mResources = new Resources(mgr, mMetrics, context.getResources().getConfiguration());
63 | }
64 |
65 | private static boolean mUseBitmapScaling = true;
66 | /**
67 | * Bitmap scaling will use smart/sane values to limit the maximum
68 | * dimension of the bitmap during decode. This will prevent any dimension of the
69 | * bitmap from being larger than the dimensions of the device itself.
70 | * Doing this will conserve memory.
71 | * @param useBitmapScaling Toggle for smart resizing.
72 | */
73 | public static void setUseBitmapScaling(boolean useBitmapScaling) {
74 | mUseBitmapScaling = useBitmapScaling;
75 | }
76 | /**
77 | * Bitmap scaling will use smart/sane values to limit the maximum
78 | * dimension of the bitmap during decode. This will prevent any dimension of the
79 | * bitmap from being larger than the dimensions of the device itself.
80 | * Doing this will conserve memory.
81 | */
82 | public static boolean getUseBitmapScaling() {
83 | return mUseBitmapScaling;
84 | }
85 |
86 | private static Bitmap loadBitmapFromStream(final Context context, final String url, final String filename, final int targetWidth, final int targetHeight) {
87 | prepareResources(context);
88 |
89 | // Log.v(Constants.LOGTAG,targetWidth);
90 | // Log.v(Constants.LOGTAG,targetHeight);
91 | InputStream stream = null;
92 | clog("Decoding: " + url + " " + filename);
93 | try {
94 | BitmapFactory.Options o = null;
95 | if (mUseBitmapScaling) {
96 | o = new BitmapFactory.Options();
97 | o.inJustDecodeBounds = true;
98 | stream = new BufferedInputStream(new FileInputStream(filename), 8192);
99 | BitmapFactory.decodeStream(stream, null, o);
100 | stream.close();
101 | int scale = 0;
102 | while ((o.outWidth >> scale) > targetWidth || (o.outHeight >> scale) > targetHeight) {
103 | scale++;
104 | }
105 | o = new Options();
106 | o.inSampleSize = 1 << scale;
107 | }
108 | stream = new BufferedInputStream(new FileInputStream(filename), 8192);
109 | final Bitmap bitmap = BitmapFactory.decodeStream(stream, null, o);
110 | clog(String.format("Loaded bitmap (%dx%d).", bitmap.getWidth(), bitmap.getHeight()));
111 | return bitmap;
112 | } catch (final IOException e) {
113 | return null;
114 | } finally {
115 | if (stream != null) {
116 | try {
117 | stream.close();
118 | } catch (IOException e) {
119 | Log.w(Constants.LOGTAG, "Failed to close FileInputStream", e);
120 | }
121 | }
122 | }
123 | }
124 |
125 | public static final int CACHE_DURATION_INFINITE = Integer.MAX_VALUE;
126 | public static final int CACHE_DURATION_ONE_DAY = 1000 * 60 * 60 * 24;
127 | public static final int CACHE_DURATION_TWO_DAYS = CACHE_DURATION_ONE_DAY * 2;
128 | public static final int CACHE_DURATION_THREE_DAYS = CACHE_DURATION_ONE_DAY * 3;
129 | public static final int CACHE_DURATION_FOUR_DAYS = CACHE_DURATION_ONE_DAY * 4;
130 | public static final int CACHE_DURATION_FIVE_DAYS = CACHE_DURATION_ONE_DAY * 5;
131 | public static final int CACHE_DURATION_SIX_DAYS = CACHE_DURATION_ONE_DAY * 6;
132 | public static final int CACHE_DURATION_ONE_WEEK = CACHE_DURATION_ONE_DAY * 7;
133 |
134 | /**
135 | * Download and shrink an Image located at a specified URL, and display it
136 | * in the provided {@link ImageView}.
137 | *
138 | * @param imageView The {@link ImageView} to display the image to after it
139 | * is loaded.
140 | * @param url The URL of the image that should be loaded.
141 | * @param defaultResource The Android resid of the {@link Drawable} that
142 | * should be displayed while the image is being downloaded.
143 | */
144 | public static void setUrlDrawable(final ImageView imageView, final String url, final int defaultResource) {
145 | setUrlDrawable(imageView.getContext(), imageView, url, defaultResource, CACHE_DURATION_THREE_DAYS);
146 | }
147 |
148 | /**
149 | * Download and shrink an Image located at a specified URL, and display it
150 | * in the provided {@link ImageView} once it finishes loading.
151 | *
152 | * @param imageView The {@link ImageView} to display the image to after it
153 | * is loaded.
154 | * @param url The URL of the image that should be loaded.
155 | */
156 | public static void setUrlDrawable(final ImageView imageView, final String url) {
157 | setUrlDrawable(imageView.getContext(), imageView, url, null, CACHE_DURATION_THREE_DAYS, null);
158 | }
159 |
160 | public static void loadUrlDrawable(final Context context, final String url) {
161 | setUrlDrawable(context, null, url, null, CACHE_DURATION_THREE_DAYS, null);
162 | }
163 |
164 | /**
165 | * Download and shrink an Image located at a specified URL, and display it
166 | * in the provided {@link ImageView}.
167 | *
168 | * @param imageView The {@link ImageView} to display the image to after it
169 | * is loaded.
170 | * @param url The URL of the image that should be loaded.
171 | * @param defaultDrawable A {@link Drawable} that should be displayed in
172 | * {@code imageView} while the image has not been loaded. This
173 | * image will also be displayed if the image fails to load. This
174 | * can be set to {@code null}.
175 | */
176 | public static void setUrlDrawable(final ImageView imageView, final String url, final Drawable defaultDrawable) {
177 | setUrlDrawable(imageView.getContext(), imageView, url, defaultDrawable, CACHE_DURATION_THREE_DAYS, null);
178 | }
179 |
180 | /**
181 | * Download and shrink an Image located at a specified URL, and display it
182 | * in the provided {@link ImageView}.
183 | *
184 | * @param imageView The {@link ImageView} to display the image to after it
185 | * is loaded.
186 | * @param url The URL of the image that should be loaded.
187 | * @param defaultResource The Android resid of the {@link Drawable} that
188 | * should be displayed while the image is being downloaded.
189 | * @param cacheDurationMs The length of time, in milliseconds, that this
190 | * image should be cached locally.
191 | */
192 | public static void setUrlDrawable(final ImageView imageView, final String url, final int defaultResource, final long cacheDurationMs) {
193 | setUrlDrawable(imageView.getContext(), imageView, url, defaultResource, cacheDurationMs);
194 | }
195 |
196 | public static void loadUrlDrawable(final Context context, final String url, final long cacheDurationMs) {
197 | setUrlDrawable(context, null, url, null, cacheDurationMs, null);
198 | }
199 |
200 | /**
201 | * Download and shrink an Image located at a specified URL, and display it
202 | * in the provided {@link ImageView}.
203 | *
204 | * @param imageView The {@link ImageView} to display the image to after it
205 | * is loaded.
206 | * @param url The URL of the image that should be loaded.
207 | * @param defaultDrawable A {@link Drawable} that should be displayed in
208 | * {@code imageView} while the image has not been loaded. This
209 | * image will also be displayed if the image fails to load. This
210 | * can be set to {@code null}.
211 | * @param cacheDurationMs The length of time, in milliseconds, that this
212 | * image should be cached locally.
213 | */
214 | public static void setUrlDrawable(final ImageView imageView, final String url, final Drawable defaultDrawable, final long cacheDurationMs) {
215 | setUrlDrawable(imageView.getContext(), imageView, url, defaultDrawable, cacheDurationMs, null);
216 | }
217 |
218 | /**
219 | * Download and shrink an Image located at a specified URL, and display it
220 | * in the provided {@link ImageView}.
221 | *
222 | * @param context A {@link Context} to allow setUrlDrawable to load and save
223 | * files.
224 | * @param imageView The {@link ImageView} to display the image to after it
225 | * is loaded.
226 | * @param url The URL of the image that should be loaded.
227 | * @param defaultResource The Android resid of the {@link Drawable} that
228 | * should be displayed while the image is being downloaded.
229 | * @param cacheDurationMs The length of time, in milliseconds, that this
230 | * image should be cached locally.
231 | */
232 | private static void setUrlDrawable(final Context context, final ImageView imageView, final String url, final int defaultResource, final long cacheDurationMs) {
233 | Drawable d = null;
234 | if (defaultResource != 0) {
235 | d = imageView.getResources().getDrawable(defaultResource);
236 | }
237 | setUrlDrawable(context, imageView, url, d, cacheDurationMs, null);
238 | }
239 |
240 | /**
241 | * Download and shrink an Image located at a specified URL, and display it
242 | * in the provided {@link ImageView}.
243 | *
244 | * @param imageView The {@link ImageView} to display the image to after it
245 | * is loaded.
246 | * @param url The URL of the image that should be loaded.
247 | * @param defaultResource The Android resid of the {@link Drawable} that
248 | * should be displayed while the image is being downloaded.
249 | * @param callback An instance of {@link UrlImageViewCallback} that is
250 | * called when the image successfully finishes loading. This
251 | * value can be null.
252 | */
253 | public static void setUrlDrawable(final ImageView imageView, final String url, final int defaultResource, final UrlImageViewCallback callback) {
254 | setUrlDrawable(imageView.getContext(), imageView, url, defaultResource, CACHE_DURATION_THREE_DAYS, callback);
255 | }
256 |
257 | /**
258 | * Download and shrink an Image located at a specified URL, and display it
259 | * in the provided {@link ImageView}.
260 | *
261 | * @param imageView The {@link ImageView} to display the image to after it
262 | * is loaded.
263 | * @param url The URL of the image that should be loaded.
264 | * @param callback An instance of {@link UrlImageViewCallback} that is
265 | * called when the image successfully finishes loading. This
266 | * value can be null.
267 | */
268 | public static void setUrlDrawable(final ImageView imageView, final String url, final UrlImageViewCallback callback) {
269 | setUrlDrawable(imageView.getContext(), imageView, url, null, CACHE_DURATION_THREE_DAYS, callback);
270 | }
271 |
272 | public static void loadUrlDrawable(final Context context, final String url, final UrlImageViewCallback callback) {
273 | setUrlDrawable(context, null, url, null, CACHE_DURATION_THREE_DAYS, callback);
274 | }
275 |
276 | /**
277 | * Download and shrink an Image located at a specified URL, and display it
278 | * in the provided {@link ImageView}.
279 | *
280 | * @param imageView The {@link ImageView} to display the image to after it
281 | * is loaded.
282 | * @param url The URL of the image that should be loaded.
283 | * @param defaultDrawable A {@link Drawable} that should be displayed in
284 | * {@code imageView} while the image has not been loaded. This
285 | * image will also be displayed if the image fails to load. This
286 | * can be set to {@code null}.
287 | * @param callback An instance of {@link UrlImageViewCallback} that is
288 | * called when the image successfully finishes loading. This
289 | * value can be null.
290 | */
291 | public static void setUrlDrawable(final ImageView imageView, final String url, final Drawable defaultDrawable, final UrlImageViewCallback callback) {
292 | setUrlDrawable(imageView.getContext(), imageView, url, defaultDrawable, CACHE_DURATION_THREE_DAYS, callback);
293 | }
294 |
295 | /**
296 | * Download and shrink an Image located at a specified URL, and display it
297 | * in the provided {@link ImageView}.
298 | *
299 | * @param imageView The {@link ImageView} to display the image to after it
300 | * is loaded.
301 | * @param url The URL of the image that should be loaded.
302 | * @param defaultResource The Android resid of the {@link Drawable} that
303 | * should be displayed while the image is being downloaded.
304 | * @param cacheDurationMs The length of time, in milliseconds, that this
305 | * image should be cached locally.
306 | * @param callback An instance of {@link UrlImageViewCallback} that is
307 | * called when the image successfully finishes loading. This
308 | * value can be null.
309 | */
310 | public static void setUrlDrawable(final ImageView imageView, final String url, final int defaultResource, final long cacheDurationMs, final UrlImageViewCallback callback) {
311 | setUrlDrawable(imageView.getContext(), imageView, url, defaultResource, cacheDurationMs, callback);
312 | }
313 |
314 | public static void loadUrlDrawable(final Context context, final String url, final long cacheDurationMs, final UrlImageViewCallback callback) {
315 | setUrlDrawable(context, null, url, null, cacheDurationMs, callback);
316 | }
317 |
318 | /**
319 | * Download and shrink an Image located at a specified URL, and display it
320 | * in the provided {@link ImageView}.
321 | *
322 | * @param imageView The {@link ImageView} to display the image to after it
323 | * is loaded.
324 | * @param url The URL of the image that should be loaded.
325 | * @param defaultDrawable A {@link Drawable} that should be displayed in
326 | * {@code imageView} while the image has not been loaded. This
327 | * image will also be displayed if the image fails to load. This
328 | * can be set to {@code null}.
329 | * @param cacheDurationMs The length of time, in milliseconds, that this
330 | * image should be cached locally.
331 | * @param callback An instance of {@link UrlImageViewCallback} that is
332 | * called when the image successfully finishes loading. This
333 | * value can be null.
334 | */
335 | public static void setUrlDrawable(final ImageView imageView, final String url, final Drawable defaultDrawable, final long cacheDurationMs, final UrlImageViewCallback callback) {
336 | setUrlDrawable(imageView.getContext(), imageView, url, defaultDrawable, cacheDurationMs, callback);
337 | }
338 |
339 | /**
340 | * Download and shrink an Image located at a specified URL, and display it
341 | * in the provided {@link ImageView}.
342 | *
343 | * @param context A {@link Context} to allow setUrlDrawable to load and save
344 | * files.
345 | * @param imageView The {@link ImageView} to display the image to after it
346 | * is loaded.
347 | * @param url The URL of the image that should be loaded.
348 | * @param defaultResource The Android resid of the {@link Drawable} that
349 | * should be displayed while the image is being downloaded.
350 | * @param cacheDurationMs The length of time, in milliseconds, that this
351 | * image should be cached locally.
352 | * @param callback An instance of {@link UrlImageViewCallback} that is
353 | * called when the image successfully finishes loading. This
354 | * value can be null.
355 | */
356 | private static void setUrlDrawable(final Context context, final ImageView imageView, final String url, final int defaultResource, final long cacheDurationMs, final UrlImageViewCallback callback) {
357 | Drawable d = null;
358 | if (defaultResource != 0) {
359 | d = imageView.getResources().getDrawable(defaultResource);
360 | }
361 | setUrlDrawable(context, imageView, url, d, cacheDurationMs, callback);
362 | }
363 |
364 | private static boolean isNullOrEmpty(final CharSequence s) {
365 | return (s == null || s.equals("") || s.equals("null") || s.equals("NULL"));
366 | }
367 |
368 | private static boolean mHasCleaned = false;
369 |
370 | public static String getFilenameForUrl(final String url) {
371 | return url.hashCode() + ".urlimage";
372 | }
373 |
374 | /**
375 | * Clear out cached images.
376 | * @param context
377 | * @param age The max age of a file. Files older than this age
378 | * will be removed.
379 | */
380 | public static void cleanup(final Context context, long age) {
381 | if (mHasCleaned) {
382 | return;
383 | }
384 | mHasCleaned = true;
385 | try {
386 | // purge any *.urlimage files over a week old
387 | final String[] files = context.getFilesDir().list();
388 | if (files == null) {
389 | return;
390 | }
391 | for (final String file : files) {
392 | if (!file.endsWith(".urlimage")) {
393 | continue;
394 | }
395 |
396 | final File f = new File(context.getFilesDir().getAbsolutePath() + '/' + file);
397 | if (System.currentTimeMillis() > f.lastModified() + age) {
398 | f.delete();
399 | }
400 | }
401 | } catch (final Exception e) {
402 | e.printStackTrace();
403 | }
404 | }
405 |
406 | /**
407 | * Clear out all cached images older than a week.
408 | * The same as calling cleanup(context, CACHE_DURATION_ONE_WEEK);
409 | * @param context
410 | */
411 | public static void cleanup(final Context context) {
412 | cleanup(context, CACHE_DURATION_ONE_WEEK);
413 | }
414 |
415 | private static boolean checkCacheDuration(File file, long cacheDurationMs) {
416 | return cacheDurationMs == CACHE_DURATION_INFINITE || System.currentTimeMillis() < file.lastModified() + cacheDurationMs;
417 | }
418 |
419 | public static Bitmap getCachedBitmap(String url) {
420 | if (url == null)
421 | return null;
422 | Bitmap ret = null;
423 | if (mDeadCache != null)
424 | ret = mDeadCache.get(url);
425 | if (ret != null)
426 | return ret;
427 | if (mLiveCache != null) {
428 | Drawable drawable = mLiveCache.get(url);
429 | if (drawable instanceof ZombieDrawable)
430 | return ((ZombieDrawable)drawable).getBitmap();
431 | }
432 | return null;
433 | }
434 |
435 | /**
436 | * Download and shrink an Image located at a specified URL, and display it
437 | * in the provided {@link ImageView}.
438 | *
439 | * @param context A {@link Context} to allow setUrlDrawable to load and save
440 | * files.
441 | * @param imageView The {@link ImageView} to display the image to after it
442 | * is loaded.
443 | * @param url The URL of the image that should be loaded.
444 | * @param defaultDrawable A {@link Drawable} that should be displayed in
445 | * {@code imageView} while the image has not been loaded. This
446 | * image will also be displayed if the image fails to load. This
447 | * can be set to {@code null}.
448 | * @param cacheDurationMs The length of time, in milliseconds, that this
449 | * image should be cached locally.
450 | * @param callback An instance of {@link UrlImageViewCallback} that is
451 | * called when the image successfully finishes loading. This
452 | * value can be null.
453 | */
454 | private static void setUrlDrawable(final Context context, final ImageView imageView, final String url, final Drawable defaultDrawable, final long cacheDurationMs, final UrlImageViewCallback callback) {
455 | assert (Looper.getMainLooper().getThread() == Thread.currentThread()) : "setUrlDrawable and loadUrlDrawable should only be called from the main thread.";
456 | cleanup(context);
457 | // disassociate this ImageView from any pending downloads
458 | if (isNullOrEmpty(url)) {
459 | if (imageView != null) {
460 | mPendingViews.remove(imageView);
461 | imageView.setImageDrawable(defaultDrawable);
462 | }
463 | return;
464 | }
465 |
466 | final int tw;
467 | final int th;
468 | if (mMetrics == null)
469 | prepareResources(context);
470 | tw = mMetrics.widthPixels;
471 | th = mMetrics.heightPixels;
472 |
473 | final String filename = context.getFileStreamPath(getFilenameForUrl(url)).getAbsolutePath();
474 | final File file = new File(filename);
475 |
476 | // check the dead and live cache to see if we can find this url's bitmap
477 | if (mDeadCache == null) {
478 | mDeadCache = new LruBitmapCache(getHeapSize(context) / 8);
479 | }
480 | Drawable drawable = null;
481 | Bitmap bitmap = mDeadCache.remove(url);
482 | if (bitmap != null) {
483 | clog("zombie load: " + url);
484 | } else {
485 | drawable = mLiveCache.get(url);
486 | }
487 |
488 | // if something was found, verify it was fresh.
489 | if (drawable != null || bitmap != null) {
490 | clog("Cache hit on: " + url);
491 | // if the file age is older than the cache duration, force a refresh.
492 | // note that the file must exist, otherwise it is using a default.
493 | // not checking for file existence would do a network call on every
494 | // 404 or failed load.
495 | if (file.exists() && !checkCacheDuration(file, cacheDurationMs)) {
496 | clog("Cache hit, but file is stale. Forcing reload: " + url);
497 | if (drawable != null && drawable instanceof ZombieDrawable)
498 | ((ZombieDrawable)drawable).headshot();
499 | drawable = null;
500 | bitmap = null;
501 | }
502 | else {
503 | clog("Using cached: " + url);
504 | }
505 | }
506 |
507 | // if the bitmap is fresh, set the imageview
508 | if (drawable != null || bitmap != null) {
509 | if (imageView != null) {
510 | mPendingViews.remove(imageView);
511 | if (drawable instanceof ZombieDrawable)
512 | drawable = ((ZombieDrawable)drawable).clone(mResources);
513 | else if (bitmap != null)
514 | drawable = new ZombieDrawable(url, mResources, bitmap);
515 |
516 | imageView.setImageDrawable(drawable);
517 | }
518 | // invoke any bitmap callbacks
519 | if (callback != null) {
520 | // when invoking the callback from cache, check to see if this was
521 | // a drawable that was successfully loaded from the filesystem or url.
522 | // this will be indicated by it being a ZombieDrawable (ie, something we are managing).
523 | // The default drawables will be BitmapDrawables (or whatever else the user passed in).
524 | if (bitmap == null && drawable instanceof ZombieDrawable)
525 | bitmap = ((ZombieDrawable)drawable).getBitmap();
526 | callback.onLoaded(imageView, bitmap, url, true);
527 | }
528 | return;
529 | }
530 |
531 | // oh noes, at this point we definitely do not have the file available in memory
532 | // let's prepare for an asynchronous load of the image.
533 |
534 | // null it while it is downloading
535 | // since listviews reuse their views, we need to
536 | // take note of which url this view is waiting for.
537 | // This may change rapidly as the list scrolls or is filtered, etc.
538 | clog("Waiting for " + url + " " + imageView);
539 | if (imageView != null) {
540 | imageView.setImageDrawable(defaultDrawable);
541 | mPendingViews.put(imageView, url);
542 | }
543 |
544 | final ArrayList currentDownload = mPendingDownloads.get(url);
545 | if (currentDownload != null && currentDownload.size() != 0) {
546 | // Also, multiple vies may be waiting for this url.
547 | // So, let's maintain a list of these views.
548 | // When the url is downloaded, it sets the imagedrawable for
549 | // every view in the list. It needs to also validate that
550 | // the imageview is still waiting for this url.
551 | if (imageView != null) {
552 | currentDownload.add(imageView);
553 | }
554 | return;
555 | }
556 |
557 | final ArrayList downloads = new ArrayList();
558 | if (imageView != null) {
559 | downloads.add(imageView);
560 | }
561 | mPendingDownloads.put(url, downloads);
562 |
563 | final int targetWidth = tw <= 0 ? Integer.MAX_VALUE : tw;
564 | final int targetHeight = th <= 0 ? Integer.MAX_VALUE : th;
565 | final Loader loader = new Loader() {
566 | @Override
567 | public void onDownloadComplete(UrlDownloader downloader, InputStream in, String existingFilename) {
568 | try {
569 | assert (in == null || existingFilename == null);
570 | if (in == null && existingFilename == null)
571 | return;
572 | String targetFilename = filename;
573 | if (in != null) {
574 | in = new BufferedInputStream(in, 8192);
575 | OutputStream fout = new BufferedOutputStream(new FileOutputStream(filename), 8192);
576 | copyStream(in, fout);
577 | fout.close();
578 | }
579 | else {
580 | targetFilename = existingFilename;
581 | }
582 | result = loadBitmapFromStream(context, url, targetFilename, targetWidth, targetHeight);
583 | }
584 | catch (final Exception ex) {
585 | // always delete busted files when we throw.
586 | new File(filename).delete();
587 | if (Constants.LOG_ENABLED)
588 | Log.e(Constants.LOGTAG, "Error loading " + url, ex);
589 | }
590 | finally {
591 | // if we're not supposed to cache this thing, delete the temp file.
592 | if (downloader != null && !downloader.allowCache())
593 | new File(filename).delete();
594 | }
595 | }
596 | };
597 |
598 | final Runnable completion = new Runnable() {
599 | @Override
600 | public void run() {
601 | assert (Looper.myLooper().equals(Looper.getMainLooper()));
602 | Bitmap bitmap = loader.result;
603 | Drawable usableResult = null;
604 | if (bitmap != null) {
605 | usableResult = new ZombieDrawable(url, mResources, bitmap);
606 | }
607 | if (usableResult == null) {
608 | clog("No usable result, defaulting " + url);
609 | usableResult = defaultDrawable;
610 | mLiveCache.put(url, usableResult);
611 | }
612 | mPendingDownloads.remove(url);
613 | // mLiveCache.put(url, usableResult);
614 | if (callback != null && imageView == null)
615 | callback.onLoaded(null, loader.result, url, false);
616 | int waitingCount = 0;
617 | for (final ImageView iv: downloads) {
618 | // validate the url it is waiting for
619 | final String pendingUrl = mPendingViews.get(iv);
620 | if (!url.equals(pendingUrl)) {
621 | clog("Ignoring out of date request to update view for " + url + " " + pendingUrl + " " + iv);
622 | continue;
623 | }
624 | waitingCount++;
625 | mPendingViews.remove(iv);
626 | if (usableResult != null) {
627 | // System.out.println(String.format("imageView: %dx%d, %dx%d", imageView.getMeasuredWidth(), imageView.getMeasuredHeight(), imageView.getWidth(), imageView.getHeight()));
628 | iv.setImageDrawable(usableResult);
629 | // System.out.println(String.format("imageView: %dx%d, %dx%d", imageView.getMeasuredWidth(), imageView.getMeasuredHeight(), imageView.getWidth(), imageView.getHeight()));
630 | // onLoaded is called with the loader's result (not what is actually used). null indicates failure.
631 | }
632 | if (callback != null && iv == imageView)
633 | callback.onLoaded(iv, loader.result, url, false);
634 | }
635 | clog("Populated: " + waitingCount);
636 | }
637 | };
638 |
639 |
640 | if (file.exists()) {
641 | try {
642 | if (checkCacheDuration(file, cacheDurationMs)) {
643 | clog("File Cache hit on: " + url + ". " + (System.currentTimeMillis() - file.lastModified()) + "ms old.");
644 |
645 | final AsyncTask fileloader = new AsyncTask() {
646 | @Override
647 | protected Void doInBackground(final Void... params) {
648 | loader.onDownloadComplete(null, null, filename);
649 | return null;
650 | }
651 | @Override
652 | protected void onPostExecute(final Void result) {
653 | completion.run();
654 | }
655 | };
656 | executeTask(fileloader);
657 | return;
658 | }
659 | else {
660 | clog("File cache has expired. Refreshing.");
661 | }
662 | }
663 | catch (final Exception ex) {
664 | }
665 | }
666 |
667 | for (UrlDownloader downloader: mDownloaders) {
668 | if (downloader.canDownloadUrl(url)) {
669 | try {
670 | downloader.download(context, url, filename, loader, completion);
671 | } catch (Exception e) {
672 | clog("Can't download from url: " + url + " Exception: " + e.getMessage());
673 | mPendingDownloads.remove(url);
674 | if (imageView != null) {
675 | mPendingViews.remove(imageView);
676 | }
677 | }
678 | return;
679 | }
680 | }
681 |
682 | imageView.setImageDrawable(defaultDrawable);
683 | }
684 |
685 | private static abstract class Loader implements UrlDownloader.UrlDownloaderCallback {
686 | Bitmap result;
687 | }
688 |
689 | private static HttpUrlDownloader mHttpDownloader = new HttpUrlDownloader();
690 | private static ContentUrlDownloader mContentDownloader = new ContentUrlDownloader();
691 | private static ContactContentUrlDownloader mContactDownloader = new ContactContentUrlDownloader();
692 | private static AssetUrlDownloader mAssetDownloader = new AssetUrlDownloader();
693 | private static FileUrlDownloader mFileDownloader = new FileUrlDownloader();
694 | private static ArrayList mDownloaders = new ArrayList();
695 | public static ArrayList getDownloaders() {
696 | return mDownloaders;
697 | }
698 |
699 | static {
700 | mDownloaders.add(mHttpDownloader);
701 | mDownloaders.add(mContactDownloader);
702 | mDownloaders.add(mContentDownloader);
703 | mDownloaders.add(mAssetDownloader);
704 | mDownloaders.add(mFileDownloader);
705 | }
706 |
707 | public static interface RequestPropertiesCallback {
708 | public ArrayList getHeadersForRequest(Context context, String url);
709 | }
710 |
711 | private static RequestPropertiesCallback mRequestPropertiesCallback;
712 |
713 | public static RequestPropertiesCallback getRequestPropertiesCallback() {
714 | return mRequestPropertiesCallback;
715 | }
716 |
717 | public static void setRequestPropertiesCallback(final RequestPropertiesCallback callback) {
718 | mRequestPropertiesCallback = callback;
719 | }
720 |
721 | private static DrawableCache mLiveCache = DrawableCache.getInstance();
722 | private static LruBitmapCache mDeadCache;
723 | private static HashSet mAllCache = new HashSet();
724 |
725 | private static int getHeapSize(final Context context) {
726 | return ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass() * 1024 * 1024;
727 | }
728 |
729 | /***
730 | * Remove a url from the cache
731 | * @param url
732 | * @return The bitmap removed, if any.
733 | */
734 | public static Bitmap remove(String url) {
735 | new File(getFilenameForUrl(url)).delete();
736 |
737 | Drawable drawable = mLiveCache.remove(url);
738 | if (drawable instanceof ZombieDrawable) {
739 | ZombieDrawable zombie = (ZombieDrawable)drawable;
740 | Bitmap ret = zombie.getBitmap();
741 | zombie.headshot();
742 | return ret;
743 | }
744 |
745 | return null;
746 | }
747 |
748 | /***
749 | * ZombieDrawable refcounts Bitmaps by hooking the finalizer.
750 | *
751 | */
752 | private static class ZombieDrawable extends BitmapDrawable {
753 | private static class Brains {
754 | int mRefCounter;
755 | boolean mHeadshot;
756 | }
757 | public ZombieDrawable(final String url, Resources resources, final Bitmap bitmap) {
758 | this(url, resources, bitmap, new Brains());
759 | }
760 |
761 | Brains mBrains;
762 | private ZombieDrawable(final String url, Resources resources, final Bitmap bitmap, Brains brains) {
763 | super(resources, bitmap);
764 | mUrl = url;
765 | mBrains = brains;
766 |
767 | mAllCache.add(bitmap);
768 | mDeadCache.remove(url);
769 | mLiveCache.put(url, this);
770 |
771 | mBrains.mRefCounter++;
772 | }
773 |
774 | public ZombieDrawable clone(Resources resources) {
775 | return new ZombieDrawable(mUrl, resources, getBitmap(), mBrains);
776 | }
777 |
778 | String mUrl;
779 |
780 | @Override
781 | protected void finalize() throws Throwable {
782 | super.finalize();
783 |
784 | mBrains.mRefCounter--;
785 | if (mBrains.mRefCounter == 0) {
786 | if (!mBrains.mHeadshot)
787 | mDeadCache.put(mUrl, getBitmap());
788 | mAllCache.remove(getBitmap());
789 | mLiveCache.remove(mUrl);
790 | clog("Zombie GC event " + mUrl);
791 | }
792 | }
793 |
794 | // kill this zombie, forever.
795 | public void headshot() {
796 | clog("BOOM! Headshot: " + mUrl);
797 | mBrains.mHeadshot = true;
798 | mLiveCache.remove(mUrl);
799 | mAllCache.remove(getBitmap());
800 | }
801 | }
802 |
803 | static void executeTask(final AsyncTask task) {
804 | if (Build.VERSION.SDK_INT < Constants.HONEYCOMB) {
805 | task.execute();
806 | } else {
807 | executeTaskHoneycomb(task);
808 | }
809 | }
810 |
811 | @TargetApi(Constants.HONEYCOMB)
812 | private static void executeTaskHoneycomb(final AsyncTask task) {
813 | task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
814 | }
815 |
816 | public static int getPendingDownloads() {
817 | return mPendingDownloads.size();
818 | }
819 |
820 | private static Hashtable mPendingViews = new Hashtable();
821 | private static Hashtable> mPendingDownloads = new Hashtable>();
822 | }
823 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | UrlImageViewHelperSample
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/UrlImageViewHelperSample.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | MANIFEST_FILE_PATH
8 | RESOURCES_DIR_PATH
9 | ASSETS_DIR_PATH
10 | NATIVE_LIBS_DIR_PATH
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
56 |
57 |
69 |
70 |
71 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/default.properties:
--------------------------------------------------------------------------------
1 | android.library.reference.1=../UrlImageViewHelper
2 | # Project target.
3 | target=android-15
4 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class * extends android.app.backup.BackupAgentHelper
14 | -keep public class * extends android.preference.Preference
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | -keepclasseswithmembernames class * {
18 | native ;
19 | }
20 |
21 | -keepclasseswithmembernames class * {
22 | public (android.content.Context, android.util.AttributeSet);
23 | }
24 |
25 | -keepclasseswithmembernames class * {
26 | public (android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers enum * {
30 | public static **[] values();
31 | public static ** valueOf(java.lang.String);
32 | }
33 |
34 | -keep class * implements android.os.Parcelable {
35 | public static final android.os.Parcelable$Creator *;
36 | }
37 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | android.library.reference.1=../UrlImageViewHelper
14 | # Project target.
15 | target=android-16
16 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koush/UrlImageViewHelper/3b1cb9240690561a1dab755a6db6cd009dd3fb0b/UrlImageViewHelperSample/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koush/UrlImageViewHelper/3b1cb9240690561a1dab755a6db6cd009dd3fb0b/UrlImageViewHelperSample/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koush/UrlImageViewHelper/3b1cb9240690561a1dab755a6db6cd009dd3fb0b/UrlImageViewHelperSample/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/res/drawable/loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koush/UrlImageViewHelper/3b1cb9240690561a1dab755a6db6cd009dd3fb0b/UrlImageViewHelperSample/res/drawable/loading.png
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/res/drawable/transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koush/UrlImageViewHelper/3b1cb9240690561a1dab755a6db6cd009dd3fb0b/UrlImageViewHelperSample/res/drawable/transparent.png
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/res/layout/image.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
17 |
18 |
23 |
24 |
25 |
29 |
30 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/res/layout/row.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
18 |
19 |
25 |
26 |
32 |
33 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello World, UrlImageViewHelperSample!
4 | UrlImageViewHelperSample
5 |
6 |
--------------------------------------------------------------------------------
/UrlImageViewHelperSample/src/com/koushikdutta/urlimageviewhelper/sample/UrlImageViewHelperSample.java:
--------------------------------------------------------------------------------
1 | package com.koushikdutta.urlimageviewhelper.sample;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.database.DataSetObserver;
6 | import android.graphics.Bitmap;
7 | import android.net.Uri;
8 | import android.os.Bundle;
9 | import android.view.Menu;
10 | import android.view.MenuItem;
11 | import android.view.MenuItem.OnMenuItemClickListener;
12 | import android.view.View;
13 | import android.view.View.OnClickListener;
14 | import android.view.ViewGroup;
15 | import android.view.animation.OvershootInterpolator;
16 | import android.view.animation.ScaleAnimation;
17 | import android.widget.*;
18 | import com.koushikdutta.urlimageviewhelper.UrlImageViewCallback;
19 | import com.koushikdutta.urlimageviewhelper.UrlImageViewHelper;
20 | import org.apache.http.HttpEntity;
21 | import org.apache.http.HttpResponse;
22 | import org.apache.http.client.methods.HttpGet;
23 | import org.apache.http.impl.client.DefaultHttpClient;
24 | import org.json.JSONArray;
25 | import org.json.JSONObject;
26 |
27 | import java.io.ByteArrayOutputStream;
28 | import java.io.DataInputStream;
29 | import java.io.IOException;
30 | import java.io.InputStream;
31 | import java.util.ArrayList;
32 |
33 | public class UrlImageViewHelperSample extends Activity {
34 | // turn a stream into a string
35 | private static String readToEnd(InputStream input) throws IOException
36 | {
37 | DataInputStream dis = new DataInputStream(input);
38 | byte[] stuff = new byte[1024];
39 | ByteArrayOutputStream buff = new ByteArrayOutputStream();
40 | int read = 0;
41 | while ((read = dis.read(stuff)) != -1)
42 | {
43 | buff.write(stuff, 0, read);
44 | }
45 |
46 | return new String(buff.toByteArray());
47 | }
48 |
49 | private ListView mListView;
50 | private MyAdapter mAdapter;
51 |
52 | private class Row extends ArrayList {
53 |
54 | }
55 |
56 | private class MyGridAdapter extends BaseAdapter {
57 | public MyGridAdapter(Adapter adapter) {
58 | mAdapter = adapter;
59 | mAdapter.registerDataSetObserver(new DataSetObserver() {
60 | @Override
61 | public void onChanged() {
62 | super.onChanged();
63 | notifyDataSetChanged();
64 | }
65 | @Override
66 | public void onInvalidated() {
67 | super.onInvalidated();
68 | notifyDataSetInvalidated();
69 | }
70 | });
71 | }
72 | Adapter mAdapter;
73 |
74 | @Override
75 | public int getCount() {
76 | return (int)Math.ceil((double)mAdapter.getCount() / 4d);
77 | }
78 |
79 | @Override
80 | public Row getItem(int position) {
81 | Row row = new Row();
82 | for (int i = position * 4; i < 4; i++) {
83 | if (mAdapter.getCount() < i)
84 | row.add(mAdapter.getItem(i));
85 | else
86 | row.add(null);
87 | }
88 | return row;
89 | }
90 |
91 | @Override
92 | public long getItemId(int position) {
93 | return position;
94 | }
95 |
96 | @Override
97 | public View getView(int position, View convertView, ViewGroup parent) {
98 | convertView = getLayoutInflater().inflate(R.layout.row, null);
99 | LinearLayout row = (LinearLayout)convertView;
100 | LinearLayout l = (LinearLayout)row.getChildAt(0);
101 | for (int child = 0; child < 4; child++) {
102 | int i = position * 4 + child;
103 | LinearLayout c = (LinearLayout)l.getChildAt(child);
104 | c.removeAllViews();
105 | if (i < mAdapter.getCount()) {
106 | c.addView(mAdapter.getView(i, null, null));
107 | }
108 | }
109 |
110 | return convertView;
111 | }
112 |
113 | }
114 |
115 | private class MyAdapter extends ArrayAdapter {
116 |
117 | public MyAdapter(Context context) {
118 | super(context, 0);
119 | }
120 |
121 | @Override
122 | public View getView(int position, View convertView, ViewGroup parent) {
123 | if (convertView == null)
124 | convertView = getLayoutInflater().inflate(R.layout.image, null);
125 |
126 | final ImageView iv = (ImageView)convertView.findViewById(R.id.image);
127 |
128 | iv.setAnimation(null);
129 | // yep, that's it. it handles the downloading and showing an interstitial image automagically.
130 | UrlImageViewHelper.setUrlDrawable(iv, getItem(position), R.drawable.loading, new UrlImageViewCallback() {
131 | @Override
132 | public void onLoaded(ImageView imageView, Bitmap loadedBitmap, String url, boolean loadedFromCache) {
133 | if (!loadedFromCache) {
134 | ScaleAnimation scale = new ScaleAnimation(0, 1, 0, 1, ScaleAnimation.RELATIVE_TO_SELF, .5f, ScaleAnimation.RELATIVE_TO_SELF, .5f);
135 | scale.setDuration(300);
136 | scale.setInterpolator(new OvershootInterpolator());
137 | imageView.startAnimation(scale);
138 | }
139 | }
140 | });
141 |
142 | return convertView;
143 | }
144 | }
145 |
146 | @Override
147 | public boolean onCreateOptionsMenu(Menu menu) {
148 | MenuItem clear = menu.add("Clear Cache");
149 | clear.setOnMenuItemClickListener(new OnMenuItemClickListener() {
150 | @Override
151 | public boolean onMenuItemClick(MenuItem item) {
152 | UrlImageViewHelper.cleanup(UrlImageViewHelperSample.this, 0);
153 | return true;
154 | }
155 | });
156 | return super.onCreateOptionsMenu(menu);
157 | }
158 |
159 | /** Called when the activity is first created. */
160 | @Override
161 | public void onCreate(Bundle savedInstanceState) {
162 | super.onCreate(savedInstanceState);
163 |
164 | setContentView(R.layout.main);
165 |
166 | final Button search = (Button)findViewById(R.id.search);
167 | final EditText searchText = (EditText)findViewById(R.id.search_text);
168 |
169 | mListView = (ListView)findViewById(R.id.results);
170 | mAdapter = new MyAdapter(this);
171 | MyGridAdapter a = new MyGridAdapter(mAdapter);
172 | mListView.setAdapter(a);
173 |
174 | search.setOnClickListener(new OnClickListener() {
175 | @Override
176 | public void onClick(View v) {
177 | // background the search call!
178 | Thread thread = new Thread() {
179 | @Override
180 | public void run() {
181 | try {
182 | // clear existing results
183 | runOnUiThread(new Runnable() {
184 | @Override
185 | public void run() {
186 | mAdapter.clear();
187 | }
188 | });
189 |
190 | // do a google image search, get the ~10 paginated results
191 | int start = 0;
192 | final ArrayList urls = new ArrayList();
193 | while (start < 40) {
194 | DefaultHttpClient client = new DefaultHttpClient();
195 | HttpGet get = new HttpGet(String.format("https://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=%s&start=%d&imgsz=medium", Uri.encode(searchText.getText().toString()), start));
196 | HttpResponse resp = client.execute(get);
197 | HttpEntity entity = resp.getEntity();
198 | InputStream is = entity.getContent();
199 | final JSONObject json = new JSONObject(readToEnd(is));
200 | is.close();
201 | final JSONArray results = json.getJSONObject("responseData").getJSONArray("results");
202 | for (int i = 0; i < results.length(); i++) {
203 | JSONObject result = results.getJSONObject(i);
204 | urls.add(result.getString("url"));
205 | }
206 |
207 | start += results.length();
208 | }
209 | // add the results to the adapter
210 | runOnUiThread(new Runnable() {
211 | @Override
212 | public void run() {
213 | for (String url: urls) {
214 | mAdapter.add(url);
215 | }
216 | }
217 | });
218 | }
219 | catch (final Exception ex) {
220 | // explodey error, lets toast it
221 | runOnUiThread(new Runnable() {
222 | @Override
223 | public void run() {
224 | Toast.makeText(UrlImageViewHelperSample.this, ex.toString(), Toast.LENGTH_LONG).show();
225 | }
226 | });
227 | }
228 | }
229 | };
230 | thread.start();
231 | }
232 | });
233 |
234 | }
235 | }
--------------------------------------------------------------------------------
/helper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koush/UrlImageViewHelper/3b1cb9240690561a1dab755a6db6cd009dd3fb0b/helper.png
--------------------------------------------------------------------------------
/helper2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koush/UrlImageViewHelper/3b1cb9240690561a1dab755a6db6cd009dd3fb0b/helper2.png
--------------------------------------------------------------------------------