如何高效加载Bitmap

高效加载大图

有的时候我们需要在App中加载一张很大的图片,比如5M甚至50M,这个时候如果直接将这么大的图片加载到布局中,将引起OOM错误。下面的方法可以有效的避免这种内存溢出错误。

图片有不同的形状与大小。在大多数情况下它们的实际大小都比需要呈现的尺寸大很多。例如,系统的图库应用会显示那些我们使用相机拍摄的照片,但是那些图片的分辨率通常都比设备屏幕的分辨率要高很多。

考虑到应用是在有限的内存下工作的,理想情况是我们只需要在内存中加载一个低分辨率的照片即可。为了更便于显示,这个低分辨率的照片应该是与其对应的UI控件大小相匹配的。加载一个超过屏幕分辨率的高分辨率照片不仅没有任何显而易见的好处,还会占用宝贵的内存资源,另外在快速滑动图片时容易产生额外的效率问题。

为此,我们可以加载一个缩小版本的图片,从而避免超出程序的内存限制。

读取位图的尺寸与类型(Read Bitmap Dimensions and Type)

BitmapFactory提供了一些解码(decode)的方法(decodeByteArray(), decodeFile(), decodeResource()等),用来从不同的资源中创建一个Bitmap。 我们应该根据图片的数据源来选择合适的解码方法。 这些方法在构造位图的时候会尝试分配内存,因此会容易导致OutOfMemory的异常。

每一种解码方法都可以通过BitmapFactory.Options设置一些附加的标记,以此来指定解码选项。设置 inJustDecodeBounds 属性为true可以在解码的时候避免内存的分配,它会返回一个null的Bitmap,但是可以获取到 outWidth, outHeightoutMimeType。该方法可以允许你在构造Bitmap之前优先读图片的尺寸与类型。

用法如下:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.mipmap.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

为了避免java.lang.OutOfMemory 的异常,我们需要在真正解析图片之前检查它的尺寸(除非你能确定这个数据源提供了准确无误的图片且不会导致占用过多的内存)。

加载一个按比例缩小的图片到内存中(Load a Scaled Down Version into Memory)

通过上面的步骤我们已经获取到了图片的尺寸,这些数据可以用来帮助我们决定应该加载整个图片到内存中还是加载一个缩小的版本。

有下面一些因素需要考虑:

  • 评估加载完整图片所需要耗费的内存。
  • 程序在加载这张图片时可能涉及到的其他内存需求。
  • 呈现这张图片的控件的尺寸大小。
  • 屏幕大小与当前设备的屏幕密度。

例如,如果把一个大小为1024x768像素的图片显示到大小为128x96像素的ImageView上,就没有必要把整张原图都加载到内存中。

为了告诉解码器去加载一个缩小版本的图片到内存中,需要在BitmapFactory.Options 中设置 inSampleSize 的值。例如, 一个分辨率为2048x1536的图片,如果设置 inSampleSize 为4,那么会产出一个大约512x384大小的Bitmap。加载这张缩小的图片仅仅使用大概0.75MB的内存,如果是加载完整尺寸的图片,那么大概需要花费12MB(前提都是Bitmap的配置是 ARGB_8888)。

下面有一段根据目标图片大小来计算Sample图片大小的代码示例:

 private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        Log.i(TAG,""+height);
        final int width = options.outWidth;
        Log.i(TAG,""+width);
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

注意:

设置inSampleSize为2的幂是因为解码器最终还是会对非2的幂的数进行向下处理,获取到最靠近2的幂的数。

为了使用该方法,首先需要设置 inJustDecodeBounds 为 true, 把options的值传递过来,然后设置 inSampleSize 的值并设置 inJustDecodeBounds 为 false,之后重新调用相关的解码方法。

 private Bitmap decodeSampledBitmapFromResource(Resources resources,
                                                   int resId, int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(resources, resId, options);
    }

使用上面这个方法可以简单地加载一张任意大小的图片。如下面的代码样例显示了一个接近 100x100像素的缩略图:

bitmap = decodeSampledBitmapFromResource(getResources(),R.mipmap.myimage,100, 100);
mImageView.setImageBitmap(bitmap);

我们可以通过替换合适的BitmapFactory.decode* 方法来实现一个类似的方法,从其他的数据源解析Bitmap。

非UI线程处理Bitmap

上面的图片是我们导入AndroidStudio中的,当图片来源是网络或者是存储卡时(或者是任何不在内存中的形式),这些方法都不应该在UI 线程中执行。因为在上述情况下加载数据时,其执行时间是不可估计的,它依赖于许多因素(从网络或者存储卡读取数据的速度,图片的大小,CPU的速度等)。如果其中任何一个子操作阻塞了UI线程,系统都会容易出现应用无响应的错误(ANR)。

因此我们可以使用AsyncTask在后台线程中处理Bitmap并且处理并发(concurrency)的问题。

使用AsyncTask(Use a AsyncTask)

AsyncTask 类提供了一个在后台线程执行一些操作的简单方法,它还可以把后台的执行结果呈现到UI线程中。

下面是一个加载大图,在程序中为100*100像素的缩略图的示例:

public class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap>{
    private String TAG = "BitmapWorerTask";
    private final WeakReference imageViewReference;
    private int data = 0;
    private Context mContext;
    private ImageView imageView;

    public BitmapWorkerTask(Context context,ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
       this.mContext = context;
        this.imageView = imageView;
        imageViewReference = new WeakReference(imageView);
    }


    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(mContext.getResources(), data, 100, 100);
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            imageView = (ImageView) imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }

为ImageView使用WeakReference确保了AsyncTask所引用的资源可以被垃圾回收器回收。由于当任务结束时不能确保ImageView仍然存在,因此我们必须在onPostExecute()里面对引用进行检查。该ImageView在有些情况下可能已经不存在了,例如,在任务结束之前用户使用了回退操作,或者是配置发生了改变(如旋转屏幕等)。

开始异步加载位图,只需要创建一个新的任务并执行它即可:

MainActivity,java

protected void onCreate(Bundle savedInstanceState){
     ...
     loadBitmap(R.mipmap.myimage,mImageView);
}

public void loadBitmap(int resId, ImageView imageView) {
        BitmapWorkerTask task = new BitmapWorkerTask(this,mImageView);
        task.execute(resId);
    }

用这样的方式也可以加载出一张大图。

处理并发问题(Handle Concurrency)

通常类似ListView与GridView等视图控件在使用上面演示的AsyncTask 方法时,会同时带来并发的问题。

首先为了更高的效率,ListView与GridView的子Item视图会在用户滑动屏幕时被循环使用。如果每一个子视图都触发一个AsyncTask,那么就无法确保关联的视图在结束任务时,分配的视图已经进入循环队列中,给另外一个子视图进行重用。而且, 无法确保所有的异步任务的完成顺序和他们本身的启动顺序保持一致。(即图片在加载过程中可能会出现乱序的情况

google为此提供了一种解决方法:ImageView保存最近使用的AsyncTask的引用,这个引用可以在任务完成的时候再次读取检查。使用这种方式, 就可以对前面提到的AsyncTask进行扩展。

  • (1)创建一个专用的Drawable的子类来储存任务的引用。在这种情况下,我们使用了一个BitmapDrawable,在任务执行的过程中,一个占位图片会显示在ImageView中:
static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}
  • (2)在执行BitmapWorkerTask 之前,你需要创建一个AsyncDrawable并且将它绑定到目标控件ImageView中:
public void loadBitmap(int resId, ImageView imageView) {
    if (cancelPotentialWork(resId, imageView)) {
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawable asyncDrawable =
                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
        imageView.setImageDrawable(asyncDrawable);
        task.execute(resId);
    }
}
  • (3)在上面的代码示例中,cancelPotentialWork方法检查是()否有另一个正在执行的任务与该ImageView关联了起来,如果的确是这样,它通过执行cancel()方法来取消另一个任务。在少数情况下,新创建的任务数据可能会与已经存在的任务相吻合,这样的话就不需要进行下一步动作了。下面是cancelPotentialWork方法的实现 。
public static boolean cancelPotentialWork(int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        if (bitmapData == 0 || bitmapData != data) {
            // Cancel previous task
            bitmapWorkerTask.cancel(true);
        } else {
            // The same work is already in progress
            return false;
        }
    }
    // No task associated with the ImageView, or an existing task was cancelled
    return true;
}
  • (4)在上面的代码中有一个辅助方法:getBitmapWorkerTask(),它被用作检索AsyncTask是否已经被分配到指定的ImageView:
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
   if (imageView != null) {
       final Drawable drawable = imageView.getDrawable();
       if (drawable instanceof AsyncDrawable) {
           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
           return asyncDrawable.getBitmapWorkerTask();
       }
    }
    return null;
}
  • (4)最后一步是在BitmapWorkerTask的onPostExecute() 方法里面做更新操作:
class BitmapWorkerTask extends AsyncTask {
    ...

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

下面看一个ListView异步加载多张图片的实例中,并发问题的解决方案:

Step 1:布局文件的配置

MainActivity.java

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

list_item.xml.java:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/mImageView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:src="@mipmap/ic_launcher"
        android:scaleType="fitXY"/>

</LinearLayout>

Step 2:MainActivity.java

public class MainActivity extends AppCompatActivity {
    private ListView mListView;
    private AsyncAdapter myAdapter;
    private String[] imageUrls = new String[]{
        "https://img-my.csdn.net/uploads/201508/05/1438760758_3497.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760758_6667.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760757_3588.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760756_3304.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760755_6715.jpeg",
                "https://img-my.csdn.net/uploads/201508/05/1438760726_5120.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760726_8364.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760725_4031.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760724_9463.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760724_2371.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760707_4653.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760706_6864.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760706_9279.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760704_2341.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760704_5707.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760685_5091.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760685_4444.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760684_8827.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760683_3691.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760683_7315.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760663_7318.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760662_3454.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760662_5113.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760661_3305.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760661_7416.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760589_2946.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760589_1100.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760588_8297.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760587_2575.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760587_8906.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760550_2875.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760550_9517.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760549_7093.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760549_1352.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760548_2780.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760531_1776.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760531_1380.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760530_4944.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760530_5750.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760529_3289.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760500_7871.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760500_6063.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760499_6304.jpeg",
                "https://img-my.csdn.net/uploads/201508/05/1438760499_5081.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760498_7007.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760478_3128.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760478_6766.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760477_1358.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760477_3540.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760476_1240.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760446_7993.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760446_3641.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760445_3283.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760444_8623.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760444_6822.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760422_2224.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760421_2824.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760420_2660.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760420_7188.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760419_4123.jpg",
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView)findViewById(R.id.listView);
        myAdapter = new AsyncAdapter(this,0,imageUrls);
        mListView.setAdapter(myAdapter);
    }
}

Step 3:ListView适配器

public class AsyncAdapter extends ArrayAdapter<String>{
    private ListView mListView;
    private String imageUrl;
    private Bitmap mLoadingBitmap;
    private LayoutInflater mInflater;

    public AsyncAdapter(Context context, int resource, String[] imageUrls) {
        super(context,resource,imageUrls);
        mLoadingBitmap = BitmapFactory.decodeResource(context.getResources(),R.mipmap.ic_launcher);
        mInflater = LayoutInflater.from(getContext());
        final int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
        final int cachSize = maxMemory/8;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (mListView == null) {
            mListView = (ListView) parent;
        }

        String url = getItem(position);
        View view;
        if(convertView==null){
            view = mInflater.inflate(R.layout.list_item,null);
        }
        else{
            view = convertView;
        }
        ImageView mImageView = (ImageView)view.findViewById(R.id.mImageView);
   /**
    在执行BitmapWorkerTask 之前,判断是否有潜在的后台下载任务,如果没有,那么你需要创建一个AsyncDrawable对象并且将它绑定到目标控件ImageView中,启动任务
    **/

         if(cancelPotentialWork(url, mImageView)){
            BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
            final AsyncDrawable asyncDrawable =
                    new AsyncDrawable(getContext().getResources(), mLoadingBitmap, task);
            mImageView.setImageDrawable(asyncDrawable);
            task.execute(url);
        }
        return view;
    }
    /**
     * 取消掉后台的潜在任务,当认为当前ImageView存在着一个另外图片请求任务时
     * ,则把它取消掉并返回true,否则返回false。
     */

    private boolean cancelPotentialWork(String url, ImageView mImageView) {
    //使用getBitmapWorkerTask()方法来获取该ImageView所对应的BitmapWorkerTask
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(mImageView);

        if (bitmapWorkerTask != null) {
            final String bitmapData = bitmapWorkerTask.imageUrl;
            if (bitmapData == null || bitmapData != imageUrl) {
                // 取消之前的下载任务
                bitmapWorkerTask.cancel(true);
            } else {
                // The same work is already in progress
                return false;
            }
        }
        // 没有下载任务和ImageView关联或者正在下载的任务被取消,则返回true
        return true;

    }

    /**
     * 获取传入的ImageView它所对应的BitmapWorkerTask。
     */
    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
        if (imageView != null) {
        //获取该ImageView控件中显示的图片(Drawable类型)           
        final Drawable drawable = imageView.getDrawable();
        /**
         *如果该Drawable是AsyncDrawable类或其子类,则获得该图片的BitmapWorkerTask引用        **/

            if (drawable instanceof AsyncDrawable) {
                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
                return asyncDrawable.getBitmapWorkerTask();
            }
        }
        return null;
    }


    /**
     * 异步下载图片的任务。
     * 
     * @author guolin
     **/ 
    public class BitmapWorkerTask extends AsyncTask<String,Void,BitmapDrawable> {
        private Context mContext;
        private ImageView mImageView;
        private ImageLoader mImageLoader;
        private String imageUrl;
    //为ImageView使用WeakReference确保了AsyncTask所引用的资源可以被垃圾回收器回收。
        private WeakReference<ImageView> imageViewReference;

        public BitmapWorkerTask(ImageView mImageView) {
            this.mImageView = mImageView;
            imageViewReference = new WeakReference<ImageView>(mImageView);
        }


        @Override
        protected BitmapDrawable doInBackground(String... params) {
            imageUrl = params[0];
            //下载图片的方法
            Bitmap bitmap = download(imageUrl);
            BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(),bitmap);

            return drawable;
        }

        protected void onPostExecute(Bitmap bitmap) {
            if (isCancelled()) {
                bitmap = null;
            }
        /**
         * 获取当前BitmapWorkerTask所关联的ImageView。
         */
            if (imageViewReference != null && bitmap != null) {
                final ImageView imageView = imageViewReference.get();
                final BitmapWorkerTask bitmapWorkerTask =
                        getBitmapWorkerTask(imageView);
                if (this == bitmapWorkerTask && imageView != null) {
                    imageView.setImageBitmap(bitmap);
                }
            }

        /**
         * 建立HTTP请求,并获取Bitmap对象。
         * 
         * @param imageUrl
         *            图片的URL地址
         * @return 解析后的Bitmap对象
         */
        private Bitmap download(String imageUrl) {
            Bitmap mBitmap = null;
            HttpURLConnection con = null;
            try {
                URL url = new URL(imageUrl);
                con = (HttpURLConnection) url.openConnection();
                con.setConnectTimeout(5 * 1000);
                con.setReadTimeout(10 * 1000);
                mBitmap = BitmapFactory.decodeStream(con.getInputStream());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (con != null) {
                    con.disconnect();
                }
            }
            return mBitmap;
        }
    }

/**
 *创建一个专用的Drawable的子类来储存任务的引用。在这种情况下,我们使用了一个BitmapDrawable,在任务执行的过程中,一个占位图片会显示在ImageView中让。这个Drawable持有BitmapWorkerTask的弱引用。
 **/
    private class AsyncDrawable extends BitmapDrawable{
        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
        public AsyncDrawable(Resources resources, Bitmap mLoadingBitmap, BitmapWorkerTask task) {
                super(resources,mLoadingBitmap);
            bitmapWorkerTaskReference =
                    new WeakReference(task);
        }

        public BitmapWorkerTask getBitmapWorkerTask() {
            return bitmapWorkerTaskReference.get();
        }
    }
}

最后别忘了加上网络权限哦~

<uses-permission android:name="android.permission.INTERNET"/>

那么我们一点点开始解析。首先刚才说到的,ImageView和BitmapWorkerTask之间要建立一个双向的弱引用关联,上述代码中已经建立好了。ImageView中可以获取到它所对应的BitmapWorkerTask,而BitmapWorkerTask也可以获取到它所对应的ImageView。

下面来看一下这个双向弱引用关联是怎么建立的。BitmapWorkerTask指向ImageView的弱引用关联比较简单,就是在BitmapWorkerTask中加入一个构造函数,并在构造函数中要求传入ImageView这个参数。不过我们不再直接持有ImageView的引用,而是使用WeakReference对ImageView进行了一层包装,这样就OK了。

但是ImageView指向BitmapWorkerTask的弱引用关联就没这么容易了,因为我们很难将BitmapWorkerTask的一个弱引用直接设置到ImageView当中。这该怎么办呢?这里使用了一个比较巧的方法,就是借助自定义Drawable的方式来实现。可以看到,我们自定义了一个AsyncDrawable类并让它继承自BitmapDrawable,然后重写了AsyncDrawable的构造函数,在构造函数中要求把BitmapWorkerTask传入,然后在这里给它包装了一层弱引用。那么现在AsyncDrawable指向BitmapWorkerTask的关联已经有了,但是ImageView指向BitmapWorkerTask的关联还不存在,怎么办呢?很简单,让ImageView和AsyncDrawable再关联一下就可以了。可以看到,在getView()方法当中,我们调用了ImageView的setImageDrawable()方法把AsyncDrawable设置了进去,那么ImageView就可以通过getDrawable()方法获取到和它关联的AsyncDrawable,然后再借助AsyncDrawable就可以获取到BitmapWorkerTask了。这样ImageView指向BitmapWorkerTask的弱引用关联也成功建立。

