简单例子教你理解ImageLoader

图文并茂是现代app的发展趋势,而随着移动互联网的发达,大家也不可能把图片写死在客户端,所以动态加载服务器上的图片成为了app起码的能力之一。当图片较少时,一般使用AsyncTask就能解决问题,但是当同屏包含大量图片的时候,简单的单线程单图片的解决方案就会遇到性能和效率的瓶颈,这时候ImageLoader框架就应运而生了。Github上成熟的框架很多,这里不会纠结于具体的功能,主要通过简单的demo让没接触过框架的童鞋最效率的理解其核心思想。

先画个图:
这里写图片描述
请各位保持冷静,不要吐槽我的图片。大概讲解一下,界面层会在某个时刻同步产生大量的图片请求,这些请求会被封装成Request对象,丢到产线,也就是BlockingQueue上去,后端会有一个线程池根据app需求产生定制数量的线程(可理解为工人)然后从queue中获取任务并进行处理。当没有任务的时候,线程会阻塞等待bolckingqueue中新的任务的到来。

好了,最重要的就是这个思想,代码其实并不重要,下面上代码。

/**
 * Created by Amuro on 16/10/10.
 */
public class SDKImageLoader
{
    private SDKImageLoader()
    {}

    private static SDKImageLoader instance;

    public static SDKImageLoader getInstance()
    {
        if(instance == null)
        {
            synchronized (SDKImageLoader.class)
            {
                if(instance == null)
                {
                    instance = new SDKImageLoader();
                }
            }
        }

        return instance;
    }

    private ImageLoaderConfig config;
    private RequestQueue requestQueue;

    public void init(ImageLoaderConfig config)
    {
        this.config = config;
        requestQueue = new RequestQueue();
        requestQueue.invoke();
    }

    public void displayImage(ImageView imageView, String url)
    {
        displayImage(imageView, url, config.getFailedResId(), config.getLoadingResId());
    }

    public void displayImage(ImageView imageView, String url, int failedResId, int loadingResId)
    {
        Request request = new Request(
                imageView, url, failedResId, loadingResId, config.getLoadPolicy());
        requestQueue.addRequest(request);
    }

    public void destroy()
    {
        if(requestQueue != null)
        {
            requestQueue.stop();
        }
    }
}

外部调用的接口核心类,初始化的时候需要传入一个Config参数,看下config类:


/**
 * Created by Amuro on 16/10/10.
 */
public class ImageLoaderConfig
{
    private int failedResId;
    private int loadingResId;
    private LoadPolicy loadPolicy;

    public int getFailedResId()
    {
        return failedResId;
    }

    public int getLoadingResId()
    {
        return loadingResId;
    }

    public LoadPolicy getLoadPolicy()
    {
        return loadPolicy;
    }

    public static class Builder
    {
        private int failedResId;
        private int loadingResId;
        private LoadPolicy loadPolicy = new SerialPolicy();

        public Builder setFailedResId(int id)
        {
            failedResId = id;
            return this;
        }

        public Builder setLoadingResId(int id)
        {
            loadingResId = id;
            return this;
        }

        public Builder setLoadPolicy(LoadPolicy loadPolicy)
        {
            this.loadPolicy = loadPolicy;
            return this;
        }

        public ImageLoaderConfig build()
        {
            ImageLoaderConfig config = new ImageLoaderConfig();
            config.failedResId = failedResId;
            config.loadingResId = loadingResId;
            config.loadPolicy = loadPolicy;

            return config;
        }
    }
}

这里用到了变体的建造者模式,可参考Android源码里的AlertDialog,这里我们也跟风玩一下,三个参数分别是默认加载失败时展示的图片,载入是加载的图片,已经加载的策略也就是Request之间的优先级处理策略。看一下初始化我们ImageLoader的代码:

ImageLoaderConfig config =
                new ImageLoaderConfig.Builder().
                        setFailedResId(R.mipmap.failed).
                        setLoadingResId(R.mipmap.loading).
                        build();

        SDKImageLoader.getInstance().init(config);

