因此建议先去阅读理解该章的内容,阅读本文你
分类:美高梅网上注册平台

在需要加载的地方使用:

  • 原文链接 : Using concurrency to improve speed and performance in Android
  • 原文作者 : Ali Muzaffar
  • 译者 : edvardHua
  • 校对者: JOJO、Jing KE

1.UIL中主要工作类

从上面ImageLoader的主要工作中可以得知大概需要以下几个类:

  1. ImageLoader:外界使用的主要工作类
  2. 线程池:用于图片的网络加载
  3. Handler:用于图片的异步传送加载
  4. ImageView:用于加载图片的控件
  5. ImageResizer:用于图片的压缩
  6. LruCache:用于图片的内存缓存
  7. DiskLruCache:用于图片的磁盘缓存

对应以上的工作,UIL中重要的几个类有:

  1. ImageLoader:主要工作类
  2. ImageLoaderConfiguration:配置类,用于配置大部分工作的类成员,包括线程池成员,内存缓存成员,磁盘缓存成员,图片下载器,图片解码器和显示图片参数等等。该类的作用就是参与ImageLoader的初始化并配置好正常工作的参数,因此该类很重要
  3. ImageLoaderEngine:用于线程池的管理,是一个管理类。该类与ImageLoaderConfiguration类的区别是,Configuration只是用于配置线程池,而真正使用线程池工作的是ImageLoaderEngine类
  4. ImageLoadingInfo:用于配置显示图片的信息类,包括图片的url、图片的加载大小、显示图片的控件和用于配置显示图片的配置类DisplayImageOptions等等
  5. DisplayImageOptions:显示图片参数的配置类,其中包含了Handler用于异步加载,BitmapDisplayer用于最终显示图片。该类和ImageLoadingInfo的关系是,DisplayImageOptions包含于ImageLoadingInfo之中,毕竟DisplayImageOptions也是跟图片的显示参数有关系的
  6. LoadAndDisplayImageTask:封装了获取图片并显示的任务,用于被线程池执行
  7. ImageDownloader:用于下载图片,即获取图片的输入流
  8. ImageDecoder:用于图片解码压缩,可以理解成将输入流转换成Bitmap的过程,因此一般ImageDecoder和ImageDownloader是一起工作的,一个负责获取图片的输入流,一个负责将输入流转换成Bitmap图片对象,其中就包含了按需求压缩图片的功能
  9. MemoryCache:内存缓存类
  10. DiskCache:磁盘缓存类

UIL中的主要工作大体上是由前5个类完成的,其实也很好理解,ImageLoader是向外提供的主要接口,ImageLoaderConfiguration为ImageLoader正常工作而提供配置参数,ImageLoaderEngine是在ImageLoader中的主要工作成员,ImageLoadingInfo是为了配置最后显示图片的参数比如如何显示、显示在哪里之类的,毕竟ImageLoader顾名思义是跟图片的显示密不可分的,因此没有DisplayImageOptions怎么让图片得到正确的显示。


与我联系,交个朋友

美高梅网上注册平台 1

2.UIL的工作流程

在深入源码分析UIL之前,我们先来了解UIL的工作流程,这样带着大局观去看程序就不会陷入了源码中不能自拔,导致只见树木不见森林的情况。因为了解了整体的工作流程之后,在看源码的时候就能迅速的找到重点流程而忽略细节的处理,有助于能够迅速地大体将框架学习一遍。因此在学习任何源码之前可以的话,最好先去搜索相关的工作原理,在掌握了一定原理之后再去看源码则会达到事半功倍的效果。

UIL的工作流程其实跟《Android开发艺术探索》中ImageLoader的工作流程大致一样,就是利用3级缓存策略
内存缓存->磁盘缓存->网络拉取

接下来如果一开始就从头到尾分析源码的工作流程可能会导致思路不清晰,因为其中用到了许多类,如果在分析工作流程的过程中再去分析一个个类的作用会将思路中断,不利于对整个框架的学习,因此下面会先对一个个在工作流程中会被用到的一些比较关键的类进行分析,最后再通过源码将整个过程过一遍。


好了,思考完毕,准备完毕,上代码!~最终代码如下:

常用方法存在的问题

举个例子,如果你想开发一个连拍应用能在1秒钟连拍10张图片。应用该具备如下的子任务:

  • 在一秒的时间内扑捉10张以byte[]形式储存的照片,并且不能够阻塞UI线程。
  • 将byte[]储存的数据格式从YUV转换成RGB。
  • 使用转换后的数据创建Bitmap。
  • 变换Bitmap的方向。
  • 生成缩略图大小的Bitmap。
  • 将全尺寸的Bitmap以Jpeg压缩文件的格式写入磁盘中。
  • 使用上传队列将图片保存到服务器中。

