Volley网络通信框架
- 使用volley加载网络图片,主要用到其中的ImageLoader类。
- ImageLoader类主要是帮我们载入和缓存从远程网络加载的图片。构造方法中我们需要传入请求队列和一个ImageCache接口的实现(这个地方谷歌并没有为我们做好图片的缓存,我们需要按照自己的思路去实现这些功能,比如LRU,LFU,FIFO等)。另外,getImageListener(ImageView view, int defaultImageResId, int errorImageResId)为我们提供了默认的ImageLoader.ImageListener实现。还有一个注意事项:所有的请求都必须在主线程中发出。
- ImageLoader提供了两个get方法,值得注意的地方是,get(java.lang.String requestUrl, ImageLoader.ImageListener imageListener, int maxWidth, int maxHeight)这个方法中,我们可以通过设置最大宽高来限制加载到内存中的图片的大小,减少OOM的发生,当加载一些大图片时,效果还是非常明显的。
- NetworkImageView是对ImageView的再封装,加入网络请求。
- 缺点就是加载大量图片的时候可能更容易出现OOM问题,因为NetworkImageView没有处理图片的压缩,当然,当保证加载的图片很小时适用NetworkImageView。或者重写NetworkImageView,在其中添加一个设置加载尺寸的方法。
public class ImageLoader {
/** RequestQueue for dispatching ImageRequests onto. */
private final RequestQueue mRequestQueue;
/** Amount of time to wait after first response arrives before delivering all responses. */
private int mBatchResponseDelayMs = 100;
/** The cache implementation to be used as an L1 cache before calling into volley. */
private final ImageCache mCache;
/**
* HashMap of Cache keys -> BatchedImageRequest used to track in-flight requests so
* that we can coalesce multiple requests to the same URL into a single network request.
*/
private final HashMap<String, BatchedImageRequest> mInFlightRequests =
new HashMap<String, BatchedImageRequest>();
/** HashMap of the currently pending responses (waiting to be delivered). */
private final HashMap<String, BatchedImageRequest> mBatchedResponses =
new HashMap<String, BatchedImageRequest>();
/** Handler to the main thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper());
/** Runnable for in-flight response delivery. */
private Runnable mRunnable;
/**
* Simple cache adapter interface. If provided to the ImageLoader, it
* will be used as an L1 cache before dispatch to Volley. Implementations
* must not block. Implementation with an LruCache is recommended.
*/
public interface ImageCache {
public Bitmap getBitmap(String url);
public void putBitmap(String url, Bitmap bitmap);
}
/**
* Constructs a new ImageLoader.
* @param queue The RequestQueue to use for making image requests.
* @param imageCache The cache to use as an L1 cache.
*/
public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
}
/**
* The default implementation of ImageListener which handles basic functionality
* of showing a default image until the network response is received, at which point
* it will switch to either the actual image or the error image.
* @param imageView The imageView that the listener is associated with.
* @param defaultImageResId Default image resource ID to use, or 0 if it doesn't exist.
* @param errorImageResId Error image resource ID to use, or 0 if it doesn't exist.
*/
public static ImageListener getImageListener(final ImageView view,
final int defaultImageResId, final int errorImageResId) {
return new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (errorImageResId != 0) {
view.setImageResource(errorImageResId);
}
}
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
view.setImageBitmap(response.getBitmap());
} else if (defaultImageResId != 0) {
view.setImageResource(defaultImageResId);
}
}
};
}
/**
* Interface for the response handlers on image requests.
*
* The call flow is this:
* 1. Upon being attached to a request, onResponse(response, true) will
* be invoked to reflect any cached data that was already available. If the
* data was available, response.getBitmap() will be non-null.
*
* 2. After a network response returns, only one of the following cases will happen:
* - onResponse(response, false) will be called if the image was loaded.
* or
* - onErrorResponse will be called if there was an error loading the image.
*/
public interface ImageListener extends ErrorListener {
/**
* Listens for non-error changes to the loading of the image request.
*
* @param response Holds all information pertaining to the request, as well
* as the bitmap (if it is loaded).
* @param isImmediate True if this was called during ImageLoader.get() variants.
* This can be used to differentiate between a cached image loading and a network
* image loading in order to, for example, run an animation to fade in network loaded
* images.
*/
public void onResponse(ImageContainer response, boolean isImmediate);
}
/**
* Checks if the item is available in the cache.
* @param requestUrl The url of the remote image
* @param maxWidth The maximum width of the returned image.
* @param maxHeight The maximum height of the returned image.
* @return True if the item exists in cache, false otherwise.
*/
public boolean isCached(String requestUrl, int maxWidth, int maxHeight) {
throwIfNotOnMainThread();
String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
return mCache.getBitmap(cacheKey) != null;
}
/**
* Returns an ImageContainer for the requested URL.
*
* The ImageContainer will contain either the specified default bitmap or the loaded bitmap.
* If the default was returned, the {@link ImageLoader} will be invoked when the
* request is fulfilled.
*
* @param requestUrl The URL of the image to be loaded.
* @param defaultImage Optional default image to return until the actual image is loaded.
*/
public ImageContainer get(String requestUrl, final ImageListener listener) {
return get(requestUrl, listener, 0, 0);
}
/**
* Issues a bitmap request with the given URL if that image is not available
* in the cache, and returns a bitmap container that contains all of the data
* relating to the request (as well as the default image if the requested
* image is not available).
* @param requestUrl The url of the remote image
* @param imageListener The listener to call when the remote image is loaded
* @param maxWidth The maximum width of the returned image.
* @param maxHeight The maximum height of the returned image.
* @return A container object that contains all of the properties of the request, as well as
* the currently available image (default if remote is not loaded).
*/
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight) {
// only fulfill requests that were initiated from the main thread.
throwIfNotOnMainThread();
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
// The bitmap did not exist in the cache, fetch it!
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
// Update the caller to let them know that they should use the default bitmap.
imageListener.onResponse(imageContainer, true);
// Check to see if a request is already in-flight.
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}
// The request is not already in flight. Send the new request to the network and
// track it.
Request<?> newRequest =
new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight,
Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
/**
* Sets the amount of time to wait after the first response arrives before delivering all
* responses. Batching can be disabled entirely by passing in 0.
* @param newBatchedResponseDelayMs The time in milliseconds to wait.
*/
public void setBatchedResponseDelay(int newBatchedResponseDelayMs) {
mBatchResponseDelayMs = newBatchedResponseDelayMs;
}
/**
* Handler for when an image was successfully loaded.
* @param cacheKey The cache key that is associated with the image request.
* @param response The bitmap that was returned from the network.
*/
private void onGetImageSuccess(String cacheKey, Bitmap response) {
// cache the image that was fetched.
mCache.putBitmap(cacheKey, response);
// remove the request from the list of in-flight requests.
BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
// Update the response bitmap.
request.mResponseBitmap = response;
// Send the batched response
batchResponse(cacheKey, request);
}
}
/**
* Handler for when an image failed to load.
* @param cacheKey The cache key that is associated with the image request.
*/
private void onGetImageError(String cacheKey, VolleyError error) {
// Notify the requesters that something failed via a null result.
// Remove this request from the list of in-flight requests.
BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
// Set the error for this request
request.setError(error);
if (request != null) {
// Send the batched response
batchResponse(cacheKey, request);
}
}
/**
* Container object for all of the data surrounding an image request.
*/
public class ImageContainer {
/**
* The most relevant bitmap for the container. If the image was in cache, the
* Holder to use for the final bitmap (the one that pairs to the requested URL).
*/
private Bitmap mBitmap;
private final ImageListener mListener;
/** The cache key that was associated with the request */
private final String mCacheKey;
/** The request URL that was specified */
private final String mRequestUrl;
/**
* Constructs a BitmapContainer object.
* @param bitmap The final bitmap (if it exists).
* @param requestUrl The requested URL for this container.
* @param cacheKey The cache key that identifies the requested URL for this container.
*/
public ImageContainer(Bitmap bitmap, String requestUrl,
String cacheKey, ImageListener listener) {
mBitmap = bitmap;
mRequestUrl = requestUrl;
mCacheKey = cacheKey;
mListener = listener;
}
/**
* Releases interest in the in-flight request (and cancels it if no one else is listening).
*/
public void cancelRequest() {
if (mListener == null) {
return;
}
BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
if (request != null) {
boolean canceled = request.removeContainerAndCancelIfNecessary(this);
if (canceled) {
mInFlightRequests.remove(mCacheKey);
}
} else {
// check to see if it is already batched for delivery.
request = mBatchedResponses.get(mCacheKey);
if (request != null) {
request.removeContainerAndCancelIfNecessary(this);
if (request.mContainers.size() == 0) {
mBatchedResponses.remove(mCacheKey);
}
}
}
}
/**
* Returns the bitmap associated with the request URL if it has been loaded, null otherwise.
*/
public Bitmap getBitmap() {
return mBitmap;
}
/**
* Returns the requested URL for this container.
*/
public String getRequestUrl() {
return mRequestUrl;
}
}
/**
* Wrapper class used to map a Request to the set of active ImageContainer objects that are
* interested in its results.
*/
private class BatchedImageRequest {
/** The request being tracked */
private final Request<?> mRequest;
/** The result of the request being tracked by this item */
private Bitmap mResponseBitmap;
/** Error if one occurred for this response */
private VolleyError mError;
/** List of all of the active ImageContainers that are interested in the request */
private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();
/**
* Constructs a new BatchedImageRequest object
* @param request The request being tracked
* @param container The ImageContainer of the person who initiated the request.
*/
public BatchedImageRequest(Request<?> request, ImageContainer container) {
mRequest = request;
mContainers.add(container);
}
/**
* Set the error for this response
*/
public void setError(VolleyError error) {
mError = error;
}
/**
* Get the error for this response
*/
public VolleyError getError() {
return mError;
}
/**
* Adds another ImageContainer to the list of those interested in the results of
* the request.
*/
public void addContainer(ImageContainer container) {
mContainers.add(container);
}
/**
* Detatches the bitmap container from the request and cancels the request if no one is
* left listening.
* @param container The container to remove from the list
* @return True if the request was canceled, false otherwise.
*/
public boolean removeContainerAndCancelIfNecessary(ImageContainer container) {
mContainers.remove(container);
if (mContainers.size() == 0) {
mRequest.cancel();
return true;
}
return false;
}
}
/**
* Starts the runnable for batched delivery of responses if it is not already started.
* @param cacheKey The cacheKey of the response being delivered.
* @param request The BatchedImageRequest to be delivered.
* @param error The volley error associated with the request (if applicable).
*/
private void batchResponse(String cacheKey, BatchedImageRequest request) {
mBatchedResponses.put(cacheKey, request);
// If we don't already have a batch delivery runnable in flight, make a new one.
// Note that this will be used to deliver responses to all callers in mBatchedResponses.
if (mRunnable == null) {
mRunnable = new Runnable() {
@Override
public void run() {
for (BatchedImageRequest bir : mBatchedResponses.values()) {
for (ImageContainer container : bir.mContainers) {
// If one of the callers in the batched request canceled the request
// after the response was received but before it was delivered,
// skip them.
if (container.mListener == null) {
continue;
}
if (bir.getError() == null) {
container.mBitmap = bir.mResponseBitmap;
container.mListener.onResponse(container, false);
} else {
container.mListener.onErrorResponse(bir.getError());
}
}
}
mBatchedResponses.clear();
mRunnable = null;
}
};
// Post the runnable.
mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
}
}
private void throwIfNotOnMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("ImageLoader must be invoked from the main thread.");
}
}
/**
* Creates a cache key for use with the L1 cache.
* @param url The URL of the request.
* @param maxWidth The max-width of the output.
* @param maxHeight The max-height of the output.
*/
private static String getCacheKey(String url, int maxWidth, int maxHeight) {
return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
.append("#H").append(maxHeight).append(url).toString();
}
}
- ImageLoader 源码:
public class ImageRequest extends Request<Bitmap> {
/** Socket timeout in milliseconds for image requests */
private static final int IMAGE_TIMEOUT_MS = 1000;
/** Default number of retries for image requests */
private static final int IMAGE_MAX_RETRIES = 2;
/** Default backoff multiplier for image requests */
private static final float IMAGE_BACKOFF_MULT = 2f;
private final Response.Listener<Bitmap> mListener;
private final Config mDecodeConfig;
private final int mMaxWidth;
private final int mMaxHeight;
/** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */
private static final Object sDecodeLock = new Object();
/**
* Creates a new image request, decoding to a maximum specified width and
* height. If both width and height are zero, the image will be decoded to
* its natural size. If one of the two is nonzero, that dimension will be
* clamped and the other one will be set to preserve the image's aspect
* ratio. If both width and height are nonzero, the image will be decoded to
* be fit in the rectangle of dimensions width x height while keeping its
* aspect ratio.
*
* @param url URL of the image
* @param listener Listener to receive the decoded bitmap
* @param maxWidth Maximum width to decode this bitmap to, or zero for none
* @param maxHeight Maximum height to decode this bitmap to, or zero for
* none
* @param decodeConfig Format to decode the bitmap to
* @param errorListener Error listener, or null to ignore errors
*/
public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
Config decodeConfig, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
setRetryPolicy(
new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
mListener = listener;
mDecodeConfig = decodeConfig;
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
}
@Override
public Priority getPriority() {
return Priority.LOW;
}
/**
* Scales one side of a rectangle to fit aspect ratio.
*
* @param maxPrimary Maximum size of the primary dimension (i.e. width for
* max width), or zero to maintain aspect ratio with secondary
* dimension
* @param maxSecondary Maximum size of the secondary dimension, or zero to
* maintain aspect ratio with primary dimension
* @param actualPrimary Actual size of the primary dimension
* @param actualSecondary Actual size of the secondary dimension
*/
private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
int actualSecondary) {
// If no dominant value at all, just return the actual.
if (maxPrimary == 0 && maxSecondary == 0) {
return actualPrimary;
}
// If primary is unspecified, scale primary to match secondary's scaling ratio.
if (maxPrimary == 0) {
double ratio = (double) maxSecondary / (double) actualSecondary;
return (int) (actualPrimary * ratio);
}
if (maxSecondary == 0) {
return maxPrimary;
}
double ratio = (double) actualSecondary / (double) actualPrimary;
int resized = maxPrimary;
if (resized * ratio > maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
return resized;
}
@Override
protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
// Serialize all decode on a global lock to reduce concurrent heap usage.
synchronized (sDecodeLock) {
try {
return doParse(response);
} catch (OutOfMemoryError e) {
VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
return Response.error(new ParseError(e));
}
}
}
/**
* The real guts of parseNetworkResponse. Broken out for readability.
*/
private Response<Bitmap> doParse(NetworkResponse response) {
byte[] data = response.data;
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
Bitmap bitmap = null;
if (mMaxWidth == 0 && mMaxHeight == 0) {
decodeOptions.inPreferredConfig = mDecodeConfig;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
} else {
// If we have to resize this image, first get the natural bounds.
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
int actualWidth = decodeOptions.outWidth;
int actualHeight = decodeOptions.outHeight;
// Then compute the dimensions we would ideally like to decode to.
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth);
// Decode to the nearest power of two scaling factor.
decodeOptions.inJustDecodeBounds = false;
// TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
// decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
decodeOptions.inSampleSize =
findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
Bitmap tempBitmap =
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
// If necessary, scale down to the maximal acceptable size.
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)) {
bitmap = Bitmap.createScaledBitmap(tempBitmap,
desiredWidth, desiredHeight, true);
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
}
if (bitmap == null) {
return Response.error(new ParseError(response));
} else {
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
}
}
@Override
protected void deliverResponse(Bitmap response) {
mListener.onResponse(response);
}
/**
* Returns the largest power-of-two divisor for use in downscaling a bitmap
* that will not result in the scaling past the desired dimensions.
*
* @param actualWidth Actual width of the bitmap
* @param actualHeight Actual height of the bitmap
* @param desiredWidth Desired width of the bitmap
* @param desiredHeight Desired height of the bitmap
*/
// Visible for testing.
static int findBestSampleSize(
int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
double wr = (double) actualWidth / desiredWidth;
double hr = (double) actualHeight / desiredHeight;
double ratio = Math.min(wr, hr);
float n = 1.0f;
while ((n * 2) <= ratio) {
n *= 2;
}
return (int) n;
}
}
- ImageRequest的代码:
- 方法1:利用ImageRequest为ImageView加载网络图片,不带图片缓存
- 方法2:利用ImageLoader为ImageView加载网络图片,自定义图片缓存
- 方法3:利用ImageLoader和NetworkImageView为ImageView加载网络图片,自定义图片缓存
public class MainActivity extends Activity {
private RequestQueue mRequestQueue = null;
private String urlString = "http://avatar.youkuaiyun.com/6/6/D/1_lfdfhl.jpg";
private ImageView imageView_main;
private NetworkImageView networkImageView_main;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView_main = (ImageView) findViewById(R.id.imageView_main);
networkImageView_main = (NetworkImageView) findViewById(R.id.networkImageView_main);
// 初始化请求序列
mRequestQueue = Volley.newRequestQueue(this);
// Volley加载方法1:利用ImageRequest为ImageView加载网络图片,不带缓存
// volleyImageRequest();
// setTitle("ImageRequest");
// Volley加载方法2:利用ImageLoader为ImageView加载网络图片
// volleyImageLoader();
// setTitle("ImageLoader");
// Volley加载方法3:利用ImageLoader和NetworkImageView为ImageView加载网络图片
volleyNetworkImageView();
setTitle("NetworkImageView");
}
// 利用ImageRequest为ImageView加载网络图片,不带缓存
private void volleyImageRequest() {
ImageRequest mImageRequest = new ImageRequest(urlString,
new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
imageView_main.setImageBitmap(response);
}
}, 300, 300, Config.ARGB_8888, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 获取图片失败,加载一个默认图片
imageView_main.setImageResource(R.drawable.ic_launcher);
}
});
mRequestQueue.add(mImageRequest);
}
// 利用ImageLoader为ImageView加载网络图片
private void volleyImageLoader() {
ImageLoader mImageLoader = new ImageLoader(mRequestQueue,
new MyBitmapCache().getInstance());
ImageListener mImageListener = ImageLoader.getImageListener(
imageView_main, R.drawable.ic_launcher, R.drawable.ic_launcher);
// mImageLoader.get(mUrl, mImageListener);
mImageLoader.get(urlString, mImageListener, 250, 250);
}
// 利用ImageLoader和NetworkImageView为ImageView加载网络图片
private void volleyNetworkImageView() {
ImageLoader mImageLoader = new ImageLoader(mRequestQueue,
new MyBitmapCache().getInstance());
networkImageView_main.setDefaultImageResId(R.drawable.ic_launcher);
networkImageView_main.setErrorImageResId(R.drawable.ic_launcher);
networkImageView_main.setImageUrl(urlString, mImageLoader);
}
static class MyBitmapCache implements ImageCache {
private static MyBitmapCache imageCache = null;
private static LruCache<String, Bitmap> lruCache = null;
private MyBitmapCache() {
int memoryCount = (int) Runtime.getRuntime().maxMemory();
// 获取剩余内存的8分之一作为缓存
int cacheSize = memoryCount / 8;
lruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
// return bitmap.getByteCount();
}
};
}
public static MyBitmapCache getInstance() {
if (imageCache == null) {
imageCache = new MyBitmapCache();
}
return imageCache;
}
@Override
public Bitmap getBitmap(String url) {
return lruCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
lruCache.put(url, bitmap);
}
}
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ImageView
android:id="@+id/imageView_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:src="@drawable/ic_launcher" />
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/networkImageView_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/imageView"
android:src="@drawable/ic_launcher" />
</RelativeLayout>
- Volley.newRequestQueue(this);
- ListView控件设置适配器;
- 实例化StringRequest;
- json解析,生成List集合;
- 数据源追加数据:totalList.addAll(result);
- 数据发生改变刷新adapter:adapter.notifyDataSetChanged();
- 将请求对象添加进请求队列;
- requestQueue.add(request);
- 适配器的构造方法中实例化ImageLoader对象;
- imageLoader = new ImageLoader(queue, MyImageCache.getInstance());
- getView()方法中使用Volley异步加载图片;
- mHolder.networkImageView_main_cover.setImageUrl(imgUrl , imageLoader);
- class MyImageCache implements ImageCache{}
- 缓冲类中的构造方法中实例化LruCache对象(采用单例模式);
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private ListView listView_main;
private String urlString = "http://m2.qiushibaike.com/article/list/suggest?page=";
private List<Map<String, Object>> totalList = new ArrayList<Map<String, Object>>();
private MyAdapter adapter = null;
private RequestQueue requestQueue = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化ListView控件
initView();
// 加载数据
loadData();
}
// 初始化控件
private void initView() {
// 创建请求队列
requestQueue = Volley.newRequestQueue(this);
listView_main = (ListView) findViewById(R.id.listView_main);
adapter = new MyAdapter(totalList, requestQueue);
listView_main.setAdapter(adapter);
}
// 加载数据
private void loadData() {
StringRequest request = new StringRequest(urlString,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 请求成功回调函数 返回json字符串,主线程,直接可以操作ui;
List<Map<String, Object>> result = jsonStringToList(response);
if (result != null) {
totalList.addAll(result);
// 数据发生改变刷新adapter;
adapter.notifyDataSetChanged();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 请求失败,
}
});
// 添加进请求队列中
requestQueue.add(request);
}
class MyAdapter extends BaseAdapter {
private ImageLoader imageLoader;
private List<Map<String, Object>> list = null;
public MyAdapter(List<Map<String, Object>> result, RequestQueue queue) {
this.list = result;
imageLoader = new ImageLoader(queue, MyImageCache.getInstance());
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder mHolder;
if (convertView == null) {
mHolder = new ViewHolder();
convertView = getLayoutInflater().inflate(
R.layout.item_listview_main, parent, false);
mHolder.text_item_content = (TextView) convertView
.findViewById(R.id.text_item_content);
mHolder.text_item_conmmentscount = (TextView) convertView
.findViewById(R.id.text_item_conmmentscount);
mHolder.networkImageView_main_cover = (NetworkImageView) convertView
.findViewById(R.id.networkImageView_main_cover);
convertView.setTag(mHolder);
} else {
mHolder = (ViewHolder) convertView.getTag();
}
mHolder.text_item_content.setText(list.get(position).get("content")
.toString());
mHolder.text_item_conmmentscount.setText(list.get(position)
.get("comments_count").toString());
// 开始加载图片
String imgUrl = getImageUrl(list.get(position).get("image")
.toString());
// Log.i(TAG, "==imgUrl:" + imgUrl);
if (imgUrl.equals("")) {
Log.i(TAG, "==imgUrl:null");
mHolder.networkImageView_main_cover
.setImageResource(R.drawable.icon02);
} else {
// 使用volley框架异步加载图片开始
// 设置图片未下载完成时候的默认图片
mHolder.networkImageView_main_cover
.setDefaultImageResId(R.drawable.icon01);
// 设置图片下载错误时的图片
mHolder.networkImageView_main_cover
.setErrorImageResId(R.drawable.ic_launcher);
mHolder.networkImageView_main_cover.setImageUrl(imgUrl,
imageLoader);
}
return convertView;
}
private class ViewHolder {
public NetworkImageView networkImageView_main_cover;
public TextView text_item_content;
public TextView text_item_conmmentscount;
}
}
// 根据json解析获取到的图片名称,生成该图片的url地址
private String getImageUrl(String imageName) {
String prefixUrlString = "http://pic.qiushibaike.com/system/pictures/";
Pattern pattern = Pattern.compile("(\\d{4})\\d+");
Matcher matcher = pattern.matcher(imageName);
StringBuffer url = new StringBuffer();
if (matcher.find()) {
url.append(prefixUrlString).append(matcher.group(1)).append('/')
.append(matcher.group(0)).append("/small/")
.append(imageName);
return url.toString();
} else {
return "";
}
}
// json解析,生成List集合
private List<Map<String, Object>> jsonStringToList(String jsonString) {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
try {
JSONObject jsonObject = new JSONObject(jsonString);
JSONArray jsonArray_items = jsonObject.getJSONArray("items");
for (int i = 0; i < jsonArray_items.length(); i++) {
JSONObject jonsObject_item = jsonArray_items.getJSONObject(i);
Map<String, Object> map = new HashMap<String, Object>();
map.put("image", jonsObject_item.getString("image"));
map.put("content", jonsObject_item.getString("content"));
map.put("comments_count",
jonsObject_item.getString("comments_count"));
list.add(map);
}
return list;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
// 图片缓存类,使用单例模式
static class MyImageCache implements ImageCache {
private static MyImageCache imageCache = null;
private static LruCache<String, Bitmap> lruCache = null;
private MyImageCache() {
int memoryCount = (int) Runtime.getRuntime().maxMemory();
// 获取剩余内存的8分之一作为缓存
int cacheSize = memoryCount / 8;
lruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
// return bitmap.getByteCount();
}
};
}
public static MyImageCache getInstance() {
if (imageCache == null) {
imageCache = new MyImageCache();
}
return imageCache;
}
@Override
public Bitmap getBitmap(String url) {
return lruCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
lruCache.put(url, bitmap);
}
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/networkImageView_main_cover"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text_item_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="#69f"
/>
<TextView
android:id="@+id/text_item_conmmentscount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textSize="12sp"
android:textColor="#999"
/>
</LinearLayout>
实现原理:瀑布流的布局方式虽然看起来好像排列的很随意,其实它是有很科学的排列规则的。整个界面会根据屏幕的宽度划分成等宽的若干列,由于手机的屏幕不是很大,这里我们就分成三列。每当需要添加一张图片时,会将这张图片的宽度压缩成和列一样宽,再按照同样的压缩比例对图片的高度进行压缩,然后在这三列中找出当前高度最小的一列,将图片添加到这一列中。之后每当需要添加一张新图片时,都去重复上面的操作,就会形成瀑布流格局的照片墙,示意图如下所示。
良心的公众号,更多精品文章,不要忘记关注哈
《Android和Java技术栈》