没错,就是这样风骚。

初始化主要就是读取配置,然后把queue启动起来,我们来看一下RequestQueue这个类:


/**
 * Created by Amuro on 16/10/10.
 */
public class RequestQueue
{
    private static final int DEFAULT_THREAD_COUNT =
            Runtime.getRuntime().availableProcessors() + 1;

    private BlockingQueue<Request> queue = new PriorityBlockingQueue<Request>();
    private List<RequestDispatcher> dispatcherList;
    private int threadCount;

    public RequestQueue()
    {
        this(DEFAULT_THREAD_COUNT);
    }

    public RequestQueue(int threadCount)
    {
        this.threadCount = threadCount;
        this.dispatcherList = new ArrayList<>(threadCount);
    }

    public void addRequest(Request request)
    {
        if(!queue.contains(request))
        {
            this.queue.add(request);
        }
    }

    public void invoke()
    {
        stop();
        for(int i = 0; i < threadCount; i++)
        {
            RequestDispatcher dispatcher = new RequestDispatcher(queue);
            dispatcherList.add(dispatcher);
            dispatcher.start();
        }
    }

    public void stop()
    {
        if(dispatcherList != null && dispatcherList.size() == threadCount)
        {

            for (int i = 0; i < threadCount; i++)
            {
                RequestDispatcher dispatcher = dispatcherList.get(i);
                if (dispatcher != null)
                {
                    dispatcher.interrupt();
                }

            }
        }
    }
}

没错,这个类启动的时候,其实就是初始化了我们的BlockingQueue队列,然后初始化了处理任务的线程池,其中的线程数量默认是CPU核心数加1,当然也可以通过外部传入指定的线程数。重点在invoke方法,这里根据线程数量初始化了同样数量的RequestDispatcher,并start,我们来看一下这个类:

/**
 * Created by Amuro on 16/10/10.
 */
public class RequestDispatcher extends Thread
{
    private BlockingQueue<Request> queue;

    public RequestDispatcher(BlockingQueue<Request> queue)
    {
        this.queue = queue;
    }

    @Override
    public void run()
    {
        while (!interrupted())
        {
            try
            {
                Request request = queue.take();
                disposeRequest(request);

            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }

    }

    private void disposeRequest(final Request request) throws Exception
    {
        final ImageView imageView = request.imageViewReference.get();

        imageView.post(new Runnable()
        {
            @Override
            public void run()
            {
                imageView.setImageResource(request.loadingResId);
            }
        });

        int random = new Random().nextInt(2000);
        sleep(random);

        int success = new Random().nextInt(2);
        if(success == 0)
        {
            imageView.post(new Runnable()
            {
                @Override
                public void run()
                {
                    imageView.setImageResource(R.mipmap.success);
                }
            });
        }
        else
        {
            imageView.post(new Runnable()
            {
                @Override
                public void run()
                {
                    imageView.setImageResource(request.failedResId);
                }
            });
        }
    }
}

注意,这里只是模拟,并没有真正的发起网络请求,但本质是一样的,就是一个耗时异步操作而已。在加载ing的时候,加载失败的时候和加载成功的时候,分别给ImageView设置对应的图片即可。这里还有一个功能点没有深入写,就是加载网络图片等时候可以根据加载策略判断本地缓存的情况,有缓存就加载缓存的,没有,就去网络上拿,这个代码很好写,就不赘述了,这篇的重点在思想。

初始化完成后,核心方法其实就是Manager里的displayImage方法,其本质就是把View层加载的请求,丢到queue中去就完了,我们的生产线会自动处理这一系列请求的,是不是轻松愉快。

最后,别忘了在程序退出时销毁我们的ImageLoader。

@Override
protected void onDestroy()
{
    super.onDestroy();
    SDKImageLoader.getInstance().destroy();
}

无论多复杂的框架,都一定是在某个核心思想的主干上,开枝散叶罢了。
就酱~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值