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
8 changes: 7 additions & 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_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 Expand Up @@ -98,6 +98,12 @@ dependencies {

compile libraries.timber

compile libraries.retrofit2
compile libraries.converterGson
compile libraries.rxandroid
compile libraries.rxjava
compile libraries.retrofit2AdapterRx

// Developer tools (Developer Settings)
compile libraries.stetho
compile libraries.leakCanary
Expand Down
5 changes: 0 additions & 5 deletions app/src/main/java/ru/yandex/yamblz/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

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

public class App extends Application {
Expand All @@ -33,9 +31,6 @@ public void onCreate() {
DevMetricsProxy devMetricsProxy = applicationComponent.devMetricsProxy();
devMetricsProxy.apply();
}

CollageLoaderManager.init(null); // add implementation
CriticalSectionsManager.init(null); // add implementation
}

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

import android.os.Handler;

import java.util.Collections;
import java.util.Queue;
import java.util.Set;
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 Queue<Task> featureTasks = new ConcurrentLinkedQueue<>();
private final Set<Integer> sections = Collections.newSetFromMap(new ConcurrentHashMap<>());

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 (featureTasks.contains(task)) {
featureTasks.remove(task);
}
if (sections.isEmpty()) {
runTask(task);
} else {
tasks.add(task);
}
}

@Override
public void postLowPriorityTaskDelayed(Task task, int delay) {
if (delay <= 0) {
postLowPriorityTask(task);
} else {
featureTasks.add(task);
uiThreadHandler.postDelayed(() -> postLowPriorityTask(task), delay);
}
}

@Override
public void removeLowPriorityTask(Task task) {
tasks.remove(task);
if (featureTasks.contains(task)) {
uiThreadHandler.removeCallbacks(() -> postLowPriorityTask(task));
featureTasks.remove(task);
}
}

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

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

private void runTask(Task task) {
tasks.remove(task);
uiThreadHandler.post(task::run);
}
}
143 changes: 143 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,143 @@
package ru.yandex.yamblz.loader;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.widget.ImageView;

import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import ru.yandex.yamblz.handler.CriticalSectionsManager;
import ru.yandex.yamblz.handler.Task;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;

public class DefaultCollageLoader implements CollageLoader {
private CollageStrategy defaultStrategy = new SquareCollageStrategy();
private CompositeSubscription compositeSubscription;
private Map<Object, Subscription> subscriptionsMap = new WeakHashMap<>();
private LruCache<List<String>, Bitmap> bitmapCache;

private Map<ImageTarget, WeakReference<Task>> loadingTasks = new WeakHashMap<>();


// Начитался https://habrahabr.ru/post/265997/
public DefaultCollageLoader(CompositeSubscription compositeSubscription) {
this.compositeSubscription = compositeSubscription;

int cacheSize = (int) (Runtime.getRuntime().maxMemory());

bitmapCache = new LruCache<List<String>, Bitmap>(cacheSize) {
@Override
protected int sizeOf(List<String> key, Bitmap value) {
return value.getByteCount();
}
};
}

@Override
public void loadCollage(List<String> urls, ImageView imageView) {
loadCollage(urls, new SimpleImageTarget(imageView), defaultStrategy);
}

@Override
public void loadCollage(List<String> urls, ImageTarget imageTarget) {
loadCollage(urls, imageTarget, defaultStrategy);
}

@Override
public void loadCollage(List<String> urls, ImageView imageView,
CollageStrategy collageStrategy) {
loadCollage(urls, new SimpleImageTarget(imageView), collageStrategy);
}

@Override
public void loadCollage(List<String> urls, ImageTarget imageTarget,
CollageStrategy collageStrategy) {
if (subscriptionsMap.containsKey(imageTarget)) {
compositeSubscription.remove(subscriptionsMap.get(imageTarget));
subscriptionsMap.remove(imageTarget);
}


Subscription loading = Observable
.concat(collageFromCache(urls), collageFromNetwork(urls, collageStrategy))
.first(bitmap -> bitmap != null)
.doOnNext(bitmap -> bitmapCache.put(urls, bitmap))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
(bitmap) -> {
if (loadingTasks.containsKey(imageTarget)) {
CriticalSectionsManager.getHandler()
.removeLowPriorityTask(loadingTasks.get(imageTarget).get());
}
Task task = () -> imageTarget.onLoadBitmap(bitmap);
CriticalSectionsManager.getHandler().postLowPriorityTask(task);
loadingTasks.put(imageTarget, new WeakReference<>(task));
},
e -> Log.e("E:DefaultCollageLoader", "Collage downloading failed: " + e),
() -> Log.d("D:DefaultCollageLoader", "Collage downloading completed")
);
subscriptionsMap.put(imageTarget, loading);
compositeSubscription.add(loading);
}

private Observable<Bitmap> collageFromCache(List<String> urls) {
return Observable.create(subscriber -> {
subscriber.onNext(bitmapCache.get(urls));
subscriber.onCompleted();
});
}

private Observable<Bitmap> collageFromNetwork(List<String> urls, CollageStrategy collageStrategy) {
return Observable.from(urls)
.flatMap(url -> getBitmapObservableFromURL(url).subscribeOn(Schedulers.io()))
.toList()
.map(collageStrategy::create);
}

private Observable<Bitmap> getBitmapObservableFromURL(String url) {
return Observable.fromCallable(
() -> getBitmapFromURL(url)
);
}

private Bitmap getBitmapFromURL(String src) {
Log.e("THREAD", " " + Thread.currentThread().toString());
InputStream inputStream = null;
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL(src).openConnection();
connection.setDoInput(true);
connection.connect();
inputStream = connection.getInputStream();
return BitmapFactory.decodeStream(inputStream);
} catch (IOException e) {
Log.e("E:DefaultCollageLoader", "Image downloading failed: " + src);
return null;
} finally {
try {
if (connection != null) {
connection.disconnect();
}
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
22 changes: 22 additions & 0 deletions app/src/main/java/ru/yandex/yamblz/loader/SimpleImageTarget.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 SimpleImageTarget implements ImageTarget {
private final WeakReference<ImageView> refImageView;

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

@Override
public void onLoadBitmap(Bitmap bitmap) {
ImageView imageView = refImageView.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ru.yandex.yamblz.loader;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import static android.graphics.Bitmap.Config.ARGB_8888;

class SquareCollageStrategy implements CollageStrategy {
@Override
public Bitmap create(List<Bitmap> bitmaps) {

if (bitmaps.isEmpty()) {
return null;
}

Collections.shuffle(bitmaps);

if (bitmaps.size() < 4) {
return bitmaps.get(0);
}

int imgPerSide = (int) Math.sqrt(bitmaps.size());
int totalSize = bitmaps.get(0).getWidth();
int imgSize = totalSize / imgPerSide;

Bitmap collage = Bitmap.createBitmap(totalSize, totalSize, ARGB_8888);
Canvas canvas = new Canvas(collage);

Iterator<Bitmap> iterator = bitmaps.iterator();
for (int row = 0; row < imgPerSide; row++) {
for (int col = 0; col < imgPerSide; col++) {
int left = col * imgSize;
int top = row * imgSize;
int right = totalSize - (imgPerSide - 1 - col) * imgSize;
int bottom = totalSize - (imgPerSide - 1 - row) * imgSize;
canvas.drawBitmap(iterator.next(), null, new Rect(left, top, right, bottom), null);
}
}

return collage;
}
}
Loading