现在双向弱引用的关联已经建立好了,接下来就是逻辑判断的工作了。那么怎样通过逻辑判断来避免图片出现乱序的情况呢?这里我们引入了两个方法,一个是getBitmapWorkerTask()方法,这个方法可以根据传入的ImageView来获取到它对应的BitmapWorkerTask,内部的逻辑就是先获取ImageView对应的AsyncDrawable,再获取AsyncDrawable对应的BitmapWorkerTask。另一个是getAttachedImageView()方法,这个方法会获取当前BitmapWorkerTask所关联的ImageView,然后调用getBitmapWorkerTask()方法来获取该ImageView所对应的BitmapWorkerTask,最后判断,如果获取到的BitmapWorkerTask等于this,也就是当前的BitmapWorkerTask,那么就将ImageView返回,否则就返回null。最后,在onPostExecute()方法当中,只需要使用getAttachedImageView()方法获取到的ImageView来显示图片就可以了。

那么为什么做了这个逻辑判断之后,图片乱序的问题就可以得到解决呢?其实最主要的奥秘就是在getAttachedImageView()方法当中,它会使用当前BitmapWorkerTask所关联的ImageView来反向获取这个ImageView所关联的BitmapWorkerTask,然后用这两个BitmapWorkerTask做对比,如果发现是同一个BitmapWorkerTask才会返回ImageView,否则就返回null。那么什么情况下这两个BitmapWorkerTask才会不同呢?比如说某个图片被移出了屏幕,它的ImageView被另外一个新进入屏幕的图片重用了,那么就会给这个ImageView关联一个新的BitmapWorkerTask,这种情况下,上一个BitmapWorkerTask和新的BitmapWorkerTask肯定就不相等了,这时getAttachedImageView()方法会返回null,而我们又判断ImageView等于null的话是不会设置图片的,因此就不会出现图片乱序的情况了。

除此之外还有另外一个方法非常值得大家注意,就是cancelPotentialWork()方法,这个方法可以大大提高整个ListView图片加载的工作效率。这个方法接收两个参数,一个图片的url,一个ImageView。看一下它的内部逻辑,首先它也是调用了getBitmapWorkerTask()方法来获取传入的ImageView所对应的BitmapWorkerTask,接下来拿BitmapWorkerTask中的imageUrl和传入的url做比较,如果两个url不等的话就调用BitmapWorkerTask的cancel()方法,然后返回true,如果两个url相等的话就返回false。

那么这段逻辑是什么意思呢?其实并不复杂,两个url做比对时,如果发现是相同的,说明请求的是同一张图片,那么直接返回false,这样就不会再去启动BitmapWorkerTask来请求图片,而如果两个url不相同,说明这个ImageView被另外一张图片重新利用了,这个时候就调用了BitmapWorkerTask的cancel()方法把之前的请求取消掉,然后重新启动BitmapWorkerTask来去请求新图片。有了这个操作保护之后,就可以把一些已经移出屏幕的无效的图片请求过滤掉,从而整体提升ListView加载图片的工作效率。

最后实现效果如下:

这里写图片描述

缓存Bitmap

将单个Bitmap加载到UI是简单直接的,但是如果我们需要一次性加载大量的图片,事情则会变得复杂起来。在大多数情况下(例如在使用ListView,GridView或ViewPager时),屏幕上的图片和因滑动将要显示的图片的数量通常是没有限制的。

通过循环利用子视图可以缓解内存的使用,垃圾回收器也会释放那些不再需要使用的Bitmap。这些机制都非常好,但是为了保证一个流畅的用户体验,我们希望避免在每次屏幕滑动回来时,都要重复处理那些图片。内存与磁盘缓存通常可以起到辅助作用,允许控件可以快速地重新加载那些处理过的图片。

使用内存缓存(Use a Memory Cache)

内存缓存以花费宝贵的程序内存为前提来快速访问位图。LruCache类(在API Level 4的Support Library中也可以找到)特别适合用来缓存Bitmaps,它使用一个强引用(strong referenced)的LinkedHashMap保存最近引用的对象,并且在缓存超出设置大小的时候剔除(evict)最近最少使用到的对象。

注意:

在过去,一种比较流行的内存缓存实现方法是使用软引用(SoftReference)或弱引用(WeakReference)对Bitmap进行缓存,然而我们并不推荐这样的做法。从Android
2.3 (API Level 9)开始,垃圾回收机制变得更加频繁,这使得释放软(弱)引用的频率也随之增高,导致使用引用的效率降低很多。而且在Android 3.0
(API Level 11)之前,备份的Bitmap会存放在Native
Memory中,它不是以可预知的方式被释放的,这样可能导致程序超出它的内存限制而崩溃。

