
com.azcltd.fluffyimageloader.loader.ResourcesLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fluffy-image-loader Show documentation
Show all versions of fluffy-image-loader Show documentation
Library that helps loading and caching images in Android applications
The newest version!
package com.azcltd.fluffyimageloader.loader;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import com.azcltd.fluffyimageloader.cache.DefaultCacheKeyGenerator;
import com.azcltd.fluffyimageloader.cache.DiskCache;
import com.azcltd.fluffyimageloader.cache.ICacheKeyGenerator;
import com.azcltd.fluffyimageloader.loader.ResourcesLoadingManager.LoadingState;
import com.squareup.okhttp.OkHttpClient;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public abstract class ResourcesLoader {
public static final long MIN_LOADING_DELAY = 100;
protected static final String TAG = "ResourcesLoader";
private static final int MAX_EXTERNAL_CACHE_SIZE = 100 * 1024 * 1024;
private static final int MAX_INTERNAL_CACHE_SIZE = 30 * 1024 * 1024;
private static final int DOWNLOAD_THREAD_POOL_SIZE = 4;
private static final int LOCAL_LOADER_THREAD_POOL_SIZE = 2;
private static final ICacheKeyGenerator DEFAULT_CACHE_KEY_GENERATOR = new DefaultCacheKeyGenerator();
private boolean mIsVerbose = false;
private final Context mAppContext;
private final DiskCache mDiskCache;
private final ResourcesLoadingManager mLoadingManager;
private ICacheKeyGenerator mCacheKeyGenerator;
private Thread mManagerThread;
private final ExecutorService mDownloadThreadPool;
private final ExecutorService mLocalLoaderThreadPool;
private final OkHttpClient mHttpClient;
private final Handler mHandler;
public ResourcesLoader(Context appContext) {
this(appContext, MAX_EXTERNAL_CACHE_SIZE, MAX_INTERNAL_CACHE_SIZE);
}
public ResourcesLoader(Context appContext, int maxExternalCacheSize, int maxInternalCacheSize) {
mAppContext = appContext;
mDiskCache = new DiskCache(appContext, maxExternalCacheSize, maxInternalCacheSize);
mLoadingManager = new ResourcesLoadingManager();
mDownloadThreadPool = Executors.newFixedThreadPool(DOWNLOAD_THREAD_POOL_SIZE);
mLocalLoaderThreadPool = Executors.newFixedThreadPool(LOCAL_LOADER_THREAD_POOL_SIZE);
mHttpClient = new OkHttpClient();
// mHttpClient = ConcurrentHttpClient.createHttpClient(DOWNLOAD_THREAD_POOL_SIZE);
mHandler = new LoadHandler(mLoadingManager);
}
public void setCacheKeyGenerator(ICacheKeyGenerator generator) {
mCacheKeyGenerator = generator;
}
public void setVerbose(boolean verbose) {
mIsVerbose = verbose;
}
public DiskCache getDiskCache() {
return mDiskCache;
}
public String toCacheKey(String uri) {
return (mCacheKeyGenerator == null ? DEFAULT_CACHE_KEY_GENERATOR : mCacheKeyGenerator).toCacheKey(uri);
}
public boolean isVerbose() {
return mIsVerbose;
}
protected void loadResource(ResourceSpecs specs) {
if (specs == null) return;
String uri = specs.getUri();
if (uri == null || uri.length() == 0) {
if (isVerbose()) Log.d(TAG, "1. Resource was not loaded, uri is empty");
specs.onLoaded(null, true, false);
return;
}
T res = getFromMemoryCache(toCacheKey(uri));
if (res != null) {
if (isVerbose()) Log.d(TAG, "1. Resource is loaded from memory cache in same moment: " + uri);
specs.onLoaded(res, true, false);
} else {
if (isVerbose()) Log.d(TAG, "1. Resource is posted to the queue: " + uri);
specs.onPrepare();
mLoadingManager.addSpecs(specs);
}
if (mManagerThread == null) {
mManagerThread = new Thread(new ManagerTask());
mManagerThread.start();
}
}
protected abstract T getFromMemoryCache(String key);
protected abstract void putToMemoryCache(String key, T res);
/**
* @param in InputStream from which resource should be loaded. Should be closed inside this method! May be null.
* @return Will be called from background thread to get resource object.
*/
protected abstract T loadFromStream(InputStream in, Collection> specsList);
private InputStream openFileUriAsInputStream(String fileUri) {
try {
return mAppContext.getContentResolver().openInputStream(Uri.parse(fileUri));
} catch (FileNotFoundException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
return null;
}
}
private T saveLoadedResource(String uri, InputStream in, boolean skipDiskCache) {
Set> specsList = mLoadingManager.getSpecsList(uri);
if (specsList == null) return null;
String key = toCacheKey(uri);
T res;
if (!skipDiskCache && ResourceSpecs.isUseDiskCache(specsList)) {
// Saving stream to cached file and then reading from this file
mDiskCache.save(key, in);
InputStream in2 = openFileUriAsInputStream(mDiskCache.get(key));
res = loadFromStream(in2, specsList);
} else {
// Reading straight from given stream
res = loadFromStream(in, specsList);
}
// Saving in memory cache if needed
if (res != null && ResourceSpecs.isUseMemoryCache(specsList)) putToMemoryCache(key, res);
return res;
}
private void notifyLoaded(String uri, T res, boolean fromMemory, boolean fromDisk) {
mLoadingManager.setResult(uri, res);
mLoadingManager.setState(uri, LoadingState.WAIT_DISPLAYING);
int action;
if (fromMemory) {
action = LoadHandler.ACTION_ON_LOADED_FROM_MEMORY;
} else if (fromDisk) {
action = LoadHandler.ACTION_ON_LOADED_FROM_DISK;
} else {
action = LoadHandler.ACTION_ON_LOADED;
}
mHandler.sendMessage(mHandler.obtainMessage(action, uri));
}
private void scheduleDownload(String uri) {
if (hasInternetConnection()) {
mLoadingManager.setState(uri, LoadingState.WAIT_DOWNLOADING);
mDownloadThreadPool.submit(new DownloadTask(uri));
} else {
if (isVerbose()) Log.d(TAG, "No internet connection is available");
notifyLoaded(uri, null, true, false);
}
}
/**
* Checks if the device has Internet connection.
*/
private boolean hasInternetConnection() {
int permissionCheck = mAppContext.getPackageManager().checkPermission(Manifest.permission.ACCESS_NETWORK_STATE,
mAppContext.getPackageName());
if (permissionCheck != PackageManager.PERMISSION_GRANTED) return true;
ConnectivityManager cm = (ConnectivityManager) mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo wifiNetwork = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (wifiNetwork != null && wifiNetwork.isConnected()) return true;
NetworkInfo mobileNetwork = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
if (mobileNetwork != null && mobileNetwork.isConnected()) return true;
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnected();
}
private void scheduleLocalLoader(String uri, String cachedFileUri) {
mLoadingManager.setState(uri, LoadingState.WAIT_LOADING);
mLocalLoaderThreadPool.submit(new LocalLoaderTask(uri, cachedFileUri));
}
private void fillHttpHeaders(HttpURLConnection connection, String uri) {
Set> specsList = mLoadingManager.getSpecsList(uri);
if (specsList == null) return;
// Getting first specs from set for given URI
ResourceSpecs lastSpecs = specsList.size() == 0 ? null : specsList.iterator().next();
if (lastSpecs == null || lastSpecs.getHeaders() == null) return;
// Adding headers to request
for (Map.Entry pair : lastSpecs.getHeaders().entrySet()) {
connection.addRequestProperty(pair.getKey(), pair.getValue());
}
}
private class ManagerTask extends FailSafeRunnable {
@Override
protected void runSafe() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_FOREGROUND);
try {
while (true) {
String uri;
synchronized (mLoadingManager) {
uri = mLoadingManager.getNextUriToManage(false);
boolean isEmptyQueue = mLoadingManager.getNextUriToManage(true) == null;
if (uri == null) {
if (isVerbose()) Log.d(TAG, "2. Manager thread is waiting for another resource to load");
if (isEmptyQueue) {
// There are no waiting resources, so we can wait infinitely
mLoadingManager.wait();
} else {
// There is other waiting resource, so we should check it again after a small delay
mLoadingManager.wait(MIN_LOADING_DELAY);
}
continue;
}
}
mLoadingManager.setState(uri, LoadingState.MANAGING);
UriHelper uriHelper = new UriHelper(uri);
if (!mLoadingManager.isOutdated(uri)) {
T res = getFromMemoryCache(toCacheKey(uri));
if (res != null) {
if (isVerbose()) Log.d(TAG, "2. Resource is found in memory cache: " + uri);
notifyLoaded(uri, res, true, false);
} else if (mDiskCache.isExists(toCacheKey(uri))) {
if (isVerbose())
Log.d(TAG, "2. Resource is found in disk cache, scheduling loader: " + uri);
scheduleLocalLoader(uri, mDiskCache.get(toCacheKey(uri)));
} else if (uriHelper.isLocal()) {
if (isVerbose())
Log.d(TAG, "2. No resources found in cache, scheduling local loader: " + uri);
scheduleLocalLoader(uri, null);
} else if (uriHelper.isRemote()) {
if (isVerbose()) Log.d(TAG, "2. No resources found in cache, scheduling download: " + uri);
scheduleDownload(uri);
} else {
if (isVerbose()) Log.d(TAG, "2. Unknown Uri scheme, skipping resource: " + uri);
notifyLoaded(uri, null, true, false);
}
} else {
if (isVerbose())
Log.d(TAG, "2. Resource was outdated and will not be loaded (manager thread): " + uri);
}
}
} catch (InterruptedException e) {
// Thread will be closed
}
mManagerThread = null;
}
}
private class DownloadTask extends FailSafeRunnable {
private String mUri;
public DownloadTask(String uri) {
mUri = uri;
}
@Override
protected void runSafe() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 8);
String uri = mUri;
if (uri == null) return;
if (!mLoadingManager.isOutdated(uri)) {
T res = getFromMemoryCache(toCacheKey(uri));
if (res != null) {
if (isVerbose())
Log.w(TAG, "3. Resource was found in memory cache - no downloading is needed: " + uri);
notifyLoaded(uri, res, true, false);
} else if (mDiskCache.isExists(toCacheKey(uri))) {
if (isVerbose()) Log.w(TAG, "3. Resource was found on disk - no downloading is needed: " + uri);
scheduleLocalLoader(uri, mDiskCache.get(toCacheKey(uri)));
} else {
if (isVerbose()) Log.d(TAG, "3. Starting download process for resource: " + uri);
mLoadingManager.setState(uri, LoadingState.DOWNLOADING);
mHandler.sendMessage(mHandler.obtainMessage(LoadHandler.ACTION_ON_START, uri));
// TODO: add progress
InputStream in = null;
try {
HttpURLConnection connection = mHttpClient.open(new URL(uri));
fillHttpHeaders(connection, uri);
int statusCode = connection.getResponseCode();
boolean isOk = statusCode / 100 == 2;
if (isOk) {
in = connection.getInputStream();
res = saveLoadedResource(uri, in, false);
if (isVerbose())
Log.d(TAG, "3. Resource downloading is " + (res == null ? "failed" : "succeeded") + ": " + uri);
} else {
if (isVerbose())
Log.d(TAG, "3. Resource downloading is failed, http status code " + statusCode + ": " + uri);
}
} catch (Exception e) {
if (isVerbose())
Log.d(TAG, "3. Exception while downloading resource: " + e.getMessage() + " (" + uri + ")");
} finally {
if (in != null)
try {
in.close();
} catch (Exception ignored) {
}
}
notifyLoaded(uri, res, false, false);
}
} else {
if (isVerbose()) Log.d(TAG, "3. Resource was outdated before downloading: " + uri);
}
}
}
private class LocalLoaderTask extends FailSafeRunnable {
private String mUri;
private String mCachedFileUri;
public LocalLoaderTask(String uri, String cachedFileUri) {
mUri = uri;
mCachedFileUri = cachedFileUri;
}
@Override
protected void runSafe() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_FOREGROUND);
String uri = mUri;
if (uri == null) return;
if (!mLoadingManager.isOutdated(uri)) {
T res = getFromMemoryCache(toCacheKey(uri));
if (res != null) {
if (isVerbose()) Log.w(TAG, "4. Resource is found in memory cache: " + uri);
notifyLoaded(uri, res, true, false);
} else if (mCachedFileUri != null) {
if (isVerbose()) Log.d(TAG, "4. Loading resource from disk cache: " + uri);
mLoadingManager.setState(uri, LoadingState.LOADING);
InputStream in = openFileUriAsInputStream(mCachedFileUri);
res = saveLoadedResource(mUri, in, true);
notifyLoaded(uri, res, false, true);
} else {
if (isVerbose()) Log.d(TAG, "4. Loading local resource: " + uri);
mLoadingManager.setState(uri, LoadingState.LOADING);
InputStream in = openFileUriAsInputStream(mUri);
res = saveLoadedResource(mUri, in, false);
notifyLoaded(uri, res, false, true);
}
if (isVerbose())
Log.d(TAG, "4. Resource loading is " + (res == null ? "failed" : "succeeded") + ": " + uri);
} else {
if (isVerbose()) Log.d(TAG, "4. Resource was outdated before loading: " + uri);
}
}
}
private abstract static class FailSafeRunnable implements Runnable {
@Override
public final void run() {
try {
runSafe();
} catch (Throwable e) {
Log.e(TAG, "Thread was finished with error", e);
}
}
protected abstract void runSafe();
}
private static class LoadHandler extends Handler {
public static final int ACTION_ON_START = 0;
public static final int ACTION_ON_LOADED = 1;
public static final int ACTION_ON_LOADED_FROM_MEMORY = 2;
public static final int ACTION_ON_LOADED_FROM_DISK = 3;
private ResourcesLoadingManager mLoadingManager;
private LoadHandler(ResourcesLoadingManager loadingManager) {
mLoadingManager = loadingManager;
}
@Override
public void handleMessage(Message msg) {
String uri = (String) msg.obj;
if (mLoadingManager.isOutdated(uri)) return;
switch (msg.what) {
case ACTION_ON_START: {
Set> set = mLoadingManager.getSpecsList(uri);
if (set == null) break;
for (ResourceSpecs specs : set)
specs.onStart();
break;
}
case ACTION_ON_LOADED_FROM_MEMORY:
case ACTION_ON_LOADED_FROM_DISK:
case ACTION_ON_LOADED: {
boolean fromMemory = (msg.what == ACTION_ON_LOADED_FROM_MEMORY);
boolean fromDisk = (msg.what == ACTION_ON_LOADED_FROM_DISK);
T res = mLoadingManager.getResult(uri);
Set> set = mLoadingManager.remove(uri);
for (ResourceSpecs specs : set)
specs.onLoaded(res, fromMemory, fromDisk);
break;
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy