如何确保我的 Android 应用程序不会同时访问文件?

How can I ensure that my Android app doesn't access a file simultaneously?

我正在构建一个健身应用程序,它会在设备上持续记录 activity。我需要经常记录,但我也不想不必要地耗尽用户的电池,这就是为什么我正在考虑将网络呼叫批量处理并在无线电激活后立即传输它们,设备是已连接到 WiFi 或正在充电。

我正在使用基于文件系统的方法来实现它。我首先将数据保存到 File - 最终我可能会使用 Tape from Square 来做到这一点 - 但这是我遇到第一个问题的地方。

我不断地向 File 写入新的日志数据,但我还需要定期将所有记录的数据发送到我的后端。发生这种情况时,我会删除 File 的内容。现在的问题是如何防止这两个操作同时发生?当然,如果我尝试将日志数据写入 File,同时其他进程正在从 File 读取并尝试删除其内容,这会导致问题。

我正在考虑使用 IntentService 本质上充当所有这些操作的队列。并且因为 - 至少我已经阅读了很多 - IntentServices 在单个工作人员 Thread 中顺序处理 Intents,所以这些操作中的两个不可能同时发生,对吧?

目前我想安排一个 PeriodicTaskGcmNetworkManager 来负责将数据发送到服务器。有没有更好的方法来做这一切?

所有 UI 和服务方法默认在同一个主线程上调用。除非您显式创建线程或使用 AsyncTask,否则 Android 应用程序本身没有并发性。

这意味着默认情况下所有意图、警报、广播都在主线程上处理。

另请注意,在主线程上可能会禁止执行 I/O and/or 网络请求(取决于 Android 版本,请参见 How to fix android.os.NetworkOnMainThreadException?)。

使用 AsyncTask 或创建自己的线程会给您带来并发问题,但它们与任何多线程编程一样,Android 没有什么特别之处。

做并发时还要考虑的一点是后台线程需要持有一个WakeLock否则CPU可能会进入休眠

只是一些想法。 您可以尝试为您的文件使用串行执行器,因此,一次只能执行一个线程。 http://developer.android.com/reference/android/os/AsyncTask.html#SERIAL_EXECUTOR

1) 你想多了!

您的方法比实际情况要复杂得多!出于某种原因 none 的其他答案指出了这一点,但是 GcmNetworkManager 已经完成了 一切 你试图实现的事情!您不需要自己实施任何东西。


2) 实现您想要做的事情的最佳方式。

您似乎没有意识到 GcmNetworkManager 已经通过自动重试等以电池效率最高的方式对调用进行了批处理,并且它还在设备启动期间保留了任务,并可以确保它们尽快执行电池高效且您的应用需要。

只要你有数据要保存计划 OneOffTask 就像这样:

final OneoffTask task = new OneoffTask.Builder()

        // The Service which executes the task.
        .setService(MyTaskService.class) 

        // A tag which identifies the task
        .setTag(TASK_TAG) 

        // Sets a time frame for the execution of this task in seconds.
        // This specifically means that the task can either be
        // executed right now, or must have executed at the lastest in one hour.
        .setExecutionWindow(0L, 3600L)  

        // Task is persisted on the disk, even across boots                          
        .setPersisted(true) 

        // Unmetered connection required for task
        .setRequiredNetwork(Task.NETWORK_STATE_UNMETERED) 

        // Attach data to the task in the form of a Bundle
        .setExtras(dataBundle)

        // If you set this to true and this task already exists
        // (just depends on the tag set above) then the old task
        // will be overwritten with this one.
        .setUpdateCurrent(true)

        // Sets if this task should only be executed when the device is charging
        .setRequiresCharging(false)

        .build();
mGcmNetworkManager.schedule(task);

这会做你想做的一切:

  • 任务将持久保存在磁盘上
  • 任务将以批处理和省电的方式执行,最好通过 Wifi
  • 您将使用省电退避模式进行可配置的自动重试
  • 任务将在您指定的时间 window 内执行。

我建议初学者阅读 this 以了解有关 GcmNetworkManager 的更多信息。


总结一下:

您真正需要做的就是在 Service 扩展 GcmTaskService 中实现您的网络调用,稍后当您需要执行此类网络调用时,您可以安排 OneOffTask其他一切 都会为您处理好!

当然,您不需要像我上面那样调用 OneOffTask.Builder 的每个 setter - 我这样做只是为了向您展示您拥有的所有选项。在大多数情况下,安排任务就像这样:

mGcmNetworkManager.schedule(new OneoffTask.Builder()
                .setService(MyTaskService.class)
                .setTag(TASK_TAG)
                .setExecutionWindow(0L, 300L)
                .setPersisted(true)
                .setExtras(bundle)
                .build());

如果你把它放在一个辅助方法中,或者更好地为你需要做的所有不同任务创建工厂方法,那么你试图做的一切都应该归结为几行代码!


顺便说一下:是的,一个 IntentService 在一个工人 Thread 中一个接一个地处理每个 Intent。可以看看相关实现here。其实很简单也很直接。