为了给LruCache选择一个合适的大小,需要考虑到下面一些因素:

  • 应用剩下了多少可用的内存?

  • 多少张图片会同时呈现到屏幕上?有多少图片需要准备好以便马上显示到屏幕?

  • 设备的屏幕大小与密度是多少?一个具有特别高密度屏幕(xhdpi)的设备,像Galaxy Nexus会比Nexus
    S(hdpi)需要一个更大的缓存空间来缓存同样数量的图片。

  • Bitmap的尺寸与配置是多少,会花费多少内存?

  • 图片被访问的频率如何?是其中一些比另外的访问更加频繁吗?如果是,那么我们可能希望在内存中保存那些最常访问的图片,或者根据访问频率给Bitmap分组,为不同的Bitmap组设置多个LruCache对象。

  • 是否可以在缓存图片的质量与数量之间寻找平衡点?某些时候保存大量低质量的Bitmap会非常有用,加载更高质量图片的任务可以交给另外一个后台线程。

通常没有指定的大小或者公式能够适用于所有的情形,我们需要分析实际的使用情况后,提出一个合适的解决方案。缓存太小会导致额外的花销却没有明显的好处,缓存太大同样会导致java.lang.OutOfMemory的异常,并且使得你的程序只留下小部分的内存用来工作(缓存占用太多内存,导致其他操作会因为内存不够而抛出异常)。

幸运的是,Google已经为我们提供了一个专门用来缓存数据的类LruCache<String,Bitmap>

接下来我们就在ListView异步加载多张图片的实例中了解这个LruCache类的用法吧:

Step 1:布局文件的配置

我们需要一个ListView来展示所有的图片。打开或修改activity_main.xml中的代码,如下所示:

activity_main.xml :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

很简单,只是在LinearLayout中写了一个ListView而已。接着我们要定义ListView中每一个子View的布局,新建一个image_item.xml布局,加入如下代码:

list_item.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/mImageView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:src="@mipmap/ic_launcher"
        android:scaleType="fitXY"/>

</LinearLayout>

仍然很简单,image_item.xml布局中只有一个ImageView控件,就是用它来显示图片的,控件在默认情况下会显示一张ic_launcher。这样我们就把所有的布局文件都写好了。

Step 2:ListView适配器

接下来新建AsyncAdapter做为ListView的适配器,代码如下所示:

public class AsyncAdapter extends ArrayAdapter<String>{
    private ListView mListView;
    //图片下载地址
    private String imageUrl;
/** 
     * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。 
     */  
    private LruCache<String,Bitmap> mMemoryCach;
    private LayoutInflater mInflater;

    public AsyncAdapter(Context context, int resource, String[] imageUrls) {
        super(context,resource,imageUrls);
        mInflater = LayoutInflater.from(getContext());
        //获取应用程序可用最大内存
        final int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);

    //使用可用内存的1/8作为缓存内存
        final int cachSize = maxMemory/8;

        mMemoryCach = new LruCache<String,Bitmap>(cachSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
            //缓存大小将以千字节为单位而不是项目数,因此这里要除以1024
                return value.getByteCount()/1024;
            }
        };
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (mListView == null) {
            mListView = (ListView) parent;
        }

        String url = getItem(position);
        View view;
        if(convertView==null){
            view = mInflater.inflate(R.layout.list_item,null);
        }
        else{
            view = convertView;
        }
        ImageView mImageView = (ImageView)view.findViewById(R.id.mImageView);
        mImageView.setImageResource(R.mipmap.error);
        //为该ImageView设置一个Tag,防止图片错位
        mImageView.setTag(url);
        /**
        *当加载Bitmap显示到ImageView 之前,会先从LruCache 中检查是否存在这个Bitmap。如果确实存在,它会立即被用来显示到ImageView上,如果没有找到,会触发一个后台线程去处理显示该Bitmap任务。
        **/
        Bitmap bitmap = getBitmapFromMemCache(url);

        if(bitmap!=null){
            mImageView.setImageBitmap(bitmap);
        }
        else{
            BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
            task.execute(url);
        }
        return view;
    }

 /** 
     * 将一张图片存储到LruCache中。 
     *  
     * @param key 
     *            LruCache的键,这里传入图片的URL地址。 
     * @param bitmap
     *            LruCache的值,这里传入从网络上下载的Bitmap对象。 
     */  
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCach.put(key, bitmap);
        }
    }

     /** 
     * 从LruCache中获取一张图片,如果不存在就返回null。 
     *  
     * @param key 
     *            LruCache的键,这里传入图片的URL地址。 
     * @return 对应返回键的Bitmap对象,或者null。 
     */  
    private Bitmap getBitmapFromMemCache(String url) {
        return mMemoryCach.get(url);
    }



    /** 
     * 异步下载图片的任务。 
     *  
     * @author guolin 
     */  
    public class BitmapWorkerTask extends AsyncTask<String,Void,Bitmap> {
        private Context mContext;
        private ImageView mImageView;
        private ImageLoader mImageLoader;

        public BitmapWorkerTask(ImageView mImageView) {
            this.mImageView = mImageView;
        }


        @Override
        protected Bitmap doInBackground(String... params) {
            imageUrl = params[0];
            //在后台开始下载图片
            Bitmap bitmap = download(imageUrl);
            //需要把解析好的Bitmap添加到内存缓存中
            addBitmapToMemoryCache(imageUrl,bitmap);
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            ImageView mImageView = (ImageView)mListView.findViewWithTag(imageUrl);
            if (mImageView != null && bitmap != null) {
                mImageView.setImageBitmap(bitmap);
            }
        }

    /** 
         * 建立HTTP请求,并获取Bitmap对象。 
         *  
         * @param imageUrl 
         *            图片的URL地址 
         * @return 解析后的Bitmap对象 
         */  
        private Bitmap download(String imageUrl) {
            Bitmap mBitmap = null;
            HttpURLConnection con = null;
            try {
                URL url = new URL(imageUrl);
                con = (HttpURLConnection) url.openConnection();
                con.setConnectTimeout(5 * 1000);
                con.setReadTimeout(10 * 1000);
                mBitmap = BitmapFactory.decodeStream(con.getInputStream());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (con != null) {
                    con.disconnect();
                }
            }
            return mBitmap;
        }
    }

}