很明显,如果你将太多的子任务放在UI线程中,你的应用在性能上的表现将不会太好。在这种情况下,唯一的解决方案就是先将相机预览的数据缓存起来,当UI线程闲置的时候再来利用缓存的数据执行剩下的任务。

另外一个可选的解决方案是创建一个长时间在后台运行的HandlerThread,它能够接受相机预览的数据,并处理完剩下的全部任务。当然这种做法的性能会好些,但是如果用户想再连拍的话,将会面临较大的延迟,因为他需要等待HandlerThread处理完前一次连拍。

 public class CameraHandlerThread extends HandlerThread implements Camera.PictureCallback, Camera.PreviewCallback { private static String TAG = "CameraHandlerThread"; private static final int WHAT_PROCESS_IMAGE = 0; Handler mHandler = null; WeakReference<camerapreviewfragment> ref = null; private PictureUploadHandlerThread mPictureUploadThread; private boolean mBurst = false; private int mCounter = 1; CameraHandlerThread(CameraPreviewFragment cameraPreview) { super; start(); mHandler = new Handler(getLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == WHAT_PROCESS_IMAGE) { // 业务逻辑 } return true; } }); ref = new WeakReference<>(cameraPreview); } ... @Override public void onPreviewFrame(byte[] data, Camera camera) { if  { CameraPreviewFragment f = ref.get(); if (f != null) { mHandler.obtainMessage(WHAT_PROCESS_IMAGE, data) .sendToTarget(); try { sleep; } catch (InterruptedException e) { e.printStackTrace(); } if (f.isAdded { f.readyForPicture(); } } if (mCounter++ == 10) { mBurst = false; mCounter = 1; } } } }

提醒: 如果你需要学习更多有关于HandlerThreads内容以及如何使用它,请阅读我发表的关于HandlerThreads的文章。

看起来所有的任务都被后台的单一线程处理完毕了,我们性能提升主要得益于后台线程长期运行并不会被销毁和重建。然而,我们后台的单一线程却要和其他优先等级更高的任务共享,而且这些任务只能够顺序执行。

我们也可以创建第二个HandlerThread来处理我们的图像,然后创建第三个HandlerThread来将照片写入磁盘,最后再创建第四个HandlerThread来将照片上传到服务器中。我们能够加快拍照的速度,但是,这些线程相互之间还是遵循顺序执行的规则,并不是真的并发。因为每张照片是顺序处理的,而且处理每一张照片需要一定的时间,导致用户在点击拍照按钮到显示全部缩略图的时候仍然能够明显的感觉到延迟。

7.DisplayImageOptions

DisplayImageOptions跟图片的最终显示密切相关,而上面ImageLoadingInfo只是提供了一些参数,但并不直接跟图片显示有关系,DisplayImageOptions与图片如何正确显示才是密切相关的,因此配置好DisplayImageOptions是ImageLoader正确工作的重要一环,跟ImageLoaderConfiguration一样对于ImageLoader来说是必不可少的。
可以说在ImageLoader中,ImageLoaderConfiguration决定了图片如何加载,DisplayImageOptions决定了图片如何正确显示,每张图片的加载都离不开DisplayImageOptions的配置参数
以下是跟ImageLoaderConfiguration一样的所有可选的显示图片参数配置。其中较重要的参数有cacheInMemory,cacheOnDisk这两个参数默认是false,即不使用缓存,displayer决定图片显示的样式,这几个一般在创建Options对象时需要重新配置,尤其是缓存。

注:如果DisplayImageOptions没有手动地创建赋予ImageLoader,则在ImageLoaderConfiguration中会默认创建一个Options对象,下面程序中后面带有default的就是默认配置的参数。

// DON'T COPY THIS CODE TO YOUR PROJECT! This is just example of ALL options using.
DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.ic_stub) // resource or drawable
        .showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable
        .showImageOnFail(R.drawable.ic_error) // resource or drawable
        .resetViewBeforeLoading(false)  // default
        .delayBeforeLoading(1000)
        .cacheInMemory(false) // default
        .cacheOnDisk(false) // default
        .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
        .handler(new Handler()) // default
        .build();

刚好,我们项目中使用了UIL,所以为了进一步提高加载效率,我顺带加了缓存.写了一个单例VideoThumbnailLoader类,来负责加载.

大家都知道Android的UI更新是在UI线程中进行的。所以如果我们在UI线程中编写耗时任务都可能会阻塞UI线程更新UI。为了避免这种情况我们可以使用 AsyncTask, IntentService和Threads。在之前我写的一篇文章介绍了Android 中异步处理的8种方法。但是,Android提供的AsyncTasks和IntentService都是利用单一的后台线程来处理异步任务的。那么,开发人员如何创建多个后台线程呢?

3.ImageLoaderConfiguration

ImageLoaderConfiguration作为一个配置类,对于ImageLoader正常工作来说必不可少,因为它主要是为了配置好使ImageLoader正常工作的各种类。
ImageLoaderConfiguration采用的是建造者Builder模式来创建对象,虽然它拥有很多类成员,但是有一些是在Configuration对象被创建的时候初始化好了的。
从下面的代码可以看出,在UIL内部,最重要的无外乎线程池Executor、内存缓存MemoryCache、磁盘缓存DiskCache、图片下载器ImageDownloader和图片显示配置参数DisplayImageOptions这几个类等等。
其中一些默认的参数是,线程池的大小默认是3,任务队列采用的是FIFO,有默认的图片下载器BaseImageDownLoader,图片解码器BaseImageDecoder和默认显示图片配置DisplayImageOptions

// 下面这些Configuration类中所有的可选方法,可以看出有些属性是默认的
File cacheDir = StorageUtils.getCacheDirectory(context);
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
        .memoryCacheExtraOptions(480, 800) // default = device screen dimensions
        .diskCacheExtraOptions(480, 800, null)
        .taskExecutor(...)
        .taskExecutorForCachedImages(...)
        .threadPoolSize(3) // default
        .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 UnlimitedDiskCache(cacheDir)) // default
        .diskCacheSize(50 * 1024 * 1024)
        .diskCacheFileCount(100)
        .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
        .imageDownloader(new BaseImageDownloader(context)) // default
        .imageDecoder(new BaseImageDecoder()) // default
        .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
        .writeDebugLogs()
        .build();

我们从源码中看默认项是怎么实现的,在源码中我们可以看到,在build()的时候会判断一些属性是否为null,如果为null则调用对应的创建方法构造一个默认对象。可以看出默认被构造的有taskExecutor、diskCache、memoryCache、downloader、decoder和defaultDisplayImageOptions等等,这些都是ImageLoader加载显示一个图片所需要的类。因此实际上不要手动配置Configuration,只需要一个build的configuration也能使ImageLoader正常工作。

/** Builds configured {@link ImageLoaderConfiguration} object */
public ImageLoaderConfiguration build() {
    initEmptyFieldsWithDefaultValues();
    return new ImageLoaderConfiguration(this);
}

private void initEmptyFieldsWithDefaultValues() {
    if (taskExecutor == null) {
        taskExecutor = DefaultConfigurationFactory
                .createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
    } else {
        customExecutor = true;
    }
    if (taskExecutorForCachedImages == null) {
        taskExecutorForCachedImages = DefaultConfigurationFactory
                .createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
    } else {
        customExecutorForCachedImages = true;
    }
    if (diskCache == null) {
        if (diskCacheFileNameGenerator == null) {
            diskCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator();
        }
        diskCache = DefaultConfigurationFactory
                .createDiskCache(context, diskCacheFileNameGenerator, diskCacheSize, diskCacheFileCount);
    }
    if (memoryCache == null) {
        memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);
    }
    if (denyCacheImageMultipleSizesInMemory) {
        memoryCache = new FuzzyKeyMemoryCache(memoryCache, MemoryCacheUtils.createFuzzyKeyComparator());
    }
    if (downloader == null) {
        downloader = DefaultConfigurationFactory.createImageDownloader(context);
    }
    if (decoder == null) {
        decoder = DefaultConfigurationFactory.createImageDecoder(writeLogs);
    }
    if (defaultDisplayImageOptions == null) {
        defaultDisplayImageOptions = DisplayImageOptions.createSimple();
    }
}

