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
相关产品推荐
相关产品推荐