ImageAdapter中的代码还算是比较简单的,在getView()方法中首先根据当前的位置获取到图片的URL地址,然后使用inflate()方法加载image_item.xml这个布局,并获取到ImageView控件的实例,接下来开启了一个BitmapWorkerTask异步任务来从网络上加载图片,最终将加载好的图片设置到ImageView上面。注意这里为了防止图片占用过多的内存,我们还是使用了LruCache技术来进行内存控制。

Step 3:主程序设置适配器

MainActivity.java :

public class MainActivity extends AppCompatActivity {
    private ListView mListView;
    private AsyncAdapter myAdapter;
    //图片数据源
    private String[] imageUrls = new String[]{
        "https://img-my.csdn.net/uploads/201508/05/1438760758_3497.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760758_6667.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760757_3588.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760756_3304.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760755_6715.jpeg",
                "https://img-my.csdn.net/uploads/201508/05/1438760726_5120.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760726_8364.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760725_4031.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760724_9463.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760724_2371.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760707_4653.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760706_6864.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760706_9279.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760704_2341.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760704_5707.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760685_5091.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760685_4444.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760684_8827.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760683_3691.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760683_7315.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760663_7318.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760662_3454.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760662_5113.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760661_3305.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760661_7416.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760589_2946.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760589_1100.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760588_8297.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760587_2575.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760587_8906.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760550_2875.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760550_9517.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760549_7093.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760549_1352.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760548_2780.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760531_1776.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760531_1380.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760530_4944.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760530_5750.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760529_3289.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760500_7871.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760500_6063.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760499_6304.jpeg",
                "https://img-my.csdn.net/uploads/201508/05/1438760499_5081.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760498_7007.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760478_3128.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760478_6766.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760477_1358.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760477_3540.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760476_1240.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760446_7993.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760446_3641.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760445_3283.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760444_8623.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760444_6822.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760422_2224.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760421_2824.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760420_2660.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760420_7188.jpg",
                "https://img-my.csdn.net/uploads/201508/05/1438760419_4123.jpg",
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView)findViewById(R.id.listView);
        myAdapter = new AsyncAdapter(this,0,imageUrls);
        mListView.setAdapter(myAdapter);
    }
}

最后别忘了加上网络权限哦~

<uses-permission android:name="android.permission.INTERNET"/>

最后实现效果图如下:

这里写图片描述

更简单的实现加载大量Bitmap

关于在ListView中异步加载大量图片,上面两种方式都实现了不错的效果,而且都避免了图片的乱序显示。

使用 ImageLoader 和 NetworkImageView

但是之前了解过Volley的同学(不了解的可以戳这里),知道Volley中有一个NetworkImageView类。在 layout XML 文件中,我们以与使用 ImageView 差不多的方法使用 NetworkImageView,例如:

<com.android.volley.toolbox.NetworkImageView
        android:id="@+id/networkImageView"
        android:layout_width="150dp"
        android:layout_height="170dp"
        android:layout_centerHorizontal="true" />

另外还有一个ImageLoader类,用它来显示一张图片非常简单:

ImageLoader mImageLoader;
ImageView mImageView;
// The URL for the image that is being loaded.
private static final String IMAGE_URL =
    "http://developer.android.com/images/training/system-ui.png";
...
mImageView = (ImageView) findViewById(R.id.regularImageView);

