You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在后台服务中监听图片/视频新增(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 09:01:26