Codename One:应用后台/手机闲置时UITimer失效,如何定时执行任务?
解决Codename One后台定时任务不生效的问题
你遇到的问题其实很典型:UITimer是依赖UI线程运行的,当应用进入后台或者手机休眠时,系统会挂起UI线程来节省资源,自然定时器就停摆了。要实现后台持续的定时任务,得用系统级的调度机制或者前台服务,下面给你两种靠谱的方案:
方案一:系统级定时闹钟(推荐,应用被杀死也能触发)
这种方式利用Android的AlarmManager,属于系统级的定时任务,不管应用是否在前台、甚至被杀死,到时间系统都会唤醒你的应用执行任务。
步骤1:定义原生接口
先创建一个Codename One的原生接口,用来调用Android的闹钟功能:
public interface BackgroundScheduler extends NativeInterface { void scheduleRepeatingTask(long intervalMillis); void cancelTask(); }
步骤2:实现Android原生代码
写Android端的实现类,封装AlarmManager的调用:
import com.codename1.impl.android.AndroidNativeUtil; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; public class BackgroundSchedulerImpl implements BackgroundScheduler { private static final int ALARM_ID = 1234; @Override public void scheduleRepeatingTask(long intervalMillis) { Context context = AndroidNativeUtil.getContext(); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, AlarmReceiver.class); PendingIntent pendingIntent = PendingIntent.getBroadcast( context, ALARM_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); // 从当前时间开始,每隔5分钟触发一次,用RTC_WAKEUP确保唤醒设备 alarmManager.setRepeating( AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), intervalMillis, pendingIntent ); } @Override public void cancelTask() { Context context = AndroidNativeUtil.getContext(); Intent intent = new Intent(context, AlarmReceiver.class); PendingIntent pendingIntent = PendingIntent.getBroadcast( context, ALARM_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); alarmManager.cancel(pendingIntent); } @Override public boolean isSupported() { return true; } }
步骤3:创建广播接收器
写一个广播接收器,用来处理闹钟触发的事件:
import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import com.codename1.ui.Display; public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 切换到UI线程执行音频播放(因为Media需要UI线程环境) Display.getInstance().callSerially(() -> { ((MyApplication) Display.getInstance().getCurrent()).sound(); }); } }
步骤4:在Codename One主类中调用
修改你的主应用类,初始化并启动定时任务:
public class MyApplication { private BackgroundScheduler scheduler; public void start() { if (current != null) { current.show(); return; } Form hi = new Form("Five minutes alert", BoxLayout.y()); hi.add(new Label("Alert every five minutes...")); Button button = new Button("Go to background"); button.addActionListener(l -> Display.getInstance().minimizeApplication()); hi.add(button); hi.show(); // 初始化原生调度器 scheduler = (BackgroundScheduler) NativeLookup.create(BackgroundScheduler.class); if (scheduler != null && scheduler.isSupported()) { // 每5分钟触发一次 scheduler.scheduleRepeatingTask(1000 * 60 * 5); } sound(); Display.getInstance().minimizeApplication(); } public void sound() { try { Media m = MediaManager.createMedia( Display.getInstance().getResourceAsStream(getClass(), "/bell.mp3"), "audio/mpeg" ); m.play(); } catch (IOException err) { Log.e(err); } } // 应用停止时取消定时任务 @Override public void stop() { if (scheduler != null && scheduler.isSupported()) { scheduler.cancelTask(); } } }
额外配置
记得在AndroidManifest中注册广播接收器,你可以通过Codename One的android.xml配置文件添加:
<receiver android:name="你的包名.AlarmReceiver" />
同时添加权限:
<uses-permission android:name="android.permission.WAKE_LOCK" />
方案二:前台服务(适合长期后台运行场景)
如果你的应用需要持续在后台活跃(比如音乐类APP),可以用Codename One的ForegroundService,它会在状态栏显示一个常驻通知,让系统不会轻易杀死进程:
public void startForegroundService() { // 创建前台服务的通知 Notification notification = Notification.createNotification() .setTitle("后台提醒") .setText("每5分钟播放一次提醒音") .setIcon(FontImage.createMaterial( FontImage.MATERIAL_NOTIFICATIONS, UIManager.getInstance().getComponentStyle("Title") )); // 启动前台服务 ForegroundService.startForegroundService(new Runnable() { private Timer timer; @Override public void run() { // 用普通Timer而非UITimer,运行在后台线程 timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { // 切换到UI线程播放音频 Display.getInstance().callSerially(() -> sound()); } }, 0, 1000 * 60 * 5); } @Override public void stop() { if (timer != null) { timer.cancel(); } } }, notification); }
然后在start()方法里调用startForegroundService()即可。
注意事项
- Doze模式适配:如果目标是Android 6.0+,可以把
AlarmManager.setRepeating()换成setAndAllowWhileIdle(),避免Doze模式下任务被延迟。 - Android 12+权限:后台播放音频需要添加
android.permission.FOREGROUND_SERVICE_MEDIA权限。
内容的提问来源于stack exchange,提问作者Francesco Galgani