// Get the ImageLoader through your singleton class.
mImageLoader = MySingleton.getInstance(this).getImageLoader();
mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView,
         R.drawable.def_image, R.drawable.err_image));

这一次我们就使用 ImageLoader 与 NetworkImageView 来有效地管理类似 ListView 等显示多张图片的情况。

看下面的实例:

Step 1 : 首先需要修改一下list_item.xml文件,因为我们已经不再使用ImageView控件了,代码如下所示:

list_item.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/networkImageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerHorizontal="true" />

</LinearLayout>

很简单,只是把ImageView替换成了NetworkImageView。

Step 2: 然后修改ImageAdapter中的代码,如下所示:

public class AsyncAdapter extends ArrayAdapter<String> {
    private String imageUrl;
    private LayoutInflater mInflater;
    private ImageLoader mImageLoader;
    public AsyncAdapter(Context context, int resource, String[] imageUrls) {
        super(context, resource,imageUrls);
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        imageUrl = getItem(position);
        Log.i("Image",imageUrl);
        View view;
        if(convertView==null){
            view = mInflater.inflate(R.layout.list_item,null);
        }else{
            view = convertView;
        }
        // 得到NetworkImageView控件显示图片
        NetworkImageView mImageView = (NetworkImageView) view.findViewById(R.id.networkImageView);
        //通过单例类获得ImageLoder对象
        mImageLoader = MySingleton.getInstance(this.getContext()).getImageLoader();
        //设置加载图片的过程中显示的图片
        mImageView.setDefaultImageResId(R.mipmap.ic_launcher);
        //设置加载图片失败的情况下显示的图片
        mImageView.setErrorImageResId(R.mipmap.error);
        //设置应加载到该NetworkImageView控件中的图像的地址和ImageLoader对象
        mImageView.setImageUrl(imageUrl, mImageLoader);

        return view;
    }
}

Step 3: 最后是MainActivity中的代码,如下所示:

MainActivity.java :

public class MainActivity extends AppCompatActivity {
    private ListView mListView;
    private AsyncAdapter myAdapter;
    private String[] imageUrls = new String[]{
            "https://img-my.csdn.net/uploads/201508/05/1438760758_3497.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760758_6667.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760757_3588.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760756_3304.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760755_6715.jpeg",
            "https://img-my.csdn.net/uploads/201508/05/1438760726_5120.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760726_8364.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760725_4031.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760724_9463.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760724_2371.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760707_4653.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760706_6864.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760706_9279.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760704_2341.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760704_5707.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760685_5091.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760685_4444.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760684_8827.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760683_3691.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760683_7315.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760663_7318.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760662_3454.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760662_5113.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760661_3305.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760661_7416.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760589_2946.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760589_1100.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760588_8297.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760587_2575.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760587_8906.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760550_2875.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760550_9517.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760549_7093.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760549_1352.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760548_2780.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760531_1776.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760531_1380.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760530_4944.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760530_5750.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760529_3289.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760500_7871.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760500_6063.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760499_6304.jpeg",
            "https://img-my.csdn.net/uploads/201508/05/1438760499_5081.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760498_7007.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760478_3128.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760478_6766.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760477_1358.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760477_3540.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760476_1240.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760446_7993.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760446_3641.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760445_3283.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760444_8623.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760444_6822.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760422_2224.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760421_2824.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760420_2660.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760420_7188.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760419_4123.jpg",
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView)this.findViewById(R.id.listView);
        myAdapter = new AsyncAdapter(this,0,imageUrls);
        mListView.setAdapter(myAdapter);
    }
}

没错,就是这么简单,我们不需要自己再去写一个BitmapWorkerTask来处理图片的下载和显示,也不需要自己再去管理LruCache的逻辑,一切NetworkImageView都帮我们做好了。

把Volley的单例类MySingleton附上:

public class MySingleton {
    private static MySingleton mInstance;
    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
    private static Context mCtx;

    private MySingleton(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();

        mImageLoader = new ImageLoader(mRequestQueue,
                new ImageLoader.ImageCache() {
            private final LruCache<String, Bitmap>
                    cache = new LruCache<String, Bitmap>(20);

            @Override
            public Bitmap getBitmap(String url) {
                return cache.get(url);
            }

            @Override
            public void putBitmap(String url, Bitmap bitmap) {
                cache.put(url, bitmap);
            }
        });
    }

    public static synchronized MySingleton getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new MySingleton(context);
        }
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
        }
        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req) {
        getRequestQueue().add(req);
    }

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }
}

最后还是别忘记加上网络权限:

<uses-permission android:name="android.permission.INTERNET"/>

最终实现效果如下:

这里写图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值