【问题标题】:How to queue multiple tasks in a foreground service, so that they execute one by one?如何在一个前台服务中对多个任务进行排队,让它们一个一个地执行?
【发布时间】:2019-09-10 15:30:54
【问题描述】:
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。我可能会完成所有需要完成的工作并立即执行它,而不是为每个操作杀死/重新启动多个任务。你也可以让你的AsyncTaskstatic,所以它不包含对Service 的隐式引用。如果需要,请使用application context
  • 你能告诉我如何使用应用程序上下文吗?是 getApplication() 还是 getApplicationContext() ?
  • 使你的内部类static(非静态嵌套类持有对其包含类的隐式引用,当封闭类扩展@987654331时,这可能导致内存泄漏@(Service 确实如此)并且涉及线程),将Service 作为构造函数参数传入,并且只保留对context.getApplicationContext() 的引用。 Application Context 是事实上的单例,因此您无需担心“泄漏”它。我会填写答案。

标签: android android-asynctask android-service android-intentservice


【解决方案1】:

正如我在 cmets 中所说,我认为您的解决方案是合理的。 Foreground 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;
}

这不会导致任何内存泄漏,因为任何待处理的工作都会与服务一起被破坏。

【讨论】:

  • 您好,首先感谢您的回答。这似乎正是我想要的,因为它不仅仅针对我发布的代码,我可以将它用于任何未来的项目。不过,好像有点小问题。我无法将全局变量任务、future 和 executor 设为 final。它说不能为最终变量赋值。并且 stopself() 由于某种原因没有被调用。即使任务完成,通知也会一直持续。
  • 啊,你是对的 - 那些final 变量将需要在服务的构造函数中分配。我是在没有 IDE 的情况下编写的,所以我可能遗漏了一些小问题。我想我明白为什么 stopSelf() 可能永远不会被调用 - 检查应该在 Runnable 执行结束时进行。我已经更新了我的答案以解决这个问题。
  • 谢谢,它运行良好!但是,您能告诉我如何更新每个任务的前台服务通知吗?
  • 将其合并到您传递给executor#submitRunnable 中。复制完成后,您可以更新通知。
猜你喜欢
  • 2014-07-15
  • 1970-01-01
  • 2019-04-09
  • 2016-05-24
  • 2018-03-30
  • 1970-01-01
  • 2015-10-06
  • 1970-01-01
  • 2011-05-31
相关资源
最近更新 更多