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

Android后台服务正常运行但内部计时器停止的解决方案咨询

问题:如何保持Android后台服务内的CountDownTimer持续运行?

环境与场景

  • 系统:Android 14
  • 开发工具:Android Studio Hedgehog
  • 应用功能:记录气压
  • 当前实现:Activity启动并绑定专属后台服务,Activity被系统关闭后服务仍能运行,重启Activity可重新连接服务获取数据。但当Activity失焦或设备休眠后,服务内的CountDownTimer会在数分钟或数小时后停止。
  • 限制:无法使用Job Scheduler(调度时机不固定,无法满足固定周期需求);CountDownTimer工作机制类似Handler的实时中断请求(ISRs)。

现有代码

MainActivity 代码

BackgroundService  mService;

protected void onCreate(Bundle savedInstanceState) {
    //benign stuff
    startService(new Intent(this, BackgroundService.class));
    //more stuff
}

private ServiceConnection connection = new ServiceConnection() {  //Bind and unbind to Service
    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        BackgroundService.LocalBinder binder = (BackgroundService.LocalBinder) service;
        mService = binder.getService();
    }

    @Override
    public void onServiceDisconnected(ComponentName arg0) {}
};

//plus usual onStart(), onReset(), ...

BackgroundService 代码

public int TestVar = -1; //[sic]

@Override
public void onCreate() { super.onCreate(); }

@Override
public int onStartCommand(Intent pIntent, int pFlags, int pStartID) {
    Notification  Notification = null;
    NotificationChannel          NotifChannel;
    NotificationCompat.Builder   builder;
    Intent                       IntentTap;
    PendingIntent                PendingTent;
    Date CurTime   = Calendar.getInstance().getTime();
    long StartTime = CurTime.getTime();  //time of first fix (mS from 1970-01-01)

    NotifManager = (NotificationManager) getSystemService(this.NOTIFICATION_SERVICE);
    NotifChannel = new NotificationChannel(
        SERVICE_NOTIFICATION_CHANNEL,
        SERVICE_NOTIFICATION_CHANNEL,
        NotificationManager.IMPORTANCE_NONE);
    NotifManager.createNotificationChannel(NotifChannel);
    NotifBuilder = new NotificationCompat.Builder(this, SERVICE_NOTIFICATION_CHANNEL)
        .setSmallIcon(R.drawable.ic_notification)
        .setContextTitle("Bold title")
        .setContentText("Minor text")
        .setOnlyAlertOnce(true)
        .setCategory(Notification.CATEGORY_SERVICE);
    IntentTap = getPackageManager().getLaunchIntentForPackage(getPackageName());  //tap notification to focus activity
    PendingTent = PendingIntent.getActivity(this, 1, IntentTap, PendingIntent.FLAG_UPDATE_CURRENT);
    Notification = NotifBuilder.build();
    startForeground(FOREGROUND_ID, Notification);

    CountDownTimer GpTimer = new CountDownTimer(60*60*24*365*1000, 15000) { //start 15 sec timer
        public void onTick(long mSleft) {  //Every 15 Sec...
            CurTime = Calendar.getInstance().getTime();
            int RunTime = (int)((CurTime.getTime() - StartTime) / 15000);  //actual 15 sec runtimes
            TestVar++;  //increment every 15 seconds ...when it does
            NotifBuilder.setContentText("Realtime: " + RunTime + ", Tics: " + TestVar);
            NotifManager.notify(FOREGROUND_ID, NotifBuilder.build());
        }
        public void onFinish () {}  //year is over, how time flies
    }.start();

    return START_STICKY;
};

//plus usual onTaskRemoved(), onDestroy(), onBind()

解决方案

1. 替换CountDownTimer为Handler+Runnable+唤醒锁

CountDownTimer依赖主线程Looper,设备休眠或系统低功耗时Looper可能被暂停,导致计时器停摆。改用Handler+Runnable结合唤醒锁,保证CPU在休眠时仍能执行定时任务:

private Handler mHandler;
private Runnable mTimerRunnable;
private long mStartTime;
private PowerManager.WakeLock mWakeLock;

@Override
public void onCreate() {
    super.onCreate();
    // 获取PowerManager并创建PARTIAL级唤醒锁(仅保持CPU运行,屏幕可关闭)
    PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
    mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::PressureRecorderLock");
    
    // 初始化定时任务
    mHandler = new Handler(Looper.getMainLooper());
    mTimerRunnable = new Runnable() {
        @Override
        public void run() {
            // 执行15秒周期任务
            Date curTime = Calendar.getInstance().getTime();
            int runTime = (int)((curTime.getTime() - mStartTime) / 15000);
            TestVar++;
            NotifBuilder.setContentText("Realtime: " + runTime + ", Tics: " + TestVar);
            NotifManager.notify(FOREGROUND_ID, NotifBuilder.build());
            
            // 重复调度任务
            mHandler.postDelayed(this, 15000);
        }
    };
}

@Override
public int onStartCommand(Intent pIntent, int pFlags, int pStartID) {
    // 保留原有通知创建逻辑...
    startForeground(FOREGROUND_ID, Notification);
    
    mStartTime = Calendar.getInstance().getTime().getTime();
    // 加唤醒锁
    if (!mWakeLock.isHeld()) {
        mWakeLock.acquire();
    }
    // 启动定时任务
    mHandler.post(mTimerRunnable);
    
    return START_STICKY;
}

@Override
public void onDestroy() {
    super.onDestroy();
    // 清理任务与资源
    mHandler.removeCallbacks(mTimerRunnable);
    if (mWakeLock.isHeld()) {
        mWakeLock.release();
    }
}

2. 提升前台服务优先级

当前通知渠道使用IMPORTANCE_NONE,易被系统判定为低优先级服务,建议调整为IMPORTANCE_LOW并设置明确的渠道名称:

NotifChannel = new NotificationChannel(
    SERVICE_NOTIFICATION_CHANNEL,
    "气压记录服务",
    NotificationManager.IMPORTANCE_LOW);

3. 处理系统低功耗限制

  • 引导用户关闭应用电池优化:通过ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS意图跳转到系统设置页面(仅在必要时使用,避免滥用)
  • 注册ACTION_DEVICE_IDLE_MODE_CHANGED广播,在设备退出低功耗模式时重新启动计时任务

4. 避免内存泄漏

原代码中匿名内部类CountDownTimer会持有Service强引用,可能导致内存泄漏。建议改用静态内部类或弱引用实现定时任务。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 02:57:02