如何在后台服务中监听图片/视频新增(Content Observer)并长期运行
实现Android下长期监听Media.Image的ContentObserver(应用卸载前持续运行)
要让你的ContentObserver在应用安装后持续监听,哪怕应用关闭也不停止,直到卸载,核心是要避开Android的后台限制,用前台服务来持有Observer,再配合一些保活和自动重启的逻辑。下面是具体的实现步骤:
1. 实现自定义ContentObserver
首先写一个继承自ContentObserver的类,重写onChange方法来处理图片的新增或变更事件。注意要在后台线程执行数据库查询,避免ANR:
class MediaImageObserver(handler: Handler) : ContentObserver(handler) { override fun onChange(selfChange: Boolean, uri: Uri?) { super.onChange(selfChange, uri) uri ?: return // 切换到IO线程处理媒体查询 CoroutineScope(Dispatchers.IO).launch { val projection = arrayOf( MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.RELATIVE_PATH ) context?.contentResolver?.query( uri, projection, null, null, null )?.use { cursor -> if (cursor.moveToFirst()) { val fileName = cursor.getString( cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME) ) Log.d("MediaImageObserver", "检测到图片变更: $fileName") // 这里写你的业务逻辑,比如上传、分析等 } } } } }
2. 创建前台服务(Foreground Service)
Android 8.0之后,普通后台服务会被系统限制甚至杀死,所以必须用前台服务——它会在通知栏显示一个低优先级通知,保证服务不会被轻易回收。
第一步:配置Manifest权限和服务声明
在AndroidManifest.xml里添加必要的权限和服务注册:
<!-- 基础权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" /> <!-- 媒体访问权限 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="29" /> <!-- 忽略电池优化(可选但推荐) --> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <application ...> <service android:name=".MediaObserverForegroundService" android:foregroundServiceType="mediaProjection" android:exported="false" /> </application>
第二步:实现前台服务类
服务的核心逻辑是在onCreate里注册ContentObserver,onDestroy里注销,同时启动前台通知:
class MediaObserverForegroundService : Service() { private lateinit var imageObserver: MediaImageObserver private val CHANNEL_ID = "MediaObserver_Channel" private val NOTIFICATION_ID = 10001 override fun onCreate() { super.onCreate() // 创建通知渠道(Android O+ 强制要求) createNotificationChannel() // 启动前台服务 startForeground(NOTIFICATION_ID, buildNotification()) // 注册ContentObserver imageObserver = MediaImageObserver(Handler(Looper.getMainLooper())) contentResolver.registerContentObserver( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, // 监听子目录的变化 imageObserver ) } override fun onBind(intent: Intent): IBinder? = null override fun onDestroy() { super.onDestroy() // 注销Observer,避免内存泄漏 contentResolver.unregisterContentObserver(imageObserver) } override fun onTaskRemoved(rootIntent: Intent?) { super.onTaskRemoved(rootIntent) // 当应用被从最近任务栏移除时,重启服务 val restartIntent = Intent(this, MediaObserverForegroundService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(restartIntent) } else { startService(restartIntent) } } private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( CHANNEL_ID, "图片监听服务", NotificationManager.IMPORTANCE_LOW // 低优先级,减少用户打扰 ).apply { description = "持续监听设备图片的新增与变更" } val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } } private fun buildNotification(): Notification { // 点击通知可打开应用主界面 val launchIntent = packageManager.getLaunchIntentForPackage(packageName) val pendingIntent = PendingIntent.getActivity( this, 0, launchIntent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) return NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("图片监听中") .setContentText("后台持续检测图片变化") .setSmallIcon(R.drawable.ic_notification) // 替换成你的通知图标 .setContentIntent(pendingIntent) .setPriority(NotificationCompat.PRIORITY_LOW) .setOngoing(true) // 设置为不可滑动删除 .build() } }
3. 启动服务并处理权限
动态申请媒体权限
在应用首次启动时,要先申请媒体访问权限,否则ContentObserver无法获取图片数据:
fun requestMediaPermissions(context: Context) { val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { arrayOf(Manifest.permission.READ_MEDIA_IMAGES) } else { arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) } if (ContextCompat.checkSelfPermission(context, permissions[0]) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(context as Activity, permissions, 1001) } else { // 权限已授予,启动前台服务 startMediaObserverService(context) } }
请求忽略电池优化
很多厂商的电池优化会杀死后台服务,所以最好引导用户将应用加入电池白名单:
fun requestIgnoreBatteryOptimization(context: Context) { val powerManager = context.getSystemService(POWER_SERVICE) as PowerManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !powerManager.isIgnoringBatteryOptimizations(context.packageName)) { val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { data = Uri.parse("package:${context.packageName}") } context.startActivity(intent) } }
启动服务的方法
fun startMediaObserverService(context: Context) { val serviceIntent = Intent(context, MediaObserverForegroundService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(serviceIntent) } else { context.startService(serviceIntent) } }
4. 额外注意事项
- 厂商ROM限制:小米、华为、vivo等厂商的定制ROM对后台服务管控严格,即使是前台服务也可能被杀死。需要引导用户手动将应用添加到「后台运行白名单」或「自启动管理」列表。
- 应用卸载自动停止:只要应用被卸载,系统会自动清理所有相关组件,包括你的前台服务和ContentObserver,无需额外处理。
- 内存泄漏:一定要在服务销毁时注销ContentObserver,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Sanoop Vasu
相关产品推荐
相关产品推荐

