android图片获取加载小结

获取方式

1.内存

内存缓存主要使用LRU缓存算法,引用support-v4中的LruCache,
通过键值对的形式获取到相应的bitmap,配置如下:

      //初始化缓存策略
        int maxMem = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMem / 8;

        mMemCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {

               //计算缓存对象大小
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };

2.磁盘缓存

磁盘缓存也是采用LRU缓存算法,一般从网上直接获取DiskLruCache源码,配置好,通过键值对的形式获取。
DiskLruCache的入口函数如下:

 /*
  *  执行初始化磁盘缓存操作,
  *  注意directory要先mkdir
  */
 public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize);

    /**
     * 获取磁盘缓存路径
     * @param context
     * @param uniqueName
     * @return 
     */
    public static File getDiskCacheDir(Context context, String uniqueName) {
        // Check if media is mounted or storage is built-in, if so, try and use external cache dir
        // otherwise use internal cache dir
        final String cachePath =
                Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                        !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                                context.getCacheDir().getPath();

        return new File(cachePath + File.separator + uniqueName);
    }

directory:为缓存存储路径,
appVersion: 版本号,改变后缓存会改变,一般设为1
valueCount: 单节点对应数据个数,一般为1
maxSize: 最大缓存容量,快满会自动清理,单位为Byte

注意的是这里的key值不能直接存图片url,而是需要将其转化为md5值,避免特殊字符干扰。


3.磁盘I/O

通过ContentResolver直接遍历获取磁盘上的图片即可。

//获取图片存储路径
Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver mContentResolver = getContentResolver();
//获得游标并执行相关数据库表的查询
Cursor mCursor = mContentResolver.query(mImageUri,null,                   MediaStore.Images.Media.MIME_TYPE + "=? or "+                       MediaStore.Images.Media.MIME_TYPE +"=?",new String[]{                              "image/jpeg","image/png"},                      MediaStore.Images.Media.DATE_MODIFIED);
if(mCursor==null){
  return;
}                  
while(mCursor.moveToNext()){
  //获取图片的路径
String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA));
  //TODO 储存path
}

4.网络

直接通过HttpURLConnection获取到IO流便可

                //网络直接获取到stream,注意getInputStream操作才执行网络请求
                HttpURLConnection connect = null;
                InputStream in = null;

                try {
                    final URL urlString = new URL(url);

                    connect = (HttpURLConnection) urlString.openConnection();
                    connect.setDoInput(true);
                    connect.connect();
                    in = connect.getInputStream();
                    }...

使用事项

1.bitmap占用内存过大问题

android里bitmap是个大胖子,加载大量图片时需要对其进行缩放处理,而加载单张全图时,则注意将对应的imageView设置为弱引用,这样gc会更快回收。
bitmap缩放使用以下方法:

      /**
     * 根据文件描述符来传入图片流并压缩成制定的bitmap
     * @param fd
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeByFD(FileDescriptor fd, int reqWidth, int reqHeight) {

        BitmapFactory.Options options  = new BitmapFactory.Options();

        //为true时是只取样,几乎不占内存
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeFileDescriptor(fd,null,options);
        options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);

        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd,null,options);

    }


    /**
     * 计算压缩比,一般是2的倍数,为1时保留原来的格式
     * @param options
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

        if (reqWidth == 0 && reqHeight == 0) {
            return 1;
        }

        int realWidth = options.outWidth;
        int realHeight = options.outHeight;
        int inSampleSize = 1;

        if (reqWidth < realWidth || reqHeight < realHeight) {

            realWidth /= 2;
            realHeight /= 2;
            while((realWidth/inSampleSize)>=reqHeight&&(realHeight/inSampleSize)>=reqWidth){
                inSampleSize*=2;
            }

            //这里是考虑到极端情况,宽度比高度大很多的图片需要继续缩放
            //总的图片大小
            long totalPixels = realWidth * realHeight / inSampleSize;

            //imageView提供的大小
            final long totalReqPixelsCap = reqWidth * reqHeight * 2;

            while (totalPixels > totalReqPixelsCap) {
                inSampleSize *= 2;
                totalPixels /= 2;
            }
        }

        return inSampleSize;
    }

不一定是decodeByFile,可以利用BitmapFactory提供的其他decode方法,但注意的是,使用decodeStream时,两次操作会改变fileInputStream的位置信息,使得第二次decode获取到的为null,根本原因是fileInputStream为有序的。

另外MedioStore.Images.Thumbnails中提供了本地图片缩略图的获取方式,如下:

                String[] projection = new String[]{MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA};
                String[] condition = new String[]{url};
                Cursor cursor = mContext.getContentResolver().query(
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, MediaStore.Images.Media.DATA + " =? ",
                        condition, null);
                if (cursor == null) {
                    return;
                }
                if (cursor.moveToNext()) {
                    long imageId = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID));


                    String data = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));

                    bitmap = MediaStore.Images.Thumbnails.getThumbnail(mContext.getContentResolver(), imageId,
                            MediaStore.Images.Thumbnails.MICRO_KIND, null);
                }

getThumbnail可以获取到本地图片的缩略图,且缩略图有两种,分别是MINI_KIND 512 x 384和MICRO_KIND 96 x 96 此方法与bitmapFactory的decode的效率还有待深入比较。
另外,这里有个小bug,就是使用getContentResolver查找不到MICRO_KIND类型图片的路径,MINI_KIND是可以获取的,欢迎有兴趣的朋友一起讨论。


2线程安全问题

大量的图片操作伴随高耗时,所以除了内存缓存中的获取,一般采用线程池来异步加载图片,保证不阻碍UI主线程。
而ImageView的setBitmap操作要在主线程中操作,所以完成图片处理需要及时把图片信息通过主线程的handler传递message出去。

通过AsyncTask的executeOnExecutor方法可以自己配置并发使用的线程池,AsyncTask自带的线程池是串行的。


3界面卡顿问题

1)listview和gridview滑动过快,导致频繁的图片加载
2)listview和gridview的imageView控件复用,导致bitmap在未被加载完全时,显示被复用的bitmap

解决方案:
1 ) 监听滑动状态,通过OnScrollStateChanged监听滑动操作,如果使快速滑动时,不执行i/o和网络请求,代码如下:

        mGridView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {

                    mPhotoAdapter.setmIsScroll(false);
                    mPhotoAdapter.notifyDataSetChanged();
                } else {
                    mPhotoAdapter.setmIsScroll(true);
                }
            }
        });

2) getView中对imageView的bitmap的url进行记录,即设置完bitmap后,将图片的url通过imageView.setTag保存起来,在getView开始时就用实际的url与getTag里对比,如果不一致,则设置bitmap为默认图像,避免图片错位。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值