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

Android实现应用关闭后持续增加积分的后台服务开发咨询

实现方案

结合Android系统后台限制规则以及你的需求,推荐使用前台服务+持久化存储的方案实现,该方案兼容Android 8.0及以上系统,满足1小时计时、积分平滑增长、退出APP后计时不中断、到期自动停止的要求:


前置说明

Android 8.0之后系统对无名后台服务做了严格的进程回收限制,长时间运行的后台任务必须使用前台服务,且必须展示常驻通知,该限制为系统级规则,普通应用无法绕过。


第一步:添加权限与服务声明

在AndroidManifest.xml中添加必要权限和服务注册:

<!-- 前台服务基础权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 13+ 通知权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application ...>
    <service
        android:name=".PointsEarningService"
        android:foregroundServiceType="dataSync"
        android:exported="false" />
</application>

第二步:实现计时积分前台服务

服务内部处理计时、积分计算、数据持久化、到期自动停止逻辑,就算APP被系统回收,服务依然可以独立运行到计时结束:

class PointsEarningService : Service() {
    private val CHANNEL_ID = "points_earning_channel"
    private val NOTIFICATION_ID = 1001
    // 总计时1小时,对应100积分
    private val TOTAL_TIME_MS = 3600 * 1000L
    private val TOTAL_POINTS = 100

    private lateinit var sharedPref: SharedPreferences
    private val handler = Handler(Looper.getMainLooper())
    private val updateTask = object : Runnable {
        override fun run() {
            val startTime = sharedPref.getLong("start_time", 0L)
            val elapsedTime = System.currentTimeMillis() - startTime
            // 计时到期
            if (elapsedTime >= TOTAL_TIME_MS) {
                sharedPref.edit()
                    .putInt("current_points", TOTAL_POINTS)
                    .putBoolean("is_earning", false)
                    .apply()
                stopSelf()
                return
            }
            // 计算当前积分(平滑增长)
            val currentPoints = (elapsedTime * TOTAL_POINTS / TOTAL_TIME_MS).toInt()
            sharedPref.edit().putInt("current_points", currentPoints).apply()
            // 每秒更新一次
            handler.postDelayed(this, 1000)
        }
    }

    override fun onCreate() {
        super.onCreate()
        sharedPref = getSharedPreferences("points_data", MODE_PRIVATE)
        createNotificationChannel()
        // 启动前台服务,必须调用否则会触发ANR
        startForeground(NOTIFICATION_ID, buildNotification())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 首次启动时记录开始时间
        if (!sharedPref.getBoolean("is_earning", false)) {
            sharedPref.edit()
                .putLong("start_time", System.currentTimeMillis())
                .putBoolean("is_earning", true)
                .putInt("current_points", 0)
                .apply()
        }
        handler.post(updateTask)
        // 服务被系统杀死后自动重启,保留原有计时数据
        return START_REDELIVER_INTENT
    }

    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacks(updateTask)
    }

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

    // 构建前台服务通知
    private fun buildNotification(): Notification {
        val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
        val pendingIntent = PendingIntent.getActivity(
            this,
            0,
            launchIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("积分赚取中")
            .setContentText("当前已获得${sharedPref.getInt("current_points", 0)}积分")
            .setSmallIcon(R.mipmap.ic_launcher) // 替换为你的应用图标
            .setContentIntent(pendingIntent)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .build()
    }

    // Android 8.0+ 需要创建通知渠道
    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                "积分赚取服务",
                NotificationManager.IMPORTANCE_LOW
            )
            getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
        }
    }
}

第三步:实现页面交互逻辑

在Activity中处理按钮点击、积分实时更新展示,通过监听持久化存储的数据变化实现和服务的数据同步:

class MainActivity : AppCompatActivity() {
    private lateinit var sharedPref: SharedPreferences
    private lateinit var pointsTv: TextView
    private lateinit var startBtn: Button

    // 监听积分变化,实时更新UI
    private val prefListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
        when (key) {
            "current_points" -> pointsTv.text = "积分:${sharedPref.getInt("current_points", 0)}"
            "is_earning" -> startBtn.isEnabled = !sharedPref.getBoolean("is_earning", false)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        pointsTv = findViewById(R.id.points_tv)
        startBtn = findViewById(R.id.start_earn_btn)
        sharedPref = getSharedPreferences("points_data", MODE_PRIVATE)

        // 初始化UI状态
        pointsTv.text = "积分:${sharedPref.getInt("current_points", 0)}"
        startBtn.isEnabled = !sharedPref.getBoolean("is_earning", false)

        startBtn.setOnClickListener {
            // 动态申请Android 13+通知权限
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
                && ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
            ) {
                ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 101)
                return@setOnClickListener
            }
            // 启动前台服务
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(Intent(this, PointsEarningService::class.java))
            } else {
                startService(Intent(this, PointsEarningService::class.java))
            }
        }

        sharedPref.registerOnSharedPreferenceChangeListener(prefListener)
    }

    override fun onDestroy() {
        super.onDestroy()
        sharedPref.unregisterOnSharedPreferenceChangeListener(prefListener)
    }
}

边界情况说明

  • 如果用户在系统设置中主动强制停止应用,服务会被系统直接销毁,该场景属于系统级限制,所有普通应用都无法绕过
  • 积分计算基于时间差实现,就算服务中途被系统重启,也不会出现积分少算、跳变的问题
  • 计时到期后服务会自动销毁,无需手动处理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.19 19:36:04