另:BitmapDisplayer 默认初始化在DisplayImageOptions中
Handler 默认初始化在ImageLoader的displayImage中,然后传送到LoadAndDisplayImageTask当中
DisplayImageOptions 默认初始化在ImageLoader的loadImage中


美高梅网上注册平台 2qq群

使用ThreadPool并发处理任务

我们可以根据需求创建多个线程,但是创建过多的线程会消耗CPU周期影响性能,并且线程的创建和销毁也需要时间成本。所以我们不想创建多余的线程,但是又想能够充分的利用设备的硬件资源。这个时候我们可以使用ThreadPool。

通过创建ThreadPool对象的单例来在你的应用中使用ThreadPool。

 public class BitmapThreadPool { private static BitmapThreadPool mInstance; private ThreadPoolExecutor mThreadPoolExec; private static int MAX_POOL_SIZE; private static final int KEEP_ALIVE = 10; BlockingQueue<runnable> workQueue = new LinkedBlockingQueue<>(); public static synchronized void post(Runnable runnable) { if (mInstance == null) { mInstance = new BitmapThreadPool(); } mInstance.mThreadPoolExec.execute; } private BitmapThreadPool() { int coreNum = Runtime.getRuntime().availableProcessors(); MAX_POOL_SIZE = coreNum * 2; mThreadPoolExec = new ThreadPoolExecutor( coreNum, MAX_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, workQueue); } public static void finish() { mInstance.mThreadPoolExec.shutdown(); } }

