Back to project page Android-J0Loader.
The source code is released under:
MIT License
If you think the Android project Android-J0Loader listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * The MIT License Copyright (c) 2014 Krayushkin Konstantin (jangokvk@gmail.com) *//from w w w . j av a 2 s . co m * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package ru.jango.j0loader.image; import java.io.IOException; import java.net.URISyntaxException; import java.util.Collection; import java.util.zip.DataFormatException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; import ru.jango.j0loader.DataLoader; import ru.jango.j0loader.image.cache.Cache; import ru.jango.j0loader.image.cache.DefaultCache; import ru.jango.j0loader.queue.Queue; import ru.jango.j0loader.Request; import ru.jango.j0loader.queue.SingleURIQueue; import ru.jango.j0util.BmpUtil; import ru.jango.j0util.LogUtil; /** * Advanced lightweight image loader. Main features: * <ul> * <li>internal cache - by default images are cached in memory as encoded byte arrays (should be * transformed into {@link android.graphics.Bitmap} with * {@link android.graphics.BitmapFactory#decodeByteArray(byte[], int, int, android.graphics.BitmapFactory.Options)}); * it takes some time to retrieve an image from cache then, but on the other hand pretty much data * could be cached (encoded data is much smaller, than raw pixel data)</li> * <li>smart scaling - loader's clients could specify the desired image size and loader will * automatically and asynchronously (in the downloading thread) scale images before passing it to * clients; for scaling are used algorithms from {@link ru.jango.j0util.BmpUtil}</li> * </ul> * <br> * * Other features: * <ul> * <li>images are cached in already scaled size</li> * <li>only one instance of a single image could be cached - if clients try to load same image with * different scales, would be selected a larger one and an image will be cached in this larger scale; * if clients try to load an image that is already cached but in smaller scale, then the image * would be reloaded, rescaled in new large scale and recached * {@link #addToQueue(ru.jango.j0loader.Request, android.graphics.Point)}</li> * <li>due to that, images could be retrieved from cache by it's {@link java.net.URI}, not a * {@link ru.jango.j0loader.Request}</li> * <li>cache is separated as a standalone class, so you can different caching strategies, * or create your own (default is simple memory cache)</li> * <li>by default cache size is limited by {@link ru.jango.j0loader.image.cache.DefaultCache#DEFAULT_MAX_CACHE_SIZE}; * be aware, that huge cache can cause {@link java.lang.OutOfMemoryError}</li> * <li>loading from cache is transparent for clients - clients don't know was requested image * cached or not (if it was, client just get required image faster)</li> * <li>loading is separated into default and cache threads and queues - defaults come from * {@link ru.jango.j0loader.DataLoader} and cache thread and queue are created and managed by * {@link ru.jango.j0loader.image.ImageLoader} itself</li> * </ul> */ public class ImageLoader extends DataLoader<Bitmap> { private Thread cacheLoaderThread; private Queue cacheQueue; private Cache cache; public ImageLoader() { super(); cacheQueue = createCacheQueue(); } public ImageLoader(LoadingListener<Bitmap> listener) { this(); addLoadingListener(listener); } @Override public void addToQueue(Request request) { doAddToQueue(request); } @Override public void addToQueue(Collection<Request> requests) { for (Request request : requests) doAddToQueue(request); } private void doAddToQueue(Request request) { if (getCache().isCached(request.getURI())) cacheQueue.add(request); else super.addToQueue(request); } /** * Adds an element into loading queues. Automatically checks cache and chooses a queue. Second * parameter specifies a size, in witch image should be cached and returned to the client. <br><br> * If clients try to load a cached image, but with smaller scale, loader will return a cached * image. <br><br> * If clients try to load a cached image, but with larger scale, loader will remove that image * from cache, then reload it from URI, rescale into size, put again in cache and return it to * the client. * * @param request a {@link java.net.URI} where to take the image * @param scale a size, in witch image should be cached and returned to the client; be aware, * that max texture size in Android is 2048x2048, loader automatically handles * appropriate scaling */ public void addToQueue(Request request, Point scale) { if (scaleLarger(scale, getCache().getScale(request.getURI()))) { getCache().setScale(request.getURI(), scale); getCache().remove(request.getURI()); removeFromQueue(request); removeFromCacheQueue(request); } addToQueue(request); } @Override public void removeFromQueue(Request request) { super.removeFromQueue(request); removeFromCacheQueue(request); } @Override public void start() { super.start(); final Thread thread = getCacheLoaderThread(); if (!thread.isAlive()) thread.start(); } /** * Resets the loader to it's initial clear state: <br> * <ul> * <li>stop operations by {@link #stopWorking()}</li> * <li>clear queue by {@link #clearQueue()}</li> * <li>clear cache by {@link ru.jango.j0loader.image.cache.Cache#clear()}</li> * <li>clear queue queue by {@link #clearCacheQueue()}</li> * </ul> */ @Override public void reset() { stopWorking(); clearQueue(); clearCacheQueue(); getCache().clear(); } protected boolean scaleLarger(Point p1, Point p2) { if (p1 == null && p2 == null) return false; if (p1 == null) return false; if (p2 == null) return true; return (p1.x * p1.y) - (p2.x * p2.y) > 100; } @Override protected Queue createQueue() { return new SingleURIQueue(); } /** * Returns internal cache object. By default in {@link ru.jango.j0loader.image.ImageLoader} * it is {@link ru.jango.j0loader.image.cache.DefaultCache}. With help of * {@link #setCache(ru.jango.j0loader.image.cache.Cache)} you can manipulate the whole cache * object at any time. */ public Cache getCache() { if (cache == null) cache = new DefaultCache(); return cache; } /** * Sets internal cache object. With help of {@link #getCache()} you can manipulate the * whole cache object at any time. */ public void setCache(Cache cache) { this.cache = cache; } //////////////////////////////////////////////////////////////////////// // // Cache queue controlling methods // //////////////////////////////////////////////////////////////////////// /** * Removes a {@link Request} from the cache loading queue. If this {@link ru.jango.j0loader.Request} is * already being processed, it could be retrieved by {@link #getCurrentCacheQueueElement()} and the * procession could be stopped by {@link #cancelCurrent()} (stops only the current, not all queue). * * @param request a {@link Request} to remove */ public void removeFromCacheQueue(Request request) { cacheQueue.remove(request); } /** * Returns number of elements in cache queue. */ public int getCacheQueueSize() { return cacheQueue.size(); } /** * Checks if the cache queue has elements. */ public boolean isCacheQueueEmpty() { return cacheQueue.isEmpty(); } /** * Clears cache queue. */ public void clearCacheQueue() { cacheQueue.clear(); } /** * Returns current element in cache queue (witch is processed now) or null. */ public Request getCurrentCacheQueueElement() { return cacheQueue.current(); } /** * Special method for queue configuration. By default {@link ru.jango.j0loader.image.ImageLoader} * creates an instance of {@link ru.jango.j0loader.queue.SingleURIQueue}, but if a queue with * different logic is required, it could be substituted here. * <br><br> * This method with conjunction of {@link ru.jango.j0loader.queue.Queue} hierarchy defines a * usual Iterator pattern. * * @return cache loading queue instance */ protected Queue createCacheQueue() { return new SingleURIQueue(); } //////////////////////////////////////////////////////////////////////// // // Loading methods // //////////////////////////////////////////////////////////////////////// /** * Returns a {@link java.lang.Thread} where the cache queue is executed. In subclasses this * method could be overwritten to provide another thread. */ protected Thread getCacheLoaderThread() { if (!(cacheLoaderThread!=null && cacheLoaderThread.isAlive())) return cacheLoaderThread = new Thread(cacheQueueRunnable); return cacheLoaderThread; } private boolean processFromCache(Request request) { if (getCache().isCached(request.getURI())) { LogUtil.i(ImageLoader.class, "loading from cache: "+request.getURI()); final byte[] raw = getCache().get(request.getURI()); onProcessFinished(request, raw, BitmapFactory.decodeByteArray(raw, 0, raw.length)); return true; } return false; } private void processFromURI(Request request) throws DataFormatException, IOException, URISyntaxException { LogUtil.i(ImageLoader.class, "loading from uri: "+request.getURI()); final byte[] loadedData = load(request); final Point scale = getCache().resolveScale(request.getURI(), loadedData); Bitmap bmp; byte[] rawData; if (scale == null) { bmp = BitmapFactory.decodeByteArray(loadedData, 0, loadedData.length); rawData = loadedData; } else { bmp = BmpUtil.scale(loadedData, BmpUtil.ScaleType.PROPORTIONAL_FIT, scale.x, scale.y); rawData = BmpUtil.bmpToByte(bmp, Bitmap.CompressFormat.PNG, 100); } getCache().put(request.getURI(), rawData); LogUtil.i(ImageLoader.class, "added to cache; cache size bytes: " + getCache().size()); onProcessFinished(request, rawData, bmp); } @Override protected void loadInBackground(Request request) throws Exception { if (!processFromCache(request)) processFromURI(request); } private Runnable cacheQueueRunnable = new Runnable() { @Override public void run() { while (!isCacheQueueEmpty() && canWork()) { final Request request = cacheQueue.next(); try { onProcessStarted(request); loadInBackground(request); } catch (Exception e) { onProcessFailed(request, e); } LogUtil.logMemoryUsage(); } } }; }