Android图片缓存框架Android-Universal-Image-Loader的使用详解

抽点时间写点关于Android-Universal-Image-Loader的使用,虽然很多人在开发中可能用到了各种各样的图片加载框架,比如:Gilde、Frescon、Picasso等,以及Volley,xUtils等这些框架中的图片加载都是很优秀的图片加载框架。但是,本人在开发中是非常喜欢Android-Universal-Image-Loader这款框架,不仅仅是因为这框架历史悠久,主要是它实现图片加载的实现值得我们去深入学习,对于异步加载图片,加载图片时的错乱,加载大量图片时的内存溢出的处理等等。(当然,可能是本人最先使用的图片加载框架吧)今天主要基于universal-image-loader-1.9.4来写的,该框架的GitHub的地址链接如下:

https://github.com/nostra13/Android-Universal-Image-Loader

该框架的一些主要特点如下:
1、支持可定制化,可以更具自己项目的需求进行配置:下载的线程池,图片下载器,内存缓存策略(可自定义)等
2、多线程异步加载和显示图片,图片来源广泛如:网络图片,sdcard图片,assets文件夹下的图片,drawable与mipmap文件夹下的图片,系统里面的视频缩略图(.9patch不能加载)
3、支持图片的内存缓存,sdcard缓存。(图片缓存可以设置,先进先出,使用最少的,占用内存最大的先移除的配置)
4、支持图片加载过程中的监听,可以暂停加载图片,图片在加载中的操作。
总的来说,该具备的功能与操作它基本俱全。那在开发中我们如何快速配置使用它呢。

  • 进入GitHub进行下载如下图:
    Android图片缓存框架Android-Universal-Image-Loader的使用详解_第1张图片 或者 Android图片缓存框架Android-Universal-Image-Loader的使用详解_第2张图片
    把资源下载下来。

  • 下载下来资源后,如果你用的是Eclipse,直接把universal-image-loader-1.9.4.jar包添加到你的libs下。如果你用的AS的话,先把jar解压出来后找到里面maven/pom.properties的文件然后打开。如下图:
    Android图片缓存框架Android-Universal-Image-Loader的使用详解_第3张图片
    按照上面显示的信息在gradle里面进行配置:compile ‘com.nostra13.universalimageloader:universal-image-loader:1.9.4’

  • 在AndroidManifest.xml里面配置ImageLodaer需要的权限

   <uses-permission android:name="android.permission.INTERNET">uses-permission>
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">uses-permission>
   <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS">uses-permission>
  • 在应用的Application中配置ImageLoaderConfiguration,该配置只能在应用中配置一次,配置多次无效。
        File cacheDir = StorageUtils.getCacheDirectory(getApplicationContext());  //缓存文件夹路径
        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
                .memoryCacheExtraOptions(480, 800) // default = device screen dimensions 内存缓存文件的最大长宽
                .diskCacheExtraOptions(480, 800, null)  // 本地缓存的详细信息(缓存的最大长宽),最好不要设置这个
                .threadPoolSize(3) // default  线程池内加载的数量
                .taskExecutor(...)
                .taskExecutorForCachedImages(...)
                .threadPriority(Thread.NORM_PRIORITY - 2) // default 设置当前线程的优先级
                .tasksProcessingOrder(QueueProcessingType.FIFO) // default
                .denyCacheImageMultipleSizesInMemory()
                .memoryCache(new LruMemoryCache(2 * 1024 * 1024)) //可以通过自己的内存缓存实现
                .memoryCacheSize(2 * 1024 * 1024)  // 内存缓存的最大值
                .memoryCacheSizePercentage(13) // default
                .diskCache(new UnlimitedDiscCache(cacheDir)) // default 可以自定义缓存路径
                .diskCacheSize(50 * 1024 * 1024) // 50 Mb sd卡(本地)缓存的最大值
                .diskCacheFileCount(100)  // 可以缓存的文件数量
                 // default为使用HASHCODE对UIL进行加密命名, 还可以用MD5(new Md5FileNameGenerator())加密
                .diskCacheFileNameGenerator(new HashCodeFileNameGenerator())
                .imageDownloader(new BaseImageDownloader(context)) // default
                .imageDecoder(new BaseImageDecoder()) // default
                .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
                .writeDebugLogs() // 打印debug log
                .build(); //开始构建

