提交 a6740390 编写于 作者: S Sam Judd 提交者: Sam Judd

initial working version of preloading

上级 8b2c41e5
package com.bumptech.glide;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.widget.AbsListView;
import com.bumptech.glide.presenter.target.SimpleTarget;
import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public abstract class ListPreloader<T> implements AbsListView.OnScrollListener {
private final int maxPreload;
private final Context context;
private final PreloadTargetQueue preloadTargetQueue;
private int lastEnd;
private int lastStart;
private int lastFirstVisible;
private int totalItemCount;
public ListPreloader(Context context, int maxPreload) {
this.context = context;
this.maxPreload = maxPreload;
preloadTargetQueue = new PreloadTargetQueue(maxPreload);
}
@Override
public void onScrollStateChanged(AbsListView absListView, int i) { }
@Override
public void onScroll(AbsListView absListView, int firstVisible, int visibleCount, int totalCount) {
totalItemCount = totalCount;
if (firstVisible > lastFirstVisible) {
preload(firstVisible + visibleCount, true);
} else if (firstVisible < lastFirstVisible) {
preload(firstVisible, false);
}
lastFirstVisible = firstVisible;
}
protected abstract int[] getDimens(T item);
protected abstract List<T> getItems(int start, int end);
protected abstract Glide.Request<T> getRequest(T item);
public void preload(int start, boolean increasing) {
preload(start, start + (increasing ? maxPreload : -maxPreload));
}
private void preload(int from, int to) {
int start;
int end;
if (from < to) {
start = Math.max(lastEnd, from);
end = to;
} else {
start = to;
end = Math.min(lastStart, from);
}
end = Math.min(totalItemCount, end);
start = Math.min(totalItemCount, Math.max(0, start));
List<T> items = getItems(start, end);
// Increasing
if (from < to) {
final int numItems = items.size();
for (int i = 0; i < numItems; i++) {
preload(items, i);
}
} else {
for (int i = items.size() - 1; i >= 0; i--) {
preload(items, i);
}
}
lastStart = start;
lastEnd = end;
}
private void preload(List<T> items, int position) {
final T item = items.get(position);
int[] dimens = getDimens(item);
getRequest(item).into(preloadTargetQueue.next(dimens[0], dimens[1])).with(context);
}
private static class PreloadTargetQueue {
private final Queue<PreloadTarget> queue;
@TargetApi(9)
private PreloadTargetQueue(int size) {
if (Build.VERSION.SDK_INT >= 9) {
queue = new ArrayDeque<PreloadTarget>(size);
} else {
queue = new LinkedList<PreloadTarget>();
}
for (int i = 0; i < size; i++) {
queue.offer(new PreloadTarget());
}
}
public PreloadTarget next(int width, int height) {
final PreloadTarget result = queue.poll();
queue.offer(result);
result.photoWidth = width;
result.photoHeight = height;
return result;
}
}
private static class PreloadTarget extends SimpleTarget {
private int photoHeight;
private int photoWidth;
@Override
protected int[] getSize() {
return new int[] { photoWidth, photoHeight };
}
@Override
public void onImageReady(Bitmap bitmap) { }
}
}
......@@ -16,11 +16,10 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
* purposes.
*/
public class ImageManagerLoader implements ImageLoader {
protected final ImageManager imageManager;
private final ImageManager imageManager;
private final Downsampler downsampler;
private Bitmap acquired;
private ImageManager.ImageManagerJob loadToken;
private ImageManager.LoadToken loadToken;
public ImageManagerLoader(Context context) {
this(context, Downsampler.AT_LEAST);
......@@ -40,13 +39,14 @@ public class ImageManagerLoader implements ImageLoader {
}
@Override
public Object fetchImage(String id, StreamLoader streamLoader, Transformation transformation, int width, int height, final ImageReadyCallback cb) {
public Object fetchImage(String id, StreamLoader streamLoader, Transformation transformation, int width, int height,
final ImageReadyCallback cb) {
if (!isHandled(width, height)) {
throw new IllegalArgumentException(getClass() + " cannot handle width=" + width + " and/or height =" +
height);
}
loadToken = imageManager.getImage(id, streamLoader, transformation, downsampler, width, height, new LoadedCallback() {
loadToken = imageManager.getImage(id, streamLoader, transformation, downsampler, width, height,
new LoadedCallback() {
@Override
public void onLoadCompleted(Bitmap loaded) {
onImageReady(loaded, cb.onImageReady(loaded));
......
......@@ -9,10 +9,7 @@ import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.*;
import com.bumptech.glide.loader.stream.StreamLoader;
import com.bumptech.glide.resize.bitmap_recycle.BitmapPool;
import com.bumptech.glide.resize.bitmap_recycle.BitmapPoolAdapter;
......@@ -35,16 +32,23 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
/**
* A class to coordinate image loading, resizing, recycling, and caching. Depending on the provided options and the
* sdk version, uses a combination of an LRU disk cache and an LRU hard memory cache to try to reduce the number of
* load and resize operations performed and to maximize the number of times Bitmaps are recycled as opposed to
* allocated. If no options are given defaults to using both a memory and a disk cache and to recycling bitmaps if possible.
* allocated. If no options are given defaults to using both a memory and a disk cache and to recycling bitmaps if
* possible.
*
* <p>
* Note that Bitmap recycling is only available on Honeycomb and up.
......@@ -60,6 +64,7 @@ public class ImageManager {
private final BitmapReferenceCounter bitmapReferenceCounter;
private final int bitmapCompressQuality;
private final BitmapPool bitmapPool;
private final Map<String, ImageManagerJob> jobs = new HashMap<String, ImageManagerJob>();
private boolean shutdown = false;
private final Handler mainHandler = new Handler();
......@@ -75,7 +80,8 @@ public class ImageManager {
private static Downsampler DISK_CACHE_DOWNSAMPLER = new Downsampler() {
@Override
public Bitmap downsample(RecyclableBufferedInputStream bis, BitmapFactory.Options options, BitmapPool pool, int outWidth, int outHeight) {
public Bitmap downsample(RecyclableBufferedInputStream bis, BitmapFactory.Options options, BitmapPool pool,
int outWidth, int outHeight) {
return downsampleWithSize(bis, options, pool, outWidth, outHeight, 1);
}
......@@ -313,11 +319,12 @@ public class ImageManager {
private void setDefaults() {
if (resizeService == null) {
resizeService = Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors()), new ThreadFactory() {
final int numThreads = Math.max(1, Runtime.getRuntime().availableProcessors());
resizeService = Executors.newFixedThreadPool(numThreads, new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
final Thread result = new Thread(runnable);
result.setPriority(Thread.MIN_PRIORITY);
result.setPriority(THREAD_PRIORITY_BACKGROUND);
return result;
}
});
......@@ -356,7 +363,7 @@ public class ImageManager {
}
private ImageManager(Builder builder) {
HandlerThread bgThread = new HandlerThread("bg_thread", android.os.Process.THREAD_PRIORITY_BACKGROUND);
HandlerThread bgThread = new HandlerThread("bg_thread", THREAD_PRIORITY_BACKGROUND);
bgThread.start();
bgHandler = new Handler(bgThread.getLooper());
executor = builder.resizeService;
......@@ -366,7 +373,6 @@ public class ImageManager {
bitmapReferenceCounter = builder.bitmapReferenceCounter;
bitmapPool = builder.bitmapPool;
resizer = new ImageResizer(builder.bitmapPool, builder.decodeBitmapOptions);
memoryCache.setImageRemovedListener(new MemoryCache.ImageRemovedListener() {
@Override
public void onImageRemoved(Bitmap removed) {
......@@ -404,18 +410,25 @@ public class ImageManager {
* @return An {@link ImageManagerJob} that must be retained while the job is still relevant and that can be used
* to cancel a job if the image is no longer needed
*/
public ImageManagerJob getImage(String id, StreamLoader streamLoader, Transformation transformation, Downsampler downsampler, int width, int height, LoadedCallback cb) {
public LoadToken getImage(String id, StreamLoader streamLoader, Transformation transformation,
Downsampler downsampler, int width, int height, LoadedCallback cb) {
if (shutdown) return null;
final String key = safeKeyGenerator.getSafeKey(id, transformation, downsampler, width, height);
ImageManagerJob job = null;
LoadToken result = null;
if (!returnFromCache(key, cb)) {
ImageManagerRunner runner = new ImageManagerRunner(key, streamLoader, transformation, downsampler, width, height, cb);
runner.execute();
job = new ImageManagerJob(runner, streamLoader, transformation, downsampler, cb);
ImageManagerJob job = jobs.get(key);
if (job == null) {
ImageManagerRunner runner = new ImageManagerRunner(key, streamLoader, transformation, downsampler,
width, height);
job = new ImageManagerJob(runner, key);
jobs.put(key, job);
runner.execute();
}
job.addCallback(cb);
result = new LoadToken(cb, job);
}
return job;
return result;
}
/**
......@@ -464,30 +477,69 @@ public class ImageManager {
* A class for tracking a particular job in the {@link ImageManager}. Cancel does not guarantee that the
* job will not finish, but rather is a best effort attempt.
*/
public static class ImageManagerJob {
private class ImageManagerJob {
private final ImageManagerRunner runner;
private StreamLoader sl;
private Transformation transformation;
private Downsampler downsampler;
private LoadedCallback cb;
private final String key;
private final List<LoadedCallback> cbs = new ArrayList<LoadedCallback>();
public ImageManagerJob(ImageManagerRunner runner, StreamLoader sl, Transformation t, Downsampler d, LoadedCallback cb) {
public ImageManagerJob(ImageManagerRunner runner, String key) {
this.runner = runner;
this.sl = sl;
this.transformation = t;
this.downsampler = d;
this.cb = cb;
this.key = key;
}
public void addCallback(LoadedCallback cb) {
cbs.add(cb);
}
/**
* Try to cancel the job. Does not guarantee that the job will not finish.
*/
public void cancel() {
runner.cancel();
sl = null;
transformation = null;
downsampler = null;
cb = null;
public void cancel(LoadedCallback cb) {
cbs.remove(cb);
if (cbs.size() == 0) {
runner.cancel();
jobs.remove(key);
}
}
public void onLoadComplete(Bitmap result) {
for (LoadedCallback cb : cbs) {
bitmapReferenceCounter.acquireBitmap(result);
cb.onLoadCompleted(result);
}
jobs.remove(key);
}
public void onLoadFailed(Exception e) {
for (LoadedCallback cb : cbs) {
cb.onLoadFailed(e);
}
jobs.remove(key);
}
}
private void putInDiskCache(String key, final Bitmap bitmap) {
diskCache.put(key, new DiskCache.Writer() {
@Override
public void write(OutputStream os) {
final Bitmap.Config config = bitmap.getConfig();
final Bitmap.CompressFormat format;
if (config == null || config == Bitmap.Config.ARGB_4444 || config == Bitmap.Config.ARGB_8888) {
format = Bitmap.CompressFormat.PNG;
} else {
format = Bitmap.CompressFormat.JPEG;
}
bitmap.compress(format, bitmapCompressQuality, os);
}
});
}
private void putInMemoryCache(String key, final Bitmap bitmap) {
final boolean inCache;
inCache = memoryCache.contains(key);
if (!inCache) {
bitmapReferenceCounter.acquireBitmap(bitmap);
memoryCache.put(key, bitmap);
}
}
......@@ -498,12 +550,11 @@ public class ImageManager {
private final StreamLoader streamLoader;
private final Transformation transformation;
private final Downsampler downsampler;
private final LoadedCallback cb;
private volatile Future<?> future;
private volatile boolean cancelled = false;
public ImageManagerRunner(String key, StreamLoader sl, Transformation t, Downsampler d, int width, int height, LoadedCallback cb) {
public ImageManagerRunner(String key, StreamLoader sl, Transformation t, Downsampler d, int width, int height) {
this.key = key;
this.height = height;
this.width = width;
......@@ -511,7 +562,6 @@ public class ImageManager {
this.streamLoader = sl;
this.transformation = t;
this.downsampler = d;
this.cb = cb;
}
private void execute() {
......@@ -618,9 +668,11 @@ public class ImageManager {
//acquire for the callback before putting in to memory cache so that the bitmap is not
//released to the pool if the bitmap is synchronously released by the memory cache
//we rely on the callback to call releaseBitmap if it doesn't want to use the bitmap
bitmapReferenceCounter.acquireBitmap(result);
putInMemoryCache(key, result);
cb.onLoadCompleted(result);
final ImageManagerJob job = jobs.get(key);
if (job != null) {
job.onLoadComplete(result);
}
}
});
} else {
......@@ -632,34 +684,26 @@ public class ImageManager {
mainHandler.post(new Runnable() {
@Override
public void run() {
cb.onLoadFailed(e);
final ImageManagerJob job = jobs.get(key);
if (job != null) {
job.onLoadFailed(e);
}
}
});
}
}
private void putInDiskCache(String key, final Bitmap bitmap) {
diskCache.put(key, new DiskCache.Writer() {
@Override
public void write(OutputStream os) {
final Bitmap.Config config = bitmap.getConfig();
final Bitmap.CompressFormat format;
if (config == null || config == Bitmap.Config.ARGB_4444 || config == Bitmap.Config.ARGB_8888) {
format = Bitmap.CompressFormat.PNG;
} else {
format = Bitmap.CompressFormat.JPEG;
}
bitmap.compress(format, bitmapCompressQuality, os);
}
});
}
public static class LoadToken {
private final ImageManagerJob job;
private final LoadedCallback cb;
private void putInMemoryCache(String key, final Bitmap bitmap) {
final boolean inCache;
inCache = memoryCache.contains(key);
if (!inCache) {
bitmapReferenceCounter.acquireBitmap(bitmap);
memoryCache.put(key, bitmap);
public LoadToken(LoadedCallback cb, ImageManagerJob job) {
this.cb = cb;
this.job = job;
}
public void cancel() {
job.cancel(cb);
}
}
}
......@@ -11,6 +11,8 @@ import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import com.actionbarsherlock.app.SherlockFragment;
import com.bumptech.glide.Glide;
import com.bumptech.glide.ListPreloader;
import com.bumptech.glide.loader.image.ImageManagerLoader;
import com.bumptech.glide.loader.model.Cache;
import com.bumptech.glide.loader.transformation.CenterCrop;
......@@ -22,15 +24,9 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* Created with IntelliJ IDEA.
* User: sam
* Date: 1/10/13
* Time: 9:48 AM
* To change this template use File | Settings | File Templates.
*/
public class FlickrPhotoGrid extends SherlockFragment implements PhotoViewer {
private static final String IMAGE_SIZE_KEY = "image_size";
private static final int PRELOAD_COUNT = 10;
private PhotoAdapter adapter;
private List<Photo> currentPhotos;
......@@ -51,8 +47,10 @@ public class FlickrPhotoGrid extends SherlockFragment implements PhotoViewer {
photoSize = args.getInt(IMAGE_SIZE_KEY);
final View result = inflater.inflate(R.layout.flickr_photo_grid, container, false);
GridView grid = (GridView) result.findViewById(R.id.images);
final GridView grid = (GridView) result.findViewById(R.id.images);
grid.setColumnWidth(photoSize);
final FlickrPreloader preloader = new FlickrPreloader(getActivity(), PRELOAD_COUNT);
grid.setOnScrollListener(preloader);
adapter = new PhotoAdapter();
grid.setAdapter(adapter);
if (currentPhotos != null)
......@@ -68,8 +66,30 @@ public class FlickrPhotoGrid extends SherlockFragment implements PhotoViewer {
adapter.setPhotos(currentPhotos);
}
private class PhotoAdapter extends BaseAdapter {
private class FlickrPreloader extends ListPreloader<Photo> {
public FlickrPreloader(Context context, int toPreload) {
super(context, toPreload);
}
@Override
protected int[] getDimens(Photo item) {
return new int[] { photoSize, photoSize };
}
@Override
protected List<Photo> getItems(int start, int end) {
return currentPhotos.subList(start, end);
}
@Override
protected Glide.Request<Photo> getRequest(Photo item) {
return Glide.using(new FlickrModelLoader(getActivity(), urlCache))
.load(item)
.centerCrop();
}
}
private class PhotoAdapter extends BaseAdapter {
private List<Photo> photos = new ArrayList<Photo>(0);
private final LayoutInflater inflater;
......@@ -135,6 +155,6 @@ public class FlickrPhotoGrid extends SherlockFragment implements PhotoViewer {
imagePresenter.setModel(current);
return view;
}
}
}
}
......@@ -21,6 +21,7 @@ import com.bumptech.glide.resize.ImageManager;
import com.bumptech.glide.resize.cache.DiskCache;
import com.bumptech.glide.resize.cache.DiskCacheAdapter;
import com.bumptech.glide.resize.cache.DiskLruCacheWrapper;
import com.bumptech.glide.resize.cache.LruMemoryCache;
import com.bumptech.glide.samples.flickr.api.Api;
import com.bumptech.glide.samples.flickr.api.Photo;
import com.bumptech.glide.util.Log;
......@@ -94,6 +95,7 @@ public class FlickrSearchActivity extends SherlockFragmentActivity {
glide.setImageManager(new ImageManager.Builder(this)
.setBitmapCompressQuality(70)
.setMemoryCache(new LruMemoryCache(ImageManager.getSafeMemoryCacheSize(this)/4))
.setDiskCache(diskCache));
}
......
......@@ -90,7 +90,7 @@ public class Api {
}
private static String getSearchUrl(String text) {
return getUrlForMethod("flickr.photos.search") + "&text=" + text + "&per_page=500";
return getUrlForMethod("flickr.photos.search") + "&text=" + text + "&per_page=300";
}
private static String getPhotoUrl(Photo photo, String sizeKey) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册