【安卓中的缓存策略系列】安卓缓存策略之综合应用ImageLoader实现照片墙的效果
在前面的【安卓缓存策略系列】安卓缓存之内存缓存LruCache和【安卓缓存策略系列】安卓缓存策略之磁盘缓存DiskLruCache这两篇博客中已经将安卓中的缓存策略的理论知识进行过详细讲解,还没看过这两篇博客的看官建议先去看一下,本博客将依据这些理论知识打造一个ImageLoader,实现照片墙的效果,关于照片墙的知识网上相关博客也很多,首先解释一下照片墙的概念:用一个GridView控件当作“墙”,然后随着GridView的滚动将一张张照片贴在“墙”上,很显然因为图片一般比较占内存,所以得考虑使用压缩与缓存策略来防止程序OOM,而完成这些核心功能的自然就是我们的ImageLoader类,当然也可以使用类似于ImageLoader的开源框架来完成这个功能,如Universal_Image_Loader。不过本博客将根据前面讲解的理论知识自己来实现一个ImageLoader类。
首先总的来说一个合格的ImageLoader应该包含如下功能:
1..图片的同步加载
2.图片的异步加载
3.图片的压缩处理
4.图片的内存缓存处理
5.图片的磁盘缓存处理
6.图片的网络拉取处理
首先图片的异步加载肯定是必须具备的,因为为了在GridView上能流畅的显示图片肯定不能用同步加载,然后为了防止程序OOM图片的压缩处理也是必须的,而网络拉取,内存缓存,磁盘缓存这时缓存策略常用的三级缓存策略,即当要使用某一张图片时,首先尝试从内存中获取,如果内存中不存在则从磁盘中缓存,如果磁盘中也不存在,则从网络上获取,当从网络上获取到该图片后先将其添加到内存缓存中,然后添加到磁盘缓存中,供下次访问时直接使用。
当然除了上述ImageLoader本身的硬性功能需求外,我们还需要考虑它与GridView交互过程中可能出现的问题,如我们在复用View时,假设某个Item X正在从网络上获取,它对应的ImageView为X,这个时候如果用户快速的向下活动GridView,则很肯能Item Y复用了ImageView X,然后等之前的图片下载完毕后,如果直接给ImageView X设置图片,则因为这个时候ImageView X被item Y复用,但是很显然item Y要显示的图片显然不是item X刚刚下载完的图片,这个时候就会出现item Y显示了item X的图片,即出现列表错位的现象,ImageLoader应该能够正确的处理这些问题。
上述的功能大纲就是我们的整个ImageLoader的功能框架,接下来我们就按照上述介绍的功能及前面学习的理论知识亲手打造一个ImageLoader,最后还会对针对GridView中列表的卡顿现象进行一些优化处理。
一图片的同步与异步加载:
所谓图片的同步加载即在该方法中不单独开启一个线程去加载图片,而是在调用该同步方法时将该同步方法放到一个子线程中去执行,而异步加载是在该方法中开启一个子线程去加载图片,这样调用该方法时不需将其放到子线程中去执行,即该方法可以直接运行在主线程中,显然从网络上加载图片可能比较耗时,所以一般我们使用异步加载的方式。首先我们来看一下同步加载,代码如下:
/** * load bitmap from memory cache or disk cache or network. * @param uri http url * @param reqWidth the width ImageView desired * @param reqHeight the height ImageView desired * @return bitmap, maybe null. */ public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) { Bitmap bitmap = loadBitmapFromMemCache(uri); if (bitmap != null) { Log.d(TAG, "loadBitmapFromMemCache,url:" + uri); return bitmap; } try { bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight); if (bitmap != null) { Log.d(TAG, "loadBitmapFromDisk,url:" + uri); return bitmap; } bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight); Log.d(TAG, "loadBitmapFromHttp,url:" + uri); } catch (IOException e) { e.printStackTrace(); } if (bitmap == null && !mIsDiskLruCacheCreated) { Log.w(TAG, "encounter error, DiskLruCache is not created."); bitmap = downloadBitmapFromUrl(uri); } return bitmap; }可以看到,在该loadBitmap()的中首先尝试通过loadBitmapFromMemCache(uri)方法从内存缓存中获取图片,如果内存缓存中存在该图片对象直接返回,如果不存在,则尝试通过loadBitmapFromDiskCache(uri, reqWidth, reqHeight)从磁盘缓存中获取,如果不存在则通过loadBitmapFromHttp(uri, reqWidth, reqHeight)来从网络上获取图片,这就是安卓缓存策略中的三步缓存策略,即内存-磁盘-网络。注意该方法不能在主线程中调用,因为图片的加载属于耗时操作,可能会导致ANR异常。
接下来看一下异步加载的代码:
public void bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight) { imageView.setTag(TAG_KEY_URI, uri); Bitmap bitmap = loadBitmapFromMemCache(uri); if (bitmap != null) { imageView.setImageBitmap(bitmap); return; } Runnable loadBitmapTask = new Runnable() { @Override public void run() { Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight); if (bitmap != null) { LoaderResult result = new LoaderResult(imageView, uri, bitmap); mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget(); } } }; THREAD_POOL_EXECUTOR.execute(loadBitmapTask); }可以看到在异步加载中首先尝试从内存缓存中获取,如果获取到相应的图片就直接返回结果,否则会创建一个Runnable对象,然后调用线程池的execute()方法去执行该runnable对象,在Runnable的run方法中调用了同步加载图片的loadBitmap(uri, reqWidth, reqHeight)方法,在图片加载成功后将图片及图片地址Uri和需要绑定的ImageView控件包装成一个LoaderResult对象,然后通过mMainHandler向主线程发送一个消息,因为在子线程中是不能更新UI的。之所以在异步加载图片的bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight)方法中添加一个ImageView参数(注意:该参数在同步加载图片的loadBitmap(String uri, int reqWidth, int reqHeight)方法中不存在),这是因为异步加载图片才是ImageLoader整个加载图片对外提供的接口,而同步加载是ImageLoader内部调用的接口,为了让ImageLoader与外部其它类最大程度的解耦,所以直接提供一个根据Uri与ImageView这两个核心参数来设置图片的功能,即将网络地址为Uri的图片设置到ImageView这个控件上去。如果不提供该参数,则只能像同步加载那样获取到一个Bitmap对象,然后通过ImageView的setImageBitmap()方法来为ImageView控件设置图片,而很显然前面一种方式较好。
另外可以看到在异步加载图片时采用了线程池,首先肯定不能采用普通的线程去完成该功能,因为照片墙的原理是当用户滑动GridView列表时会从网络上去获取图片资源,即调用该异步加载的bindBitmap方法,如果采用普通的线程去完成该功能,则在列表滑动的过程中会产生大量的线程,这显然是不利于整体效率的提高,而线程池可以复用线程,使线程的数量始终维持在一个合理的范围之内。也没采用AsyncTask类是因为从安卓3.0开始默认情况下AsyncTask是串行执行的,这显然是不符合要求的。
二图片的压缩处理:
关于图片的压缩处理的理论知识,请参看我的博客:安卓图片缓存技术这里我们将图片的压缩处理功能单独的抽象为一个类ImageResizer,其代码如下:
public class ImageResizer { private static final String TAG = "ImageResizer"; public ImageResizer() { } public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fd, null, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeFileDescriptor(fd, null, options); } public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { if (reqWidth == 0 || reqHeight == 0) { return 1; } // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; Log.d(TAG, "origin, w= " + width + " h=" + height); 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; } } Log.d(TAG, "sampleSize:" + inSampleSize); return inSampleSize; } }图片压缩功能的代码的理论讲解请参考我的博客:安卓图片缓存技术,在此不再赘述。
三内存缓存与磁盘缓存功能及网络拉取功能:(磁盘缓存的添加是在网络拉取功能中完成的)
正如前面理论知识介绍的使用LruCach与DiskLlruCache来完成内存缓存与磁盘缓存的功能,在ImageLoader初始化的时候,会创建LruCache与DiskLruCache,代码如下:
private Context mContext; private ImageResizer mImageResizer = new ImageResizer(); private LruCache<String, Bitmap> mMemoryCache; private DiskLruCache mDiskLruCache; private ImageLoader(Context context) { mContext = context.getApplicationContext(); int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getRowBytes() * bitmap.getHeight() / 1024; } }; File diskCacheDir = getDiskCacheDir(mContext, "bitmap"); if (!diskCacheDir.exists()) { diskCacheDir.mkdirs(); } if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) { try { mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE); mIsDiskLruCacheCreated = true; } catch (IOException e) { e.printStackTrace(); } } }在创建磁盘缓存时首先作了一个判断,即可用磁盘空间是否大于磁盘缓存所需的空间大小,如果小于则表示用户手机上的内存空间不足以创建该大小的磁盘缓存,一般情况下我们将内存缓存的容量定义为当前进程可用内存的1/8,磁盘缓存容量定为50MB就足够。
在创建完内存缓存与磁盘缓存后还需要提供一下方法来完成缓存的添加与获取等功能,首先看内存缓存,代码如下:
‘
private void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } private Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); }这个在前面博客的理论知识部分已经进行过详细讲解,在此不再赘述。
再来看一下磁盘缓存:首先看一下磁盘缓存的添加功能:代码如下:
private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) throws IOException { if (Looper.myLooper() == Looper.getMainLooper()) { throw new RuntimeException("can not visit network from UI Thread."); } if (mDiskLruCache == null) { return null; } String key = hashKeyFormUrl(url); DiskLruCache.Editor editor = mDiskLruCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX); if (downloadUrlToStream(url, outputStream)) { editor.commit(); } else { editor.abort(); } mDiskLruCache.flush(); } return loadBitmapFromDiskCache(url, reqWidth, reqHeight); }
注意磁盘缓存的添加是在从网络上获取到图片时添加的,而内存缓存的添加是在磁盘缓存添加完成时添加的,可以看到磁盘缓存的添加需要通过 DiskLruCache.Editor这个类来完成,Editor提供了commit()与abort()方法来提交和撤销对磁盘文件系统的写操作,具体理论知识请参看我的博客:【安卓缓存策略系列】安卓缓存策略之磁盘缓存DiskLruCache
在来看一下磁盘缓存的获取,代码如下:
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) throws IOException { if (Looper.myLooper() == Looper.getMainLooper()) { Log.w(TAG, "load bitmap from UI Thread, it's not recommended!"); } if (mDiskLruCache == null) { return null; } Bitmap bitmap = null; String key = hashKeyFormUrl(url); DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) { FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX); FileDescriptor fileDescriptor = fileInputStream.getFD(); bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight); if (bitmap != null) { addBitmapToMemoryCache(key, bitmap); } } return bitmap; }可以看到磁盘缓存的读取是通过DiskLruCache.Snapshot这个类来完成的,通过Snapshot可以得到磁盘缓存对象对应的FileInputStream,但是FileInputStream不能很好的进行图片的压缩处理,因此通过FileDescriptor来加载压缩后的图片,最后将加载后的图片添加到内存缓存中,注意内存缓存的添加是在磁盘缓存添加完成时添加的。即三级缓存策略的获取与添加过程为网络-磁盘-内存。
四滑动GridView时列表图片显示错位的解决方案:
private Handler mMainHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { LoaderResult result = (LoaderResult) msg.obj; ImageView imageView = result.imageView; String uri = (String) imageView.getTag(TAG_KEY_URI); if (uri.equals(result.uri)) { imageView.setImageBitmap(result.bitmap); } else { Log.w(TAG, "set image bitmap,but url has changed, ignored!"); } }; };前面我们在bindBitmap中的Runnable对象中通过 mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget()发送一个消息,该消息是一个包含图片Uri,bitmap图片,显示控件ImageView的LoaderResult对象,该消息交给mMainHandler的handleMessage()方法来处理,在该方法中主要逻辑是获取到传递过来的LoaderResult对象,然后通过LoaderResult对象获取到bitmap图片将其显示在对应的ImageView控件上,正如我们在前面所说的,当用户快速滑动GridView列表时可能导致列表错位现象,因此在设置bitmap图片时首先需要做个判断,即在给ImageView设置图片之前先判断接受到的uri与该图片的Uri是否相同,如果相同则将其设置为该ImagView控件的图片,否则不处理。
另外我们可以看到mMainHandler的创建是直接通过主线程的Looper来构造的Handler对象,这样我们在子线程中就可以直接构造ImageLoader对象。而不需要在子线程中创建Looper对象。
五针对GridView中列表卡顿现象的优化处理:
首先,不要再getView中执行耗时操作,即在getView中必须通过异步的方式加载图片。
其次,控制异步任务的执行频率,对于列表而言,仅仅在getView中采用异步操作是不够的,以照片墙来说,在getView方法中会通过ImageLoader的binBitmap方法来异步加载图片,但是如果用户刻意的频繁上下滑动,这会在瞬时产生上百个异步任务,这些异步任务会造成线程池的拥堵和大量的UI更新操作,这是没有意义的,但因为瞬时产生大量的UI更新操作,这些UI操作是运行在主线程中的,会造成一定程度上的卡顿,那如何解决呢?可以考虑在列表滑动的时候停止加载图片,等列表停止滑动后再加载图片,这样仍可以获得良好的用户体验,具体实现时,可以给ListView或GridView设置setOnScrollListener,,在 OnScrollListener的onScrollStateChanged方法中判断列表是否处于滑动状态,如果是则停止加载图片,代码如下:
public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) { mIsGridViewIdle = true; mImageAdapter.notifyDataSetChanged(); } else { mIsGridViewIdle = false; } }
在getView方法中,仅当列表静止时才能加载图片,代码如下:
if (mIsGridViewIdle && mCanGetBitmapFromNetWork) { imageView.setTag(uri); mImageLoader.bindBitmap(uri, imageView, mImageWidth, mImageWidth);
最后给出整个ImageLoader的实现代码:
public class ImageLoader { private static final String TAG = "ImageLoader"; public static final int MESSAGE_POST_RESULT = 1; private static final int CPU_COUNT = Runtime.getRuntime() .availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT + 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final long KEEP_ALIVE = 10L; private static final int TAG_KEY_URI = R.id.imageloader_uri; private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50; private static final int IO_BUFFER_SIZE = 8 * 1024; private static final int DISK_CACHE_INDEX = 0; private boolean mIsDiskLruCacheCreated = false; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "ImageLoader#" + mCount.getAndIncrement()); } }; public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), sThreadFactory); private Handler mMainHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { LoaderResult result = (LoaderResult) msg.obj; ImageView imageView = result.imageView; String uri = (String) imageView.getTag(TAG_KEY_URI); if (uri.equals(result.uri)) { imageView.setImageBitmap(result.bitmap); } else { Log.w(TAG, "set image bitmap,but url has changed, ignored!"); } }; }; private Context mContext; private ImageResizer mImageResizer = new ImageResizer(); private LruCache<String, Bitmap> mMemoryCache; private DiskLruCache mDiskLruCache; private ImageLoader(Context context) { mContext = context.getApplicationContext(); int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getRowBytes() * bitmap.getHeight() / 1024; } }; File diskCacheDir = getDiskCacheDir(mContext, "bitmap"); if (!diskCacheDir.exists()) { diskCacheDir.mkdirs(); } if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) { try { mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE); mIsDiskLruCacheCreated = true; } catch (IOException e) { e.printStackTrace(); } } } /** * build a new instance of ImageLoader * @param context * @return a new instance of ImageLoader */ public static ImageLoader build(Context context) { return new ImageLoader(context); } private void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } private Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } /** * load bitmap from memory cache or disk cache or network async, then bind imageView and bitmap. * NOTE THAT: should run in UI Thread * @param uri http url * @param imageView bitmap's bind object */ public void bindBitmap(final String uri, final ImageView imageView) { bindBitmap(uri, imageView, 0, 0); } public void bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight) { imageView.setTag(TAG_KEY_URI, uri); Bitmap bitmap = loadBitmapFromMemCache(uri); if (bitmap != null) { imageView.setImageBitmap(bitmap); return; } Runnable loadBitmapTask = new Runnable() { @Override public void run() { Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight); if (bitmap != null) { LoaderResult result = new LoaderResult(imageView, uri, bitmap); mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget(); } } }; THREAD_POOL_EXECUTOR.execute(loadBitmapTask); } /** * load bitmap from memory cache or disk cache or network. * @param uri http url * @param reqWidth the width ImageView desired * @param reqHeight the height ImageView desired * @return bitmap, maybe null. */ public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) { Bitmap bitmap = loadBitmapFromMemCache(uri); if (bitmap != null) { Log.d(TAG, "loadBitmapFromMemCache,url:" + uri); return bitmap; } try { bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight); if (bitmap != null) { Log.d(TAG, "loadBitmapFromDisk,url:" + uri); return bitmap; } bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight); Log.d(TAG, "loadBitmapFromHttp,url:" + uri); } catch (IOException e) { e.printStackTrace(); } if (bitmap == null && !mIsDiskLruCacheCreated) { Log.w(TAG, "encounter error, DiskLruCache is not created."); bitmap = downloadBitmapFromUrl(uri); } return bitmap; } private Bitmap loadBitmapFromMemCache(String url) { final String key = hashKeyFormUrl(url); Bitmap bitmap = getBitmapFromMemCache(key); return bitmap; } private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) throws IOException { if (Looper.myLooper() == Looper.getMainLooper()) { throw new RuntimeException("can not visit network from UI Thread."); } if (mDiskLruCache == null) { return null; } String key = hashKeyFormUrl(url); DiskLruCache.Editor editor = mDiskLruCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX); if (downloadUrlToStream(url, outputStream)) { editor.commit(); } else { editor.abort(); } mDiskLruCache.flush(); } return loadBitmapFromDiskCache(url, reqWidth, reqHeight); } private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) throws IOException { if (Looper.myLooper() == Looper.getMainLooper()) { Log.w(TAG, "load bitmap from UI Thread, it's not recommended!"); } if (mDiskLruCache == null) { return null; } Bitmap bitmap = null; String key = hashKeyFormUrl(url); DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) { FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX); FileDescriptor fileDescriptor = fileInputStream.getFD(); bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight); if (bitmap != null) { addBitmapToMemoryCache(key, bitmap); } } return bitmap; } public boolean downloadUrlToStream(String urlString, OutputStream outputStream) { HttpURLConnection urlConnection = null; BufferedOutputStream out = null; BufferedInputStream in = null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE); out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE); int b; while ((b = in.read()) != -1) { out.write(b); } return true; } catch (IOException e) { Log.e(TAG, "downloadBitmap failed." + e); } finally { if (urlConnection != null) { urlConnection.disconnect(); } MyUtils.close(out); MyUtils.close(in); } return false; } private Bitmap downloadBitmapFromUrl(String urlString) { Bitmap bitmap = null; HttpURLConnection urlConnection = null; BufferedInputStream in = null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE); bitmap = BitmapFactory.decodeStream(in); } catch (final IOException e) { Log.e(TAG, "Error in downloadBitmap: " + e); } finally { if (urlConnection != null) { urlConnection.disconnect(); } MyUtils.close(in); } return bitmap; } private String hashKeyFormUrl(String url) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(url.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(url.hashCode()); } return cacheKey; } private String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]); if (hex.length() == 1) { sb.append('0'); } sb.append(hex); } return sb.toString(); } public File getDiskCacheDir(Context context, String uniqueName) { boolean externalStorageAvailable = Environment .getExternalStorageState().equals(Environment.MEDIA_MOUNTED); final String cachePath; if (externalStorageAvailable) { cachePath = context.getExternalCacheDir().getPath(); } else { cachePath = context.getCacheDir().getPath(); } return new File(cachePath + File.separator + uniqueName); } @TargetApi(VERSION_CODES.GINGERBREAD) private long getUsableSpace(File path) { if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) { return path.getUsableSpace(); } final StatFs stats = new StatFs(path.getPath()); return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks(); } private static class LoaderResult { public ImageView imageView; public String uri; public Bitmap bitmap; public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) { this.imageView = imageView; this.uri = uri; this.bitmap = bitmap; } } }
注:本博文为本人阅读安卓开发艺术探索这本书的读书记录,绝大部分内容来自该书,中间融合了本人自己的思考过程,如果看官觉得不错记得顶个赞哦。