自定义ImageLoader

先上几张效果图:
这里写图片描述
这里写图片描述
这里写图片描述

在加载多图片时,我们采用后进先出策略(即滑动到哪里就先加载哪里的图片),节省了内存的使用,也有了更好的用户体验。接着我们就先定义自己的ImageLoader。

①首先我们先定义一些基本的变量

private static final int MSG_ADDTASK = 0x001;

private LruCache<String, Bitmap> mLruCache;// 图片缓存核心对象,LruCache是android提供的一个缓存工具类,其算法是最近最少使用算法

private ExecutorService mThreadPool;// 线程池

private LinkedList<Runnable> mTaskQueue;// 任务队列

private Thread mPollThread;// 后台轮询线程

private Handler mUIThread;// UI线程的Handler,将图片回显到UI界面上

private Handler mPollThreadHandler;// 后台线程的Handler,给后台线程发送消息

private Semaphore mSemphorePollThreadHandler = new Semaphore(0);// 信号量,用于同步addTask()中与mPollThreadHandler的同步问题

private Semaphore mSemphoreTreadPool;// 控制线程池空闲的时候才去取线程执行

private Type mType = Type.LIFO;// 队列的调度模式,默认为后进先出

/**
 * 队列的调度方法,图片的缓冲模式:先进先出(First In First Out),后进先出(Last In First Out)
  */
 private enum Type {
     FIFO, LIFO
 }

②关于ImageLoader的使用直接调用ImageLoader.getInstance()获取,我们采用单例模式

private static ImageLoader mInstance;

private ImageLoader(int threadCount, Type type) {
    init(threadCount, type);
}

public static ImageLoader getInstance() {
    if (mInstance == null) {
        synchronized (ImageLoader.class) {
            if (mInstance == null) {
                mInstance = new ImageLoader(3, Type.LIFO);// 设置线程池个数为3个,图片的加载模式是后进先加载
            }
        }
    }
    return mInstance;
}

③实现第一个init()方法,初始化一些变量以及后台线程