然后,在上面的代码中,简单的修改Handler的回调函数为:

 mHandler = new Handler(getLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == WHAT_PROCESS_IMAGE) { BitmapThreadPool.post(new Runnable() { @Override public void run() { // 做你想做的任何事情 } }); } return true; } });

优化已经完成!通过下面的视频,我们观察到加载缩略图的速度提升是非常明显的。

这种做法的优点是我们可以定义线程池的大小并且指定空余线程保持活动的时间。我们也可以创建多个ThreadPools来处理多个任务或者使用单个ThreadPool来处理多个任务。但是在使用完后记得清理资源。

我们甚至可以为每一个功能创建一个独立的ThreadPool。譬如说在这个例子中我们可以创建三个ThreadPool,第一个ThreadPool负责数据转换成Bitmap,第二个ThreadPool负责写数据到磁盘中去,第三个ThreadPool上传Bitmap到服务器中去。这样做的话,如果我们的ThreadPool最大拥有4条线程,那么我们就能够同时的转换,写入,上传四张相片。用户将看到4张缩略图是同时显示而不是一个个的显示出来的。

上面这个简单例子代码可以在我的GitHub上得到,欢迎看完代码后给我反馈

另外,你也可以在Google Play上面下载演示应用。

使用ThreadPool前: 如果可以,从顶部观察计数器的变化来得知当底部缩略图从开始显示到全部显示完成所耗费的时间。在程序中除了adapter中的notifyDataSetChanged()方法外,我已经将大部分的操作从主线程中剥离,所以计数器的运行是很流畅的。)

使用ThreadPool后: 通过顶部的计数器,我们发现使用了ThreadPool后,照片的缩略图加载速度明显变快。)

8.ImageDownloader

图片下载器,即通过图片的URI来获取到图片的InputStream。ImageDownloader是一个interface,主要方法就只有getStream(String imgUri, Object extra),即外界通过该方法便可通过imgUri获取到图片的InputStream。UIL中有实现了该接口的BaseImageDownloader,我们来看看它是怎么实现的。

关注重点:

  1. ImageDownloader的作用主要是向外提供一个下载的接口方法getStream(String imgUri, Object extra)
  2. 默认连接超时时间为5s,读取超时时间为20s
  3. 可以从多个地方比如网络、文件、Content、Assets等地方获取图片输入流
  4. getStreamFromNetwork从网络获取图片输入流:
    • 采用HttpURLConnection作为连接类
    • 如果服务器返回3xx则进行重定向
    • 将InputStream封装成ContentLengthInputStream返回
  5. getStreamFromFile从文件获取图片输入流
  6. ...

总的来说,ImageDownLoader的作用就是从图片的uri获取到相应的InputStream

public class BaseImageDownloader implements ImageDownloader {
    //连接超时时间
    public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
    //读取超时时间
    public static final int DEFAULT_HTTP_READ_TIMEOUT = 20 * 1000; // milliseconds

    //缓存大小
    protected static final int BUFFER_SIZE = 32 * 1024; // 32 Kb
    //允许使用的用于URI中的字符
    protected static final String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%";
    //最大重定向的次数,用于在服务器返回3xx时进行重定向
    protected static final int MAX_REDIRECT_COUNT = 5;

    ...

    /**
     * 构造方法,可以自定义连接超时和读取超时时间
     *     默认分别是5s和20s
     */
    public BaseImageDownloader(Context context) {
        this(context, DEFAULT_HTTP_CONNECT_TIMEOUT, DEFAULT_HTTP_READ_TIMEOUT);
    }

    public BaseImageDownloader(Context context, int connectTimeout, int readTimeout) {
        this.context = context.getApplicationContext();
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
    }

    /**
     * 实现ImageLoader方法,也是最主要的方法
     *     可以看出可以分别从网络、文件、Content、Assets等等地方获取图片输入流
     *  其中extra是传到DisplayImageOptions.Builder中的extraForDownloader,该参数可以为null
     */
    @Override
    public InputStream getStream(String imageUri, Object extra) throws IOException {
        switch (Scheme.ofUri(imageUri)) {
            case HTTP:
            case HTTPS:
                return getStreamFromNetwork(imageUri, extra);
            case FILE:
                return getStreamFromFile(imageUri, extra);
            case CONTENT:
                return getStreamFromContent(imageUri, extra);
            case ASSETS:
                return getStreamFromAssets(imageUri, extra);
            case DRAWABLE:
                return getStreamFromDrawable(imageUri, extra);
            case UNKNOWN:
            default:
                return getStreamFromOtherSource(imageUri, extra);
        }
    }

    /**
     * 从网络的uri获取到InputStream
     * 
     */
    protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
        HttpURLConnection conn = createConnection(imageUri, extra);

        //重定向
        int redirectCount = 0;
        while (conn.getResponseCode() / 100 == 3 && redirectCount < 
        MAX_REDIRECT_COUNT) {
            conn = createConnection(conn.getHeaderField("Location"), extra);
            redirectCount++;
        }

        //通过HttpURLConnection获取InputStream
        InputStream imageStream;
        try {
            imageStream = conn.getInputStream();
        } catch (IOException e) {
            // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
            IoUtils.readAndCloseStream(conn.getErrorStream());
            throw e;
        }

        if (!shouldBeProcessed(conn)) {
            IoUtils.closeSilently(imageStream);
            throw new IOException("Image request failed with response code " + conn.getResponseCode());
        }

        return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
    }

    //创建HttpURLConnection连接
    protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
        String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
        HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
        conn.setConnectTimeout(connectTimeout);
        conn.setReadTimeout(readTimeout);
        return conn;
    }

    /**
     * 从文件获取图片输入流
     */
    protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {
        String filePath = Scheme.FILE.crop(imageUri);
        if (isVideoFileUri(imageUri)) {
            return getVideoThumbnailStream(filePath);
        } else {
            BufferedInputStream imageStream = new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE);
            return new ContentLengthInputStream(imageStream, (int) new File(filePath).length());
        }
    }

    ...

}