这些配置一般最好在Application中进行配置(根据自己实际情况进行配置,没有必要也不需要全部进行配置),全局调用。配置好这些后,记得调用ImageLoader.getInstance().init(config); 进行初始化。但是如果你觉的麻烦可以使用默认的配置ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this); 不推荐使用默认配置。

  • 如何使用ImageLoader来展示图片呢?首先我们要得到ImageLoader的实例
ImageLoader mImageLoader = ImageLoader.getInstance(); 

注意,ImageLoader 是单例模式。有兴趣可以看看源码里面是如何设计ImageLoader的。

  • 如何显示图片呢?ImageLoader里面提供了如下方法:
        ImageLoader.getInstance().displayImage(uri, imageView);
        ImageLoader.getInstance().displayImage(uri, imageView, options);
        ImageLoader.getInstance().displayImage(uri, imageView, listener);
        ImageLoader.getInstance().displayImage(uri, imageView, options, listener);
        ImageLoader.getInstance().displayImage(uri, imageView, options, listener, progressListener);
        ImageLoader.getInstance().loadImage(uri, imageView);
        ImageLoader.getInstance().loadImage(uri, imageView, options);
        ImageLoader.getInstance().loadImage(uri, imageView, listener);
        ImageLoader.getInstance().loadImage(uri, imageView, options, listener);
        ImageLoader.getInstance().loadImage(uri, imageView, options, listener, progressListener);

等等都是用来对图片进行加载操作的。其中里面的options是什么呢?这是ImageLoader为我们提供在图片在加载的对图片进行一系列操作提供的方法。

  • 图片相关的显示操作配置
        DisplayImageOptions options = new DisplayImageOptions.Builder()
                .showImageOnLoading(R.drawable.ic_stub) // 设置图片下载期间显示的图片
                .showImageForEmptyUri(R.drawable.ic_empty) // 设置图片Uri为空或是错误的时候显示的图片
                .showImageOnFail(R.drawable.ic_error) // 设置图片加载或解码过程中发生错误显示的图片
                .resetViewBeforeLoading(false)  // default 设置图片在加载前是否重置、复位
                .delayBeforeLoading(1000)  // 下载前的延迟时间
                .cacheInMemory(false) // default  设置下载的图片是否缓存在内存中
                .cacheOnDisk(false) // default  设置下载的图片是否缓存在SD卡中
                .preProcessor(...)
                .postProcessor(...)
                .extraForDownloader(...)
                .considerExifParams(false) // default
                .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default 设置图片以如何的编码方式显示
                .bitmapConfig(Bitmap.Config.ARGB_8888) // default 设置图片的解码类型
                .decodingOptions(...)  // 图片的解码设置
                .displayer(new SimpleBitmapDisplayer()) // default  还可以设置圆角图片new RoundedBitmapDisplayer(20)
                .handler(new Handler()) // default
                .build();

