├── .gitignore ├── AndroidManifest.xml ├── ImageLoaderSample.iml ├── README.md ├── build.gradle ├── build.xml ├── gen └── com │ └── wangjie │ └── imageloadersample │ ├── Manifest.java │ └── R.java ├── library └── ImageLoaderSample_1.0.jar ├── local.properties ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ ├── aaa.jpg │ └── ic_launcher.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png └── values │ └── strings.xml ├── settings.gradle └── src └── com └── wangjie └── imageloadersample ├── customviews └── FadeImageView.java └── imageloader ├── CacheConfig.java ├── FileCache.java ├── ImageLoader.java └── MemoryCache.java /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | bulid/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Eclipse project files 20 | .classpath 21 | .project 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Intellij project files 27 | *.iml 28 | *.ipr 29 | *.iws 30 | .idea/ 31 | 32 | # User define 33 | out/ 34 | proguard_logs/* 35 | 36 | .git 37 | .gradle 38 | build 39 | *.svn 40 | *~ 41 | target 42 | *.ipr 43 | *.iml 44 | *.iws 45 | .settings 46 | .project 47 | .classpath 48 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ImageLoaderSample.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ImageLoaderSample 2 | ================= 3 | 4 | android端用于异步加载图片,内存缓存,文件缓存,imageview显示图片时增加淡入淡出动画。 5 | 6 | ### 注意: 7 | 需要添加Library[AndroidBucket](https://github.com/wangjiegulu/AndroidBucket)项目的支持(日志、线程、util等)的依赖,ImageLoader使用的ThreadPool,修改配置文件代码 8 | 9 | ###Gradle 10 | compile 'com.github.wangjiegulu:ImageLoaderSample:1.0.1' 11 | ###Maven 12 | 13 | com.github.wangjiegulu 14 | ImageLoaderSample 15 | 1.0.1 16 | 17 | 18 | 初始化配置 19 | ----------------- 20 | ### 创建MyApplication 继承Application,在onCreate中增加如下代码:
21 | /** 22 | * Created with IntelliJ IDEA. 23 | * Author: wangjie email:tiantian.china.2@gmail.com 24 | * Date: 14-2-27 25 | * Time: 上午11:25 26 | */ 27 | public class MyApplication extends ABApplication{ 28 | 29 | @Override 30 | protected void initImageLoader() { 31 | ImageLoader.init(getApplicationContext(), 32 | new CacheConfig() 33 | .setDefRequiredSize(600) // 设置默认的加载图片尺寸(表示宽高任一不超过该值,默认是70px) 34 | .setDefaultResId(R.drawable.ic_launcher) // 设置显示的默认图片(默认是0,即空白图片) 35 | .setBitmapConfig(Bitmap.Config.ARGB_8888) // 设置图片位图模式(默认是Bitmap.CacheConfig.ARGB_8888) 36 | .setMemoryCachelimit(Runtime.getRuntime().maxMemory() / 3) // 设置图片内存缓存大小(默认是Runtime.getRuntime().maxMemory() / 4) 37 | // .setFileCachePath(Environment.getExternalStorageDirectory().toString() + "/mycache") // 设置文件缓存保存目录 38 | ); 39 | } 40 | ...... 41 | } 42 |
43 | ###然后再AndroidManifest.xml中添加:
44 | 47 | ...... 48 | 49 | 50 | ### 加载图片的调用方式如下:
51 | holder.progress.setText("0%"); 52 | holder.progress.setVisibility(View.VISIBLE); 53 | final ViewHolder vhr = holder; 54 | ImageLoader.getInstances().displayImage(list.get(position), holder.image, new ImageLoader.OnImageLoaderListener() { 55 | @Override 56 | public void onProgressImageLoader(ImageView imageView, int currentSize, int totalSize) { 57 | vhr.progress.setText(currentSize * 100 / totalSize + "%"); 58 | } 59 | 60 | @Override 61 | public void onFinishedImageLoader(ImageView imageView, Bitmap bitmap) { 62 | vhr.progress.setVisibility(View.GONE); 63 | } 64 | }); 65 | 或者: 66 | ImageLoader.getInstances().displayImage(url, imageIv); 67 | 或者 68 | ImageLoader.getInstances().displayImage(url, imageIv, 100); 69 | 70 | 备注 71 | ------------ 72 | 例子中,用到了一部分注解(与ImageLoader功能无关,但是可以简化代码的编写) 73 | 可以点下面连接进入AndroidInject 74 | [AndroidInject](https://github.com/wangjiegulu/androidInject)
75 | 76 | 77 | License 78 | ======= 79 | 80 | Copyright 2013 Wang Jie 81 | 82 | Licensed under the Apache License, Version 2.0 (the "License"); 83 | you may not use this file except in compliance with the License. 84 | You may obtain a copy of the License at 85 | 86 | http://www.apache.org/licenses/LICENSE-2.0 87 | 88 | Unless required by applicable law or agreed to in writing, software 89 | distributed under the License is distributed on an "AS IS" BASIS, 90 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 91 | See the License for the specific language governing permissions and 92 | limitations under the License. 93 | 94 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() //你所使用的仓库 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:1.0.0' // Gradle 的Android 插件版本 7 | } 8 | } 9 | apply plugin: 'com.android.library' // 导入Android Application 插件,将此module 配置成application module 10 | 11 | repositories { 12 | jcenter() // 仓库 13 | } 14 | 15 | android { 16 | 17 | compileSdkVersion 21 // 使用SDK的版本,请配置你SDK中有的最新版本 18 | buildToolsVersion "21.0.2" // buildTools 版本,你SDK中有哪个版本配哪个版本,建议更新到最新的版本 19 | 20 | 21 | defaultConfig { 22 | minSdkVersion 9 23 | targetSdkVersion 21 24 | versionCode 2 25 | versionName "1.0.1" 26 | } 27 | 28 | buildTypes { // 配置打包的版本 29 | release { // 发行版 30 | minifyEnabled false // 是否混淆 31 | // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' // 默认混淆文件 32 | // proguardFiles 'proguard-project.txt' // 自定义混淆文件 33 | } 34 | debug { // debug 版 35 | 36 | } 37 | } 38 | 39 | sourceSets { // 如果你的工程是从ANT 中迁移过来,可以使用sourceSets 来配置工程结构,如果你使用的是标准Gradle 结构,可以不需要配置。 40 | main { 41 | java.srcDirs = ['src'] 42 | aidl.srcDirs = ['src'] 43 | renderscript.srcDirs = ['src'] 44 | res.srcDirs = ['res'] 45 | assets.srcDirs = ['assets'] 46 | jniLibs.srcDirs = ['libs'] // 配置此处才会打包jni 的.so 文件 47 | jni.srcDirs=['jni'] 48 | manifest.srcFile 'AndroidManifest.xml' 49 | } 50 | } 51 | 52 | packagingOptions { 53 | exclude 'META-INF/DEPENDENCIES.txt' 54 | exclude 'META-INF/LICENSE.txt' 55 | exclude 'META-INF/NOTICE.txt' 56 | exclude 'META-INF/NOTICE' 57 | exclude 'META-INF/LICENSE' 58 | exclude 'META-INF/DEPENDENCIES' 59 | exclude 'META-INF/notice.txt' 60 | exclude 'META-INF/MANIFEST.MF' 61 | exclude 'META-INF/license.txt' 62 | exclude 'META-INF/dependencies.txt' 63 | } 64 | 65 | } 66 | 67 | /** 68 | * https://gradle.org/docs/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html 69 | */ 70 | dependencies { 71 | // compile fileTree(dir: 'libs', include: ['*.jar']) 72 | // compile 'com.android.support:support-v4:21.0.0' 73 | compile project(":AndroidBucket") 74 | // compile 'com.github.wangjiegulu:AndroidBucket:1.0.1' 75 | 76 | } -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 51 | 52 | 53 | 54 | 58 | 59 | 71 | 72 | 73 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /gen/com/wangjie/imageloadersample/Manifest.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.wangjie.imageloadersample; 4 | 5 | /* This stub is for using by IDE only. It is NOT the Manifest class actually packed into APK */ 6 | public final class Manifest { 7 | } -------------------------------------------------------------------------------- /gen/com/wangjie/imageloadersample/R.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.wangjie.imageloadersample; 4 | 5 | /* This stub is for using by IDE only. It is NOT the R class actually packed into APK */ 6 | public final class R { 7 | } -------------------------------------------------------------------------------- /library/ImageLoaderSample_1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjiegulu/ImageLoaderSample/05d9378bf367cb1d89d02c761f16bc7ef5d275f4/library/ImageLoaderSample_1.0.jar -------------------------------------------------------------------------------- /local.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 *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | 7 | # location of the SDK. This is only used by Ant 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | sdk.dir=C:\\Users\\Administrator\\android-sdks 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | # Project target. 14 | target=android-21 15 | android.library=true 16 | android.library.reference.1=../AndroidBucket 17 | 18 | -------------------------------------------------------------------------------- /res/drawable-hdpi/aaa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjiegulu/ImageLoaderSample/05d9378bf367cb1d89d02c761f16bc7ef5d275f4/res/drawable-hdpi/aaa.jpg -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjiegulu/ImageLoaderSample/05d9378bf367cb1d89d02c761f16bc7ef5d275f4/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjiegulu/ImageLoaderSample/05d9378bf367cb1d89d02c761f16bc7ef5d275f4/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjiegulu/ImageLoaderSample/05d9378bf367cb1d89d02c761f16bc7ef5d275f4/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjiegulu/ImageLoaderSample/05d9378bf367cb1d89d02c761f16bc7ef5d275f4/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ImageLoaderSample 4 | 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':AndroidBucket' 2 | //include ':AndroidInject' 3 | 4 | //project(":AndroidBucket").projectDir = new File(settingsDir, "../AndroidBucket"); 5 | //project(":AndroidInject").projectDir = new File(settingsDir, "../androidInject"); 6 | 7 | Properties props = new Properties() 8 | props.load(new FileInputStream("local.properties")) 9 | project(":AndroidBucket").projectDir = new File(settingsDir, props.getProperty("AndroidBucket")); -------------------------------------------------------------------------------- /src/com/wangjie/imageloadersample/customviews/FadeImageView.java: -------------------------------------------------------------------------------- 1 | package com.wangjie.imageloadersample.customviews; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.drawable.BitmapDrawable; 6 | import android.util.AttributeSet; 7 | import android.view.animation.AlphaAnimation; 8 | import android.view.animation.Animation; 9 | import android.widget.ImageView; 10 | 11 | /** 12 | * 设置bitmap时会显示淡入淡出的动画的ImageView 13 | * Created with IntelliJ IDEA. 14 | * Author: wangjie email:tiantian.china.2@gmail.com 15 | * Date: 14-2-27 16 | * Time: 下午5:43 17 | */ 18 | public class FadeImageView extends ImageView{ 19 | private AlphaAnimation alphaOut; 20 | private AlphaAnimation alphaIn; 21 | 22 | private Context context; 23 | public FadeImageView(Context context) { 24 | super(context); 25 | this.context = context; 26 | } 27 | 28 | public FadeImageView(Context context, AttributeSet attrs) { 29 | super(context, attrs); 30 | this.context = context; 31 | } 32 | 33 | public FadeImageView(Context context, AttributeSet attrs, int defStyle) { 34 | super(context, attrs, defStyle); 35 | this.context = context; 36 | } 37 | 38 | private AlphaAnimation getAlphaOut(){ 39 | if(null != alphaOut){ 40 | return alphaOut; 41 | } 42 | alphaOut = new AlphaAnimation(1.0f, 0f); 43 | alphaOut.setFillAfter(true); 44 | alphaOut.setDuration(200); 45 | return alphaOut; 46 | } 47 | 48 | private AlphaAnimation getAlphaIn(){ 49 | if(null != alphaIn){ 50 | return alphaIn; 51 | } 52 | alphaIn = new AlphaAnimation(0f, 1.0f); 53 | alphaIn.setFillAfter(true); 54 | alphaIn.setDuration(200); 55 | return alphaIn; 56 | } 57 | 58 | public void setImageBitmapAnim(final Bitmap bm) { 59 | // super.setImageBitmap(bm); 60 | getAlphaOut().setAnimationListener(new Animation.AnimationListener() { 61 | @Override 62 | public void onAnimationStart(Animation animation) { 63 | } 64 | 65 | @Override 66 | public void onAnimationEnd(Animation animation) { 67 | setImageDrawable(new BitmapDrawable(context.getResources(), bm)); 68 | startAnimation(getAlphaIn()); 69 | } 70 | 71 | @Override 72 | public void onAnimationRepeat(Animation animation) { 73 | } 74 | }); 75 | this.startAnimation(getAlphaOut()); 76 | 77 | } 78 | 79 | 80 | 81 | 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/com/wangjie/imageloadersample/imageloader/CacheConfig.java: -------------------------------------------------------------------------------- 1 | package com.wangjie.imageloadersample.imageloader; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * Author: wangjie email:tiantian.china.2@gmail.com 8 | * Date: 14-2-28 9 | * Time: 下午1:00 10 | */ 11 | public class CacheConfig { 12 | 13 | /** 14 | * 默认的加载图片尺寸(表示宽高任一不超过该值,默认是70px) 15 | */ 16 | private int defRequiredSize = 70; 17 | 18 | /** 19 | * 显示的默认图片(默认是0,即空白图片) 20 | */ 21 | private int defaultResId; 22 | 23 | /** 24 | * 图片位图模式(默认是Bitmap.CacheConfig.ARGB_8888) 25 | */ 26 | private Bitmap.Config bitmapConfig = Bitmap.Config.ARGB_8888; 27 | 28 | /** 29 | * 图片内存缓存大小(默认是Runtime.getRuntime().maxMemory() / 4) 30 | */ 31 | private long memoryCachelimit; 32 | 33 | /** 34 | * 文件缓存保存目录 35 | */ 36 | private String fileCachePath; 37 | 38 | 39 | 40 | 41 | public int getDefRequiredSize() { 42 | return defRequiredSize; 43 | } 44 | 45 | public CacheConfig setDefRequiredSize(int defRequiredSize) { 46 | this.defRequiredSize = defRequiredSize; 47 | return this; 48 | } 49 | 50 | public int getDefaultResId() { 51 | return defaultResId; 52 | } 53 | 54 | public CacheConfig setDefaultResId(int defaultResId) { 55 | this.defaultResId = defaultResId; 56 | return this; 57 | } 58 | 59 | public Bitmap.Config getBitmapConfig() { 60 | return bitmapConfig; 61 | } 62 | 63 | public CacheConfig setBitmapConfig(Bitmap.Config bitmapConfig) { 64 | this.bitmapConfig = bitmapConfig; 65 | return this; 66 | } 67 | 68 | public String getFileCachePath() { 69 | return fileCachePath; 70 | } 71 | 72 | public CacheConfig setFileCachePath(String fileCachePath) { 73 | this.fileCachePath = fileCachePath; 74 | return this; 75 | } 76 | 77 | public long getMemoryCachelimit() { 78 | return memoryCachelimit; 79 | } 80 | 81 | public CacheConfig setMemoryCachelimit(long memoryCachelimit) { 82 | this.memoryCachelimit = memoryCachelimit; 83 | return this; 84 | } 85 | 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/com/wangjie/imageloadersample/imageloader/FileCache.java: -------------------------------------------------------------------------------- 1 | package com.wangjie.imageloadersample.imageloader; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.File; 6 | 7 | /** 8 | * @author wangjie email:tiantian.china.2@gmail.com 9 | * @version 创建时间:2012-10-14 下午7:29:23 10 | */ 11 | public class FileCache { 12 | 13 | /** 14 | * 文件缓存保存目录 15 | */ 16 | private File cacheDir; 17 | 18 | public FileCache(Context context, String cachePath) { 19 | // 如果有SD卡则在SD卡中建一个目录存放缓存的图片 20 | // 没有SD卡就放在系统的缓存目录中 21 | if (android.os.Environment.getExternalStorageState().equals( 22 | android.os.Environment.MEDIA_MOUNTED)){ 23 | cacheDir = new File(cachePath); 24 | }else{ 25 | cacheDir = context.getCacheDir(); 26 | } 27 | if (!cacheDir.exists()){ 28 | cacheDir.mkdirs(); 29 | } 30 | 31 | } 32 | 33 | /** 34 | * 获取指定文件缓存 35 | * @param url 36 | * @return 37 | */ 38 | public File getFile(String url) { 39 | // 将url的hashCode作为缓存的文件名 40 | String filename = String.valueOf(url.hashCode()); 41 | // Another possible solution 42 | // String filename = URLEncoder.encode(url); 43 | File f = new File(cacheDir, filename); 44 | return f; 45 | 46 | } 47 | 48 | /** 49 | * 清除文件缓存 50 | */ 51 | public void clear() { 52 | File[] files = cacheDir.listFiles(); 53 | if (files == null){ 54 | return; 55 | } 56 | 57 | for (File f : files){ 58 | f.delete(); 59 | } 60 | } 61 | 62 | 63 | 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/com/wangjie/imageloadersample/imageloader/ImageLoader.java: -------------------------------------------------------------------------------- 1 | package com.wangjie.imageloadersample.imageloader; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.os.Environment; 7 | import android.os.Handler; 8 | import android.os.Message; 9 | import android.util.Log; 10 | import android.widget.ImageView; 11 | import com.wangjie.androidbucket.log.Logger; 12 | import com.wangjie.androidbucket.thread.Runtask; 13 | import com.wangjie.androidbucket.thread.ThreadPool; 14 | import com.wangjie.androidbucket.utils.ABFileUtil; 15 | import com.wangjie.androidbucket.utils.ABIOUtil; 16 | import com.wangjie.androidbucket.utils.imageprocess.ABImageProcess; 17 | import com.wangjie.imageloadersample.customviews.FadeImageView; 18 | 19 | import java.io.*; 20 | import java.net.HttpURLConnection; 21 | import java.net.URL; 22 | import java.util.Collections; 23 | import java.util.Map; 24 | import java.util.WeakHashMap; 25 | 26 | /** 27 | * @author wangjie email:tiantian.china.2@gmail.com 28 | * @version 创建时间:2012-10-14 下午7:30:07 29 | */ 30 | public class ImageLoader { 31 | 32 | /** 33 | * 图片加载监听器 34 | */ 35 | public interface OnImageLoaderListener { 36 | /** 37 | * 用于在加载过程中回调更新UI进度 38 | * 39 | * @param imageView 40 | * @param currentSize 41 | * @param totalSize 42 | */ 43 | public void onProgressImageLoader(ImageView imageView, int currentSize, int totalSize); 44 | 45 | /** 46 | * 完成加载后回调 47 | * 48 | * @param imageView 要加载ImageView 49 | * @param bitmap 加载的图片 50 | */ 51 | public void onFinishedImageLoader(ImageView imageView, Bitmap bitmap); 52 | } 53 | 54 | 55 | private static final String TAG = ImageLoader.class.getSimpleName(); 56 | 57 | private static ImageLoader imageLoader; 58 | 59 | public MemoryCache memoryCache; // 内存缓存 60 | public FileCache fileCache; // 文件缓存 61 | private Map imageViews = Collections 62 | .synchronizedMap(new WeakHashMap()); // 为每个imageview设置对应的url 63 | // 线程池 64 | // ExecutorService executorService; 65 | 66 | /** 67 | * ****************************配置信息BEGIN****************************** 68 | */ 69 | // applicationContext 70 | private Context context; 71 | 72 | /** 73 | * 配置文件 74 | */ 75 | private CacheConfig config; 76 | 77 | /*******************************配置信息END*******************************/ 78 | 79 | // private ImageLoader() { 80 | // executorService = Executors.newFixedThreadPool(5); 81 | // } 82 | 83 | /** 84 | * 初始化方法(初始化各种缓存配置),推荐在Application中调用 85 | * 86 | * @param context 87 | */ 88 | public static void init(Context context, CacheConfig config) { 89 | imageLoader = new ImageLoader(); 90 | imageLoader.context = context; 91 | /** 92 | * 如果配置了CacheConfig,则使用配置的Config;否则,使用默认的Config 93 | */ 94 | imageLoader.config = null != config ? config : new CacheConfig(); 95 | imageLoader.memoryCache = new MemoryCache(config.getMemoryCachelimit()); 96 | /** 97 | * 如果在config中配置了缓存目录,则使用之;否则,使用默认的缓存路径 98 | */ 99 | imageLoader.fileCache = new FileCache(imageLoader.context, null != config.getFileCachePath() ? config.getFileCachePath() : Environment.getExternalStorageDirectory().toString() + "/Android/data/" + context.getPackageName() + "/cache"); 100 | 101 | 102 | } 103 | 104 | /** 105 | * 获取ImageLoader实例 106 | * 107 | * @return 108 | */ 109 | public static ImageLoader getInstances() { 110 | if (null == imageLoader) { 111 | Log.e(TAG, "imageLoader had not be initialized"); 112 | } 113 | return imageLoader; 114 | } 115 | 116 | /** 117 | * 最主要的方法 118 | * 119 | * @param url 120 | * @param imageView 121 | * @param requiredSize 裁剪图片大小尺寸(一直裁剪到图片宽或高 至少有一个小与requiredSize的时候) 122 | * @param listener 123 | * @param defaultPicResId 124 | */ 125 | public void displayImage(String url, ImageView imageView, int requiredSize, OnImageLoaderListener listener, int defaultPicResId, boolean isOnlyMonmery) { 126 | // String identityCode = url + "_" + requiredSize; 127 | if (null == url) { 128 | imageView.setImageResource(defaultPicResId); 129 | } 130 | url = getIdentityCode(url, requiredSize); 131 | imageViews.put(imageView, url); 132 | // 先从内存缓存中查找 133 | Bitmap bitmap = memoryCache.get(url); 134 | if (bitmap != null) { 135 | imageView.setImageBitmap(bitmap); 136 | if (null != listener) { 137 | listener.onFinishedImageLoader(imageView, bitmap); // 通知完成加载 138 | } 139 | } else { 140 | if (isOnlyMonmery) { // 是否只是从内存中读取(保证显示速度) 141 | imageView.setImageResource(defaultPicResId); 142 | return; 143 | } 144 | /** 145 | * 如果defaultPicResId小于0,则不设置默认图片 146 | */ 147 | if (defaultPicResId < 0) { 148 | queuePhoto(url, imageView, requiredSize, listener); 149 | return; 150 | } 151 | /** 152 | * 如果defaultPicResId等于0,则设置默认图片为config中的默认图片,并开启新线程加载真实需要的图片 153 | * 如果defaultPicResId大于0,则设置默认图片为指定的默认图片,并开启新线程加载真实需要的图片 154 | */ 155 | if (defaultPicResId == 0) { 156 | defaultPicResId = config.getDefaultResId(); 157 | } 158 | imageView.setImageResource(defaultPicResId); 159 | 160 | queuePhoto(url, imageView, requiredSize, listener); 161 | } 162 | } 163 | 164 | public void displayImage(String url, ImageView imageView, int requiredSize, OnImageLoaderListener listener, int defaultPicResId) { 165 | displayImage(url, imageView, requiredSize, listener, defaultPicResId, false); 166 | } 167 | 168 | public void displayImage(String url, ImageView imageView) { 169 | displayImage(url, imageView, config.getDefRequiredSize(), null, 0, false); 170 | } 171 | 172 | public void displayImage(String url, ImageView imageView, int requiredSize) { 173 | displayImage(url, imageView, requiredSize, null, 0, false); 174 | } 175 | 176 | public void displayImage(String url, ImageView imageView, OnImageLoaderListener listener) { 177 | displayImage(url, imageView, config.getDefRequiredSize(), listener, 0, false); 178 | } 179 | 180 | public void displayImage(String url, ImageView imageView, OnImageLoaderListener listener, int defaultPicResId) { 181 | displayImage(url, imageView, config.getDefRequiredSize(), listener, defaultPicResId, false); 182 | } 183 | 184 | /** 185 | * ********* 增加可以设置是否是从内存中读取的方法 *********** 186 | */ 187 | public void displayImage(String url, ImageView imageView, boolean isOnlyMomery) { 188 | displayImage(url, imageView, config.getDefRequiredSize(), null, 0, isOnlyMomery); 189 | } 190 | 191 | public void displayImage(String url, ImageView imageView, int requiredSize, boolean isOnlyMomery) { 192 | displayImage(url, imageView, requiredSize, null, 0, isOnlyMomery); 193 | } 194 | 195 | public void displayImage(String url, ImageView imageView, OnImageLoaderListener listener, boolean isOnlyMomery) { 196 | displayImage(url, imageView, config.getDefRequiredSize(), listener, 0, isOnlyMomery); 197 | } 198 | 199 | public void displayImage(String url, ImageView imageView, OnImageLoaderListener listener, int defaultPicResId, boolean isOnlyMomery) { 200 | displayImage(url, imageView, config.getDefRequiredSize(), listener, defaultPicResId, isOnlyMomery); 201 | } 202 | 203 | 204 | /** 205 | * 启动线程加载图片 206 | * 207 | * @param url 208 | * @param imageView 209 | * @param requiredSize 210 | * @param listener 211 | */ 212 | private void queuePhoto(String url, ImageView imageView, int requiredSize, OnImageLoaderListener listener) { 213 | PhotoToLoad p = new PhotoToLoad(url, imageView, listener); 214 | // executorService.submit(new PhotosLoader(p, requiredSize)); 215 | ThreadPool.go(new PhotosLoader(p, requiredSize)); 216 | 217 | } 218 | 219 | /** 220 | * 执行网络请求加载图片 221 | * 222 | * @param url 223 | * @param requiredSize 224 | * @return 225 | */ 226 | private Bitmap getBitmap(String url, int requiredSize, PhotoToLoad photoToLoad) { 227 | File f = fileCache.getFile(url); 228 | // 先从文件缓存中查找是否有 229 | Bitmap b = decodeFile(f, requiredSize); 230 | if (b != null) 231 | return b; 232 | 233 | InputStream is = null; 234 | OutputStream os = null; 235 | try { 236 | if (f.exists() && f.isDirectory()) { 237 | f.delete(); 238 | f.createNewFile(); 239 | } 240 | ABFileUtil.getFileAutoCreated(f.getAbsolutePath()); 241 | os = new FileOutputStream(f); 242 | String realUri = getUriFromIdentityCode(url); 243 | 244 | // 如果是本地文件 245 | if (!realUri.startsWith("http")) { 246 | Bitmap bm = ABImageProcess.getSmallBitmap(realUri, requiredSize, requiredSize); 247 | bm.compress(Bitmap.CompressFormat.JPEG, 100, os); 248 | return bm; 249 | } 250 | 251 | // 如果是网络图片,从指定的url中下载图片 252 | Bitmap bitmap = null; 253 | URL imageUrl = new URL(realUri); 254 | HttpURLConnection conn = (HttpURLConnection) imageUrl 255 | .openConnection(); 256 | conn.setConnectTimeout(30000); 257 | conn.setReadTimeout(30000); 258 | conn.setInstanceFollowRedirects(true); 259 | is = conn.getInputStream(); 260 | 261 | // CopyStream(is, os, conn.getContentLength(), photoToLoad); 262 | 263 | photoToLoad.totalSize = conn.getContentLength(); 264 | int buffer_size = 1024; 265 | byte[] bytes = new byte[buffer_size]; 266 | for (; ; ) { 267 | int count = is.read(bytes, 0, buffer_size); 268 | 269 | if (count == -1) { 270 | break; 271 | } 272 | os.write(bytes, 0, count); 273 | 274 | if (null != photoToLoad.onImageLoaderListener) { // 如果设置了图片加载监听,则回调 275 | Message msg = loaderHandler.obtainMessage(); 276 | photoToLoad.currentSize += count; 277 | msg.arg1 = IMAGE_LOADER_PROCESS; 278 | msg.obj = photoToLoad; 279 | loaderHandler.sendMessage(msg); 280 | } 281 | 282 | } 283 | 284 | bitmap = decodeFile(f, requiredSize); 285 | return bitmap; 286 | } catch (Exception ex) { 287 | Logger.w(TAG, ex); 288 | return null; 289 | } finally { 290 | ABIOUtil.closeIO(is, os); 291 | } 292 | } 293 | 294 | /** 295 | * decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的 296 | * 297 | * @param f 298 | * @param requiredSize 299 | * @return 300 | */ 301 | private Bitmap decodeFile(File f, int requiredSize) { 302 | try { 303 | // decode image size 304 | BitmapFactory.Options o = new BitmapFactory.Options(); 305 | o.inJustDecodeBounds = true; 306 | BitmapFactory.decodeStream(new FileInputStream(f), null, o); 307 | 308 | // 计算合理的缩放指数(2的整幂数) 309 | int width_tmp = o.outWidth, height_tmp = o.outHeight; 310 | int scale = 1; 311 | 312 | while (true) { 313 | // if (width_tmp / 2 < requiredSize || height_tmp / 2 < requiredSize){ 314 | // break; 315 | // } 316 | // width_tmp /= 2; 317 | // height_tmp /= 2; 318 | // scale *= 2; 319 | if (width_tmp <= requiredSize || height_tmp <= requiredSize) { 320 | break; 321 | } 322 | width_tmp /= 2; 323 | height_tmp /= 2; 324 | scale *= 2; 325 | 326 | } 327 | 328 | // decode with inSampleSize 329 | BitmapFactory.Options o2 = new BitmapFactory.Options(); 330 | // o2.inSampleSize = (int) scale; 331 | o2.inSampleSize = scale; 332 | o2.inPreferredConfig = config.getBitmapConfig(); 333 | return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); 334 | 335 | } catch (FileNotFoundException e) { 336 | } 337 | return null; 338 | } 339 | 340 | /** 341 | * 图片加载对象的封装 342 | */ 343 | private class PhotoToLoad { 344 | public String url; 345 | public ImageView imageView; 346 | public OnImageLoaderListener onImageLoaderListener; 347 | public int currentSize; 348 | public int totalSize; 349 | public Bitmap bitmap; 350 | 351 | public PhotoToLoad(String u, ImageView i, OnImageLoaderListener listener) { 352 | url = u; 353 | imageView = i; 354 | this.onImageLoaderListener = listener; 355 | } 356 | 357 | } 358 | 359 | /** 360 | * 异步加载图片 361 | */ 362 | class PhotosLoader extends Runtask { 363 | PhotosLoader(Object... objs) { 364 | super(objs); 365 | } 366 | 367 | @Override 368 | public Object runInBackground() { 369 | PhotoToLoad photoToLoad = (PhotoToLoad) objs[0]; 370 | int requiredSize = (Integer) objs[1]; 371 | 372 | if (imageViewReused(photoToLoad)) { // 防止图片错位(如果加载的图片不是当前需要加载的图片,则不做任何处理) 373 | return null; 374 | } 375 | Bitmap bmp = getBitmap(photoToLoad.url, requiredSize, photoToLoad); // 网络加载图片 376 | 377 | memoryCache.put(photoToLoad.url, bmp); 378 | if (imageViewReused(photoToLoad)) { // 防止图片错位(如果加载的图片不是当前需要加载的图片,则不做任何处理) 379 | return null; 380 | } 381 | 382 | // 加载结束,更新UI 383 | Message msg = rHandler.obtainMessage(); 384 | msg.arg1 = IMAGE_LOADER_FINISHED; 385 | // photoToLoad.bitmap = bmp; 386 | msg.obj = photoToLoad; 387 | loaderHandler.sendMessage(msg); 388 | 389 | return null; 390 | } 391 | } 392 | 393 | /** 394 | * 防止图片错位 395 | * 396 | * @param photoToLoad 397 | * @return 398 | */ 399 | boolean imageViewReused(PhotoToLoad photoToLoad) { 400 | String tag = imageViews.get(photoToLoad.imageView); 401 | // if (tag == null || !tag.equals(photoToLoad.url)) 402 | // return true; 403 | // return false; 404 | return null == tag || !tag.equals(photoToLoad.url); 405 | } 406 | 407 | 408 | /** 409 | * 清空文件缓存或内存缓存 410 | */ 411 | public void clearCache() { 412 | memoryCache.clear(); 413 | fileCache.clear(); 414 | } 415 | 416 | public void clearMemoryCache() { 417 | memoryCache.clear(); 418 | } 419 | 420 | public void clearFileCache() { 421 | fileCache.clear(); 422 | } 423 | 424 | 425 | static final int IMAGE_LOADER_PROCESS = 0x01; 426 | static final int IMAGE_LOADER_FINISHED = 0x02; 427 | 428 | Handler loaderHandler = new Handler() { 429 | @Override 430 | public void handleMessage(Message msg) { 431 | super.handleMessage(msg); 432 | PhotoToLoad photoToLoad = (PhotoToLoad) msg.obj; 433 | if (null == photoToLoad) { 434 | return; 435 | } 436 | switch (msg.arg1) { 437 | case IMAGE_LOADER_PROCESS: // 更新加载进度 438 | photoToLoad.onImageLoaderListener.onProgressImageLoader(photoToLoad.imageView, photoToLoad.currentSize, photoToLoad.totalSize); 439 | 440 | break; 441 | case IMAGE_LOADER_FINISHED: // 加载完毕 442 | if (imageViewReused(photoToLoad)) { // 防止图片错位(如果加载的图片不是当前需要加载的图片,则不做任何处理) 443 | return; 444 | } 445 | Bitmap bitmap = memoryCache.get(photoToLoad.url); 446 | if (null != bitmap) { 447 | if (photoToLoad.imageView instanceof FadeImageView) { 448 | ((FadeImageView) photoToLoad.imageView).setImageBitmapAnim(bitmap); 449 | } else { 450 | photoToLoad.imageView.setImageBitmap(bitmap); 451 | } 452 | } 453 | // 如果设置了监听器 454 | if (null != photoToLoad.onImageLoaderListener) { 455 | // 通知观察者完成 456 | photoToLoad.onImageLoaderListener.onFinishedImageLoader(photoToLoad.imageView, bitmap); 457 | 458 | } 459 | 460 | 461 | break; 462 | } 463 | 464 | 465 | } 466 | }; 467 | 468 | 469 | private static final String DIVIDER = "_"; 470 | 471 | public static String getIdentityCode(String uri, int requiredSize) { 472 | return uri + DIVIDER + requiredSize; 473 | } 474 | 475 | public static String getUriFromIdentityCode(String indentityCode) { 476 | return indentityCode.substring(0, indentityCode.lastIndexOf(DIVIDER)); 477 | } 478 | 479 | private int getRequiredSizeFromIdentityCode(String indentityCode) { 480 | return Integer.valueOf(indentityCode.substring(indentityCode.lastIndexOf(DIVIDER))); 481 | } 482 | 483 | } 484 | -------------------------------------------------------------------------------- /src/com/wangjie/imageloadersample/imageloader/MemoryCache.java: -------------------------------------------------------------------------------- 1 | package com.wangjie.imageloadersample.imageloader; 2 | 3 | import android.graphics.Bitmap; 4 | import android.util.Log; 5 | 6 | import java.util.Collections; 7 | import java.util.Iterator; 8 | import java.util.LinkedHashMap; 9 | import java.util.Map; 10 | import java.util.Map.Entry; 11 | 12 | /** 13 | * @author wangjie 14 | * @version 创建时间:2012-10-14 下午7:28:39 15 | */ 16 | public class MemoryCache { 17 | 18 | private static final String TAG = "MemoryCache"; 19 | // 放入缓存时是个同步操作 20 | // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU 21 | // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率 22 | private Map cache = Collections 23 | .synchronizedMap(new LinkedHashMap(10, 1.5f, true)); 24 | // 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存 25 | private long size = 0;// current allocated size 26 | /** 27 | * 缓存占用的最大堆内存 28 | */ 29 | private long limit = 1000000;// max memory in bytes 30 | 31 | public MemoryCache(long limit) { 32 | // use 25% of available heap size 33 | this.limit = limit > 1000000l ? limit : Runtime.getRuntime().maxMemory() / 4; 34 | Log.i(TAG, "MemoryCache limit 初始化为 " + this.limit / 1024. / 1024. + "MB"); 35 | } 36 | 37 | /** 38 | * 从缓存中取出图片 39 | * @param id 40 | * @return 41 | */ 42 | public Bitmap get(String id) { 43 | try { 44 | if (!cache.containsKey(id)){ 45 | return null; 46 | } 47 | return cache.get(id); 48 | } catch (NullPointerException ex) { 49 | return null; 50 | } 51 | } 52 | 53 | /** 54 | * 把图片放入缓存 55 | * @param id 56 | * @param bitmap 57 | */ 58 | public void put(String id, Bitmap bitmap) { 59 | try { 60 | if (cache.containsKey(id)){ 61 | size -= getSizeInBytes(cache.get(id)); 62 | } 63 | cache.put(id, bitmap); 64 | size += getSizeInBytes(bitmap); 65 | checkSize(); 66 | } catch (Throwable th) { 67 | th.printStackTrace(); 68 | } 69 | } 70 | 71 | /** 72 | * 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存 73 | * 74 | */ 75 | private synchronized void checkSize() { 76 | Log.i(TAG, "cache size=" + size + "bytes, length=" + cache.size()); 77 | if (size > limit) { 78 | // 先遍历最近最少使用的元素 79 | Iterator> iter = cache.entrySet().iterator(); 80 | while (iter.hasNext()) { 81 | Entry entry = iter.next(); 82 | size -= getSizeInBytes(entry.getValue()); 83 | iter.remove(); 84 | if (size <= limit) 85 | break; 86 | } 87 | Log.i(TAG, "Clean cache. New size " + cache.size()); 88 | } 89 | } 90 | 91 | /** 92 | * 清清除内存缓存 93 | */ 94 | public void clear() { 95 | cache.clear(); 96 | } 97 | 98 | /** 99 | * 获取图片占用的内存大小 100 | * 101 | * @param bitmap 102 | * @return 103 | */ 104 | long getSizeInBytes(Bitmap bitmap) { 105 | if (bitmap == null){ 106 | return 0; 107 | } 108 | return bitmap.getRowBytes() * bitmap.getHeight(); 109 | } 110 | 111 | } 112 | --------------------------------------------------------------------------------