FindJpg(3)-图片加载中的线程池应用

来源:互联网 发布:淘宝 i7主机 编辑:程序博客网 时间:2024/06/05 04:14

一、线程池的优点:

  1. 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
  2. 能有效控制线程池的最大并发数量,避免大量线程间因相互抢占系统资源而导致阻塞。
  3. 能够对线程进行简单的管理,提供定时执行、指定间隔循环执行等功能。


二、ThreadPoolExecutor:

  • TreadPoolExecutor是线程池的真正实现,它的构造方法提供了一系列参数来配置线程池。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)


    • corePoolSize:核心线程数,默认情况下会在线程池中一直存活,即使它们处于闲置状态。
    • maximumPoolSize:线程池所能容纳的最大线程数,当活动线程数到达此数值后,后续的新任务将被阻塞。
    • keepAliveTime:非核心线程闲置时的超时时长,超过次时长后非核心线程将被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。
    • unit:用于指定keepAliveTime参数的时间单位,常用的有TimeUnit.MILLSECONDS、TimeUnit.SECONDS、TimeUnit.MINUTES等。
    • workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中
    • threadFactory:线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r){}。


  • ThreadPoolExecutor执行任务时遵循的规则:

    1. 若线程池中的线程数量未达到corePoolSize,那么会直接启动一个核心线程来执行任务。
    2. 若达到或超过corePoolSize,那么认为会被插入到workQueue排队等待执行。
    3. 若在步骤2中无法将任务插入,往往是因为workQueue已满,这时若线程数量未达到maximumPoolSize,就会立刻启动一个非核心线程来执行任务。
    4. 若步骤3中达到了maximumPoolSize,那么就拒绝执行此任务,ThreadPoolExecotor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。


三、图片加载中线程池的具体应用

  • 对ThreadPoolExecutor进行参数配置(参考AsyncTask中的线程池配置情况)

代码如下:

private static final int CPU_COUNT=Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE=CPU_COUNT+1;
private static final int MAXIMUM_POOL_SIZE=CPU_COUNT*2+1;
private static final long KEEP_ALIVE=10L;

private static final ThreadFactory sThreadFactory=new ThreadFactory() {
    private final AtomicInteger mCount=new AtomicInteger(1);
   
@Override
   
public Thread newThread(Runnable r) {
        return new Thread(r,"ImageLoader#"+mCount.getAndIncrement());//取当前值并自增
   
}
};

public static final
Executor THREAD_POOL_EXECUTOR=newThreadPoolExecutor(
        CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,
       
KEEP_ALIVE,TimeUnit.SECONDS,
        new
LinkedBlockingQueue<Runnable>(),sThreadFactory);


  • 图片的异步加载
    • 先从内存缓存中读取图片,若成功则直接返回结果。否则去线程池调用loadBitmap方法(见上篇博文),loadBitmap是同步加载的过程,一般同步加载过程(特别是有网络请求的情况)会比较耗时,所以这里放在在线程池中调用。
    • 当图片加载成功后将图片、图片地址和需要绑定的imageView封装成一个LoaderResult对象,通过mMainHandler来访问UI进行UI层面的相关操作。
    • 当中的一个细节是:在bindBitmap里对imageView setTag,这样在主线程中给imageView设置图片前先检查uri是否改变,这样可以解决由于View复用导致列表错位的问题。

具体代码如下:

public void bindBitmap(finalString uri,finalImageView imageView,final intreqWidth,final intreqHeight){
    imageView.setTag(TAG_KEY_URI,uri);
   
Bitmap bitmap=loadBitmapFromMemCache(uri);
    if
(bitmap!=null){
        imageView.setImageBitmap(bitmap);
        return;
   
}

    final Runnable loadBitmapTask=newRunnable() {
        @Override
       
public void run() {
            Bitmap bitmap=loadBitmap(uri,reqWidth,reqHeight);
            if
(bitmap!=null){
                LoaderResult result=new LoaderResult(imageView,uri,bitmap);
               
mMainHandler.obtainMessage(MESSAGE_POST_RESULT,result).sendToTarget();
           
}
        }
    };
   
THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
}


四、后记:异步操作带来的列表错位问题

  • 关于列表错位的问题,一般是重用convertView和异步操作同时发生才会出现,这儿要联系getView来理解(之前的博文ListView优化详解有简单介绍)。比如Item8复用Item1的View,如果Item1的数据(比如图片)加载慢,Item8的快,当上滑使Item8可见时,Item8先显示自己的图片,但等到Item1图片也完成加载时Item8的图片变成Item1的图片了。因为它们复用的是同一个View,指向同一块内存。同理若Item1快Item8慢,上滑使Item8可见时先显示Item1的图片。因此,重用convertView和异步操作同时发生时,会出现列表错位的问题。


Demo地址:FindJpg


0 0
原创粉丝点击