开发Android程序,一般情况下都会有两个操作,图片的异步加载与缓存,而图片的异步加载大都是从网络读取图片(还有生成本地图片缩略图等操作),为了减少网络操作,加快图片加载速度就需要对图片进行缓存,所以网上的好多图片异步加载方法都是与图片的缓存紧密关联的。但也有可能用户已经有了缓存的相关类库,这样使用起来就会有点麻烦。
最近一段处理跟图片相关的问题,本来是自己写的图片加载,不过有些状态的控制还是比较烦人的,比如ListView滚动时ImageView的重用,所以本着偷懒与充分利用现有资源的态度去网上搜罗图片异步加载的代码,最终在GreenDroid UI库中找到一个,其中有个AsyncImageView的自定义View用于异步加载图片,不过也像网上的大多数图片异步加载方法一样,是跟图片的缓存关联在一起的,不过只是很简单的内存缓存,无文件缓存。图片的加载方法也如其他的一样是写死了的,这就限制了其使用范围,只可通过InputStream来decode图片,而像生成缩略图或其他一些图片处理的异步处理就无法用途。修改现有类库总比自己从头写来的简单,于是稍微修改了下AsyncImageView,使其可以自定义缓存与图片加载方法,对于AsyncImageView只有一点点的修改,大都是别人源码。
public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); final Handler h = mHandler; Bitmap bitmap = null; Throwable throwable = null; h.sendMessage(Message.obtain(h, ON_START)); try { if (TextUtils.isEmpty(mUrl)) { throw new Exception("The given URL cannot be null or empty"); } // 如果自定义了加载方法,则用自定义的方法 if (mLoadMethod != null) { bitmap = mLoadMethod.load(mUrl); } else { InputStream inputStream = null; // Asset if (mUrl.startsWith("file:///android_asset/")) { inputStream = sAssetManager.open(mUrl.replaceFirst( "file:///android_asset/", "")); } // File else if (mUrl.startsWith("file:///") || mUrl.startsWith("/")) { if (mUrl.startsWith("file:///")) mUrl = mUrl.replaceFirst("file:///", "/"); inputStream = new FileInputStream(mUrl); } // NetWork else { // 在用URL类加载图片时,发现有的机型上面通过URL类获得的InputStream解析获得的图片总是null,故使用HttpClient HttpGet httpRequest = new HttpGet(mUrl); HttpClient httpclient = new DefaultHttpClient(); HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, 5000); HttpConnectionParams.setSoTimeout(httpParams, 5000); httpRequest.setParams(httpParams); HttpResponse response = (HttpResponse)httpclient.execute(httpRequest); HttpEntity entity = response.getEntity(); BufferedHttpEntity bufHttpEntity = new BufferedHttpEntity(entity); InputStream instream = bufHttpEntity.getContent(); BufferedInputStream bi = new BufferedInputStream(instream); inputStream = bi; } // 虽然AsyncImageView中有设置BitmapFactory.Options的方法,但一般情况下都未知图片的大小,也就无法计算相应的inSampleSize, // 也就无法设置相应的BitmapFactory.Options,所以一般情况下还是根据自己的需要自定义LoadMethod为好 bitmap = BitmapFactory.decodeStream(inputStream, null, (mOptions == null) ? sDefaultOptions : mOptions); inputStream.close(); } if (mBitmapProcessor != null && bitmap != null) { final Bitmap processedBitmap = mBitmapProcessor.processImage(bitmap); if (processedBitmap != null) { bitmap.recycle(); bitmap = processedBitmap; } } } catch (Exception e) { Log.e(LOG_TAG, "Error while fetching image", e); throwable = e; } if (bitmap == null) { if (throwable == null) { throwable = new Exception("Skia image decoding failed"); } h.sendMessage(Message.obtain(h, ON_FAIL, throwable)); } else { h.sendMessage(Message.obtain(h, ON_END, bitmap)); if (mCache != null) { mCache.writeCache(TextUtils.isEmpty(mCacheKey) ? mUrl : mCacheKey, bitmap); } } }
如果自定义了LoadMethod,会调用相应的方法加载图片,如果没有自定义,会使用默认的加载方法,可以加载本地图片,Asset图片与网络图片,GreenDroid的源码中加载网络图片是用的URL的,但我们以前在加载网络图片时遇到一个问题,有的机型通过URL类获得的ImputStream解析图片总是返回null,所以就改为了HttpClient。
通过AsyncImageView的setPath方法来加载图片,setPath有3个重载方法:
第一个参数指定要加载的图片的路径,第二个参数为自定义的图片加载方法,若不指定则用默认的。
至于加第三个参数,是做缓存用的,一般要加载的图片的路径都是唯一的,所以一般用第一个参数来做为缓存的Key就行了,但也有特殊情况,比如读取局域网中的图片,一般都是自动获取IP,所以根据图片路径做为缓存的Key可能是不合适的,所以就需要根据需要手动指定用来作为缓存的Key。
/** * 设置要加载的图片的路径, 可为网络路径, Asset文件路径(file:///android_asset), 本地图片路径(file:///或/) * * @param path 要加载的图片的路径, 若为null则加载默认图片 * @param loadMethod 自定义的图片加载的方法, 可以null, 使用默认的加载方法 * @param cacheKey 缓存key */ public void setPath(String path, LoadMethod loadMethod, String cacheKey) { // Check the url has changed if (mBitmap != null && path != null && path.equals(mUrl)) { // TODO mBitmap != null necessary? return; } stopLoading(); mUrl = path; mCacheKey = cacheKey; mLoadMethod = loadMethod; // Setting the url to an empty string force the displayed image to the // default image if (TextUtils.isEmpty(mUrl)) { mBitmap = null; setDefaultImage(); } else { if (!mPaused) { reload(); } else { // We're paused: let's look in a synchronous and efficient cache // prior using the default image. mBitmap = readCache(); // TODO 可能会耗时间 if (mBitmap != null) { setImageBitmap(mBitmap); } else { setDefaultImage(); } } } }
public void reload(boolean force) { if (mRequest == null && mUrl != null) { // Prior downloading the image ... let's look in a cache ! mBitmap = null; if (!force) { // This may take a long time. mBitmap = readCache(); } if (mBitmap != null) { setImageBitmap(mBitmap); return; } setDefaultImage(); mRequest = new ImageRequest(mUrl, this, mImageProcessor, mOptions, mCacheKey); mRequest.load(getContext(), mLoadMethod); if (ImageLoader.getInstance() != null && ImageLoader.getInstance().getCache() == null) { ImageLoader.getInstance().setCache(mCache); } }
readCache()用于读取缓存,代码如下:
private Bitmap readCache() { if (mCache != null) return mCache.readCache(TextUtils.isEmpty(mCacheKey) ? mUrl : mCacheKey); return null; }
其中的mCache由用户能过setCacheCallback(CacheCallback callback)设置用户自定义的缓存方法,由此将图片的加载与缓存分离开,使用户可以使用现有的缓存实现。如要用户指定了缓存Key就使用用户指定的Key,否则就用图片的路径作Key。
源码下载:AsyncImage