From 699187be22175370dc027a9ffa8edfaf20c0e60f Mon Sep 17 00:00:00 2001 From: aleien Date: Sun, 31 Jul 2016 18:24:43 +0300 Subject: [PATCH 01/10] Basic parallel downloading --- app/build.gradle | 20 +- .../java/ru/yandex/yamblz/data/Artist.java | 36 +++ .../ru/yandex/yamblz/data/ArtistsApi.java | 15 ++ .../java/ru/yandex/yamblz/data/Genre.java | 51 ++++ .../ui/fragments/ArtistsLoadingPresenter.java | 68 ++++++ .../yamblz/ui/fragments/ContentFragment.java | 65 +++++ .../yamblz/ui/fragments/GenreAdapter.java | 229 ++++++++++++++++++ app/src/main/res/layout/fragment_content.xml | 9 +- .../main/res/layout/item_artists_genre.xml | 34 +++ app/src/main/res/layout/item_placeholder.xml | 13 + dependencies.gradle | 6 +- 11 files changed, 537 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/ru/yandex/yamblz/data/Artist.java create mode 100644 app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java create mode 100644 app/src/main/java/ru/yandex/yamblz/data/Genre.java create mode 100644 app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java create mode 100644 app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java create mode 100644 app/src/main/res/layout/item_artists_genre.xml create mode 100644 app/src/main/res/layout/item_placeholder.xml diff --git a/app/build.gradle b/app/build.gradle index 445372a..c9b0998 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,7 +15,8 @@ android { applicationId 'ru.yandex.yamblz' 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. + versionCode versions.code + // Notice that you may want to use BUILD_NUMBER from CI in real project with own CI. versionName versions.name // Make generated apks contain version name and code. @@ -121,6 +122,23 @@ dependencies { exclude module: 'recyclerview-v7' exclude module: 'support-v4' } + + // Web requests + compile 'com.squareup.retrofit2:retrofit:2.1.0' + compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' + compile 'com.squareup.okhttp3:logging-interceptor:3.3.0' + compile 'com.squareup.retrofit2:converter-gson:2.1.0' + + // Json parsing + compile 'com.google.code.gson:gson:2.7' + compile 'me.tatarka:auto-parcel-gson:0.1' + apt 'me.tatarka:auto-parcel-gson-processor:0.1' + + //RX <3 + compile 'io.reactivex:rxandroid:1.2.1' + compile 'io.reactivex:rxjava:1.1.6' + + compile 'com.github.bumptech.glide:glide:3.7.0' } configurations.all { diff --git a/app/src/main/java/ru/yandex/yamblz/data/Artist.java b/app/src/main/java/ru/yandex/yamblz/data/Artist.java new file mode 100644 index 0000000..3544230 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/data/Artist.java @@ -0,0 +1,36 @@ +package ru.yandex.yamblz.data; + +import java.util.List; + +public class Artist { + public final int id; + public final String name; + public final List genres; + public final int tracks; + public final int albums; + public final String link; + public final String description; + public final Cover cover; + + public Artist(int id, String name, List genres, int tracks, int albums, String link, String description, Cover cover) { + this.id = id; + this.name = name; + this.genres = genres; + this.tracks = tracks; + this.albums = albums; + this.link = link; + this.description = description; + this.cover = cover; + } + + public static class Cover { + public final String small; + public final String big; + + public Cover(String small, String big) { + this.small = small; + this.big = big; + } + } + +} diff --git a/app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java b/app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java new file mode 100644 index 0000000..6f181af --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java @@ -0,0 +1,15 @@ +package ru.yandex.yamblz.data; + +import java.util.List; + +import retrofit2.http.GET; +import rx.Single; + +/** + * Created by aleien on 31.07.16. + */ + +public interface ArtistsApi { + @GET("artists.json") + Single> getArtists(); +} diff --git a/app/src/main/java/ru/yandex/yamblz/data/Genre.java b/app/src/main/java/ru/yandex/yamblz/data/Genre.java new file mode 100644 index 0000000..afb217e --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/data/Genre.java @@ -0,0 +1,51 @@ +package ru.yandex.yamblz.data; + +import android.net.Uri; +import android.support.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by aleien on 31.07.16. + */ + +public class Genre { + private String name; + @Nullable + private List artists; + @Nullable + private Uri cachedImage; + + public Genre(String name, List artists) { + this.name = name; + this.artists = artists; + } + + public String getName() { + return name; + } + + public List getArtists() { + return artists; + } + + public List getCollageUrls() { + List urls = new ArrayList<>(); + for (Artist artist : artists) { + urls.add(artist.cover.small); + if (urls.size() >= 4) return urls; + } + + return urls; + } + + @Nullable + public Uri getImage() { + return cachedImage; + } + + public void saveImage(@Nullable Uri cachedImage) { + this.cachedImage = cachedImage; + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java new file mode 100644 index 0000000..c2c8e7f --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java @@ -0,0 +1,68 @@ +package ru.yandex.yamblz.ui.fragments; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import ru.yandex.yamblz.data.Artist; +import ru.yandex.yamblz.data.ArtistsApi; +import ru.yandex.yamblz.data.Genre; +import ru.yandex.yamblz.ui.presenters.Presenter; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; +import timber.log.Timber; + +/** + * Created by aleien on 31.07.16. + */ + +public class ArtistsLoadingPresenter extends Presenter { + + CompositeSubscription subs = new CompositeSubscription(); + private ArtistsApi artistsApi; + + ArtistsLoadingPresenter(ArtistsApi artistsApi) { + this.artistsApi = artistsApi; + } + + void loadArtists() { + subs.add(artistsApi.getArtists() + .subscribeOn(Schedulers.io()) + .map(this::extractGenres) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(list -> view().showContent(list), + Throwable::printStackTrace)); + } + + private List extractGenres(List artistsList) { + ConcurrentHashMap> genres = new ConcurrentHashMap<>(); + List genreList = new ArrayList<>(); + + for (Artist artist : artistsList) { + for (String genreName : artist.genres) { + if (genres.containsKey(genreName)) { + genres.get(genreName).add(artist); + } else { + if (genreName != null && !genreName.equals("")) { + genres.put(genreName, new ArrayList() {{ + add(artist); + }}); + + } + } + } + } + + for (Map.Entry> entry : genres.entrySet()) { +// Timber.d("Key = %s, Value = %s", entry.getKey(), entry.getValue()); + genreList.add(new Genre(entry.getKey(), entry.getValue())); + } + + return genreList; + + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index d46490f..0cef709 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -3,16 +3,81 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import java.util.List; + +import butterknife.BindView; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; import ru.yandex.yamblz.R; +import ru.yandex.yamblz.data.ArtistsApi; +import ru.yandex.yamblz.data.Genre; public class ContentFragment extends BaseFragment { + @BindView(R.id.main_recycler) + RecyclerView recycler; + + ArtistsLoadingPresenter presenter; + String baseUrl = "http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/"; + private Retrofit retrofit; + private ArtistsApi artistsApi; + private GenreAdapter adapter; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Lazy libraries initialization + HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + + OkHttpClient client = new OkHttpClient.Builder() +// .addInterceptor(loggingInterceptor) + .build(); + + retrofit = new Retrofit.Builder() + .baseUrl(baseUrl) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build(); + + artistsApi = retrofit.create(ArtistsApi.class); + + presenter = new ArtistsLoadingPresenter(artistsApi); + presenter.bindView(this); + presenter.loadArtists(); + } + @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_content, container, false); } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + recycler.setLayoutManager(new LinearLayoutManager(getContext())); + adapter = new GenreAdapter(getContext()); + recycler.setAdapter(adapter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + presenter.unbindView(this); + } + + public void showContent(List genreList) { + adapter.setContent(genreList); + } } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java new file mode 100644 index 0000000..967a41d --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java @@ -0,0 +1,229 @@ +package ru.yandex.yamblz.ui.fragments; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import butterknife.BindView; +import butterknife.ButterKnife; +import ru.yandex.yamblz.R; +import ru.yandex.yamblz.data.Artist; +import ru.yandex.yamblz.data.Genre; +import rx.Observable; +import rx.Single; +import rx.SingleSubscriber; +import rx.Subscriber; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Func2; +import rx.functions.FuncN; +import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; +import timber.log.Timber; + +import static java.lang.Thread.sleep; + +/** + * Created by aleien on 31.07.16. + */ + +public class GenreAdapter extends RecyclerView.Adapter { + public final static int PLACEHOLDER = 0; + public final static int GENRE = 1; + + CompositeSubscription subs = new CompositeSubscription(); + + List genres = new ArrayList<>(); + private Context context; + + public GenreAdapter(Context context) { + this.context = context.getApplicationContext(); + } + + public void setContent(List genreList) { + if (genreList != null && genreList.size() != 0) { + this.genres.addAll(genreList); + } + + notifyDataSetChanged(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case PLACEHOLDER: + View placeholder = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_placeholder, parent, false); + return new PlaceHolder(placeholder); + case GENRE: + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_artists_genre, parent, false); + return new GenreHolder(view); + } + + return null; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder instanceof GenreHolder) { + GenreHolder genreHolder = (GenreHolder) holder; + genreHolder.genre.setText(genres.get(position).getName()); + genreHolder.image.setImageResource(R.color.d2m_transparent); + loadCollage(genres.get(position), genreHolder.image); + } + } + + private void loadCollage(Genre genre, ImageView image) { + List strings = genre.getCollageUrls(); + if (strings.size() == 2) { + subs.add(Observable.zip(loadBitmap(strings.get(0)), + loadBitmap(strings.get(1)), + (s1, s2) -> s1) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { +// Timber.e(result); + image.setImageBitmap(result); +// Glide.with(context) +// .load(result) +// .into(image); + }, + Throwable::printStackTrace + )); + } + + +// subs.add(rx.Observable.zip(getImageObservables(genre), new FuncN() { +// @Override +// public String call(String... args) { +// StringBuffer combination = new StringBuffer(); +// for (String s: args) { +// combination.append(s); +// } +// return combination.toString(); +// } +// })); + } + + @NonNull + private String concatStrings(String[] args) { + StringBuffer combination = new StringBuffer(); + for (String s : args) { + combination.append(s); + } + return combination.toString(); + } + + private Observable getUrlSingle(String string) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + Timber.d(Thread.currentThread().getName()); + try { + sleep((long) (Math.random() * 5000)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + subscriber.onNext(string); + subscriber.onCompleted(); + } + }); + } + + private Observable loadBitmap(String url) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + Timber.d(Thread.currentThread().getName()); + Target bitmapTarget = new SimpleTarget() { + @Override + public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { + subscriber.onNext(resource); + subscriber.onCompleted(); + } + }; + try { + Bitmap bitmap = Glide.with(context) + .load(url) + .asBitmap() + .into(100, 100) + .get(); + subscriber.onNext(bitmap); + subscriber.onCompleted(); + + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + subscriber.onError(e); + } + + } + }); + } + + private List> getImageObservables(Genre genre) { + List> observables = new ArrayList<>(); + List genreArtists = genre.getCollageUrls(); + + for (String url : genreArtists) { + observables.add(Single.create(singleSubscriber -> { + Timber.d(Thread.currentThread().getName()); + try { + wait((long) (Math.random() * 5000)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + singleSubscriber.onSuccess(url); + })); + } + + return observables; + } + + @Override + public int getItemCount() { + return isEmpty() ? 1 : genres.size(); + } + + private boolean isEmpty() { + return genres.size() == 0; + } + + @Override + public int getItemViewType(int position) { + if (isEmpty() && position == 0) return PLACEHOLDER; + return GENRE; + } + + public static class GenreHolder extends RecyclerView.ViewHolder { + @BindView(R.id.item_image) + ImageView image; + @BindView(R.id.item_genres) + TextView genre; + + public GenreHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + public static class PlaceHolder extends RecyclerView.ViewHolder { + + public PlaceHolder(View itemView) { + super(itemView); + } + } +} diff --git a/app/src/main/res/layout/fragment_content.xml b/app/src/main/res/layout/fragment_content.xml index 81016ea..a3fc809 100644 --- a/app/src/main/res/layout/fragment_content.xml +++ b/app/src/main/res/layout/fragment_content.xml @@ -3,12 +3,9 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:id="@+id/main_recycler" + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_artists_genre.xml b/app/src/main/res/layout/item_artists_genre.xml new file mode 100644 index 0000000..471ec18 --- /dev/null +++ b/app/src/main/res/layout/item_artists_genre.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_placeholder.xml b/app/src/main/res/layout/item_placeholder.xml new file mode 100644 index 0000000..2619390 --- /dev/null +++ b/app/src/main/res/layout/item_placeholder.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/dependencies.gradle b/dependencies.gradle index d9cb375..779569b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,7 +7,7 @@ ext.versions = [ compileSdk : 23, buildTools : '23.0.3', - androidGradlePlugin : '2.2.0-alpha4', + androidGradlePlugin : '2.2.0-alpha6', aptGradlePlugin : '1.8', retrolambdaGradlePlugin : '3.2.5', lombokGradlePlugin : '0.2.3.a2', @@ -36,7 +36,9 @@ ext.versions = [ assertJ : '2.4.0', equalsVerifier : '1.7.5', mockito : '1.10.19', - supportTestRunner : '0.5', + supportTestRunner : '0.5' + + ] ext.gradlePlugins = [ From ba18683db37e477e3a467514f8c842d8ff402c01 Mon Sep 17 00:00:00 2001 From: aleien Date: Sun, 31 Jul 2016 21:37:59 +0300 Subject: [PATCH 02/10] Task #1 complete --- .../java/ru/yandex/yamblz/data/Genre.java | 21 +-- .../yamblz/loader/ParallelCollageLoader.java | 98 +++++++++++ .../yamblz/loader/SimpleCollageStrategy.java | 54 ++++++ .../yamblz/ui/fragments/ContentFragment.java | 23 +-- .../yamblz/ui/fragments/GenreAdapter.java | 164 +++--------------- .../main/res/layout/item_artists_genre.xml | 1 - app/src/main/res/layout/item_placeholder.xml | 17 +- app/src/main/res/values/colors.xml | 2 +- app/src/main/res/values/strings.xml | 2 + 9 files changed, 203 insertions(+), 179 deletions(-) create mode 100644 app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java create mode 100644 app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java diff --git a/app/src/main/java/ru/yandex/yamblz/data/Genre.java b/app/src/main/java/ru/yandex/yamblz/data/Genre.java index afb217e..5d30f53 100644 --- a/app/src/main/java/ru/yandex/yamblz/data/Genre.java +++ b/app/src/main/java/ru/yandex/yamblz/data/Genre.java @@ -1,21 +1,16 @@ package ru.yandex.yamblz.data; -import android.net.Uri; -import android.support.annotation.Nullable; - import java.util.ArrayList; import java.util.List; /** * Created by aleien on 31.07.16. + * */ public class Genre { private String name; - @Nullable private List artists; - @Nullable - private Uri cachedImage; public Genre(String name, List artists) { this.name = name; @@ -26,10 +21,7 @@ public String getName() { return name; } - public List getArtists() { - return artists; - } - + // TODO: Отдавать на загрузку больше 4-х ссылок public List getCollageUrls() { List urls = new ArrayList<>(); for (Artist artist : artists) { @@ -39,13 +31,4 @@ public List getCollageUrls() { return urls; } - - @Nullable - public Uri getImage() { - return cachedImage; - } - - public void saveImage(@Nullable Uri cachedImage) { - this.cachedImage = cachedImage; - } } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java new file mode 100644 index 0000000..f0f4305 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java @@ -0,0 +1,98 @@ +package ru.yandex.yamblz.loader; + +import android.content.Context; +import android.graphics.Bitmap; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; + +import java.util.ArrayList; +import java.util.List; +import java.util.WeakHashMap; +import java.util.concurrent.ExecutionException; + +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; + + +public class ParallelCollageLoader implements CollageLoader { + private WeakHashMap subscriptionTargets = new WeakHashMap<>(); + private CompositeSubscription subs = new CompositeSubscription(); + private CollageStrategy collageStrategy = new SimpleCollageStrategy(); + private Context context; + + public ParallelCollageLoader(Context context) { + this.context = context.getApplicationContext(); + } + + @Override + public void loadCollage(List urls, ImageView imageView) { + loadCollage(urls, imageView, this.collageStrategy); + } + + @Override + public void loadCollage(List urls, ImageTarget imageTarget) { + loadCollage(urls, imageTarget, this.collageStrategy); + } + + @Override + public void loadCollage(List urls, ImageView imageView, CollageStrategy collageStrategy) { + if (subscriptionTargets.get(imageView) != null) { + subs.remove(subscriptionTargets.get(imageView)); + } + Subscription subscription = Observable.zip(loadBitmaps(urls), + args -> { + List loadedBitmaps = new ArrayList<>(); + for (Object o : args) { + if (o instanceof Bitmap) { + loadedBitmaps.add((Bitmap) o); + } + } + return collageStrategy.create(loadedBitmaps); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(collage -> { + imageView.setAlpha(0f); + imageView.setImageBitmap(collage); + imageView.animate() + .alpha(1); + + }, + Throwable::printStackTrace + ); + subs.add(subscription); + subscriptionTargets.put(imageView, subscription); + } + + @Override + public void loadCollage(List urls, ImageTarget imageTarget, CollageStrategy collageStrategy) { + + } + + private List> loadBitmaps(List urls) { + List> observables = new ArrayList<>(); + for (String url : urls) { + observables.add(Observable.create(subscriber -> { + try { + Bitmap bitmap = Glide.with(context) + .load(url) + .asBitmap() + .into(100, 100) + .get(); + subscriber.onNext(bitmap); + subscriber.onCompleted(); + + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + subscriber.onError(e); + } + })); + } + + return observables; + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java b/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java new file mode 100644 index 0000000..aeac1ac --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java @@ -0,0 +1,54 @@ +package ru.yandex.yamblz.loader; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; + +import java.util.List; + +/** + * Created by aleien on 31.07.16. + * + */ + +class SimpleCollageStrategy implements CollageStrategy { + @Override + public Bitmap create(List bitmaps) { + return combineBitmaps(bitmaps); + } + + private Bitmap combineBitmaps(List bitmaps) { + int width = 300, height = 300; + Bitmap combinedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + Canvas comboImage = new Canvas(combinedBitmap); + Bitmap scaledBitmap; + + switch (bitmaps.size()) { + case 1: + return bitmaps.get(0); + case 2: + scaledBitmap = Bitmap.createScaledBitmap(bitmaps.get(0), width, height, true); + comboImage.drawBitmap(scaledBitmap, 0f, 0f, null); + scaledBitmap = Bitmap.createScaledBitmap(bitmaps.get(1), width, height, true); + comboImage.drawBitmap(scaledBitmap, width / 2, 0f, null); + break; + case 3: + scaledBitmap = Bitmap.createScaledBitmap(bitmaps.get(0), width, height, true); + comboImage.drawBitmap(scaledBitmap, 0f, 0f, null); + comboImage.drawBitmap(bitmaps.get(1), width / 2, 0f, null); + comboImage.drawBitmap(bitmaps.get(2), width / 2, height / 2, null); + break; + default: + case 4: + comboImage.drawBitmap(bitmaps.get(0), 0f, 0f, null); + comboImage.drawBitmap(bitmaps.get(1), 0f, height / 2, null); + comboImage.drawBitmap(bitmaps.get(2), width / 2, 0f, null); + comboImage.drawBitmap(bitmaps.get(3), width / 2, bitmaps.get(1).getHeight(), null); + break; + } + + return combinedBitmap; + + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index 0cef709..ba3111f 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -20,41 +20,42 @@ import ru.yandex.yamblz.R; import ru.yandex.yamblz.data.ArtistsApi; import ru.yandex.yamblz.data.Genre; +import ru.yandex.yamblz.loader.CollageLoaderManager; +import ru.yandex.yamblz.loader.ParallelCollageLoader; public class ContentFragment extends BaseFragment { @BindView(R.id.main_recycler) RecyclerView recycler; - ArtistsLoadingPresenter presenter; - String baseUrl = "http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/"; - private Retrofit retrofit; - private ArtistsApi artistsApi; + private ArtistsLoadingPresenter presenter; private GenreAdapter adapter; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Lazy libraries initialization + // TODO: Use dagger HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient client = new OkHttpClient.Builder() -// .addInterceptor(loggingInterceptor) .build(); - retrofit = new Retrofit.Builder() - .baseUrl(baseUrl) + String baseUrl = getString(R.string.base_url); + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(baseUrl) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .client(client) .build(); - artistsApi = retrofit.create(ArtistsApi.class); + ArtistsApi artistsApi = retrofit.create(ArtistsApi.class); presenter = new ArtistsLoadingPresenter(artistsApi); presenter.bindView(this); presenter.loadArtists(); + + CollageLoaderManager.init(new ParallelCollageLoader(getContext())); } @NonNull @@ -67,7 +68,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); recycler.setLayoutManager(new LinearLayoutManager(getContext())); - adapter = new GenreAdapter(getContext()); + adapter = new GenreAdapter(); recycler.setAdapter(adapter); } @@ -77,7 +78,7 @@ public void onDestroy() { presenter.unbindView(this); } - public void showContent(List genreList) { + void showContent(List genreList) { adapter.setContent(genreList); } } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java index 967a41d..6341d56 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java @@ -1,9 +1,6 @@ package ru.yandex.yamblz.ui.fragments; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; @@ -11,30 +8,16 @@ import android.widget.ImageView; import android.widget.TextView; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.animation.GlideAnimation; -import com.bumptech.glide.request.target.SimpleTarget; -import com.bumptech.glide.request.target.Target; - import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutionException; import butterknife.BindView; import butterknife.ButterKnife; import ru.yandex.yamblz.R; -import ru.yandex.yamblz.data.Artist; import ru.yandex.yamblz.data.Genre; -import rx.Observable; -import rx.Single; -import rx.SingleSubscriber; -import rx.Subscriber; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Func2; -import rx.functions.FuncN; -import rx.schedulers.Schedulers; -import rx.subscriptions.CompositeSubscription; -import timber.log.Timber; +import ru.yandex.yamblz.loader.CollageLoader; +import ru.yandex.yamblz.loader.CollageLoaderManager; +import ru.yandex.yamblz.loader.ParallelCollageLoader; import static java.lang.Thread.sleep; @@ -42,17 +25,15 @@ * Created by aleien on 31.07.16. */ -public class GenreAdapter extends RecyclerView.Adapter { - public final static int PLACEHOLDER = 0; - public final static int GENRE = 1; - - CompositeSubscription subs = new CompositeSubscription(); +class GenreAdapter extends RecyclerView.Adapter { + private final static int PLACEHOLDER = 0; + private final static int GENRE = 1; + private CollageLoader imagesLoader; - List genres = new ArrayList<>(); - private Context context; + private List genres = new ArrayList<>(); - public GenreAdapter(Context context) { - this.context = context.getApplicationContext(); + GenreAdapter() { + imagesLoader = CollageLoaderManager.getLoader(); } public void setContent(List genreList) { @@ -63,12 +44,17 @@ public void setContent(List genreList) { notifyDataSetChanged(); } + @Override + public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) { + return true; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case PLACEHOLDER: View placeholder = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_placeholder, parent, false); - return new PlaceHolder(placeholder); + return new ProgrssHolder(placeholder); case GENRE: View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_artists_genre, parent, false); return new GenreHolder(view); @@ -83,114 +69,8 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { GenreHolder genreHolder = (GenreHolder) holder; genreHolder.genre.setText(genres.get(position).getName()); genreHolder.image.setImageResource(R.color.d2m_transparent); - loadCollage(genres.get(position), genreHolder.image); - } - } - - private void loadCollage(Genre genre, ImageView image) { - List strings = genre.getCollageUrls(); - if (strings.size() == 2) { - subs.add(Observable.zip(loadBitmap(strings.get(0)), - loadBitmap(strings.get(1)), - (s1, s2) -> s1) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { -// Timber.e(result); - image.setImageBitmap(result); -// Glide.with(context) -// .load(result) -// .into(image); - }, - Throwable::printStackTrace - )); + imagesLoader.loadCollage(genres.get(position).getCollageUrls(), genreHolder.image); } - - -// subs.add(rx.Observable.zip(getImageObservables(genre), new FuncN() { -// @Override -// public String call(String... args) { -// StringBuffer combination = new StringBuffer(); -// for (String s: args) { -// combination.append(s); -// } -// return combination.toString(); -// } -// })); - } - - @NonNull - private String concatStrings(String[] args) { - StringBuffer combination = new StringBuffer(); - for (String s : args) { - combination.append(s); - } - return combination.toString(); - } - - private Observable getUrlSingle(String string) { - return Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - Timber.d(Thread.currentThread().getName()); - try { - sleep((long) (Math.random() * 5000)); - } catch (InterruptedException e) { - e.printStackTrace(); - } - subscriber.onNext(string); - subscriber.onCompleted(); - } - }); - } - - private Observable loadBitmap(String url) { - return Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - Timber.d(Thread.currentThread().getName()); - Target bitmapTarget = new SimpleTarget() { - @Override - public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { - subscriber.onNext(resource); - subscriber.onCompleted(); - } - }; - try { - Bitmap bitmap = Glide.with(context) - .load(url) - .asBitmap() - .into(100, 100) - .get(); - subscriber.onNext(bitmap); - subscriber.onCompleted(); - - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - subscriber.onError(e); - } - - } - }); - } - - private List> getImageObservables(Genre genre) { - List> observables = new ArrayList<>(); - List genreArtists = genre.getCollageUrls(); - - for (String url : genreArtists) { - observables.add(Single.create(singleSubscriber -> { - Timber.d(Thread.currentThread().getName()); - try { - wait((long) (Math.random() * 5000)); - } catch (InterruptedException e) { - e.printStackTrace(); - } - singleSubscriber.onSuccess(url); - })); - } - - return observables; } @Override @@ -208,21 +88,21 @@ public int getItemViewType(int position) { return GENRE; } - public static class GenreHolder extends RecyclerView.ViewHolder { + + static class GenreHolder extends RecyclerView.ViewHolder { @BindView(R.id.item_image) ImageView image; @BindView(R.id.item_genres) TextView genre; - public GenreHolder(View itemView) { + GenreHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } } - public static class PlaceHolder extends RecyclerView.ViewHolder { - - public PlaceHolder(View itemView) { + private static class ProgrssHolder extends RecyclerView.ViewHolder { + ProgrssHolder(View itemView) { super(itemView); } } diff --git a/app/src/main/res/layout/item_artists_genre.xml b/app/src/main/res/layout/item_artists_genre.xml index 471ec18..850e34d 100644 --- a/app/src/main/res/layout/item_artists_genre.xml +++ b/app/src/main/res/layout/item_artists_genre.xml @@ -6,7 +6,6 @@ android:layout_height="match_parent" app:cardBackgroundColor="#ffffff" app:cardUseCompatPadding="true" - app:cardCornerRadius="15dp" app:contentPadding="12dp"> + android:layout_height="match_parent" + android:orientation="vertical"> + android:padding="16dp" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d106673..5be328b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,5 +2,5 @@ #009688 #00796B - #03A9F4 + #88bbd2 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7474f04..f9c5f25 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ Yamblz Hello + Wait, please... + http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/ From fd1c4f1fa32c236ee1942a51d2b189adacdea4af Mon Sep 17 00:00:00 2001 From: aleien Date: Sun, 31 Jul 2016 23:56:29 +0300 Subject: [PATCH 03/10] Started task #2 --- app/src/main/java/ru/yandex/yamblz/App.java | 3 +- .../handler/DisableScrollLoadingHandler.java | 42 +++++++++++++++++++ .../ru/yandex/yamblz/handler/LoadingTask.java | 12 ++++++ .../yamblz/loader/ParallelCollageLoader.java | 24 ++++++----- .../yamblz/ui/fragments/ContentFragment.java | 2 - 5 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java create mode 100644 app/src/main/java/ru/yandex/yamblz/handler/LoadingTask.java diff --git a/app/src/main/java/ru/yandex/yamblz/App.java b/app/src/main/java/ru/yandex/yamblz/App.java index e5f9972..41ebf5d 100644 --- a/app/src/main/java/ru/yandex/yamblz/App.java +++ b/app/src/main/java/ru/yandex/yamblz/App.java @@ -8,6 +8,7 @@ import ru.yandex.yamblz.developer_settings.DeveloperSettingsModel; import ru.yandex.yamblz.handler.CriticalSectionsManager; import ru.yandex.yamblz.loader.CollageLoaderManager; +import ru.yandex.yamblz.loader.ParallelCollageLoader; import timber.log.Timber; public class App extends Application { @@ -34,7 +35,7 @@ public void onCreate() { devMetricsProxy.apply(); } - CollageLoaderManager.init(null); // add implementation + CollageLoaderManager.init(new ParallelCollageLoader()); CriticalSectionsManager.init(null); // add implementation } diff --git a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java new file mode 100644 index 0000000..bae7537 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java @@ -0,0 +1,42 @@ +package ru.yandex.yamblz.handler; + +/** + * Created by aleien on 31.07.16. + */ + +public class DisableScrollLoadingHandler implements CriticalSectionsHandler { + @Override + public void startSection(int id) { + + } + + @Override + public void stopSection(int id) { + + } + + @Override + public void stopSections() { + + } + + @Override + public void postLowPriorityTask(Task task) { + + } + + @Override + public void postLowPriorityTaskDelayed(Task task, int delay) { + + } + + @Override + public void removeLowPriorityTask(Task task) { + + } + + @Override + public void removeLowPriorityTasks() { + + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/handler/LoadingTask.java b/app/src/main/java/ru/yandex/yamblz/handler/LoadingTask.java new file mode 100644 index 0000000..7a753e3 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/handler/LoadingTask.java @@ -0,0 +1,12 @@ +package ru.yandex.yamblz.handler; + +/** + * Created by aleien on 31.07.16. + */ + +public class LoadingTask implements Task { + @Override + public void run() { + + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java index f0f4305..5b067cc 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java @@ -2,10 +2,15 @@ import android.content.Context; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.widget.ImageView; import com.bumptech.glide.Glide; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.WeakHashMap; @@ -22,10 +27,9 @@ public class ParallelCollageLoader implements CollageLoader { private WeakHashMap subscriptionTargets = new WeakHashMap<>(); private CompositeSubscription subs = new CompositeSubscription(); private CollageStrategy collageStrategy = new SimpleCollageStrategy(); - private Context context; - public ParallelCollageLoader(Context context) { - this.context = context.getApplicationContext(); + public ParallelCollageLoader() { + } @Override @@ -75,18 +79,16 @@ public void loadCollage(List urls, ImageTarget imageTarget, CollageStrat private List> loadBitmaps(List urls) { List> observables = new ArrayList<>(); - for (String url : urls) { + for (String urlString : urls) { observables.add(Observable.create(subscriber -> { try { - Bitmap bitmap = Glide.with(context) - .load(url) - .asBitmap() - .into(100, 100) - .get(); + URL url = new URL(urlString); + InputStream is = url.openConnection().getInputStream(); + Bitmap bitmap = BitmapFactory.decodeStream(is); + is.close(); subscriber.onNext(bitmap); subscriber.onCompleted(); - - } catch (InterruptedException | ExecutionException e) { + } catch (IOException e) { e.printStackTrace(); subscriber.onError(e); } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index ba3111f..83c9f11 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -54,8 +54,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) { presenter = new ArtistsLoadingPresenter(artistsApi); presenter.bindView(this); presenter.loadArtists(); - - CollageLoaderManager.init(new ParallelCollageLoader(getContext())); } @NonNull From 496229c8f0d0e497e93f403adccda9a557a152ad Mon Sep 17 00:00:00 2001 From: aleien Date: Tue, 2 Aug 2016 16:27:46 +0300 Subject: [PATCH 04/10] Fixed rotation memory leak --- app/src/main/java/ru/yandex/yamblz/App.java | 2 ++ .../yandex/yamblz/loader/CollageLoader.java | 1 + .../yamblz/loader/ParallelCollageLoader.java | 31 +++++++++++++------ .../yamblz/loader/SimpleCollageStrategy.java | 2 +- .../yamblz/loader/StubCollageLoader.java | 5 +++ .../yamblz/ui/fragments/ContentFragment.java | 7 +++++ .../yamblz/ui/fragments/GenreAdapter.java | 13 +++++--- 7 files changed, 46 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/ru/yandex/yamblz/App.java b/app/src/main/java/ru/yandex/yamblz/App.java index 41ebf5d..fd52ab8 100644 --- a/app/src/main/java/ru/yandex/yamblz/App.java +++ b/app/src/main/java/ru/yandex/yamblz/App.java @@ -35,6 +35,8 @@ public void onCreate() { devMetricsProxy.apply(); } + Timber.plant(new Timber.DebugTree()); + CollageLoaderManager.init(new ParallelCollageLoader()); CriticalSectionsManager.init(null); // add implementation } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/CollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/CollageLoader.java index dad5b76..c3124ba 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/CollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/CollageLoader.java @@ -14,4 +14,5 @@ public interface CollageLoader { void loadCollage(List urls, ImageTarget imageTarget, CollageStrategy collageStrategy); + void destroyAll(); } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java index 5b067cc..e45d754 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java @@ -42,14 +42,17 @@ public void loadCollage(List urls, ImageTarget imageTarget) { loadCollage(urls, imageTarget, this.collageStrategy); } + // Для параллельной загрузки еще можно использовать операторы .groupBy и @Override public void loadCollage(List urls, ImageView imageView, CollageStrategy collageStrategy) { + if (subs == null) subs = new CompositeSubscription(); if (subscriptionTargets.get(imageView) != null) { subs.remove(subscriptionTargets.get(imageView)); } Subscription subscription = Observable.zip(loadBitmaps(urls), args -> { List loadedBitmaps = new ArrayList<>(); + // Вот тут хз, наверняка можно как-то лаконичнее написать for (Object o : args) { if (o instanceof Bitmap) { loadedBitmaps.add((Bitmap) o); @@ -60,6 +63,7 @@ public void loadCollage(List urls, ImageView imageView, CollageStrategy .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(collage -> { + //TODO: Сделать так, чтобы только при первой загрузке изображения происходило выцветание imageView.setAlpha(0f); imageView.setImageBitmap(collage); imageView.animate() @@ -80,21 +84,30 @@ public void loadCollage(List urls, ImageTarget imageTarget, CollageStrat private List> loadBitmaps(List urls) { List> observables = new ArrayList<>(); for (String urlString : urls) { - observables.add(Observable.create(subscriber -> { + observables.add(Observable.fromCallable(() -> { + InputStream in = null; try { - URL url = new URL(urlString); - InputStream is = url.openConnection().getInputStream(); - Bitmap bitmap = BitmapFactory.decodeStream(is); - is.close(); - subscriber.onNext(bitmap); - subscriber.onCompleted(); - } catch (IOException e) { + in = new java.net.URL(urlString).openStream(); + return BitmapFactory.decodeStream(in); + } catch (Exception e) { e.printStackTrace(); - subscriber.onError(e); + } finally { + if (in != null) in.close(); } + return null; })); } return observables; } + + @Override + public void destroyAll() { + if (subs != null) { + subs.clear(); + subs = null; + } + subscriptionTargets.clear(); + } + } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java b/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java index aeac1ac..d13b49d 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java @@ -44,7 +44,7 @@ private Bitmap combineBitmaps(List bitmaps) { comboImage.drawBitmap(bitmaps.get(0), 0f, 0f, null); comboImage.drawBitmap(bitmaps.get(1), 0f, height / 2, null); comboImage.drawBitmap(bitmaps.get(2), width / 2, 0f, null); - comboImage.drawBitmap(bitmaps.get(3), width / 2, bitmaps.get(1).getHeight(), null); + comboImage.drawBitmap(bitmaps.get(3), width / 2, height / 2, null); break; } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/StubCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/StubCollageLoader.java index 1dc051b..a0d6dba 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/StubCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/StubCollageLoader.java @@ -28,4 +28,9 @@ public void loadCollage(List urls, ImageTarget imageTarget, } + @Override + public void destroyAll() { + + } + } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index 83c9f11..7b228c9 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -22,6 +22,7 @@ import ru.yandex.yamblz.data.Genre; import ru.yandex.yamblz.loader.CollageLoaderManager; import ru.yandex.yamblz.loader.ParallelCollageLoader; +import timber.log.Timber; public class ContentFragment extends BaseFragment { @BindView(R.id.main_recycler) @@ -70,6 +71,12 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { recycler.setAdapter(adapter); } + @Override + public void onDestroyView() { + super.onDestroyView(); + adapter.reset(); + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java index 6341d56..201da41 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java @@ -1,6 +1,5 @@ package ru.yandex.yamblz.ui.fragments; -import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; @@ -17,7 +16,7 @@ import ru.yandex.yamblz.data.Genre; import ru.yandex.yamblz.loader.CollageLoader; import ru.yandex.yamblz.loader.CollageLoaderManager; -import ru.yandex.yamblz.loader.ParallelCollageLoader; +import timber.log.Timber; import static java.lang.Thread.sleep; @@ -44,6 +43,10 @@ public void setContent(List genreList) { notifyDataSetChanged(); } + public void reset() { + imagesLoader.destroyAll(); + } + @Override public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) { return true; @@ -54,7 +57,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType switch (viewType) { case PLACEHOLDER: View placeholder = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_placeholder, parent, false); - return new ProgrssHolder(placeholder); + return new ProgressHolder(placeholder); case GENRE: View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_artists_genre, parent, false); return new GenreHolder(view); @@ -101,8 +104,8 @@ static class GenreHolder extends RecyclerView.ViewHolder { } } - private static class ProgrssHolder extends RecyclerView.ViewHolder { - ProgrssHolder(View itemView) { + private static class ProgressHolder extends RecyclerView.ViewHolder { + ProgressHolder(View itemView) { super(itemView); } } From bf35399f808ca3250c42daf5a28877310a3ffa2e Mon Sep 17 00:00:00 2001 From: aleien Date: Wed, 3 Aug 2016 10:39:32 +0300 Subject: [PATCH 05/10] IdleHandler implementation --- app/src/main/java/ru/yandex/yamblz/App.java | 3 +- .../handler/CriticalSectionsHandler.java | 4 +- .../handler/DisableScrollLoadingHandler.java | 47 +++++++++++++++++-- .../ru/yandex/yamblz/handler/LoadingTask.java | 12 ----- .../handler/StubCriticalSectionsHandler.java | 5 ++ .../yamblz/loader/ParallelCollageLoader.java | 15 ++++-- .../yamblz/ui/fragments/ContentFragment.java | 37 ++++++++++++++- .../yamblz/ui/fragments/GenreAdapter.java | 1 + dependencies.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 10 files changed, 102 insertions(+), 28 deletions(-) delete mode 100644 app/src/main/java/ru/yandex/yamblz/handler/LoadingTask.java diff --git a/app/src/main/java/ru/yandex/yamblz/App.java b/app/src/main/java/ru/yandex/yamblz/App.java index fd52ab8..4a20233 100644 --- a/app/src/main/java/ru/yandex/yamblz/App.java +++ b/app/src/main/java/ru/yandex/yamblz/App.java @@ -7,6 +7,7 @@ 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.DisableScrollLoadingHandler; import ru.yandex.yamblz.loader.CollageLoaderManager; import ru.yandex.yamblz.loader.ParallelCollageLoader; import timber.log.Timber; @@ -38,7 +39,7 @@ public void onCreate() { Timber.plant(new Timber.DebugTree()); CollageLoaderManager.init(new ParallelCollageLoader()); - CriticalSectionsManager.init(null); // add implementation + CriticalSectionsManager.init(new DisableScrollLoadingHandler()); // add implementation } @NonNull diff --git a/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java index 8b8cc56..fade9a4 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java @@ -1,6 +1,8 @@ package ru.yandex.yamblz.handler; -public interface CriticalSectionsHandler { +import android.os.MessageQueue; + +public interface CriticalSectionsHandler extends MessageQueue.IdleHandler { void startSection(int id); diff --git a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java index bae7537..e8e42f8 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java @@ -1,42 +1,79 @@ package ru.yandex.yamblz.handler; +import android.os.Handler; +import android.os.MessageQueue; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +import timber.log.Timber; + /** * Created by aleien on 31.07.16. */ public class DisableScrollLoadingHandler implements CriticalSectionsHandler { + private Set runningSections = Collections.newSetFromMap(new ConcurrentHashMap()); + List tasks = new CopyOnWriteArrayList<>(); + @Override public void startSection(int id) { - + // Как вести себя в случае если низкоприоритетные таски еще не закончились? + runningSections.add(id); } @Override public void stopSection(int id) { - + if (runningSections.contains(id)) { + runningSections.remove(id); + } } @Override public void stopSections() { + runningSections.clear(); + } + private void runTasks() { + for (Task task : tasks) { + task.run(); + removeLowPriorityTask(task); + } } @Override public void postLowPriorityTask(Task task) { - + tasks.add(task); } @Override public void postLowPriorityTaskDelayed(Task task, int delay) { - + new Handler().postDelayed(() -> postLowPriorityTask(task), delay); } @Override public void removeLowPriorityTask(Task task) { - + tasks.remove(task); } @Override public void removeLowPriorityTasks() { + tasks.clear(); + } + @Override + public boolean queueIdle() { + Timber.d("Queue idle is called"); + runTasks(); + // TODO: + return runningSections.size() == 0; } } diff --git a/app/src/main/java/ru/yandex/yamblz/handler/LoadingTask.java b/app/src/main/java/ru/yandex/yamblz/handler/LoadingTask.java deleted file mode 100644 index 7a753e3..0000000 --- a/app/src/main/java/ru/yandex/yamblz/handler/LoadingTask.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.yandex.yamblz.handler; - -/** - * Created by aleien on 31.07.16. - */ - -public class LoadingTask implements Task { - @Override - public void run() { - - } -} diff --git a/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java index 0af9646..07e9b3a 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java @@ -36,4 +36,9 @@ public void removeLowPriorityTask(Task task) { public void removeLowPriorityTasks() { } + + @Override + public boolean queueIdle() { + return false; + } } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java index e45d754..620f3c6 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java @@ -16,6 +16,8 @@ import java.util.WeakHashMap; import java.util.concurrent.ExecutionException; +import ru.yandex.yamblz.handler.CriticalSectionsManager; +import ru.yandex.yamblz.handler.Task; import rx.Observable; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; @@ -64,10 +66,13 @@ public void loadCollage(List urls, ImageView imageView, CollageStrategy .observeOn(AndroidSchedulers.mainThread()) .subscribe(collage -> { //TODO: Сделать так, чтобы только при первой загрузке изображения происходило выцветание - imageView.setAlpha(0f); - imageView.setImageBitmap(collage); - imageView.animate() - .alpha(1); + CriticalSectionsManager.getHandler().postLowPriorityTask(() -> { + imageView.setAlpha(0f); + imageView.setImageBitmap(collage); + imageView.animate() + .alpha(1); + + }); }, Throwable::printStackTrace @@ -101,6 +106,8 @@ private List> loadBitmaps(List urls) { return observables; } + // TODO: Можно ли как-то автоматизировать очистку подписок? + // Нехорошо, что фрагмент управляет очисткой подписок. Мб вынести метод в менеджер? @Override public void destroyAll() { if (subs != null) { diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index 7b228c9..7491552 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -1,10 +1,14 @@ package ru.yandex.yamblz.ui.fragments; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.OnScrollListener; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -20,10 +24,12 @@ import ru.yandex.yamblz.R; import ru.yandex.yamblz.data.ArtistsApi; import ru.yandex.yamblz.data.Genre; -import ru.yandex.yamblz.loader.CollageLoaderManager; -import ru.yandex.yamblz.loader.ParallelCollageLoader; +import ru.yandex.yamblz.handler.CriticalSectionsManager; import timber.log.Timber; +import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING; +import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE; + public class ContentFragment extends BaseFragment { @BindView(R.id.main_recycler) RecyclerView recycler; @@ -69,11 +75,14 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { recycler.setLayoutManager(new LinearLayoutManager(getContext())); adapter = new GenreAdapter(); recycler.setAdapter(adapter); + recycler.addOnScrollListener(scrollingListener); + Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); } @Override public void onDestroyView() { super.onDestroyView(); + // TODO: Фрагмент управляет очисткой лоадеров через адаптер. Нехорошо. adapter.reset(); } @@ -86,4 +95,28 @@ public void onDestroy() { void showContent(List genreList) { adapter.setContent(genreList); } + + private OnScrollListener scrollingListener = new OnScrollListener() { + boolean scrollingStarted; + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + switch (newState) { + case SCROLL_STATE_IDLE: + Timber.d("Scroll state idle"); + CriticalSectionsManager.getHandler().stopSection(0); + Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); + scrollingStarted = false; + break; + case SCROLL_STATE_DRAGGING: + if (!scrollingStarted) { +// Looper.myQueue().removeIdleHandler(CriticalSectionsManager.getHandler()); + CriticalSectionsManager.getHandler().startSection(0); + scrollingStarted = true; + } + break; + } + } + }; } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java index 201da41..56b8cf6 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java @@ -43,6 +43,7 @@ public void setContent(List genreList) { notifyDataSetChanged(); } + // Лажа public void reset() { imagesLoader.destroyAll(); } diff --git a/dependencies.gradle b/dependencies.gradle index 779569b..098134d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,7 +7,7 @@ ext.versions = [ compileSdk : 23, buildTools : '23.0.3', - androidGradlePlugin : '2.2.0-alpha6', + androidGradlePlugin : '2.2.0-alpha7', aptGradlePlugin : '1.8', retrolambdaGradlePlugin : '3.2.5', lombokGradlePlugin : '0.2.3.a2', diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 86a3e94..38580b1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon May 30 20:17:48 ICT 2016 +#Wed Aug 03 09:56:42 MSK 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip From a26d7b1535fa255108d136555c020a1404e4d967 Mon Sep 17 00:00:00 2001 From: aleien Date: Wed, 3 Aug 2016 12:28:17 +0300 Subject: [PATCH 06/10] Fixed queueIdle() calling times --- .../yandex/yamblz/handler/DisableScrollLoadingHandler.java | 6 ++---- .../java/ru/yandex/yamblz/loader/ParallelCollageLoader.java | 2 ++ .../java/ru/yandex/yamblz/ui/fragments/ContentFragment.java | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java index e8e42f8..282140f 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java @@ -42,7 +42,7 @@ public void stopSections() { runningSections.clear(); } - private void runTasks() { + synchronized private void runTasks() { for (Task task : tasks) { task.run(); removeLowPriorityTask(task); @@ -71,9 +71,7 @@ public void removeLowPriorityTasks() { @Override public boolean queueIdle() { - Timber.d("Queue idle is called"); runTasks(); - // TODO: - return runningSections.size() == 0; + return runningSections.size() == 0 && tasks.size() != 0; } } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java index 620f3c6..ff64413 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java @@ -3,6 +3,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.os.Looper; import android.widget.ImageView; import com.bumptech.glide.Glide; @@ -73,6 +74,7 @@ public void loadCollage(List urls, ImageView imageView, CollageStrategy .alpha(1); }); + Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); }, Throwable::printStackTrace diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index 7491552..6b7d34a 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -76,7 +76,6 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { adapter = new GenreAdapter(); recycler.setAdapter(adapter); recycler.addOnScrollListener(scrollingListener); - Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); } @Override @@ -111,7 +110,6 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) { break; case SCROLL_STATE_DRAGGING: if (!scrollingStarted) { -// Looper.myQueue().removeIdleHandler(CriticalSectionsManager.getHandler()); CriticalSectionsManager.getHandler().startSection(0); scrollingStarted = true; } From 7863142a285a6798ecbb18fd9c1943be9c2d7883 Mon Sep 17 00:00:00 2001 From: aleien Date: Wed, 3 Aug 2016 13:22:28 +0300 Subject: [PATCH 07/10] Caching && scrolling fix --- app/src/main/java/ru/yandex/yamblz/App.java | 2 +- .../handler/DisableScrollLoadingHandler.java | 3 +- .../yamblz/loader/ParallelCollageLoader.java | 88 ++++++++++++------- .../yamblz/ui/fragments/ContentFragment.java | 6 +- .../yamblz/ui/fragments/GenreAdapter.java | 3 +- app/src/main/res/values/colors.xml | 2 + 6 files changed, 68 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/ru/yandex/yamblz/App.java b/app/src/main/java/ru/yandex/yamblz/App.java index 4a20233..877c774 100644 --- a/app/src/main/java/ru/yandex/yamblz/App.java +++ b/app/src/main/java/ru/yandex/yamblz/App.java @@ -38,7 +38,7 @@ public void onCreate() { Timber.plant(new Timber.DebugTree()); - CollageLoaderManager.init(new ParallelCollageLoader()); + CollageLoaderManager.init(new ParallelCollageLoader(this)); CriticalSectionsManager.init(new DisableScrollLoadingHandler()); // add implementation } diff --git a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java index 282140f..d2cda80 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java @@ -71,7 +71,8 @@ public void removeLowPriorityTasks() { @Override public boolean queueIdle() { - runTasks(); + Timber.d("Trying to run tasks, tasks size: %d", tasks.size()); + if (runningSections.size() == 0) runTasks(); return runningSections.size() == 0 && tasks.size() != 0; } } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java index ff64413..8f7ca41 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java @@ -4,6 +4,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Looper; +import android.util.LruCache; import android.widget.ImageView; import com.bumptech.glide.Glide; @@ -24,15 +25,21 @@ import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; +import timber.log.Timber; public class ParallelCollageLoader implements CollageLoader { private WeakHashMap subscriptionTargets = new WeakHashMap<>(); private CompositeSubscription subs = new CompositeSubscription(); private CollageStrategy collageStrategy = new SimpleCollageStrategy(); - - public ParallelCollageLoader() { - + private LruCache, Bitmap> memoryCache; + private Context context; + + public ParallelCollageLoader(Context context) { + this.context = context.getApplicationContext(); + final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + final int cacheSize = maxMemory / 8; + memoryCache = new LruCache<>(cacheSize); } @Override @@ -52,35 +59,54 @@ public void loadCollage(List urls, ImageView imageView, CollageStrategy if (subscriptionTargets.get(imageView) != null) { subs.remove(subscriptionTargets.get(imageView)); } - Subscription subscription = Observable.zip(loadBitmaps(urls), - args -> { - List loadedBitmaps = new ArrayList<>(); - // Вот тут хз, наверняка можно как-то лаконичнее написать - for (Object o : args) { - if (o instanceof Bitmap) { - loadedBitmaps.add((Bitmap) o); + Bitmap cachedCollage = memoryCache.get(urls); + if (cachedCollage != null) { + Timber.d("Cache hit! Adding task to handler"); + CriticalSectionsManager.getHandler().postLowPriorityTask(() -> { + Timber.d("Loading cached image"); + imageView.setAlpha(0f); + imageView.setImageBitmap(cachedCollage); + imageView.animate() + .alpha(1); + + }); + Timber.d("Adding handler"); + Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); + + } else { + Subscription subscription = Observable.zip(loadBitmaps(urls), + args -> { + List loadedBitmaps = new ArrayList<>(); + // Вот тут хз, наверняка можно как-то лаконичнее написать + for (Object o : args) { + if (o instanceof Bitmap) { + loadedBitmaps.add((Bitmap) o); + } } - } - return collageStrategy.create(loadedBitmaps); - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(collage -> { - //TODO: Сделать так, чтобы только при первой загрузке изображения происходило выцветание - CriticalSectionsManager.getHandler().postLowPriorityTask(() -> { - imageView.setAlpha(0f); - imageView.setImageBitmap(collage); - imageView.animate() - .alpha(1); - - }); - Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); - - }, - Throwable::printStackTrace - ); - subs.add(subscription); - subscriptionTargets.put(imageView, subscription); + return collageStrategy.create(loadedBitmaps); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(collage -> { + memoryCache.put(urls, collage); + Timber.d("Image loaded! Adding task to handler"); + CriticalSectionsManager.getHandler().postLowPriorityTask(() -> { + Timber.d("Loading image from web into view"); + imageView.setAlpha(0f); + imageView.setImageBitmap(collage); + imageView.animate() + .alpha(1); + + }); + Timber.d("Adding handler"); + Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); + + }, + Throwable::printStackTrace + ); + subs.add(subscription); + subscriptionTargets.put(imageView, subscription); + } } @Override diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index 6b7d34a..a912561 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -9,11 +9,13 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.OnScrollListener; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.List; +import java.util.concurrent.TimeUnit; import butterknife.BindView; import okhttp3.OkHttpClient; @@ -60,6 +62,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { presenter = new ArtistsLoadingPresenter(artistsApi); presenter.bindView(this); + Log.d("TAG", "Loading artists"); presenter.loadArtists(); } @@ -103,13 +106,14 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); switch (newState) { case SCROLL_STATE_IDLE: - Timber.d("Scroll state idle"); + Timber.d("Stopped scrolling"); CriticalSectionsManager.getHandler().stopSection(0); Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); scrollingStarted = false; break; case SCROLL_STATE_DRAGGING: if (!scrollingStarted) { + Timber.d("Started scrolling"); CriticalSectionsManager.getHandler().startSection(0); scrollingStarted = true; } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java index 56b8cf6..a73bc80 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java @@ -16,7 +16,6 @@ import ru.yandex.yamblz.data.Genre; import ru.yandex.yamblz.loader.CollageLoader; import ru.yandex.yamblz.loader.CollageLoaderManager; -import timber.log.Timber; import static java.lang.Thread.sleep; @@ -72,7 +71,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof GenreHolder) { GenreHolder genreHolder = (GenreHolder) holder; genreHolder.genre.setText(genres.get(position).getName()); - genreHolder.image.setImageResource(R.color.d2m_transparent); + genreHolder.image.setImageResource(R.color.colorGray); imagesLoader.loadCollage(genres.get(position).getCollageUrls(), genreHolder.image); } } diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 5be328b..86172b5 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,4 +3,6 @@ #009688 #00796B #88bbd2 + #b1b1b1 + From 0f24ca9f2a920f5e1acbe9d491f8930bfb822dec Mon Sep 17 00:00:00 2001 From: aleien Date: Wed, 3 Aug 2016 15:12:36 +0300 Subject: [PATCH 08/10] Cleanup --- app/src/main/java/ru/yandex/yamblz/App.java | 4 +- .../handler/DisableScrollLoadingHandler.java | 11 +- .../yamblz/loader/ParallelCollageLoader.java | 113 ++++++++++-------- .../ru/yandex/yamblz/loader/TargetImage.java | 23 ++++ .../ui/fragments/ArtistsLoadingPresenter.java | 15 +-- .../yamblz/ui/fragments/ContentFragment.java | 10 +- .../yamblz/ui/fragments/GenreAdapter.java | 11 -- .../java/ru/yandex/yamblz/utils/Utils.java | 39 ++++++ 8 files changed, 136 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/ru/yandex/yamblz/loader/TargetImage.java create mode 100644 app/src/main/java/ru/yandex/yamblz/utils/Utils.java diff --git a/app/src/main/java/ru/yandex/yamblz/App.java b/app/src/main/java/ru/yandex/yamblz/App.java index 877c774..4aa9f1b 100644 --- a/app/src/main/java/ru/yandex/yamblz/App.java +++ b/app/src/main/java/ru/yandex/yamblz/App.java @@ -36,9 +36,7 @@ public void onCreate() { devMetricsProxy.apply(); } - Timber.plant(new Timber.DebugTree()); - - CollageLoaderManager.init(new ParallelCollageLoader(this)); + CollageLoaderManager.init(new ParallelCollageLoader()); CriticalSectionsManager.init(new DisableScrollLoadingHandler()); // add implementation } diff --git a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java index d2cda80..045ddd2 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java @@ -1,28 +1,19 @@ package ru.yandex.yamblz.handler; import android.os.Handler; -import android.os.MessageQueue; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Queue; import java.util.Set; -import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicInteger; import timber.log.Timber; -/** - * Created by aleien on 31.07.16. - */ public class DisableScrollLoadingHandler implements CriticalSectionsHandler { private Set runningSections = Collections.newSetFromMap(new ConcurrentHashMap()); - List tasks = new CopyOnWriteArrayList<>(); + private List tasks = new CopyOnWriteArrayList<>(); @Override public void startSection(int id) { diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java index 8f7ca41..fb2a368 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java @@ -1,25 +1,15 @@ package ru.yandex.yamblz.loader; -import android.content.Context; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.os.Looper; import android.util.LruCache; import android.widget.ImageView; -import com.bumptech.glide.Glide; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.WeakHashMap; -import java.util.concurrent.ExecutionException; import ru.yandex.yamblz.handler.CriticalSectionsManager; -import ru.yandex.yamblz.handler.Task; import rx.Observable; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; @@ -27,16 +17,17 @@ import rx.subscriptions.CompositeSubscription; import timber.log.Timber; +import static ru.yandex.yamblz.utils.Utils.loadBitmapFromUrl; + public class ParallelCollageLoader implements CollageLoader { - private WeakHashMap subscriptionTargets = new WeakHashMap<>(); + private WeakHashMap subscriptionImages = new WeakHashMap<>(); + private WeakHashMap subscriptionTargets = new WeakHashMap<>(); private CompositeSubscription subs = new CompositeSubscription(); private CollageStrategy collageStrategy = new SimpleCollageStrategy(); private LruCache, Bitmap> memoryCache; - private Context context; - public ParallelCollageLoader(Context context) { - this.context = context.getApplicationContext(); + public ParallelCollageLoader() { final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); final int cacheSize = maxMemory / 8; memoryCache = new LruCache<>(cacheSize); @@ -52,27 +43,18 @@ public void loadCollage(List urls, ImageTarget imageTarget) { loadCollage(urls, imageTarget, this.collageStrategy); } - // Для параллельной загрузки еще можно использовать операторы .groupBy и + // Для параллельной загрузки еще можно использовать операторы .groupBy или связку .flatMap().toList().toBlocking() + // Очень длинный метод. Ну прямо очень. Фу. @Override public void loadCollage(List urls, ImageView imageView, CollageStrategy collageStrategy) { if (subs == null) subs = new CompositeSubscription(); - if (subscriptionTargets.get(imageView) != null) { - subs.remove(subscriptionTargets.get(imageView)); + if (subscriptionImages.get(imageView) != null) { + subs.remove(subscriptionImages.get(imageView)); } Bitmap cachedCollage = memoryCache.get(urls); if (cachedCollage != null) { Timber.d("Cache hit! Adding task to handler"); - CriticalSectionsManager.getHandler().postLowPriorityTask(() -> { - Timber.d("Loading cached image"); - imageView.setAlpha(0f); - imageView.setImageBitmap(cachedCollage); - imageView.animate() - .alpha(1); - - }); - Timber.d("Adding handler"); - Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); - + loadBitmap(imageView, cachedCollage); } else { Subscription subscription = Observable.zip(loadBitmaps(urls), args -> { @@ -90,59 +72,86 @@ public void loadCollage(List urls, ImageView imageView, CollageStrategy .subscribe(collage -> { memoryCache.put(urls, collage); Timber.d("Image loaded! Adding task to handler"); - CriticalSectionsManager.getHandler().postLowPriorityTask(() -> { - Timber.d("Loading image from web into view"); - imageView.setAlpha(0f); - imageView.setImageBitmap(collage); - imageView.animate() - .alpha(1); - - }); - Timber.d("Adding handler"); - Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); + loadBitmap(imageView, collage); }, Throwable::printStackTrace ); subs.add(subscription); - subscriptionTargets.put(imageView, subscription); + subscriptionImages.put(imageView, subscription); } } + private void loadBitmap(ImageView imageView, Bitmap cachedCollage) { + CriticalSectionsManager.getHandler().postLowPriorityTask(() -> { + imageView.setAlpha(0f); + imageView.setImageBitmap(cachedCollage); + imageView.animate() + .alpha(1); + + }); + Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); + } + @Override public void loadCollage(List urls, ImageTarget imageTarget, CollageStrategy collageStrategy) { + if (subs == null) subs = new CompositeSubscription(); + Subscription s = subscriptionTargets.get(imageTarget); + if (s != null) { + subs.remove(s); + } + Bitmap cachedCollage = memoryCache.get(urls); + if (cachedCollage != null) { + Timber.d("Cache hit! Adding task to handler"); + imageTarget.onLoadBitmap(cachedCollage); + } else { + Subscription subscription = Observable.zip(loadBitmaps(urls), + args -> { + List loadedBitmaps = new ArrayList<>(); + // Вот тут хз, наверняка можно как-то лаконичнее написать + for (Object o : args) { + if (o instanceof Bitmap) { + loadedBitmaps.add((Bitmap) o); + } + } + return collageStrategy.create(loadedBitmaps); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(collage -> { + memoryCache.put(urls, collage); + Timber.d("Image loaded! Adding task to handler"); + imageTarget.onLoadBitmap(collage); + }, + Throwable::printStackTrace + ); + subs.add(subscription); + subscriptionTargets.put(imageTarget, subscription); + + } } private List> loadBitmaps(List urls) { List> observables = new ArrayList<>(); for (String urlString : urls) { - observables.add(Observable.fromCallable(() -> { - InputStream in = null; - try { - in = new java.net.URL(urlString).openStream(); - return BitmapFactory.decodeStream(in); - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (in != null) in.close(); - } - return null; - })); + observables.add(Observable.fromCallable(() -> loadBitmapFromUrl(urlString))); } return observables; } + // TODO: Можно ли как-то автоматизировать очистку подписок? // Нехорошо, что фрагмент управляет очисткой подписок. Мб вынести метод в менеджер? + // Нужно это как-то автоматизировать @Override public void destroyAll() { if (subs != null) { subs.clear(); subs = null; } - subscriptionTargets.clear(); + subscriptionImages.clear(); } } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/TargetImage.java b/app/src/main/java/ru/yandex/yamblz/loader/TargetImage.java new file mode 100644 index 0000000..b78b174 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/loader/TargetImage.java @@ -0,0 +1,23 @@ +package ru.yandex.yamblz.loader; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.widget.ImageView; + +public class TargetImage extends ImageView implements ImageTarget { + public TargetImage(Context context) { + super(context); + } + + public TargetImage(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void onLoadBitmap(Bitmap bitmap) { + setAlpha(0f); + setImageBitmap(bitmap); + animate().alpha(1); + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java index c2c8e7f..fb52003 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java @@ -16,13 +16,10 @@ import rx.subscriptions.CompositeSubscription; import timber.log.Timber; -/** - * Created by aleien on 31.07.16. - */ -public class ArtistsLoadingPresenter extends Presenter { +class ArtistsLoadingPresenter extends Presenter { - CompositeSubscription subs = new CompositeSubscription(); + private CompositeSubscription subs = new CompositeSubscription(); private ArtistsApi artistsApi; ArtistsLoadingPresenter(ArtistsApi artistsApi) { @@ -34,10 +31,15 @@ void loadArtists() { .subscribeOn(Schedulers.io()) .map(this::extractGenres) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(list -> view().showContent(list), + .subscribe(this::showContent, Throwable::printStackTrace)); } + private void showContent(List list) { + ContentFragment view = view(); + if (view != null) view.showContent(list); + } + private List extractGenres(List artistsList) { ConcurrentHashMap> genres = new ConcurrentHashMap<>(); List genreList = new ArrayList<>(); @@ -58,7 +60,6 @@ private List extractGenres(List artistsList) { } for (Map.Entry> entry : genres.entrySet()) { -// Timber.d("Key = %s, Value = %s", entry.getKey(), entry.getValue()); genreList.add(new Genre(entry.getKey(), entry.getValue())); } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index a912561..5f089fa 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -1,21 +1,17 @@ package ru.yandex.yamblz.ui.fragments; import android.os.Bundle; -import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.OnScrollListener; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.List; -import java.util.concurrent.TimeUnit; import butterknife.BindView; import okhttp3.OkHttpClient; @@ -27,6 +23,7 @@ import ru.yandex.yamblz.data.ArtistsApi; import ru.yandex.yamblz.data.Genre; import ru.yandex.yamblz.handler.CriticalSectionsManager; +import ru.yandex.yamblz.loader.CollageLoaderManager; import timber.log.Timber; import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING; @@ -62,7 +59,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { presenter = new ArtistsLoadingPresenter(artistsApi); presenter.bindView(this); - Log.d("TAG", "Loading artists"); + Timber.d("Loading artists"); presenter.loadArtists(); } @@ -84,8 +81,7 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { @Override public void onDestroyView() { super.onDestroyView(); - // TODO: Фрагмент управляет очисткой лоадеров через адаптер. Нехорошо. - adapter.reset(); + CollageLoaderManager.getLoader().destroyAll(); } @Override diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java index a73bc80..6420550 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java @@ -17,12 +17,6 @@ import ru.yandex.yamblz.loader.CollageLoader; import ru.yandex.yamblz.loader.CollageLoaderManager; -import static java.lang.Thread.sleep; - -/** - * Created by aleien on 31.07.16. - */ - class GenreAdapter extends RecyclerView.Adapter { private final static int PLACEHOLDER = 0; private final static int GENRE = 1; @@ -42,11 +36,6 @@ public void setContent(List genreList) { notifyDataSetChanged(); } - // Лажа - public void reset() { - imagesLoader.destroyAll(); - } - @Override public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) { return true; diff --git a/app/src/main/java/ru/yandex/yamblz/utils/Utils.java b/app/src/main/java/ru/yandex/yamblz/utils/Utils.java new file mode 100644 index 0000000..42b2711 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/utils/Utils.java @@ -0,0 +1,39 @@ +package ru.yandex.yamblz.utils; + + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.support.annotation.Nullable; + +import java.io.IOException; +import java.io.InputStream; + +public class Utils { + private Utils() { + + } + + @Nullable + public static Bitmap loadBitmapFromUrl(String urlString) { + InputStream in = null; + try { + in = new java.net.URL(urlString).openStream(); + return BitmapFactory.decodeStream(in); + } catch (Exception e) { + e.printStackTrace(); + } finally { + closeConnection(in); + } + return null; + } + + + private static void closeConnection(InputStream in) { + try { + if (in != null) in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} From 54b9c5aecba7373b9009e54435e3b2d739e3a2d9 Mon Sep 17 00:00:00 2001 From: aleien Date: Fri, 5 Aug 2016 01:48:21 +0300 Subject: [PATCH 09/10] Cleanup && review fixes --- app/src/main/java/ru/yandex/yamblz/App.java | 3 +- .../java/ru/yandex/yamblz/data/Artist.java | 16 +-- .../ru/yandex/yamblz/data/ArtistsApi.java | 4 - .../java/ru/yandex/yamblz/data/Genre.java | 10 +- .../handler/CriticalSectionsHandler.java | 4 +- .../handler/DisableScrollLoadingHandler.java | 45 +++++--- .../handler/StubCriticalSectionsHandler.java | 5 - .../yandex/yamblz/loader/CollageStrategy.java | 2 +- .../ru/yandex/yamblz/loader/ImageTarget.java | 2 +- .../yamblz/loader/ParallelCollageLoader.java | 100 +++++------------- .../yamblz/loader/SimpleCollageStrategy.java | 6 -- .../ru/yandex/yamblz/loader/TargetImage.java | 23 ---- .../ui/fragments/ArtistsLoadingPresenter.java | 11 +- .../yamblz/ui/fragments/ContentFragment.java | 4 +- .../yamblz/ui/fragments/GenreAdapter.java | 7 +- .../main/res/layout/item_artists_genre.xml | 5 +- app/src/main/res/values/strings.xml | 4 +- 17 files changed, 88 insertions(+), 163 deletions(-) delete mode 100644 app/src/main/java/ru/yandex/yamblz/loader/TargetImage.java diff --git a/app/src/main/java/ru/yandex/yamblz/App.java b/app/src/main/java/ru/yandex/yamblz/App.java index 4aa9f1b..ea7d6ab 100644 --- a/app/src/main/java/ru/yandex/yamblz/App.java +++ b/app/src/main/java/ru/yandex/yamblz/App.java @@ -2,6 +2,7 @@ import android.app.Application; import android.content.Context; +import android.os.Looper; import android.support.annotation.NonNull; import ru.yandex.yamblz.developer_settings.DevMetricsProxy; @@ -37,7 +38,7 @@ public void onCreate() { } CollageLoaderManager.init(new ParallelCollageLoader()); - CriticalSectionsManager.init(new DisableScrollLoadingHandler()); // add implementation + CriticalSectionsManager.init(new DisableScrollLoadingHandler(Looper.myQueue())); // add implementation } @NonNull diff --git a/app/src/main/java/ru/yandex/yamblz/data/Artist.java b/app/src/main/java/ru/yandex/yamblz/data/Artist.java index 3544230..aff3dc6 100644 --- a/app/src/main/java/ru/yandex/yamblz/data/Artist.java +++ b/app/src/main/java/ru/yandex/yamblz/data/Artist.java @@ -6,11 +6,11 @@ public class Artist { public final int id; public final String name; public final List genres; - public final int tracks; - public final int albums; - public final String link; - public final String description; - public final Cover cover; + private final int tracks; + private final int albums; + private final String link; + private final String description; + final Cover cover; public Artist(int id, String name, List genres, int tracks, int albums, String link, String description, Cover cover) { this.id = id; @@ -23,9 +23,9 @@ public Artist(int id, String name, List genres, int tracks, int albums, this.cover = cover; } - public static class Cover { - public final String small; - public final String big; + static class Cover { + final String small; + final String big; public Cover(String small, String big) { this.small = small; diff --git a/app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java b/app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java index 6f181af..7a38946 100644 --- a/app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java +++ b/app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java @@ -5,10 +5,6 @@ import retrofit2.http.GET; import rx.Single; -/** - * Created by aleien on 31.07.16. - */ - public interface ArtistsApi { @GET("artists.json") Single> getArtists(); diff --git a/app/src/main/java/ru/yandex/yamblz/data/Genre.java b/app/src/main/java/ru/yandex/yamblz/data/Genre.java index 5d30f53..6bd02e9 100644 --- a/app/src/main/java/ru/yandex/yamblz/data/Genre.java +++ b/app/src/main/java/ru/yandex/yamblz/data/Genre.java @@ -3,14 +3,9 @@ import java.util.ArrayList; import java.util.List; -/** - * Created by aleien on 31.07.16. - * - */ - public class Genre { - private String name; - private List artists; + private final String name; + private final List artists; public Genre(String name, List artists) { this.name = name; @@ -21,7 +16,6 @@ public String getName() { return name; } - // TODO: Отдавать на загрузку больше 4-х ссылок public List getCollageUrls() { List urls = new ArrayList<>(); for (Artist artist : artists) { diff --git a/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java index fade9a4..68edf64 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java @@ -1,8 +1,6 @@ package ru.yandex.yamblz.handler; -import android.os.MessageQueue; - -public interface CriticalSectionsHandler extends MessageQueue.IdleHandler { +public interface CriticalSectionsHandler { void startSection(int id); diff --git a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java index 045ddd2..ec79bde 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java @@ -1,23 +1,27 @@ package ru.yandex.yamblz.handler; import android.os.Handler; +import android.os.Looper; +import android.os.MessageQueue; +import java.lang.ref.WeakReference; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; -import timber.log.Timber; +public class DisableScrollLoadingHandler implements CriticalSectionsHandler, MessageQueue.IdleHandler { + private final Set runningSections = Collections.newSetFromMap(new ConcurrentHashMap()); + private final List tasks = new CopyOnWriteArrayList<>(); + private final WeakReference listenableQueue; - -public class DisableScrollLoadingHandler implements CriticalSectionsHandler { - private Set runningSections = Collections.newSetFromMap(new ConcurrentHashMap()); - private List tasks = new CopyOnWriteArrayList<>(); + public DisableScrollLoadingHandler(MessageQueue queue) { + listenableQueue = new WeakReference<>(queue); + } @Override public void startSection(int id) { - // Как вести себя в случае если низкоприоритетные таски еще не закончились? runningSections.add(id); } @@ -26,28 +30,46 @@ public void stopSection(int id) { if (runningSections.contains(id)) { runningSections.remove(id); } + addIdleHandler(); } @Override public void stopSections() { runningSections.clear(); + addIdleHandler(); } - synchronized private void runTasks() { + private void runTasks() { for (Task task : tasks) { - task.run(); - removeLowPriorityTask(task); + if (runningSections.size() == 0) { + task.run(); + removeLowPriorityTask(task); + } + } + } + + // Вот тут я хз, честно. + // Вот вроде по дяде Бобу это неправильно написано, а с другой стороны вроде логично выглядит + private boolean addIdleHandler() { + if (listenableQueue.get() != null) { + if (tasks.size() != 0 && !queueIdle()) { + listenableQueue.get().addIdleHandler(this); + } + return true; } + + return false; } @Override public void postLowPriorityTask(Task task) { - tasks.add(task); + if (addIdleHandler()) tasks.add(task); + // throw exception? } @Override public void postLowPriorityTaskDelayed(Task task, int delay) { - new Handler().postDelayed(() -> postLowPriorityTask(task), delay); + new Handler(Looper.getMainLooper()).postDelayed(() -> postLowPriorityTask(task), delay); } @Override @@ -62,7 +84,6 @@ public void removeLowPriorityTasks() { @Override public boolean queueIdle() { - Timber.d("Trying to run tasks, tasks size: %d", tasks.size()); if (runningSections.size() == 0) runTasks(); return runningSections.size() == 0 && tasks.size() != 0; } diff --git a/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java index 07e9b3a..0af9646 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java @@ -36,9 +36,4 @@ public void removeLowPriorityTask(Task task) { public void removeLowPriorityTasks() { } - - @Override - public boolean queueIdle() { - return false; - } } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/CollageStrategy.java b/app/src/main/java/ru/yandex/yamblz/loader/CollageStrategy.java index d81d52d..9ad29e3 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/CollageStrategy.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/CollageStrategy.java @@ -4,7 +4,7 @@ import java.util.List; -public interface CollageStrategy { +interface CollageStrategy { Bitmap create(List bitmaps); } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ImageTarget.java b/app/src/main/java/ru/yandex/yamblz/loader/ImageTarget.java index 9a8be2b..01ca5a0 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ImageTarget.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ImageTarget.java @@ -2,7 +2,7 @@ import android.graphics.Bitmap; -public interface ImageTarget { +interface ImageTarget { void onLoadBitmap(Bitmap bitmap); } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java index fb2a368..630bb19 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java @@ -1,7 +1,7 @@ package ru.yandex.yamblz.loader; import android.graphics.Bitmap; -import android.os.Looper; +import android.support.annotation.NonNull; import android.util.LruCache; import android.widget.ImageView; @@ -21,13 +21,15 @@ public class ParallelCollageLoader implements CollageLoader { - private WeakHashMap subscriptionImages = new WeakHashMap<>(); - private WeakHashMap subscriptionTargets = new WeakHashMap<>(); - private CompositeSubscription subs = new CompositeSubscription(); - private CollageStrategy collageStrategy = new SimpleCollageStrategy(); - private LruCache, Bitmap> memoryCache; + @NonNull private final WeakHashMap subscriptionTargets; + @NonNull private final CompositeSubscription subs; + @NonNull private final CollageStrategy collageStrategy; + @NonNull private final LruCache, Bitmap> memoryCache; public ParallelCollageLoader() { + subscriptionTargets = new WeakHashMap<>(); + subs = new CompositeSubscription(); + collageStrategy = new SimpleCollageStrategy(); final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); final int cacheSize = maxMemory / 8; memoryCache = new LruCache<>(cacheSize); @@ -43,59 +45,22 @@ public void loadCollage(List urls, ImageTarget imageTarget) { loadCollage(urls, imageTarget, this.collageStrategy); } - // Для параллельной загрузки еще можно использовать операторы .groupBy или связку .flatMap().toList().toBlocking() - // Очень длинный метод. Ну прямо очень. Фу. @Override - public void loadCollage(List urls, ImageView imageView, CollageStrategy collageStrategy) { - if (subs == null) subs = new CompositeSubscription(); - if (subscriptionImages.get(imageView) != null) { - subs.remove(subscriptionImages.get(imageView)); - } - Bitmap cachedCollage = memoryCache.get(urls); - if (cachedCollage != null) { - Timber.d("Cache hit! Adding task to handler"); - loadBitmap(imageView, cachedCollage); - } else { - Subscription subscription = Observable.zip(loadBitmaps(urls), - args -> { - List loadedBitmaps = new ArrayList<>(); - // Вот тут хз, наверняка можно как-то лаконичнее написать - for (Object o : args) { - if (o instanceof Bitmap) { - loadedBitmaps.add((Bitmap) o); - } - } - return collageStrategy.create(loadedBitmaps); - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(collage -> { - memoryCache.put(urls, collage); - Timber.d("Image loaded! Adding task to handler"); - loadBitmap(imageView, collage); - - }, - Throwable::printStackTrace - ); - subs.add(subscription); - subscriptionImages.put(imageView, subscription); - } - } - - private void loadBitmap(ImageView imageView, Bitmap cachedCollage) { - CriticalSectionsManager.getHandler().postLowPriorityTask(() -> { + public void loadCollage(List urls, final ImageView imageView, CollageStrategy collageStrategy) { + ImageTarget target = bitmap -> { imageView.setAlpha(0f); - imageView.setImageBitmap(cachedCollage); - imageView.animate() - .alpha(1); + imageView.setImageBitmap(bitmap); + imageView.animate().alpha(1); + }; + loadCollage(urls, target, collageStrategy); + } - }); - Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); + private void loadBitmap(ImageTarget imageTarget, Bitmap cachedCollage) { + CriticalSectionsManager.getHandler().postLowPriorityTask(() -> imageTarget.onLoadBitmap(cachedCollage)); } @Override public void loadCollage(List urls, ImageTarget imageTarget, CollageStrategy collageStrategy) { - if (subs == null) subs = new CompositeSubscription(); Subscription s = subscriptionTargets.get(imageTarget); if (s != null) { subs.remove(s); @@ -103,26 +68,20 @@ public void loadCollage(List urls, ImageTarget imageTarget, CollageStrat Bitmap cachedCollage = memoryCache.get(urls); if (cachedCollage != null) { Timber.d("Cache hit! Adding task to handler"); - imageTarget.onLoadBitmap(cachedCollage); + loadBitmap(imageTarget, cachedCollage); } else { Subscription subscription = Observable.zip(loadBitmaps(urls), - args -> { - List loadedBitmaps = new ArrayList<>(); - // Вот тут хз, наверняка можно как-то лаконичнее написать - for (Object o : args) { - if (o instanceof Bitmap) { - loadedBitmaps.add((Bitmap) o); - } - } - return collageStrategy.create(loadedBitmaps); - }) + args -> args) + .flatMap(Observable::from) + .map(o -> (Bitmap) o) + .toList() + .map(collageStrategy::create) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(collage -> { memoryCache.put(urls, collage); Timber.d("Image loaded! Adding task to handler"); - imageTarget.onLoadBitmap(collage); - + loadBitmap(imageTarget, collage); }, Throwable::printStackTrace ); @@ -141,17 +100,10 @@ private List> loadBitmaps(List urls) { return observables; } - - // TODO: Можно ли как-то автоматизировать очистку подписок? - // Нехорошо, что фрагмент управляет очисткой подписок. Мб вынести метод в менеджер? - // Нужно это как-то автоматизировать @Override public void destroyAll() { - if (subs != null) { - subs.clear(); - subs = null; - } - subscriptionImages.clear(); + subs.clear(); + subscriptionTargets.clear(); } } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java b/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java index d13b49d..299d62f 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java @@ -2,15 +2,9 @@ import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Paint; import java.util.List; -/** - * Created by aleien on 31.07.16. - * - */ - class SimpleCollageStrategy implements CollageStrategy { @Override public Bitmap create(List bitmaps) { diff --git a/app/src/main/java/ru/yandex/yamblz/loader/TargetImage.java b/app/src/main/java/ru/yandex/yamblz/loader/TargetImage.java deleted file mode 100644 index b78b174..0000000 --- a/app/src/main/java/ru/yandex/yamblz/loader/TargetImage.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.yandex.yamblz.loader; - -import android.content.Context; -import android.graphics.Bitmap; -import android.util.AttributeSet; -import android.widget.ImageView; - -public class TargetImage extends ImageView implements ImageTarget { - public TargetImage(Context context) { - super(context); - } - - public TargetImage(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public void onLoadBitmap(Bitmap bitmap) { - setAlpha(0f); - setImageBitmap(bitmap); - animate().alpha(1); - } -} diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java index fb52003..018c1f4 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java @@ -1,8 +1,8 @@ package ru.yandex.yamblz.ui.fragments; +import android.support.annotation.NonNull; + import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -14,15 +14,14 @@ import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; -import timber.log.Timber; class ArtistsLoadingPresenter extends Presenter { - private CompositeSubscription subs = new CompositeSubscription(); - private ArtistsApi artistsApi; + @NonNull private final CompositeSubscription subs = new CompositeSubscription(); + @NonNull private final ArtistsApi artistsApi; - ArtistsLoadingPresenter(ArtistsApi artistsApi) { + ArtistsLoadingPresenter(@NonNull ArtistsApi artistsApi) { this.artistsApi = artistsApi; } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index 5f089fa..6b4ab41 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -1,7 +1,6 @@ package ru.yandex.yamblz.ui.fragments; import android.os.Bundle; -import android.os.Looper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.LinearLayoutManager; @@ -94,7 +93,7 @@ void showContent(List genreList) { adapter.setContent(genreList); } - private OnScrollListener scrollingListener = new OnScrollListener() { + private final OnScrollListener scrollingListener = new OnScrollListener() { boolean scrollingStarted; @Override @@ -104,7 +103,6 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) { case SCROLL_STATE_IDLE: Timber.d("Stopped scrolling"); CriticalSectionsManager.getHandler().stopSection(0); - Looper.myQueue().addIdleHandler(CriticalSectionsManager.getHandler()); scrollingStarted = false; break; case SCROLL_STATE_DRAGGING: diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java index 6420550..fe497cd 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java @@ -20,15 +20,14 @@ class GenreAdapter extends RecyclerView.Adapter { private final static int PLACEHOLDER = 0; private final static int GENRE = 1; - private CollageLoader imagesLoader; - - private List genres = new ArrayList<>(); + private final CollageLoader imagesLoader; + private final List genres = new ArrayList<>(); GenreAdapter() { imagesLoader = CollageLoaderManager.getLoader(); } - public void setContent(List genreList) { + void setContent(List genreList) { if (genreList != null && genreList.size() != 0) { this.genres.addAll(genreList); } diff --git a/app/src/main/res/layout/item_artists_genre.xml b/app/src/main/res/layout/item_artists_genre.xml index 850e34d..e196a24 100644 --- a/app/src/main/res/layout/item_artists_genre.xml +++ b/app/src/main/res/layout/item_artists_genre.xml @@ -17,14 +17,15 @@ android:id="@+id/item_image" android:layout_width="100dp" android:layout_height="100dp" - tools:background="#61b393" /> + tools:background="#61b393" + android:contentDescription="@string/content_desc_artist_image" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f9c5f25..bcff4bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ Yamblz - Hello - Wait, please... + Wait, please… http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/ + Artist image From 73c11c1b0963f114850d2e3b5e61cb680f6d3c02 Mon Sep 17 00:00:00 2001 From: aleien Date: Fri, 5 Aug 2016 13:52:37 +0300 Subject: [PATCH 10/10] Cleaner rx --- .../yamblz/loader/ParallelCollageLoader.java | 28 ++++------------- .../java/ru/yandex/yamblz/utils/Utils.java | 30 +++++++++++-------- 2 files changed, 22 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java index 630bb19..c096647 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java @@ -5,11 +5,11 @@ import android.util.LruCache; import android.widget.ImageView; -import java.util.ArrayList; import java.util.List; import java.util.WeakHashMap; import ru.yandex.yamblz.handler.CriticalSectionsManager; +import ru.yandex.yamblz.utils.Utils; import rx.Observable; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; @@ -17,8 +17,6 @@ import rx.subscriptions.CompositeSubscription; import timber.log.Timber; -import static ru.yandex.yamblz.utils.Utils.loadBitmapFromUrl; - public class ParallelCollageLoader implements CollageLoader { @NonNull private final WeakHashMap subscriptionTargets; @@ -70,34 +68,18 @@ public void loadCollage(List urls, ImageTarget imageTarget, CollageStrat Timber.d("Cache hit! Adding task to handler"); loadBitmap(imageTarget, cachedCollage); } else { - Subscription subscription = Observable.zip(loadBitmaps(urls), - args -> args) - .flatMap(Observable::from) - .map(o -> (Bitmap) o) + Subscription subscription = Observable.from(urls) + .flatMap(url -> Utils.loadBitmapAsync(url).subscribeOn(Schedulers.io())) .toList() .map(collageStrategy::create) - .subscribeOn(Schedulers.io()) + .doOnNext(collage -> memoryCache.put(urls, collage)) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(collage -> { - memoryCache.put(urls, collage); - Timber.d("Image loaded! Adding task to handler"); - loadBitmap(imageTarget, collage); - }, + .subscribe(collage -> loadBitmap(imageTarget, collage), Throwable::printStackTrace ); subs.add(subscription); subscriptionTargets.put(imageTarget, subscription); - - } - } - - private List> loadBitmaps(List urls) { - List> observables = new ArrayList<>(); - for (String urlString : urls) { - observables.add(Observable.fromCallable(() -> loadBitmapFromUrl(urlString))); } - - return observables; } @Override diff --git a/app/src/main/java/ru/yandex/yamblz/utils/Utils.java b/app/src/main/java/ru/yandex/yamblz/utils/Utils.java index 42b2711..c7f729d 100644 --- a/app/src/main/java/ru/yandex/yamblz/utils/Utils.java +++ b/app/src/main/java/ru/yandex/yamblz/utils/Utils.java @@ -3,28 +3,32 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.support.annotation.Nullable; import java.io.IOException; import java.io.InputStream; +import rx.Observable; +import timber.log.Timber; + public class Utils { private Utils() { } - @Nullable - public static Bitmap loadBitmapFromUrl(String urlString) { - InputStream in = null; - try { - in = new java.net.URL(urlString).openStream(); - return BitmapFactory.decodeStream(in); - } catch (Exception e) { - e.printStackTrace(); - } finally { - closeConnection(in); - } - return null; + public static Observable loadBitmapAsync(String urlString) { + return Observable.fromCallable(() -> { + Timber.d("Loading image on thread " + Thread.currentThread().getName()); + InputStream in = null; + try { + in = new java.net.URL(urlString).openStream(); + return BitmapFactory.decodeStream(in); + } catch (Exception e) { + e.printStackTrace(); + } finally { + closeConnection(in); + } + return null; + }); }