public class VideoThumbnailLoader { private static final String TAG = "VideoThumbnailLoader"; private MemoryCache mMCache;//一级缓存,内存缓存 private static VideoThumbnailLoader ins = new VideoThumbnailLoader(); public static VideoThumbnailLoader getIns(){ return ins; } private VideoThumbnailLoader() { mMCache = ImageLoader.getInstance().getMemoryCache(); } public void display(TLLiveEntity mEntity,String url,ImageView iv,int width,int height,ThumbnailListener thumbnailListener){ new ThumbnailLoadTask(mEntity,url,iv,width,height,thumbnailListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);//使用AsyncTask自带的线程池 } private class ThumbnailLoadTask extends AsyncTask<Void, Void, Bitmap> { private String url; private ImageView iv; private ThumbnailListener thumbnailListener; private int width; private int height; public ThumbnailLoadTask(String url,ImageView iv,int width,int height,ThumbnailListener thumbnailListener){ this.url = url; this.iv = iv; this.width = width; this.height = height; this.thumbnailListener = thumbnailListener; } @Override protected Bitmap doInBackground(Void... params) { /** * 注意,由于我们使用了缓存,所以在加载缩略图之前,我们需要去缓存里读取,如果缓存里有,我们则直接获取,如果没有,则去加载.并且加载完成之后记得放入缓存. */ Bitmap bitmap = null; if (!TextUtils.isEmpty { String key = getMemoryKey; bitmap = mMCache.get;//先去内存缓存取 if (bitmap == null||bitmap.isRecycled { File file = TLFileUtils.getExternalFile(path, "xxx.png");//创建文件,这里由于项目原因,我就随便写一个,实际情况不是这样,大家留意一下 if (null != file && file.exists {//去磁盘缓存取 bitmap = BitmapFactory.decodeFile(file.getPath; if (null==bitmap) { bitmap = getVideoThumbnail(url, width, height, MediaStore.Video.Thumbnails.MICRO_KIND); //将图片保存到磁盘文件,作为缓存 BitmapUtils.saveBitmapToFile(file, bitmap,Bitmap.CompressFormat.PNG); } } else { bitmap = getVideoThumbnail(url, width, height, MediaStore.Video.Thumbnails.MICRO_KIND); if (null==bitmap) { bitmap = getVideoThumbnail(url, width, height, MediaStore.Video.Thumbnails.MICRO_KIND); //将图片保存到磁盘文件,作为缓存 BitmapUtils.saveBitmapToFile(file, bitmap,Bitmap.CompressFormat.PNG); } } if (null != bitmap) { mMCache.put(key, bitmap);//存入内存缓存 } } } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute; thumbnailListener.onThumbnailLoadCompleted(url, iv, bitmap);//回调 } } /** * @param videoPath 视频路径 * @param width * @param height * @param kind eg:MediaStore.Video.Thumbnails.MICRO_KIND MINI_KIND: 512 x 384,MICRO_KIND: 96 x 96 * @return */ private Bitmap getVideoThumbnail(String videoPath, int width, int height, int kind) { // 获取视频的缩略图 Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(videoPath, kind); bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); return bitmap; } /** * imageloader 的内存缓存的 key 以_ 结尾 截取key比较的时候如果没有加_ 会报错崩溃,所以自己自定义 * @param filePath 文件地址 * @return */ private String getMemoryKey(String filePath) { String key ; int index = filePath.lastIndexOf; key = filePath.substring(index + 1, filePath.length+"_"; return key; } //自己定义一个回调,通知外部图片加载完毕 public interface ThumbnailListener{ void onThumbnailLoadCompleted(String url,ImageView iv,Bitmap bitmap); }}

创建多线程常用的方法

在大多数使用场景下,我们没有必要产生多个后台线程,简单的创建AsyncTasks或者使用基于任务队列的IntentService就可以很好的满足我们对异步处理的需求。然而当我们真的需要多个后台线程的时候,我们常常会使用下面的代码简单的创建多个线程。

