Как вызывать один и тот же thread несколько раз?
Я получаю список фото из ВК в виде url. Потом по каждому url получаю сами фотки. Делаю это, естественно, в отдельном потоке:
final int finalI = i;
Thread thread = new Thread(() -> {
try {
synchronized (this) {
arrayListParticipants.get(finalI).setAvatarUser(
BitmapFactory.decodeStream((InputStream) new URL(tempURLs.get(finalI)).getContent()));
Objects.requireNonNull(getActivity()).runOnUiThread(adapterParticipants::notifyDataSetChanged);
}
}
catch (Exception e) { e.printStackTrace(); }
});
thread.setDaemon(true);
thread.start();
Всё прекрасно загружается, но в логах я вижу это:
W/grams.managerv: Long monitor contention with owner Thread-436 (3651) at void com.toprograms.managervk.ParticipantsGroup$1.lambda$onComplete$0$ParticipantsGroup$1(int)(ParticipantsGroup.java:113) waiters=60 in void com.toprograms.managervk.ParticipantsGroup$1.lambda$onComplete$0$ParticipantsGroup$1(int) for 12.026s
Current owner stack:
at com.toprograms.managervk.ParticipantsGroup$1.lambda$onComplete$0$ParticipantsGroup$1(ParticipantsGroup.java:113)
- waiting to lock <0x0bb7db7b> (a com.toprograms.managervk.ParticipantsGroup$1) held by thread 378
at com.toprograms.managervk.-$$Lambda$ParticipantsGroup$1$rl4kQMqWEgy_4KiHiS9d9kFwpps.run(lambda:-1)
at java.lang.Thread.run(Thread.java:764)
Contender stack:
at com.toprograms.managervk.ParticipantsGroup$1.lambda$onComplete$0$ParticipantsGroup$1(ParticipantsGroup.java:113)
at com.toprograms.managervk.-$$Lambda$ParticipantsGroup$1$rl4kQMqWEgy_4KiHiS9d9kFwpps.run(lambda:-1)
at java.lang.Thread.run(Thread.java:764)
Как я понимаю, я кучу раз создаю кучу потоков. И это нехорошо. Более того, когда ухожу из этого фрагмента, а потом возвращаюсь, получаю OutOfMemoryError при попытке thread.start():
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.toprograms.managervk, PID: 2768
java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log.
Вопрос: как я могу создать один поток, но так, чтобы он вызывался каждый раз, когда у меня меняется url? Или может есть другие варианты реализации такой задачи? Может быть RXJava? Но тут я совсем новичок)
Ответы (3 шт):
Блок synchronized здесь вообще лишний.
В потоке обычным способом читайте и декодируйте Bitmap.
Как он будет готов, вызывайте runOnUiThread() с setAvatarUser() и notifyDataSetChanged() внутри.
Как эффективно загружать Bitmaps хорошо описано здесь со ссылками на рекомендуемые библиотеки.
https://developer.android.com/topic/performance/graphics/load-bitmap
Во-первых, всё-таки Picasso лучше всех) Используя его, получил быстрее загрузку картинок. Во-вторых, Picasso тоже нужен UI) НО! Нашёл решение-обход (взял отсюда):
int finalI = i;
Picasso.with(getContext()).load(tempURLs.get(finalI)).into(new Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
arrayListParticipants.get(finalI).setAvatarUser(bitmap);
adapterParticipants.notifyDataSetChanged();
}
@Override
public void onBitmapFailed(Drawable errorDrawable) { }
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) { }
});
В-третьих, крутил вертел как только мог synchronized, но либо всё зависало, пока не прогрузятся все картинки, либо работало также с OutMemory. Как-то так. Возможно кому-то это тоже поможет.
Потоки не стоит создавать направо и налево, это дорогой ресурс. В подобных ситуация стоит использовать thread pool.
ForkJoinPool.commonPool().execute(() -> {
try {
arrayListParticipants
.get(finalI)
.setAvatarUser(
BitmapFactory.decodeStream((InputStream) new URL(tempURLs.get(finalI)).getContent())
);
Objects.requireNonNull(getActivity())
.runOnUiThread(adapterParticipants::notifyDataSetChanged);
} catch (Exception e) {
e.printStackTrace();
}
});
Thread pool не создаёт новые потоки на каждую задачу, а ставит в очередь, если все потоки заняты.