Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@

Пример - при скролле списка не сетить Bitmap'ы в ImageView

# Demo

![](demo.gif)
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ android {
buildToolsVersion versions.buildTools

defaultConfig {
applicationId 'ru.yandex.yamblz'
applicationId 'ru.yandex.yamblz.euv.background'
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
versionCode versions.code // Notice that you may want to use BUILD_NUMBER from CI in real project with own CI.
Expand Down
7 changes: 5 additions & 2 deletions app/src/main/java/ru/yandex/yamblz/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import android.app.Application;
import android.content.Context;
import android.os.Handler;
import android.support.annotation.NonNull;

import ru.yandex.yamblz.developer_settings.DevMetricsProxy;
import ru.yandex.yamblz.developer_settings.DeveloperSettingsModel;
import ru.yandex.yamblz.handler.CriticalSectionsManager;
import ru.yandex.yamblz.handler.DefaultCriticalSectionsHandler;
import ru.yandex.yamblz.loader.CollageLoaderManager;
import ru.yandex.yamblz.loader.DefaultCollageLoader;
import timber.log.Timber;

public class App extends Application {
Expand All @@ -34,8 +37,8 @@ public void onCreate() {
devMetricsProxy.apply();
}

CollageLoaderManager.init(null); // add implementation
CriticalSectionsManager.init(null); // add implementation
CollageLoaderManager.init(new DefaultCollageLoader(getResources()));
CriticalSectionsManager.init(new DefaultCriticalSectionsHandler(new Handler(getMainLooper())));
}

@NonNull
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package ru.yandex.yamblz.handler;

import android.os.Handler;

import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

public class DefaultCriticalSectionsHandler implements CriticalSectionsHandler {
private final Handler uiThreadHandler;
private final Queue<Task> tasks = new ConcurrentLinkedQueue<>();
private final Set<Integer> sections = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Map<Task, WeakReference<Runnable>> futureTasks = Collections.synchronizedMap(new WeakHashMap<>());

public DefaultCriticalSectionsHandler(Handler uiThreadHandler) {
this.uiThreadHandler = uiThreadHandler;
}


@Override
public void startSection(int id) {
sections.add(id);
}


@Override
public void stopSection(int id) {
sections.remove(id);
if (sections.isEmpty()) {
runTasks();
}
}


@Override
public void stopSections() {
sections.clear();
runTasks();
}


@Override
public void postLowPriorityTask(Task task) {
if (sections.isEmpty()) {
runTask(task);
} else {
tasks.add(task);
}
}


@Override
public void postLowPriorityTaskDelayed(Task task, int delay) {
if (delay <= 0) {
postLowPriorityTask(task);
return;
}

Runnable futureTask = () -> postLowPriorityTask(task);
futureTasks.put(task, new WeakReference<>(futureTask));
uiThreadHandler.postDelayed(futureTask, delay);
}


@Override
public void removeLowPriorityTask(Task task) {
tasks.remove(task);

WeakReference<Runnable> refFutureTask = futureTasks.remove(task);
if (refFutureTask != null) {
Runnable futureTask = refFutureTask.get();
if (futureTask != null) {
uiThreadHandler.removeCallbacks(futureTask);
}
}
}


@Override
public void removeLowPriorityTasks() {
for (Task task : tasks) {
removeLowPriorityTask(task);
}
}


private void runTasks() {
for (Task task : tasks) {
runTask(task);
}
}


private void runTask(Task task) {
tasks.remove(task);
uiThreadHandler.post(task::run);
}
}
10 changes: 4 additions & 6 deletions app/src/main/java/ru/yandex/yamblz/loader/CollageLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@

import android.widget.ImageView;

import java.util.List;

public interface CollageLoader {

void loadCollage(List<String> urls, ImageView imageView);
void loadCollage(int[] ids, ImageView imageView);

void loadCollage(List<String> urls, ImageTarget imageTarget);
void loadCollage(int[] ids, ImageTarget imageTarget);

void loadCollage(List<String> urls, ImageView imageView, CollageStrategy collageStrategy);
void loadCollage(int[] ids, ImageView imageView, CollageStrategy collageStrategy);

void loadCollage(List<String> urls, ImageTarget imageTarget, CollageStrategy collageStrategy);
void loadCollage(int[] ids, ImageTarget imageTarget, CollageStrategy collageStrategy);

}
196 changes: 196 additions & 0 deletions app/src/main/java/ru/yandex/yamblz/loader/DefaultCollageLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package ru.yandex.yamblz.loader;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.os.Process.setThreadPriority;

public class DefaultCollageLoader implements CollageLoader {
private static final String TAG = DefaultCollageLoader.class.getSimpleName();
private static final long MEMORY_MAX = Runtime.getRuntime().maxMemory();
private static final float MEMORY_USE_THRESHOLD = 0.9f;

private static ExecutorService executorService = Executors.newCachedThreadPool();

private Resources resources;
private LruCache<Integer, Bitmap> bitmapCache;
private Map<Object, WeakReference<AsyncCollageLoader>> asyncLoaders = new WeakHashMap<>();

public DefaultCollageLoader(Resources resources) {
this.resources = resources;

int maxCacheSize = (int) (MEMORY_MAX / 1024 / 2);
bitmapCache = new LruCache<Integer, Bitmap>(maxCacheSize) {
@Override
protected int sizeOf(Integer key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
}


@Override
public void loadCollage(int[] ids, ImageView imageView) {
loadCollage(ids, imageView, null, null);
}


@Override
public void loadCollage(int[] ids, ImageTarget imageTarget) {
loadCollage(ids, null, imageTarget, null);

}


@Override
public void loadCollage(int[] ids, ImageView imageView, CollageStrategy collageStrategy) {
loadCollage(ids, imageView, null, collageStrategy);

}


@Override
public void loadCollage(int[] ids, ImageTarget imageTarget, CollageStrategy collageStrategy) {
loadCollage(ids, null, imageTarget, collageStrategy);
}


private void loadCollage(int[] ids, ImageView iv, ImageTarget it, CollageStrategy strategy) {
if (strategy == null) {
strategy = new SquareCollageStrategy();
}

// If there is a loader task for the same target, cancel it
Object key = (iv == null) ? it : iv;
WeakReference<AsyncCollageLoader> refLoader = asyncLoaders.remove(key);
if (refLoader != null) {
AsyncCollageLoader loader = refLoader.get();
if (loader != null) {
loader.cancel(false);
}
}

WeakReference<ImageView> refImageView = new WeakReference<>(iv);
WeakReference<ImageTarget> refImageTarget = new WeakReference<>(it);

AsyncCollageLoader loader = new AsyncCollageLoader(ids, refImageView, refImageTarget, strategy);
loader.execute();

asyncLoaders.put(key, new WeakReference<>(loader));
}


/**
* Asynchronously loads (or gets from cache) all the images whose ids are specified.
* <p>
* There is a drawback in current architecture: since only a {@link CollageStrategy}
* knows all the details about collage management, we can't load only a useful
* part of images, nor we can load a scaled down images (the latter is also impossible
* since an {@link ImageTarget} is unable to tell the desired width and height).
*/
private class AsyncCollageLoader extends AsyncTask<Void, Void, Bitmap> {
private int[] ids;
private WeakReference<ImageView> refImageView;
private WeakReference<ImageTarget> refImageTarget;
private CollageStrategy collageStrategy;

AsyncCollageLoader(int[] ids, WeakReference<ImageView> iv, WeakReference<ImageTarget> it, CollageStrategy strategy) {
this.ids = ids;
this.refImageView = iv;
this.refImageTarget = it;
this.collageStrategy = strategy;
}


@Override
protected Bitmap doInBackground(Void... params) {
List<Future<Bitmap>> futures = new ArrayList<>(ids.length);
List<Bitmap> bitmaps = new ArrayList<>(ids.length);

for (int id : ids) {
Bitmap bitmap = bitmapCache.get(id);
if (bitmap == null) {
futures.add(executorService.submit(() -> {
setThreadPriority(THREAD_PRIORITY_BACKGROUND);

Bitmap bmp = loadBitmap(id);
if (bmp == null) {
return null;
}

bitmapCache.put(id, bmp);
return bmp;
}));
} else {
bitmaps.add(bitmap);
}
}

for (Future<Bitmap> future : futures) {
try {
Bitmap bitmap = future.get();
if (bitmap != null) {
bitmaps.add(bitmap);
}
if (isCancelled()) return null;
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}

return collageStrategy.create(bitmaps);
}


@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap == null) {
return;
}

ImageView imageView = refImageView.get();
if (imageView != null) {
imageView.setAlpha(0f);
imageView.setImageBitmap(bitmap);
imageView.animate().alpha(1).setDuration(500).start();
}

ImageTarget imageTarget = refImageTarget.get();
if (imageTarget != null) {
imageTarget.onLoadBitmap(bitmap);
}
}


private Bitmap loadBitmap(int resId) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, resId, options);
long bitmapSize = options.outWidth * options.outHeight * 4;

long memoryRequired = Runtime.getRuntime().totalMemory() + bitmapSize;
if (memoryRequired > MEMORY_MAX * MEMORY_USE_THRESHOLD) {
Log.d(TAG, "Could not load a new image because of exceeding memory consumption limit");
return null;
}

return BitmapFactory.decodeResource(resources, resId);
}
}
}
22 changes: 22 additions & 0 deletions app/src/main/java/ru/yandex/yamblz/loader/ImageViewTarget.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ru.yandex.yamblz.loader;

import android.graphics.Bitmap;
import android.widget.ImageView;

import java.lang.ref.WeakReference;

public class ImageViewTarget implements ImageTarget {
private final WeakReference<ImageView> refImageView;

public ImageViewTarget(ImageView imageView) {
refImageView = new WeakReference<>(imageView);
}

@Override
public void onLoadBitmap(Bitmap bitmap) {
ImageView imageView = refImageView.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
Loading