 String[] urls = … for (final String url : urls) { new Thread(new Runnable() { public void run() { // 调用API、下载数据或图片 } }).start(); }

该方法有几个问题。一方面,操作系统限制了同一域下连接数。这意味着,你的代码并没有真的按照你的意愿执行。新建的线程如果超过数量限制则需要等待旧线程执行完毕。 另外,每一个线程都被创建来执行一个任务,然后销毁。这些线程也没有被重用。

5.LoadAndDisplayImageTask & DisplayBitmapTask

UIL工作中最主要的任务类,其中LoadAndDisplayImageTask包含了DisplayBitmapTask,从名称上就能看出关系,LoadAndDisplayImageTask是下载和显示图片,而DisplayBitmapTask只是显示显示图片。
其实这个任务类也是UIL的核心工作类,有必要深刻地理解其工作流程。

下面是LoadAndDisplayImageTask的run方法,这里跟之前学过ImageLoader的同步加载图片流程差不多,这里主要分为几步:

  • 加上同步锁:loadFromUriLock.lock();
  • 从内存缓存中获取图片:bmp = configuration.memoryCache.get(memoryCacheKey);
  • 从磁盘缓存、网络中获取图片:bmp = tryLoadBitmap();
  • 利用DisplayBitmapTask异步显示图片:runTask(displayBitmapTask, syncLoading, handler, engine);注意该方法中有利用到Handler用于异步加载图片,而该Handler是在ImageLoader的displayImage中被默认初始化的
final class LoadAndDisplayImageTask
{
    ...

    @Override
    public void run() {
        if (waitIfPaused()) return;
        if (delayIfNeed()) return;

        ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
        L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
        if (loadFromUriLock.isLocked()) {
            L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
        }

        //加锁
        loadFromUriLock.lock();
        Bitmap bmp;
        try {
            checkTaskNotActual();

            //从内存缓存中获取
            bmp = configuration.memoryCache.get(memoryCacheKey);
            if (bmp == null || bmp.isRecycled()) {
                //如果内存中获取不到则从磁盘、网络中获取
                bmp = tryLoadBitmap();
                if (bmp == null) return; // listener callback already was fired

                checkTaskNotActual();
                checkTaskInterrupted();

                if (options.shouldPreProcess()) {
                    L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
                    bmp = options.getPreProcessor().process(bmp);
                    if (bmp == null) {
                        L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
                    }
                }

                //在从磁盘或者网络获取到图片之后将其加载到内存里面
                if (bmp != null && options.isCacheInMemory()) {
                    L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
                    configuration.memoryCache.put(memoryCacheKey, bmp);
                }
            } else {
                loadedFrom = LoadedFrom.MEMORY_CACHE;
                L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
            }

            if (bmp != null && options.shouldPostProcess()) {
                L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
                bmp = options.getPostProcessor().process(bmp);
                if (bmp == null) {
                    L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
                }
            }
            checkTaskNotActual();
            checkTaskInterrupted();
        } catch (TaskCancelledException e) {
            fireCancelEvent();
            return;
        } finally {
            loadFromUriLock.unlock();
        }

        //在获取了图片之后将其放进DisplayBitmapTask中显示图片
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        runTask(displayBitmapTask, syncLoading, handler, engine);
    }

    /**
     * 该方法的作用是先从磁盘缓存里获取数据
     * 如果没有则调用tryCacheImageOnDisk()从网络获取图片并将其存到磁盘中
     *     然后再从磁盘中获取图片
     * 因此整个流程是:
     *     磁盘缓存获取->网络获取并存到磁盘缓存并从磁盘缓存获取
     */
    private Bitmap tryLoadBitmap() throws TaskCancelledException {
        Bitmap bitmap = null;
        try {
            //先从磁盘中获取图片
            File imageFile = configuration.diskCache.get(uri);
            if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
                L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
                loadedFrom = LoadedFrom.DISC_CACHE;

                checkTaskNotActual();
                bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
            }
            if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
                L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
                loadedFrom = LoadedFrom.NETWORK;

                String imageUriForDecoding = uri;
                //从网络上获取图片存到磁盘中
                if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
                    //从磁盘中获取
                    imageFile = configuration.diskCache.get(uri);
                    if (imageFile != null) {
                        imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
                    }
                }

                checkTaskNotActual();
                bitmap = decodeImage(imageUriForDecoding);

                if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
                    fireFailEvent(FailType.DECODING_ERROR, null);
                }
            }
        } catch (IllegalStateException e) {
            fireFailEvent(FailType.NETWORK_DENIED, null);
        } catch (TaskCancelledException e) {
            throw e;
        } catch (IOException e) {
            L.e(e);
            fireFailEvent(FailType.IO_ERROR, e);
        } catch (OutOfMemoryError e) {
            L.e(e);
            fireFailEvent(FailType.OUT_OF_MEMORY, e);
        } catch (Throwable e) {
            L.e(e);
            fireFailEvent(FailType.UNKNOWN, e);
        }
        return bitmap;
    }

    /** 
     * 从网络中获取图片存储到磁盘缓存中,通过downloadImage()实现的
     *     而后的resizeAndSaveImage(width, height)是为了根据实际需求将图片压缩并重新存入磁盘缓存
     *     
     * 下载成功则返回true
     */
    private boolean tryCacheImageOnDisk() throws TaskCancelledException {
        L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);

        boolean loaded;
        try {
            loaded = downloadImage();
            if (loaded) {
                int width = configuration.maxImageWidthForDiskCache;
                int height = configuration.maxImageHeightForDiskCache;
                if (width > 0 || height > 0) {
                    L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
                    resizeAndSaveImage(width, height); // TODO : process boolean result
                }
            }
        } catch (IOException e) {
            L.e(e);
            loaded = false;
        }
        return loaded;
    }

    /**
     * 该方法主要分为两步:
     * 1.从网络获取图片输入流:InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
     *     其中Downloader在Configuration被创建的时候被默认初始化成BaseImageDownloader
     * 2.通过输入流将图片存入磁盘缓存:configuration.diskCache.save(uri, is, this);
     */
    private boolean downloadImage() throws IOException {
        InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
        if (is == null) {
            L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
            return false;
        } else {
            try {
                return configuration.diskCache.save(uri, is, this);
            } finally {
                IoUtils.closeSilently(is);
            }
        }
    }

}

上面的任务是图片的同步加载过程,这是通过线程池执行的,最后还要将图片显示出来,此时就需要转到主线程设置图片,也就是通过Handler来操作,而显示图片的过程是在DisplayBitmapTask中实现的,下面来看一下DisplayBitmapTask的运行过程。

final class DisplayBitmapTask
{
    /**
     * 在该方法中,主要的作用是在加载图片的过程中触发各个回调函数
     * 其中最主要的是通过displayer将图片加载显示到imageAware控件当中
     *
     * 注:该任务应该运行在主线程
     */
    @Override
    public void run() {
        if (imageAware.isCollected()) {
            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
        } else if (isViewWasReused()) {
            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
        } else {
            L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
            //将图片bitmap加载到imageAware中用于显示
            displayer.display(bitmap, imageAware, loadedFrom);
            engine.cancelDisplayTaskFor(imageAware);
            listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
        }
    }

}

上面DisplayBitmapTask只是定义好了如何加载图片到控件当中,真正被转移到主线程中执行还是在LoadAndDisplayImageTask中的runTask方法中,可以看到,runTask方法中是通过参数中的handler将DisplayBitmapTask执行到主线程中的。

final class LoadAndDisplayImageTask
{
    ...
        @Override
    public void run() {

        ...

        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        //displayBitmapTask执行
        runTask(displayBitmapTask, syncLoading, handler, engine);
    }

