#android #service #loader #lazy-evaluation
#Android #Обслуживание #загрузчик #отложенная оценка
Вопрос:
Я использую IntentService для загрузки 200 больших файлов JPG из списка. Во время загрузки пользователь может пропустить незагруженные файлы JPG и загрузить, например, JPG # 156, но после его загрузки он должен продолжить загрузку остальных. Так что это похоже на ленивый загрузчик… но это продолжается, когда он простаивает.
Ранее я использовал onHandleIntent и поместил цикл с # 1 по # 200… который, очевидно, не работает, когда я пытаюсь отправить другой вызов IntentService для JPG # 156. Таким образом, вызов #156 происходит только после того, как onHandleIntent выполняется с #200.
Затем я изменил это так, что onHandleIntent переупорядочивает запрос # 156, чтобы он был в верхней части списка, затем запрашивает верхнюю часть списка (и загружает JPG), затем удаляет его из списка. Затем он снова вызывает IntentService, что звучит довольно рискованно с точки зрения рекурсивности / переполнения стека. Иногда это работает, и я вижу, что файл # 156 ставится первым … иногда.
Есть ли лучший способ сделать это? Способ, о котором я мог бы подумать, — запустить все это через базу данных.
РЕДАКТИРОВАТЬ: это то, что я придумал:
code
public class PBQDownloader extends IntentService {
int currentWeight = 0;
PriorityBlockingQueue<WeightedAsset> pbQueue = new PriorityBlockingQueue<WeightedAsset>(100, new CompareWeightedAsset());
public PBQDownloader() {
super("PBQDownloader");
}
public PBQDownloader(String name) {
super(name);
}
@Override
protected void onHandleIntent(Intent intent) {
String downloadUrl = "-NULL-";
Bundle extras = intent.getExtras();
if (extras!=null) {
downloadUrl = extras.getString("url");
Log.d("onHandleIntent 1.1", "asked to download: " downloadUrl);
} else {
Log.d("onHandleIntent 1.2", "no URL sent so let's start queueing everything");
int MAX = 10;
for (int i = 1; i <= MAX; i ) {
// should read URLs from list
WeightedAsset waToAdd = new WeightedAsset("url: " i, MAX - i);
if (pbQueue.contains(waToAdd)) {
Log.d("onStartCommand 1", downloadUrl " already exists, so we are removing it and adding it back with a new priority");
pbQueue.remove(waToAdd);
}
pbQueue.put(waToAdd);
}
currentWeight = MAX 1;
}
while (!pbQueue.isEmpty()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
WeightedAsset waToProcess = pbQueue.poll();
Log.d("onHandleIntent 2 DOWNLOADED", waToProcess.url);
}
Log.d("onHandleIntent 99", "finished all IntentService calls");
}
@Override
public int onStartCommand(Intent intent, int a, int b) {
super.onStartCommand(intent, a, b);
currentWeight ;
String downloadUrl = "-NULL-";
Bundle extras = intent.getExtras();
if (extras!=null) downloadUrl = extras.getString("url");
Log.d("onStartCommand 0", "download: " downloadUrl " with current weight: " currentWeight);
WeightedAsset waToAdd = new WeightedAsset(downloadUrl, currentWeight);
if (pbQueue.contains(waToAdd)) {
Log.d("onStartCommand 1", downloadUrl " already exists, so we are removing it and adding it back with a new priority");
pbQueue.remove(waToAdd);
}
pbQueue.put(waToAdd);
return 0;
}
private class CompareWeightedAsset implements Comparator<WeightedAsset> {
@Override
public int compare(WeightedAsset a, WeightedAsset b) {
if (a.weight < b.weight) return 1;
if (a.weight > b.weight) return -1;
return 0;
}
}
private class WeightedAsset {
String url;
int weight;
public WeightedAsset(String u, int w) {
url = u;
weight = w;
}
}
}
code
Затем у меня есть это действие:
code
public class HelloPBQ extends Activity {
int sCount = 10;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button tv01 = (Button) findViewById(R.id.tv01);
Button tv02 = (Button) findViewById(R.id.tv02);
Button tv03 = (Button) findViewById(R.id.tv03);
tv01.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
doPBQ();
}
});
tv02.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
doInitPBQ();
}
});
tv03.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
sCount = 0;
}
});
}
private void doInitPBQ() {
Intent intent = new Intent(getApplicationContext(), PBQDownloader.class);
//intent.putExtra("url", "url: " sCount);
startService(intent);
}
private void doPBQ() {
sCount ;
Intent intent = new Intent(getApplicationContext(), PBQDownloader.class);
intent.putExtra("url", "url: " sCount);
startService(intent);
}
}
code
Теперь проблема в том, что я должен поддерживать постоянно увеличивающийся счетчик, который рискует выйти за пределы int (WeightedAsset.weight) — есть ли способ программно добавить в очередь и автоматически сделать его главой очереди? Я попытался заменить WeightedAsset на String, но он не запустил функцию poll(), как я хотел, в виде FIFO вместо стека LIFO.
Ответ №1:
Вот как я бы попробовал это сначала:
Шаг # 1: IntentService
Удерживайте PriorityBlockingQueue
.
Шаг № 2: onHandleIntent()
Выполните итерацию по PriorityBlockingQueue
, загружая каждый файл по очереди по мере его извлечения из очереди.
Шаг # 3: onStartCommand()
проверьте, является ли команда командой «начать все загрузки» (в этом случае выполните привязку к суперклассу). Если вместо этого используется команда «установить приоритет для этой загрузки», измените приоритет этой записи в PriorityBlockingQueue
, чтобы она была следующей onHandleIntent()
, когда текущая загрузка завершится.
Комментарии:
1. Спасибо, Марк, я сделал, как ты предложил! 🙂 Я новичок в StackOverflow, поэтому я не уверен, как добавить код в комментарий, поскольку существует ограничение на количество символов, поэтому я отредактировал исходное сообщение и вставил код, а также другой вопрос.
2. @albonk: Пользователи вряд ли будут запрашивать два миллиарда изображений, так что ваша
int
проблема не в этом.PriorityBlockingQueue
— это всего лишь одна из возможных реализаций — до тех пор, пока вы делаете свою коллекцию потокобезопасной, вы можете делать все, что вам заблагорассудится.3. Спасибо, Марк! Еще немного погуглим, и, похоже,
BlockingDeque
это то, что я ищу, где это позволяет мне предлагать элементы в начало очереди, но это на уровне API 9, который выходит за рамки моей целевой сборки. Также добавлена проверка на случай, если приложение столкнется с обезьяной… но поведение LIFO завершается неудачей, как только мы достигаем этого предела:code
if (currentWeight 1 < целое число. MAX_VALUE) currentWeight ;code
4. @albonk: Вы всегда можете получить исходный код для
BlockingDeque
из Apache Harmony, преобразовать его в свое собственное пространство имен и использовать его. В этом одна из прелестей open source. Ключ — это ваше собственное пространство имен — в противном случае у вас могут возникнуть проблемы на устройствах, которые имеютBlockingDeque
встроенные.