private void init(int threadCount, Type type) {
    mType = type;
    mSemphoreTreadPool = new Semaphore(3);// 设置信号量为3,当有第四个线程进入时就会阻塞

    // 后台轮询线程
    mPollThread = new Thread() {
        @Override
        public void run() {
            Looper.prepare();// 准备Looper
            mPollThreadHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case MSG_ADDTASK:
                            mThreadPool.execute(getTask());// 去线程中取出一个任务进行执行

                            try {
                                mSemphoreTreadPool.acquire();// 此时有三个线程在执行,当进来第四个线程的时候就会被阻塞
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            mSemphorePollThreadHandler.release();// 释放一个信号量,mSemphorePollThreadHandler.acquire的阻塞就会被取消,这里的作用是防止mPollThreadHandler还没有创建完毕,就发送消息,见addTask
                            break;
                    }
                }
            };
            Looper.loop();// 开始loop,遍历消息
        }
    };
    mPollThread.start();// 开启线程

    int maxMemory = (int) Runtime.getRuntime().maxMemory();// 获取最大使用内存
    int cacheMemory = maxMemory / 8;// 一般默认使用均为1/8
    mLruCache = new LruCache<String, Bitmap>(cacheMemory) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            return bitmap.getHeight() * bitmap.getRowBytes();// 每张图片占用的内存大小
        }
    };

    mThreadPool = Executors.newFixedThreadPool(threadCount);// 设置线程数
    mTaskQueue = new LinkedList<Runnable>();// 设置线程队列,使用LinkedList,便于获取任意位置的task
}

 /** 添加一个任务 */
 private synchronized void addTask(Runnable task) {
     mTaskQueue.add(task);

     try {
         if(mPollThreadHandler == null) {
             mSemphorePollThreadHandler.acquire();// 当mPollThreadHandler还没有建立的时候,此时就会被阻塞,直到mSemphorePollThreadHandler.realase
         }
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
     mPollThreadHandler.sendEmptyMessage(MSG_ADDTASK);
 }

/** 获取一个任务 */
private Runnable getTask() {
    if(mType == Type.FIFO) {
        return mTaskQueue.removeFirst();
    } else if(mType == Type.LIFO) {
        return mTaskQueue.removeLast();
    }
    return null;
}

④接下来进入我们的核心方法loadImage(imageView,path),根据图片路径和imageView控件,把图片设置到imageView控件上。

public void loadImage(final ImageView imageView, final String path) {
    imageView.setTag(path);// imageView设置一个Tag,防止图片加载过程中错乱

    mUIThread = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // 获取得到的图片,给imageView设置图片
            ImageHolder imageHolder = (ImageHolder) msg.obj;
            ImageView imageView = imageHolder.imageView;
            Bitmap bitmap = imageHolder.bitmap;
            String path = imageHolder.path;

            if(imageView.getTag().toString().equals(path)) {
                imageView.setImageBitmap(bitmap);
            }
        }
    };

    // 根据path从缓存中获取bitmap
    Bitmap bm = getBitmapFromLruCache(path);
    if (bm != null) {
        sendBitmapToUIHandler(imageView, bm, path);
    } else {
        addTask(new Runnable(){
            @Override
            public void run() {
                // 加载图片,图片压缩,放入缓冲中
                // 获取图片的要显示的大小
                ImageSize imageSize = getImageViewSize(imageView);

                // 压缩图片
                Bitmap bitmap = decodeSempledBitmapFromPath(path, imageSize);

                // 把图片放入LruCache中
                addBitmapToLruCache(bitmap, path);

                // 发送给UIhandlder,刷新图片
                sendBitmapToUIHandler(imageView, bitmap, path);

                mSemphoreTreadPool.release();// 释放线程所占用的信号量
            }
        });
    }
}

 /** 发送图片信息到主线程中,从而更新图片 */
 private void sendBitmapToUIHandler(ImageView imageView, Bitmap bitmap, String path) {
     ImageHolder imageHolder = new ImageHolder();
     imageHolder.imageView = imageView;
     imageHolder.bitmap = bitmap;
     imageHolder.path = path;

     Message msg = Message.obtain();
     msg.obj = imageHolder;
     mUIThread.sendMessage(msg);
 }

/**
 * 给imageView设置图片的实体类,便于handler的数据传输
 */
private class ImageHolder {
    ImageView imageView;
    Bitmap bitmap;
    String path;
}

/** imageView的大小 */
private class ImageSize {
    int width;
    int height;
}

关于图片处理的一些方法

/** 获取图片要显示的大小 */
private ImageSize getImageViewSize(ImageView imageView) {
     DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
     ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();

     int width = imageView.getWidth();
     if(width < 0) {
         width = layoutParams.width;
     }
     if(width < 0) {
         width = getFieldValue(imageView, "mMaxWidth");
     }
     if(width < 0) {
         width = displayMetrics.widthPixels;
     }

     int height = imageView.getHeight();
     if(height < 0) {
         height = layoutParams.height;
     }
     if(height < 0) {
         height = getFieldValue(imageView, "mMaxHeight");
     }
     if(height < 0) {
         height = displayMetrics.heightPixels;
     }

     ImageSize imageSize = new ImageSize();
     imageSize.height = height;
     imageSize.width = width;
     return imageSize;
 }

/** 反射获取字段值 */
private int getFieldValue(Object obj, String fieldName) {
     int value = 0;
     try {
         Field field = ImageView.class.getDeclaredField(fieldName);
         field.setAccessible(true);
         int fieldInt = field.getInt(obj);
         if(fieldInt > 0 && fieldInt < Integer.MAX_VALUE){
             value = fieldInt;
         }
     }catch (Exception e) {
         e.printStackTrace();
     }
     return value;
 }