    /**
     * 从该方法中可以发现,displayBitmapTask是通过handler的post达到运行在主线程中加载图片的效果
     */
    static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
            if (sync) {
                r.run();
            } else if (handler == null) {
                engine.fireCallback(r);
            } else {
                handler.post(r);
            }
        }
}

通过阅读本文你将能够学到:

最后

如果想要开发更加快的应用程序, 请阅读我的文章.

文章已经到底了,在Medium上Follow我吧。 LinkedIn, Google+ or Twitter.

4.ImageLoaderEngine

线程池的管理类,因为主要的任务都是通过线程池来完成的,因此可以认为ImageLoader里面干活的就是ImageLoaderEngine类里面的线程池。
下面主要介绍ImageLoaderEngine的构造方法和两个主要工作方法

/**
 * 构造方法,可以看到利用的是Configuration类当中的参数来配置本身的线程池成员
 */
ImageLoaderEngine(ImageLoaderConfiguration configuration) {
    this.configuration = configuration;

    taskExecutor = configuration.taskExecutor;
    taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;

    taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
}

/** 执行任务的方法,可以看到内部是利用线程池成员来执行的*/
void submit(final LoadAndDisplayImageTask task) {
    taskDistributor.execute(new Runnable() {
        @Override
        public void run() {
            File image = configuration.diskCache.get(task.getLoadingUri());
            boolean isImageCachedOnDisk = image != null && image.exists();
            initExecutorsIfNeed();
            if (isImageCachedOnDisk) {
                taskExecutorForCachedImages.execute(task);
            } else {
                taskExecutor.execute(task);
            }
        }
    });
}

/** Submits task to execution pool */
void submit(ProcessAndDisplayImageTask task) {
    initExecutorsIfNeed();
    taskExecutorForCachedImages.execute(task);
}

谢谢,下次见!

更新: Marco Kotz 指出结合使用ThreadPool Executor和AsyncTask,后台可以有多个线程同时处理AsyncTask。

UniversalImageLoader

[TOC]

本章主要介绍UniversalImageLoader的主要实现原理,和使用方法

在介绍之前,理解《Android开发艺术探索》的第十二章Bitmap的加载和Cache是十分重要的,因为该章节介绍了ImageLoader的最核心的工作原理,这样能够为更好地理解UIL打下坚实的基础。因此建议先去阅读理解该章的内容。

由该章的内容中可以知道,ImageLoader最重要的几点工作如下

  • 图片的同步加载
  • 图片的异步加载
  • 图片压缩
  • 内存缓存
  • 磁盘缓存
  • 网络拉取

因此我们从这几点出发,结合UniversalImageLoader关键部分的源码来具体分析UIL的实现原理以及如果使用。

我的新浪微博

我的Github

6.ImageLoadingInfo

ImageLoadingInfo信息类的作用类似于ImageLoaderConfiguration配置类,只是担当了信息参数储存的角色,实际并不做任何工作。
比如ImageLoaderConfiguration配置类是为ImageLoader正常工作流程提供参数,比如线程池、缓存和下载器等等。
而ImageLoadingInfo的作用则是为图片的加载显示提供参数,比如图片的url、图片的显示控件、目标显示大小和显示配置类DisplayImageOptions等等,注意DisplayImageOptions的作用是提供在显示图片时所需的参数和一些回调接口,下面有详细说明。 这里只需要记住ImageLoadingInfo的作用就是跟图片加载显示有关。

final class ImageLoadingInfo {

    final String uri;
    final String memoryCacheKey;
    final ImageAware imageAware;
    final ImageSize targetSize;
    final DisplayImageOptions options;
    final ImageLoadingListener listener;
    final ImageLoadingProgressListener progressListener;
    final ReentrantLock loadFromUriLock;

    public ImageLoadingInfo(String uri, ImageAware imageAware, ImageSize targetSize, String memoryCacheKey,
            DisplayImageOptions options, ImageLoadingListener listener,
            ImageLoadingProgressListener progressListener, ReentrantLock loadFromUriLock) {
        this.uri = uri;
        this.imageAware = imageAware;
        this.targetSize = targetSize;
        this.options = options;
        this.listener = listener;
        this.progressListener = progressListener;
        this.loadFromUriLock = loadFromUriLock;
        this.memoryCacheKey = memoryCacheKey;
    }
}

本文由美高梅网上注册平台发布于美高梅网上注册平台,转载请注明出处:因此建议先去阅读理解该章的内容,阅读本文你

上一篇:还有其他更专业的如从电影本身制作等角度来描 下一篇:没有了
猜你喜欢
热门排行
精彩图文