public class CopyService extends Service {
private List<CustomFile> taskList;
private AsyncTask fileTask;
@Override
public void onCreate() {
super.onCreate();
taskList = new ArrayList<>();
fileTask = new fileTaskAsync();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String filePath = intent.getStringExtra("filePath");
String fileType = intent.getStringExtra("fileType");
String taskType = intent.getStringExtra("taskType");
String fileName = intent.getStringExtra("fileName");
CustomFile customFile = new CustomFile();
customFile.filePath = filePath;
customFile.fileType = fileType;
customFile.taskType = taskType;
customFile.fileName = fileName;
taskList.add(customFile);
Notification notification = getNotification();
startForeground(787, notification);
if (fileTask.getStatus() != AsyncTask.Status.RUNNING) {
CustomFile current = taskList.get(0);
taskList.remove(current);
fileTask = new fileTaskAsync().execute(current);
}
stopSelf();
return START_NOT_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private class fileTaskAsync extends AsyncTask<CustomFile, Void, String> {
@Override
protected String doInBackground(CustomFile... customFiles) {
CustomFile customFile = customFiles[0];
FileUtils.doFileTask(customFile.filePath, customFile.fileType,
customFile.taskType);
return customFile.fileName;
}
@Override
protected void onPostExecute(String name) {
sendResult(name);
if (!taskList.isEmpty()) {
CustomFile newCurrent = taskList.get(0);
taskList.remove(newCurrent);
fileTask = new fileTaskAsync().execute(newCurrent);
}
}
}
private void sendResult(String name) {
Intent intent = new Intent("taskStatus");
intent.putExtra("taskName", name);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
Мне нужно выполнить несколько задач в службе одну за другой. Задача либо копирует, либо перемещает локальные файлы. Предположим, пользователь копирует большой файл и хочет скопировать или переместить другие файлы. Мне нужно, чтобы последующие задачи ставились в очередь и выполнялись одна за другой.
В настоящее время я создаю список внутри службы и запускаю асинхронную задачу. В onPostExecute я проверяю оставшиеся задачи в списке и снова запускаю асинхронную задачу оттуда. Как показано в коде.
Но меня беспокоит утечка памяти. И я очень новичок в программировании, поэтому я не знаю, что лучше всего делать в таких ситуациях.
Я не могу использовать IntentService, потому что хочу, чтобы задача продолжалась, даже если пользователь нажимает кнопку «Домой», чтобы открыть другое приложение.
@PPartisan Я отредактировал свой пост и включил код. Причина, по которой я не публиковал код ранее, заключается в том, что я хотел знать общее решение, независимо от того, что это за код. Но, видимо, я немного неясно выразился в своем вопросе.
Я думаю, что ваше решение разумно. Для работы, которую вы выполняете (длительная работа, которую необходимо выполнить немедленно), ForegroundService — лучшее решение. В качестве альтернативы AsyncTask вы можете использовать ExecutorService или (если вы не против добавить дополнительную библиотеку) RxJava. Я, вероятно, получил бы всю работу, которую необходимо выполнить, и выполнил бы ее сразу, вместо того, чтобы убивать/перезапускать несколько задач для каждой операции. Вы также можете сделать свой AsyncTask класс static, чтобы он не содержал неявной ссылки на Service. Используйте application context, если это необходимо.
Не могли бы вы рассказать мне, как использовать контекст приложения? Это getApplication() или getApplicationContext()?
Сделайте свой внутренний класс static (нестатические вложенные классы содержат неявную ссылку на содержащий их класс, что могу приводит к утечке памяти, когда охватывающий класс расширяется Context (что делает Service) и задействована многопоточность), передайте Service как конструктор аргумент и сохраните только ссылку на context.getApplicationContext(). Контекст приложения де-факто является синглтоном, поэтому вам не нужно беспокоиться о его «утечке». Я заполню ответ.
Как я уже сказал в комментариях, я думаю, что ваше решение разумно. Передний план Service — хороший кандидат для длительной работы, которую необходимо выполнить немедленно, и, судя по вашему описанию, ваша задача копирования файлов соответствует этим критериям.
Тем не менее, я не верю, что AsyncTask является хорошим кандидатом для решения вашей проблемы. AsyncTasks лучше всего развертывать, когда вам нужно выполнить некоторую быструю работу вне основного потока, максимум порядка нескольких сотен миллисекунд, тогда как ваша задача копирования может занять несколько секунд.
Поскольку у вас есть несколько задач, которые не зависят напрямую друг от друга, я бы рекомендовал вам использовать пул потоков для выполнения этой работы. Для этого вы можете использовать ExecutorService:
public class CopyService extends Service {
private final Deque<CustomFile> tasks = new ArrayDeque<>();
private final Deque<Future<?>> futures = new LinkedBlockingDequeue<>();
private final ExecutorService executor = Executors.newCachedThreadPool();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//May as well add a factory method to your CustomFile that creates one from an Intent
CustomFile customFile = CustomFile.fromIntent(intent);
tasks.offer(customFile);
//...Add any other tasks to this queue...
Notification notification = getNotification();
startForeground(787, notification);
for(CustomFile file : tasks) {
final Future<?> future = executor.submit(new Runnable() {
@Override
public void run() {
final CustomFile file = tasks.poll();
//Ddo work with the file...
LocalBroadcastManager.getInstance(CopyService.this).sendBroadcast(...);
//Check to see whether we've now executed all tasks. If we have, kill the Service.
if (tasks.isEmpty()) stopSelf();
}
});
futures.offer(future);
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
//Clear pending and active work if the Service is being shutdown
//You may want to think about whether you want to reschedule any work here too
for(Future<?> future : futures) {
if (!future.isDone() && !future.isCancelled()) {
future.cancel(true); //May pass "false" here. Terminating work immediately may produce side effects.
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
Это не должно вызывать утечек памяти, так как любая незавершенная работа уничтожается вместе со службой.
Привет, во-первых, позвольте мне поблагодарить вас за ответ. Похоже, это именно то, что я хотел, поскольку это не относится только к моему опубликованному коду, и я могу использовать его для любых будущих проектов. Однако, похоже, есть небольшая проблема. Я не могу сделать задачи глобальных переменных, будущее и исполнителя окончательными. Он говорит, что не может присвоить значение конечной переменной. И stopself() по какой-то причине не вызывается. И уведомление идет навсегда даже после того, как задача выполнена.
Ах, вы правы — эти переменные final нужно будет присвоить в конструкторе сервиса. Я писал это без IDE, поэтому, возможно, я упустил несколько незначительных моментов. Думаю, я понимаю, почему stopSelf() может никогда не вызываться — проверка должна происходить в конце выполнения в Runnable. Я обновил свой ответ, чтобы учесть это.
Спасибо, работает отлично! Однако, не могли бы вы рассказать мне, как обновить уведомление службы переднего плана для каждой задачи?
Включите его в Runnable, который вы переходите в executor#submit. Как только ваша копия будет завершена, вы можете обновить свое уведомление.
Какую работу вы выполняете в своей службе? Не могли бы вы опубликовать код, который вы написали до сих пор?