看到这里的朋友可能会注意到.displayImage(uri, imageView).loadImage(uri, imageView) 这两个方法里面是没有Options的,那么调用这写没有把Options传递进去的话,那么从配置默认显示选项
(ImageLoaderConfiguration.defaultDisplayImageOptions(…))将被使用。

  • 自定义配置中我们需要理解的内容:
    1、ImageLoaderConfiguration的builder中的diskCache(),diskCacheSize()和diskCacheFileCount()这三个方法彼此功能重叠,设置了其中一个就不用设置另外两个了。

    2、ImageLoaderConfiguration的builder中的memoryCache()方法和memoryCacheSize()方法彼此重叠,设置了其中一个就不用设置另外一个了。

    3、DiskCache磁盘缓存中UnlimitedDiscCache不限制缓存大小;UnlimitedDiscCache简单的继承了BaseDiscCache并未对其做任何扩展;LimitedAgeDiscCache限制缓存时间;LimitedAgeDiscCache该类实现了在缓存中删除被加载超过规定时间的文件:满足以下条件的时候就从缓存中删除文件:系统当前时间-文件的最新修改时间 > maxFileAge;

    4、MemoryCache内存缓存UsingFreqLimitedCache (最少被用到的对象会被删除) ;UsingAgeLimitedCache (最早被添加的对象会被删除) ;LargestLimitedCache (空间占用最大的对象会被删除);FIFOLimitedCache (根据先进先出的原则上删除多余对象);通过实现接口MemoryCacheAware < String,Bitmap >来实现自己的缓存。

    5、.imageScaleType(ImageScaleType imageScaleType) 中imageScaleType缩放类型的属性EXACTLY :图像将完全按比例缩小的目标大小;EXACTLY_STRETCHED:图片会缩放到目标大小完全;IN_SAMPLE_INT:图像将被二次采样的整数倍;IN_SAMPLE_POWER_OF_2:图片将降低2倍,直到下一减少步骤,使图像更小的目标大小;NONE:图片不会调整。

    6、.displayer(BitmapDisplayer displayer)中displayer显示方式RoundedBitmapDisplayer(int roundPixels)设置圆角图片;FakeBitmapDisplayer()这个类什么都没做;FadeInBitmapDisplayer(int durationMillis)设置图片渐显的时间;SimpleBitmapDisplayer()正常显示一张图片(默认的)。

  • 在GirdView,ListView加载图片的情况下会出现快速滑动的操作,实际开发中为了更加友好的用户体验,在快速滑动的情况下是不用加载图片的,只有停止后才去加载图片增加流畅度。ImageLodaer内置了滑动监听的操作的代理模式。

public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling{
        this(imageLoader, pauseOnScroll, pauseOnFling, null);
}

public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling,
            OnScrollListener customListener) {
        this.imageLoader = imageLoader;
        this.pauseOnScroll = pauseOnScroll;
        this.pauseOnFling = pauseOnFling;
        externalListener = customListener;
}

pauseOnScroll-是否在滑动过程中停止加载图片;pauseOnFling-是否在快速猛的滑动中停止加载图片。
实际使用:mImgsListView.setOnScrollListener(new PauseOnScrollListener(mImageLoader,true,true));

  • 如果有需要再加载图片的过程中需要加一些进度条,或做一些处理。在displayImage()里面可以实现ImageLoadingListener(),ImageLoadingProgressListener()。
public interface ImageLoadingListener {

    /**
     * Is called when image loading task was started (开始加载)
     *
     * @param imageUri Loading image URI
     * @param view     View for image
     */
    void onLoadingStarted(String imageUri, View view);

    /**
     * Is called when an error was occurred during image loading (加载失败)
     *
     * @param imageUri   Loading image URI
     * @param view       View for image. Can be null.
     * @param failReason {@linkplain com.nostra13.universalimageloader.core.assist.FailReason The reason} why image
     *                   loading was failed
     */
    void onLoadingFailed(String imageUri, View view, FailReason failReason);

    /**
     * Is called when image is loaded successfully (and displayed in View if one was specified) (加载成功)
     *
     * @param imageUri    Loaded image URI
     * @param view        View for image. Can be null.
     * @param loadedImage Bitmap of loaded and decoded image
     */
    void onLoadingComplete(String imageUri, View view, Bitmap loadedImage);

    /**
     * Is called when image loading task was cancelled because View for image was reused in newer task (加载取消)
     *
     * @param imageUri Loading image URI
     * @param view     View for image. Can be null.
     */
    void onLoadingCancelled(String imageUri, View view);
}
public interface ImageLoadingProgressListener {

    /**
     * Is called when image loading progress changed.
     *
     * @param imageUri Image URI
     * @param view     View for image. Can be null.
     * @param current  Downloaded size in bytes
     * @param total    Total size in bytes
     */
    void onProgressUpdate(String imageUri, View view, int current, int total);
}
  • 我想看到这里的朋友对于ImageLoader的一些参数的使用有了一定的了解吧,下面我们直接上一份简单的demo代码来看看效果(使用的AS):