/** 压缩图片 */
private Bitmap decodeSempledBitmapFromPath(String path, ImageSize imageSize) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;// 获取图片大小,并不把图片大小加入到内存中
    BitmapFactory.decodeFile(path, options);

    options.inSampleSize = caculateInSampleSize(options, imageSize);// 设置图片的压缩率

    options.inJustDecodeBounds = false;
    Bitmap bitmap = BitmapFactory.decodeFile(path, options);

    return bitmap;
}

/** 计算出图片的最佳压缩比例 */
private int caculateInSampleSize(BitmapFactory.Options options, ImageSize imageSize) {
    int width = options.outWidth;
    int height = options.outHeight;

    int inSampleSize = 1;// 设置取样率

    if(width > imageSize.width || height > imageSize.height) {
        int widthRadio = Math.round(width * 1.0f / imageSize.width);
        int heightRadio = Math.round(height * 1.0f / imageSize.height);
        inSampleSize = Math.max(widthRadio, heightRadio);
    }
    return inSampleSize;
}

/** 把图片放入到LruCache中 */
private void addBitmapToLruCache(Bitmap bitmap, String path) {
    if(getBitmapFromLruCache(path) == null) {
        if(bitmap != null) {
            mLruCache.put(path, bitmap);
        }
    }
}

================至此我们的ImageLoader定义完============================

MainActivity中获取手机图片代码

private void initData() {
    // 利用ContentProvider扫描手机的照片
    if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
        Toast.makeText(this, "当前储存卡不可用", Toast.LENGTH_SHORT).show();
        return;
    }
    // 扫描手机中的照片
    new Thread() {
        @Override
        public void run() {
            Uri mUriImg = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            ContentResolver contentResolver = MainActivity.this.getContentResolver();

            String selection = MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?";
            String[] selectionArgs = new String[]{"image/jpeg", "image/png"};
            String sortOrder = MediaStore.Images.Media.DATE_MODIFIED;
            Cursor cursor = contentResolver.query(mUriImg, null, selection, selectionArgs, sortOrder);

            Set<String> dirPaths = new HashSet<String>();// 储存遍历过的parentPath,防止重复遍历
            FolderBean bean = null;
            while (cursor.moveToNext()) {
                String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));// 拿到图片路径
                File parentFile = new File(path).getParentFile();// 拿到父文件夹
                if (parentFile == null) {
                    continue;
                }

                String parentPath = parentFile.getAbsolutePath();
                if (dirPaths.contains(parentPath)) {
                    continue;
                }

                String[] imgNames = parentFile.list(mFilter);// 获取文件夹下的所有图片
                if(imgNames == null) {
                    continue;
                }

                dirPaths.add(parentPath);

                bean = new FolderBean();
                bean.setDirPath(parentPath);
                bean.setFirstImgPath(path);
                Log.d(TAG, path + "------------------------------------");
                bean.setDirName(parentFile.getName());
                bean.setImgCount(imgNames.length);

                mFolderBeans.add(bean);
            }
            cursor.close();
            bean = new FolderBean();
            bean.setDirName("所有图片");
            bean.setFirstImgPath(mFolderBeans.get(0).getFirstImgPath());
            int imgCount = 0;
            String imgDirPath = "";
            for(FolderBean folderBean : mFolderBeans) {
                imgCount += folderBean.getImageCount();
                imgDirPath +=  folderBean.getDirPath() + SEPERATOR;
            }
            bean.setImgCount(imgCount);
            bean.setDirPath(imgDirPath.substring(0, imgDirPath.length() - 1));

            mFolderBeans.add(0, bean);

            initShowImgData(bean);

            // 扫描完毕,发送消息给handler
            Message msg = handler.obtainMessage();
            msg.what = MSG_DATA_LOADED;
            msg.obj = bean;
            handler.sendMessage(msg);
        }
    }.start();
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值