You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动