Android之打造自己加载高清大图及瀑布流框架.解决错位等问题.

来源:互联网 发布:如何经营一家淘宝网店 编辑:程序博客网 时间:2024/04/30 13:52

    首先看效果图如下:

    https://github.com/q422013/ImageLoader

    本框架支持本地图片和网络图片的获取.采用LruCache算法,最少使用的最先释放.有效的避免OOM,项目结构图:

    

    核心加载类在于ImageLoader.采用了TreadPool去做并发请求.UI处理采用Handler去管理,实现的思路类似于AsnycTask类.该类采用单例模式:

    public static ImageLoader getInstance(Context context) {        if (null == loader) {            synchronized (ImageLoader.class) {                if (null == loader) {                    loader = new ImageLoader(context, defThreadCount, mType);                }            }        }        return loader;    }    public static ImageLoader getInstance(Context context, int threadCount, Type type) {        if (null == loader) {            synchronized (ImageLoader.class) {                if (null == loader) {                    loader = new ImageLoader(context, threadCount, type);                }            }        }        return loader;    }

第一种类不需要配置线程池及加载方式.加载方式分为两种:1.先进先加载,2.后进先加载.

    /**     * 队列调度模式     */    public enum Type {        FIFO, LIFO    }
工作线程中核心是用Loop去不断的取消息,取到消息后就加入到线程池当中去执行,这样减少了自己去维护轮训,减少内存开销.

        //工作线程        mThread = new Thread() {            @Override            public void run() {                Looper.prepare();                mPoolThreadHandler = new Handler() {                    @Override                    public void handleMessage(Message msg) {                        mThreadPool.execute(getTask());                        try {                            mPoolSemaphore.acquire();//信号量 + 1                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                };                mSemapHore.release();//初始化完成后信号量 -1                Looper.loop();            }        };

从上面代码可以看出PoolTreadHandler收到一个消息后会让mThreadPool去执行一个任务,该任务通过getTask()方法获得一个Runnable对象,并且让信号量增加表示,线程池中有一个任务了.

看看getTask()代码很简单,仅仅是将任务按不同的方式取出来:

    /**     * 获取任务     *     * @return     */    private synchronized Runnable getTask() {        if (0 < mTask.size()) {            if (mType == Type.LIFO)                return mTask.removeFirst();            else                return mTask.removeLast();        }        return null;    }

真正的工作在于mTask去add,mTask是一个LinkedList<Runnable>类型的集合.所以核心在于方法Load()

    /**     * 加载图片     *     * @param path     * @param imageview     */    public void load(final String path, final View view, final LoadListener<View> loadListener) {        if (null == path)            throw new RuntimeException("this path is null");        if (null == loadListener)            throw new RuntimeException("this loadListener is null");        view.setTag(path);        //1.从磁盘,2.从内存        if (null == mDisPlayHandler)            mDisPlayHandler = new Handler() {                @Override                public void handleMessage(Message msg) {                    int code = msg.what;                    ViewBeanHolder holder = (ViewBeanHolder) msg.obj;                    final View view = holder.view;                    Bitmap bm = holder.bitmap;                    String path = holder.path;                    switch (code) {                        case LOAD_SUCCESS://加载成功                            if (view.getTag().toString().equals(path)) {                                loadListener.LoadSuccess(view, bm, path);                                if (isNeedAnim)                                    new LoadAnimCore(view);                            }                            break;                        case LOAD_ING://加载中                            if (view.getTag().toString().equals(path)) {                                loadListener.Loading(view, path);                            }                            break;                        case LOAD_FAILE://加载失败                            if (view.getTag().toString().equals(path)) {                                loadListener.LoadError(view, path, null);//暂时消息为空                            }                            break;                    }                }            };        addTask(path, view);    }

其中view.setTag是为了防止错乱.上面代码可以看出来仅仅是用于callBack,核心的东西其实在addTask方法.我们看看addTask方法做了什么事情:

    /**     * 添加任务     *     * @param path     * @param view     */    private synchronized void addTask(final String path, final View view) {        Runnable runnable = new Runnable() {            @Override            public void run() {                ViewBeanHolder holder = new ViewBeanHolder();                holder.view = view;                holder.path = path;                sendMsg(LOAD_ING, holder);                //TODO 从内存中获取                Bitmap bitmap = LruCacheUtils.getInstance().get(path);                if (null == bitmap) {                    //TODO 从磁盘中获取                    String tempPath = getImageFromDiskUrl(path);                    if (null != tempPath) {                        bitmap = decodeSampledBitmapFromResource(tempPath, (ImageView)view);                    } else {                        if (null == bitmap) {                            // TODO 从网络中获取                            bitmap = decodeSampledBitmapFromNetWork(path, (ImageView)view);                        } else {                            // TODO 失败                            sendMsg(LOAD_FAILE, holder);                        }                    }                }                //加载成功                if (null != bitmap) {                    LruCacheUtils.getInstance().put(path, bitmap);                    holder.bitmap = bitmap;//唯一的                    sendMsg(LOAD_SUCCESS, holder);                } else {                    //加载失败                    sendMsg(LOAD_FAILE, holder);                }            }        };        if (null == mPoolThreadHandler) {            try {                mSemapHore.acquire();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        mTask.add(runnable);        mPoolThreadHandler.sendEmptyMessage(0x1000);        mPoolSemaphore.release();//信号量 -1    }

    缓存策略:先从内存中获取,如果没有获取到,就从磁盘获取,磁盘也没有获取到,那就从网络获取.最后并将该bitmap设置到内存缓存,假象:如果设置非常多的bitmap到内存缓存中肯定会让内存占满导致OOM,所以便采用了google推荐使用的LruCache缓存算法.该算法可以实现固定内存加载,并且最近少使用的会被内存回收掉.

    然后在MainActivity中可以使用如下:

  ImageLoader.getInstance(MainActivity.this, 3, ImageLoader.Type.LIFO).load(IMAGES[position], holder.imageView);

上面加载方式是直接交给内部处理.图片默认加载RGB_565.

    ImageLoader.getInstance(MainActivity.this, 3, ImageLoader.Type.LIFO).load(IMAGES[position], holder.imageView, new LoadListener<View>() {                @Override                public <T> void Loading(View view, String path) {                }                @Override                public <T> void LoadSuccess(View view, Bitmap bitmap, String path) {                        ((ImageView) view).setImageBitmap(bitmap);                }                @Override                public <T> void LoadError(View view, String path, String errorMsg) {                    Log.d("Tanck","加载失败:"+path);                    ((ImageView)view).setImageResource(R.mipmap.ic_launcher);                }            });


采用几个加载配置方式内存对比:

RGB_565:


约11.31MB,效果如下:


ARGB_8888:


约12.86MB效果图如下:


可以看出差别不是很大.

但是ARGB_4444使用内存和RGB_565相近,但是效果很差,效果图如下:


2 0
原创粉丝点击