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