AndroidManifest.xml代码

    <uses-permission android:name="android.permission.INTERNET">uses-permission>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">uses-permission>
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS">uses-permission>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:name="com.example.rrichard.myapplication.MainApplication"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
    application>

Gradle中配置

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.4'
}

Application代码

package com.example.rrichard.myapplication;

import android.app.Application;
import android.os.Environment;

import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;

import java.io.File;

/**
 * Created by Rrichard on 2015/10/17.
 */
public class MainApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        initConfig();
    }

    /**
     * 配置ImageLoader基本属性,最好放在Application中(只能配置一次,如多次配置,则会默认第一次的配置参数)
     */
    private void initConfig() {
        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
                .threadPriority(Thread.NORM_PRIORITY - 2)//设置线程优先级
                .threadPoolSize(4)//线程池内加载的数量,推荐范围1-5内。
                .denyCacheImageMultipleSizesInMemory()//当同一个Uri获取不同大小的图片缓存到内存中时只缓存一个。不设置的话默认会缓存多个不同大小的图片
                .memoryCacheExtraOptions(480, 800)//内存缓存文件的最大长度
                .memoryCache(new LruMemoryCache(10 * 1024 * 1024))//内存缓存方式,这里可以换成自己的内存缓存实现。(推荐LruMemoryCache,道理自己懂的)
                .memoryCacheSize(10 * 1024 * 1024)//内存缓存的最大值
                .diskCache(new UnlimitedDiskCache(createSavePath()))//可以自定义缓存路径
                .diskCacheFileNameGenerator(new Md5FileNameGenerator())//对保存的URL进行加密保存
                .defaultDisplayImageOptions(DisplayImageOptions.createSimple())
                .imageDownloader(new BaseImageDownloader(getApplicationContext(), 5 * 1000, 30 * 1000))//设置连接时间5s,超时时间30s
                .writeDebugLogs()
                .build();
        ImageLoader.getInstance().init(config);
    }

    /**
     * 创建存储缓存的文件夹路径
     *
     * @return
     */
    private File createSavePath() {
        String path;
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            path = Environment.getExternalStorageDirectory().getPath() + "/TestCash/";
        } else {
            path = "/TestCash/";
        }
        File file = new File(path);
        if (!file.exists()) {
            file.mkdirs();
        }
        return file;
    }

}

Adapter

package com.example.rrichard.myapplication;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;

import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;

import java.util.List;

/**
 * Created by Rrichard on 2015/10/17.
 */
public class ImgsAdapter extends BaseAdapter {

    private Context mContext;
    private List imgsList;
    private LayoutInflater mInflater;
    private DisplayImageOptions mImageOptions;

    public ImgsAdapter(Context ctx, List list, DisplayImageOptions options) {
        this.mContext = ctx;
        this.imgsList = list;
        this.mImageOptions = options;
        mInflater = LayoutInflater.from(mContext);
    }

    @Override
    public int getCount() {
        return imgsList.size();
    }

    @Override
    public Object getItem(int position) {
        return imgsList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder mViewHolder = null;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.imgs_layout, parent, false);
            mViewHolder = new ViewHolder();
            mViewHolder.img_iv = (ImageView) convertView.findViewById(R.id.show_img_iv);
            convertView.setTag(mViewHolder);
        } else {
            mViewHolder = (ViewHolder) convertView.getTag();
        }

        ImageLoader.getInstance().displayImage(imgsList.get(position), mViewHolder.img_iv, mImageOptions);

        return convertView;
    }

    private static class ViewHolder {
        ImageView img_iv;
    }

}

MainActivity

package com.example.rrichard.myapplication;

import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.widget.ListView;

