├── .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 | ![](https://raw.github.com/koush/UrlImageViewHelper/master/helper2.png) 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 | 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 |