├── .gitignore ├── res ├── drawable │ └── icon.png ├── values │ └── strings.xml └── layout │ └── main.xml ├── .classpath ├── AndroidManifest.xml ├── .project ├── proguard.cfg ├── src └── jp │ └── sharakova │ └── android │ └── urlimageview │ ├── Request.java │ ├── sample │ └── UrlImageViewSampleActivity.java │ ├── Channel.java │ ├── WorkerThread.java │ ├── UrlImageView.java │ └── ImageCache.java └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | gen/ 3 | project.properties 4 | -------------------------------------------------------------------------------- /res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharakova/UrlImageView/master/res/drawable/icon.png -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World, UrlImageViewSample! 4 | UrlImageView 5 | 6 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | UrlImageView 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/jp/sharakova/android/urlimageview/Request.java: -------------------------------------------------------------------------------- 1 | package jp.sharakova.android.urlimageview; 2 | 3 | import java.io.File; 4 | 5 | public final class Request { 6 | private final String url; 7 | private final File cacheDir; 8 | private final Runnable runnable; 9 | private Status status = Status.WAIT; 10 | 11 | public enum Status { 12 | WAIT, LOADING, LOADED 13 | } 14 | 15 | public Request(String url, File cacheDir) { 16 | this.url = url; 17 | this.cacheDir = cacheDir; 18 | this.runnable = getDefaultRunnable(); 19 | } 20 | 21 | public Request(String url, File cacheDir, Runnable runnable) { 22 | this.url = url; 23 | this.cacheDir = cacheDir; 24 | this.runnable = runnable; 25 | } 26 | 27 | public synchronized void setStatus(Status status) { 28 | this.status = status; 29 | } 30 | 31 | public synchronized Status getStatus() { 32 | return status; 33 | } 34 | 35 | public String getUrl() { 36 | return url; 37 | } 38 | 39 | public File getCacheDir() { 40 | return cacheDir; 41 | } 42 | 43 | public Runnable getRunnable() { 44 | return (runnable != null) ? runnable : getDefaultRunnable(); 45 | } 46 | 47 | private Runnable getDefaultRunnable() { 48 | return new Runnable() { 49 | public void run() { 50 | } 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/jp/sharakova/android/urlimageview/sample/UrlImageViewSampleActivity.java: -------------------------------------------------------------------------------- 1 | package jp.sharakova.android.urlimageview.sample; 2 | 3 | import jp.sharakova.android.urlimageview.ImageCache; 4 | import jp.sharakova.android.urlimageview.R; 5 | import jp.sharakova.android.urlimageview.UrlImageView; 6 | import jp.sharakova.android.urlimageview.UrlImageView.OnImageLoadListener; 7 | import android.app.Activity; 8 | import android.os.Bundle; 9 | import android.widget.Toast; 10 | 11 | public class UrlImageViewSampleActivity extends Activity { 12 | 13 | UrlImageView mImageView; 14 | 15 | /** Called when the activity is first created. */ 16 | @Override 17 | public void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.main); 20 | 21 | mImageView = (UrlImageView) findViewById(R.id.imageView); 22 | mImageView.setImageUrl("http://k.yimg.jp/images/top/sp/logo.gif", 23 | imageLoadListener); 24 | } 25 | 26 | @Override 27 | public void onDestroy() { 28 | ImageCache.deleteAll(getCacheDir()); 29 | super.onDestroy(); 30 | } 31 | 32 | final private OnImageLoadListener imageLoadListener = new OnImageLoadListener() { 33 | @Override 34 | public void onStart(String url) { 35 | Toast.makeText(getApplicationContext(), "start", Toast.LENGTH_SHORT) 36 | .show(); 37 | } 38 | 39 | @Override 40 | public void onComplete(String url) { 41 | Toast.makeText(getApplicationContext(), "end", Toast.LENGTH_SHORT) 42 | .show(); 43 | } 44 | }; 45 | } -------------------------------------------------------------------------------- /src/jp/sharakova/android/urlimageview/Channel.java: -------------------------------------------------------------------------------- 1 | package jp.sharakova.android.urlimageview; 2 | 3 | import java.util.LinkedList; 4 | 5 | public final class Channel { 6 | 7 | public enum Priority { 8 | HIGH, LOW 9 | } 10 | 11 | private final static Channel instance = new Channel(); 12 | 13 | static { 14 | instance.startWorkers(); 15 | } 16 | 17 | private static final int MAX_THREAD = 5; 18 | private final LinkedList requestQueue = new LinkedList(); 19 | private final WorkerThread[] threadPool; 20 | 21 | private Channel() { 22 | threadPool = new WorkerThread[MAX_THREAD]; 23 | for (int i = 0; i < threadPool.length; i++) { 24 | threadPool[i] = new WorkerThread("Worker-" + i, this); 25 | } 26 | } 27 | 28 | public static Channel getInstance() { 29 | return instance; 30 | } 31 | 32 | public synchronized void removeQueueAll() { 33 | requestQueue.clear(); 34 | } 35 | 36 | public void startWorkers() { 37 | for (WorkerThread thread : threadPool) { 38 | thread.start(); 39 | } 40 | } 41 | 42 | public void stopWorkers() { 43 | for (WorkerThread thread : threadPool) { 44 | thread.interrupt(); 45 | } 46 | } 47 | 48 | public synchronized void putRequest(Request request, Priority priority) { 49 | if (priority == Priority.HIGH) { 50 | requestQueue.addFirst(request); 51 | } else { 52 | requestQueue.addLast(request); 53 | } 54 | notifyAll(); 55 | } 56 | 57 | public synchronized Request takeRequest() { 58 | while (requestQueue.size() <= 0) { 59 | try { 60 | wait(); 61 | } catch (InterruptedException e) { 62 | } 63 | } 64 | return requestQueue.poll(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/jp/sharakova/android/urlimageview/WorkerThread.java: -------------------------------------------------------------------------------- 1 | package jp.sharakova.android.urlimageview; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.lang.ref.SoftReference; 6 | import java.net.HttpURLConnection; 7 | import java.net.URL; 8 | 9 | import android.graphics.Bitmap; 10 | import android.graphics.BitmapFactory; 11 | 12 | public final class WorkerThread extends Thread { 13 | private final Channel channel; 14 | 15 | public WorkerThread(String name, Channel channel) { 16 | super(name); 17 | this.channel = channel; 18 | } 19 | 20 | @Override 21 | public void run() { 22 | while (true) { 23 | Request request = channel.takeRequest(); 24 | request.setStatus(Request.Status.LOADING); 25 | SoftReference image = ImageCache.getImage( 26 | request.getCacheDir(), request.getUrl()); 27 | if (image == null || image.get() == null) { 28 | image = getImage(request.getUrl()); 29 | if (image != null && image.get() != null) { 30 | ImageCache.saveBitmap(request.getCacheDir(), 31 | request.getUrl(), image.get()); 32 | } 33 | } 34 | request.setStatus(Request.Status.LOADED); 35 | request.getRunnable().run(); 36 | } 37 | } 38 | 39 | private SoftReference getImage(String url) { 40 | try { 41 | return new SoftReference(getBitmapFromURL(url)); 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | return null; 45 | } catch (OutOfMemoryError e) { 46 | e.printStackTrace(); 47 | return null; 48 | } 49 | } 50 | 51 | private Bitmap getBitmapFromURL(String strUrl) throws IOException { 52 | HttpURLConnection con = null; 53 | InputStream in = null; 54 | 55 | try { 56 | URL url = new URL(strUrl); 57 | con = (HttpURLConnection) url.openConnection(); 58 | con.setUseCaches(true); 59 | con.setRequestMethod("GET"); 60 | con.setReadTimeout(500000); 61 | con.setConnectTimeout(50000); 62 | con.connect(); 63 | in = con.getInputStream(); 64 | return BitmapFactory.decodeStream(in); 65 | } finally { 66 | try { 67 | if (con != null) 68 | con.disconnect(); 69 | if (in != null) 70 | in.close(); 71 | } catch (Exception e) { 72 | e.printStackTrace(); 73 | } 74 | } 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /src/jp/sharakova/android/urlimageview/UrlImageView.java: -------------------------------------------------------------------------------- 1 | package jp.sharakova.android.urlimageview; 2 | 3 | import java.lang.ref.SoftReference; 4 | 5 | import android.content.Context; 6 | import android.graphics.Bitmap; 7 | import android.os.Handler; 8 | import android.util.AttributeSet; 9 | import android.widget.ImageView; 10 | 11 | public final class UrlImageView extends ImageView { 12 | private final Context context; 13 | 14 | private Request request; 15 | private String url; 16 | private boolean isLoading = false; 17 | private final Handler handler = new Handler(); 18 | private OnImageLoadListener listener = new OnImageLoadListener() { 19 | public void onStart(String url) { 20 | } 21 | 22 | public void onComplete(String url) { 23 | } 24 | }; 25 | 26 | public static interface OnImageLoadListener { 27 | public void onStart(String url); 28 | 29 | public void onComplete(String url); 30 | } 31 | 32 | private final Runnable threadRunnable = new Runnable() { 33 | public void run() { 34 | handler.post(imageLoadRunnable); 35 | } 36 | }; 37 | 38 | private final Runnable imageLoadRunnable = new Runnable() { 39 | public void run() { 40 | setImageLocalCache(); 41 | } 42 | }; 43 | 44 | private boolean setImageLocalCache() { 45 | SoftReference image = ImageCache.getImage( 46 | context.getCacheDir(), url); 47 | if (image != null && image.get() != null) { 48 | setImageBitmap(image.get()); 49 | listener.onComplete(url); 50 | isLoading = false; 51 | return true; 52 | } 53 | return false; 54 | } 55 | 56 | public UrlImageView(Context context) { 57 | super(context); 58 | this.context = context; 59 | } 60 | 61 | public UrlImageView(Context context, AttributeSet attrs) { 62 | super(context, attrs); 63 | this.context = context; 64 | } 65 | 66 | public UrlImageView(Context context, AttributeSet attrs, int defStyle) { 67 | super(context, attrs, defStyle); 68 | this.context = context; 69 | } 70 | 71 | public void setOnImageLoadListener(OnImageLoadListener listener) { 72 | this.listener = listener; 73 | } 74 | 75 | public void setImageUrl(String url, OnImageLoadListener listener) { 76 | setOnImageLoadListener(listener); 77 | setImageUrl(url); 78 | } 79 | 80 | public void setImageUrl(String url) { 81 | this.url = url; 82 | isLoading = true; 83 | request = new Request(url, context.getCacheDir(), threadRunnable); 84 | if (setImageLocalCache()) { 85 | return; 86 | } 87 | listener.onStart(url); 88 | Channel.getInstance().putRequest(request, Channel.Priority.HIGH); 89 | } 90 | 91 | public Boolean isLoading() { 92 | return isLoading; 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UrlImageView 2 | 3 | AndroidのImageViewで、インターネット上にある画像を表示する独自ImageView。 4 | MIT Licenseで、自由にお使いください。 5 | 6 | 変更点: 7 | * 複数のUrlImageViewを利用されると、画像の読み込み順番がLIFOの方がなにかと良かったので、画像を読み込む優先順位を変更。 8 | * WorkerThreadを実装しました。スレッド数は5つ。 9 | * これにより、GridViewなどで利用した場合、ユーザ操作で見たい画像がある程度先に読まれるようになった。 10 | * 画像のキャッシュの圧縮率は90です。また、アニメーションGIFなどには対応してません。 11 | * 画像のキャッシュデータをファイルに書き出す際に若干もたつくので、今後の課題です。 12 | 13 | ## 以下、注意点とサンプルActivity 14 | 15 | # Android マニフェスト 16 | * マニフェストファイルに android.permission.INTERNET のパーミッションをつける 17 | 18 | 19 | 20 | # layoutファイル 21 | * レイアウトファイルでは、ImageViewと同様の設定が可能。設定例。 22 | 23 | 28 | 29 | # Activity サンプル 30 | package jp.sharakova.android.urlimageview.sample; 31 | import jp.sharakova.android.urlimageview.ImageCache; 32 | import jp.sharakova.android.urlimageview.R; 33 | import jp.sharakova.android.urlimageview.UrlImageView; 34 | import jp.sharakova.android.urlimageview.UrlImageView.OnImageLoadListener; 35 | import android.app.Activity; 36 | import android.os.Bundle; 37 | import android.widget.Toast; 38 | 39 | public class UrlImageViewSampleActivity extends Activity { 40 | 41 | private UrlImageView mImageView; 42 | 43 | @Override 44 | public void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.main); 47 | mImageView = (UrlImageView)findViewById(R.id.imageView); 48 | mImageView.setImageUrl("http://k.yimg.jp/images/top/sp/logo.gif", imageLoadListener); 49 | } 50 | 51 | @Override 52 | public void onDestroy() { 53 | ImageCache.deleteAll(getCacheDir()); 54 | super.onDestroy(); 55 | } 56 | 57 | final private OnImageLoadListener imageLoadListener = new OnImageLoadListener() { 58 | @Override 59 | public void onStart(String url) { 60 | Toast.makeText(getApplicationContext(), "start", Toast.LENGTH_SHORT).show(); 61 | } 62 | 63 | @Override 64 | public void onComplete(String url) { 65 | Toast.makeText(getApplicationContext(), "end", Toast.LENGTH_SHORT).show(); 66 | } 67 | }; 68 | } 69 | 70 | 71 | * OnImageLoadListener の onStart で、画像を読み込む、直前に処理を入れる事ができます。 72 | * OnImageLoadListener の onComplete で、画像を読み込んだ後の処理を実行できます。 73 | * setImageUrl で、画像をインターネットから読み込みを開始して、読み込み終わると画像を表示いたします。読み込んだ画像は、一時的にAndroid内にキャッシュし、2度目の表示では高速に読み込む事ができます。 74 | * onDestroyで実行している。ImageCache.deleteAll(getCacheDir());で、Android内に保存したキャッシュを削除いたします。 -------------------------------------------------------------------------------- /src/jp/sharakova/android/urlimageview/ImageCache.java: -------------------------------------------------------------------------------- 1 | package jp.sharakova.android.urlimageview; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.lang.ref.SoftReference; 7 | import java.util.HashMap; 8 | 9 | import android.graphics.Bitmap; 10 | import android.graphics.BitmapFactory; 11 | import android.util.Log; 12 | 13 | public class ImageCache { 14 | 15 | private ImageCache() { 16 | } 17 | 18 | private static HashMap> cache = new HashMap>(); 19 | 20 | private static String getFileName(String url) { 21 | int hash = url.hashCode(); 22 | return String.valueOf(hash); 23 | } 24 | 25 | public static void saveBitmap(File cacheDir, String url, Bitmap bitmap) { 26 | cache.put(url, new SoftReference(bitmap)); 27 | 28 | String fileName = getFileName(url); 29 | File localFile = new File(cacheDir, fileName); 30 | 31 | FileOutputStream fos = null; 32 | try { 33 | fos = new FileOutputStream(localFile); 34 | bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos); 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | } catch (OutOfMemoryError e) { 38 | e.printStackTrace(); 39 | } finally { 40 | if (fos != null) { 41 | try { 42 | fos.close(); 43 | } catch (IOException e1) { 44 | Log.w("save", "finally"); 45 | } 46 | } 47 | } 48 | } 49 | 50 | public static SoftReference getImage(File cacheDir, String url) { 51 | 52 | SoftReference ref = cache.get(url); 53 | if (ref != null && ref.get() != null) { 54 | return ref; 55 | } 56 | 57 | String fileName = getFileName(url); 58 | File localFile = new File(cacheDir, fileName); 59 | SoftReference bitmap = null; 60 | try { 61 | bitmap = new SoftReference( 62 | BitmapFactory.decodeFile(localFile.getPath())); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } catch (OutOfMemoryError e) { 66 | e.printStackTrace(); 67 | cache.clear(); 68 | } 69 | return bitmap; 70 | } 71 | 72 | public static void memoryCacheClear() { 73 | cache.clear(); 74 | } 75 | 76 | public static void deleteAll(File cacheDir) { 77 | if (!cacheDir.isDirectory()) { 78 | return; 79 | } 80 | File[] files = cacheDir.listFiles(); 81 | for (File file : files) { 82 | if (file.isFile()) { 83 | if (!file.delete()) { 84 | Log.v("file", "file delete false"); 85 | } 86 | } 87 | } 88 | } 89 | 90 | public static long dirSize(File cacheDir) { 91 | long size = 0L; 92 | if (cacheDir == null) { 93 | return size; 94 | } 95 | if (cacheDir.isDirectory()) { 96 | for (File file : cacheDir.listFiles()) { 97 | size += file.length(); 98 | } 99 | } else { 100 | size = cacheDir.length(); 101 | } 102 | return size; 103 | } 104 | } --------------------------------------------------------------------------------