import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;
import com.nostra13.universalimageloader.core.listener.PauseOnScrollListener;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    private ListView mImgsListView;

    private ImgsAdapter mImgsAdapter;
    private List imgsStrList = new ArrayList();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mImgsListView = (ListView) findViewById(R.id.imgs_listview);
        imgsStrList.addAll(setImageData());
        mImgsAdapter = new ImgsAdapter(this,imgsStrList,setImageOptionsConfig());
        mImgsListView.setAdapter(mImgsAdapter);
        ImageLoader mImageLoader = ImageLoader.getInstance();
        mImgsListView.setOnScrollListener(new PauseOnScrollListener(mImageLoader, true, true));
    }


    /**
     * 配置图片加载时候的配置,在实际开发中可以对这些参数进行一次封装。
     */
    private DisplayImageOptions setImageOptionsConfig(){
        DisplayImageOptions options = new DisplayImageOptions.Builder()
                .showImageOnLoading(R.mipmap.ic_launcher)//设置图片在下载期间显示的图片
                .showImageForEmptyUri(R.mipmap.ic_launcher)//设置图片Uri为null或是错误的时候显示的图片
                .showImageOnFail(R.mipmap.ic_launcher)//设置图片加载/解码过程中错误时显示的图片
                .cacheInMemory(true)//设置下载的图片是否缓存在内存中
                .cacheOnDisk(true)//设置下载的图片是否缓存在SD卡中
                .considerExifParams(true)//是否考虑JPEG图像的旋转,翻转
                .imageScaleType(ImageScaleType.IN_SAMPLE_INT)//设置图片以如何的编码方式显示
                .bitmapConfig(Bitmap.Config.ARGB_8888)//设置图片的解码类型
                .resetViewBeforeLoading(true)//设置图片在下载前是否重置和复位
                .displayer(new SimpleBitmapDisplayer())//不设置的时候是默认的
                //.displayer(new RoundedBitmapDisplayer(20))//是否为圆角,弧度是多少
                //displayer()还可以设置渐入动画
                .build();
        return options;
    }


    private List setImageData(){
        List mImags = new ArrayList();
        mImags.add("http://imgsrc.baidu.com/forum/pic/item/09be3f094b36acaf0ad6eb717cd98d1000e99cde.jpg");
        mImags.add("http://img.popoho.com/UploadPic/2012-1/2012110163244154.jpg");
        mImags.add("http://img.popoho.com/UploadPic/2012-1/2012110163244261.jpg");
        mImags.add("http://img.popoho.com/UploadPic/2012-1/2012110163244424.jpg");
        mImags.add("http://21chance.com/upload/22/findings/1444922543-5984.jpg");
        return mImags;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ImageLoader.getInstance().clearMemoryCache();
        ImageLoader.getInstance().clearDiskCache();
    }
}

效果图
Android图片缓存框架Android-Universal-Image-Loader的使用详解_第4张图片 Android图片缓存框架Android-Universal-Image-Loader的使用详解_第5张图片

  • 在使用ImageLoader时如果出现OOM的情况,我们可以从下面的思路去优化
    1、减少配置的线程池的大小(threadPoolSize 推荐1-5);
    2、使用.bitmapConfig(Bitmap.config.RGB_565)代替ARGB_8888;
    3、使用.imageScaleType(ImageScaleType.IN_SAMPLE_INT)或者.imageScaleType(ImageScaleType.EXACTLY);
    4、避免使用RoundedBitmapDisplayer.他会创建新的ARGB_8888格式的Bitmap对象;
    5、如果以上都解决不了你的问题,可以尝试禁用内存中的缓存cacheInMemory(false),或者使用软引用.memoryCache(new WeakMemoryCache())。

  • 注意在程序退出时我们可以调用

ImageLoader.getInstance().clearMemoryCache();  // 清除内存缓存 
ImageLoader.getInstance().clearDiskCache();  // 清除本地缓存 
  • 如果需要加载本地图片看下面格式
"http://site.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)

(^o^)/~!大致说到这吧,可能有的地方不足欢迎底下留言提出,本人吃中饭去….

你可能感兴趣的:(Android学习开发)