You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android监听下载目录新增文件:后台服务托管FileObserver恐被系统杀死怎么办?

嘿,针对你遇到的这个问题——想用FileObserver监听下载目录但担心后台服务被系统杀掉,我整理了几个实用的解决方案,你可以根据自己的需求来选:

方案1:用前台服务托管FileObserver

系统对前台服务的优先级更高,不容易被后台回收,唯一的代价是需要显示一个持续的通知(Android 8+还得先创建通知渠道)。具体步骤如下:

  • 先在Manifest里声明必要权限:android.permission.FOREGROUND_SERVICE,Android 13+还要额外添加android.permission.POST_NOTIFICATIONS
  • 创建继承自Service的类,在onCreate里初始化FileObserver,监听下载目录的CREATE事件;
  • onStartCommand中调用startForeground(),传入一个通知实例,把服务提升为前台状态;
  • 务必在服务销毁时(onDestroy)调用FileObserver.stopWatching()释放资源,避免内存泄漏。

示例代码片段:

class DownloadObserverService : Service() {
    private lateinit var fileObserver: FileObserver

    override fun onCreate() {
        super.onCreate()
        val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        fileObserver = object : FileObserver(downloadDir.path, FileObserver.CREATE) {
            override fun onEvent(event: Int, path: String?) {
                path?.let {
                    // 处理新增文件,发送自定义Intent
                    val intent = Intent("com.your.app.DOWNLOAD_FILE_CREATED")
                    intent.putExtra("file_path", "${downloadDir.path}/$it")
                    sendBroadcast(intent)
                }
            }
        }
        fileObserver.startWatching()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Android 8+ 创建通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                "download_observer",
                "下载目录监听",
                NotificationManager.IMPORTANCE_LOW
            )
            getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
        }
        // 构建低优先级通知,减少对用户干扰
        val notification = NotificationCompat.Builder(this, "download_observer")
            .setContentTitle("正在监听下载目录")
            .setContentText("后台运行中")
            .setSmallIcon(R.drawable.ic_notification)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .build()
        startForeground(1, notification)
        return START_STICKY // 服务被杀死后尝试自动重启
    }

    override fun onDestroy() {
        super.onDestroy()
        fileObserver.stopWatching()
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

方案2:结合WorkManager做周期性检查

如果不想用前台服务(不想显示通知),可以用WorkManager定期触发任务,扫描下载目录的新增文件。WorkManager会根据系统状态自动调度,比普通后台服务更稳定可靠:

  • 先添加WorkManager依赖到你的build.gradle;
  • 创建Worker子类,在doWork()方法里扫描下载目录,对比上次记录的文件列表(存在SharedPreferences或Room数据库中),找出新增文件并处理;
  • PeriodicWorkRequest设置周期性任务,最小间隔为15分钟(系统限制);
  • 首次启动时,先记录当前下载目录的所有文件作为基准。

示例代码片段:

class DownloadCheckWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        val currentFiles = downloadDir.listFiles()?.map { it.absolutePath } ?: emptyList()
        val prefs = applicationContext.getSharedPreferences("download_observer", Context.MODE_PRIVATE)
        val lastFiles = prefs.getStringSet("last_files", emptySet()) ?: emptySet()

        // 筛选出新增的文件
        val newFiles = currentFiles.filter { !lastFiles.contains(it) }
        newFiles.forEach { filePath ->
            val intent = Intent("com.your.app.DOWNLOAD_FILE_CREATED")
            intent.putExtra("file_path", filePath)
            applicationContext.sendBroadcast(intent)
        }

        // 更新记录的文件列表
        prefs.edit().putStringSet("last_files", currentFiles.toSet()).apply()
        return Result.success()
    }
}

// 在合适的地方启动周期性任务
val periodicRequest = PeriodicWorkRequestBuilder<DownloadCheckWorker>(15, TimeUnit.MINUTES)
    .build()
WorkManager.getInstance(context).enqueue(periodicRequest)

方案3:使用ContentObserver监听MediaStore

下载的文件通常会被MediaStore索引,你可以注册ContentObserver监听MediaStore.Downloads的变化,这样不需要后台服务,只要应用进程存活就能收到通知:

  • 在Activity或Application类里注册ContentObserver,监听MediaStore.Downloads.EXTERNAL_CONTENT_URI
  • 重写onChange()方法,查询MediaStore获取最新添加的文件;
  • 注意:如果应用进程被杀,这个观察者会失效,适合不需要持续监听的场景,或者结合WorkManager在进程重启后重新注册。

示例代码片段:

class DownloadContentObserver(private val context: Context, handler: Handler?) : ContentObserver(handler) {
    override fun onChange(selfChange: Boolean, uri: Uri?) {
        super.onChange(selfChange, uri)
        // 查询最新添加的下载文件
        val projection = arrayOf(MediaStore.Downloads._ID, MediaStore.Downloads.DATA)
        val sortOrder = "${MediaStore.Downloads.DATE_ADDED} DESC LIMIT 1"
        context.contentResolver.query(
            MediaStore.Downloads.EXTERNAL_CONTENT_URI,
            projection,
            null,
            null,
            sortOrder
        )?.use { cursor ->
            if (cursor.moveToFirst()) {
                val filePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Downloads.DATA))
                val intent = Intent("com.your.app.DOWNLOAD_FILE_CREATED")
                intent.putExtra("file_path", filePath)
                context.sendBroadcast(intent)
            }
        }
    }
}

// 在Application的onCreate中注册观察者
val observer = DownloadContentObserver(this, Handler(Looper.getMainLooper()))
contentResolver.registerContentObserver(
    MediaStore.Downloads.EXTERNAL_CONTENT_URI,
    true,
    observer
)

额外优化建议

  • 前台服务的通知可以设为IMPORTANCE_LOW,这样不会弹出通知,仅在状态栏显示小图标,减少对用户的打扰;
  • FileObserver时,记得处理目录不存在的情况,还要动态申请存储权限(Android 10+可选择分区存储或MANAGE_EXTERNAL_STORAGE权限);
  • Android 12+对后台启动限制更严格,前台服务需要用户授权,要做好权限引导;
  • WorkManager的周期任务不要设置过短,遵循系统最小间隔要求,避免不必要的电量消耗。

总结一下:如果需要实时性高的监听,优先选前台服务+FileObserver;如果可以接受一定延迟,WorkManager的周期性检查更省电稳定;ContentObserver适合轻量、非持续的监听场景。

内容的提问来源于stack exchange,提问作者Parteek Dheri

火山引擎 最新活动