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

Android游戏后台线程防被杀方法及替代方案咨询

解决Android游戏后台线程被杀死的问题

嘿,我来帮你梳理下这个问题~首先得明确:普通的后台Thread在Android里根本没法保证不被系统杀掉,尤其是从Android 8.0开始的后台限制机制,加上用户手动清除应用时系统会直接终止整个进程,你的线程肯定会停。所以直接避免线程被杀是做不到的,但我们可以用Android官方提供的组件来替代,保证任务即使在后台也能执行(除了用户手动清除应用的情况)。

为什么你的Thread会被杀死?

Android为了节省电量和内存,会对后台应用进行严格限制:

  • 当应用进入后台(用户切到其他应用),系统会逐渐降低后台进程的优先级,最终可能杀掉进程释放资源。
  • 用户从任务概览中手动清除应用时,系统会直接终止该应用的所有进程,包括所有线程。
  • 低内存、低电量模式下,系统会优先杀掉后台进程,你的线程首当其冲。

替代方案推荐

根据你的游戏需求(每15秒更新数值、最多运行720次即3小时),推荐以下几种方案:

1. WorkManager(最推荐)

WorkManager是Jetpack提供的专门处理延迟/周期性后台任务的组件,它会自动适配不同Android版本,根据系统状态选择合适的执行方式(比如JobScheduler、AlarmManager),而且能保证任务即使在应用重启后也能继续执行。

改造步骤:

首先把你的increase_time逻辑放到一个Worker类中,同时保存任务执行次数到本地(比如SharedPreferences),这样即使进程被杀,重启后也能继续计数:

public class GameUpdateWorker extends Worker {
    private static final String KEY_TIME_COUNT = "game_time_count";

    public GameUpdateWorker(@NonNull Context context, @NonNull WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public Result doWork() {
        try {
            // 读取当前执行次数
            SharedPreferences prefs = getApplicationContext().getSharedPreferences("GameData", Context.MODE_PRIVATE);
            int time = prefs.getInt(KEY_TIME_COUNT, 0);

            if (time < 720) {
                // 执行原increase_time的逻辑
                System.out.println("Worker running!");
                time++;
                Share_Prices share_prices = new Share_Prices();
                share_prices.start();
                Business_Development business_development = new Business_Development();
                business_development.start();
                Balance.money += My_RealEstate.revenue_of_RE_per_minute();
                share_prices.join();
                business_development.join();
                Worker_Utils.start_Status_Notification_Worker();

                // 保存更新后的次数
                prefs.edit().putInt(KEY_TIME_COUNT, time).apply();

                // 调度下一次任务,15秒后执行
                OneTimeWorkRequest nextUpdate = new OneTimeWorkRequest.Builder(GameUpdateWorker.class)
                        .setInitialDelay(15, TimeUnit.SECONDS)
                        .build();
                WorkManager.getInstance(getApplicationContext()).enqueue(nextUpdate);

                return Result.success();
            } else {
                // 执行任务结束逻辑
                Balance.money += Buy_Stocks.daily_dividend();
                Save_Utils.set_values_day_over();
                // 重置计数
                prefs.edit().putInt(KEY_TIME_COUNT, 0).apply();
                return Result.success();
            }
        } catch (Exception e) {
            Crash_Utils.send_to_firebase(e);
            // 出错时重试一次
            return Result.retry();
        }
    }
}

然后在游戏启动时(比如MainActivity的onCreate),初始化任务:

// 检查是否有未完成的任务
SharedPreferences prefs = getSharedPreferences("GameData", MODE_PRIVATE);
int time = prefs.getInt(GameUpdateWorker.KEY_TIME_COUNT, 0);
if (time < 720) {
    // 第一次延迟2秒执行(对应原Thread的sleep(2000))
    OneTimeWorkRequest firstUpdate = new OneTimeWorkRequest.Builder(GameUpdateWorker.class)
            .setInitialDelay(2, TimeUnit.SECONDS)
            .build();
    WorkManager.getInstance(this).enqueue(firstUpdate);
    // 初始化市场情绪
    Share_Prices.create_market_mood();
}

2. Foreground Service(适合需要即时执行的场景)

如果你的任务需要非常精确的15秒间隔,且能接受显示一个前台通知(前台服务优先级极高,不容易被系统杀掉),可以用Foreground Service来承载你的线程逻辑。

改造步骤:

创建一个前台服务类:

public class GameUpdateService extends Service {
    private static final int NOTIFICATION_ID = 1001;
    private static final String CHANNEL_ID = "game_update_channel";
    private Thread updateThread;
    private int time;
    private SharedPreferences prefs;

    @Override
    public void onCreate() {
        super.onCreate();
        prefs = getSharedPreferences("GameData", MODE_PRIVATE);
        time = prefs.getInt("game_time_count", 0);
        // 创建通知渠道(Android 8.0+ 必须)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "游戏更新", NotificationManager.IMPORTANCE_LOW);
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
        // 启动前台通知
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("游戏运行中")
                .setContentText("正在同步游戏状态")
                .setSmallIcon(R.drawable.ic_game_icon) // 替换成你的图标
                .build();
        startForeground(NOTIFICATION_ID, notification);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (updateThread == null || !updateThread.isAlive()) {
            updateThread = new Thread(() -> {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Crash_Utils.send_to_firebase(e);
                }
                Share_Prices.create_market_mood();
                while (time < 720) {
                    try {
                        // 执行原increase_time逻辑
                        System.out.println("Service Thread running!");
                        time++;
                        Share_Prices share_prices = new Share_Prices();
                        share_prices.start();
                        Business_Development business_development = new Business_Development();
                        business_development.start();
                        Balance.money += My_RealEstate.revenue_of_RE_per_minute();
                        share_prices.join();
                        business_development.join();
                        Worker_Utils.start_Status_Notification_Worker();

                        // 保存计数
                        prefs.edit().putInt("game_time_count", time).apply();

                        Thread.sleep(15_000);
                    } catch (Exception e) {
                        Crash_Utils.send_to_firebase(e);
                    }
                }
                // 任务结束逻辑
                try {
                    Balance.money += Buy_Stocks.daily_dividend();
                } catch (Exception e) {
                    Crash_Utils.send_to_firebase(e);
                }
                Save_Utils.set_values_day_over();
                prefs.edit().putInt("game_time_count", 0).apply();
                // 停止服务
                stopSelf();
            });
            updateThread.start();
        }
        // 系统重启服务时重新传递intent
        return START_REDELIVER_INTENT;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (updateThread != null) {
            updateThread.interrupt();
        }
    }
}

在Manifest中注册服务:

<service android:name=".GameUpdateService" android:foregroundServiceType="dataSync"/>

在游戏启动时启动服务:

Intent serviceIntent = new Intent(this, GameUpdateService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(serviceIntent);
} else {
    startService(serviceIntent);
}

3. AlarmManager + BroadcastReceiver(不推荐短间隔任务)

AlarmManager可以设置重复闹钟,但短间隔(15秒)的闹钟会被系统限制,尤其是在Doze模式下会被延迟执行,所以只适合更长时间间隔的任务。如果要用的话,需要在BroadcastReceiver中启动WorkManager或者JobIntentService来执行任务。

关键注意事项

  • 手动清除应用无法避免:不管用哪种方案,用户从任务概览中手动清除应用时,系统会终止所有相关进程和任务,这是Android的设计。所以必须把所有游戏状态(time计数、Balance.money等)保存到本地(SharedPreferences、Room等),用户再次打开应用时恢复状态。
  • 电量消耗优化:15秒一次的高频任务会增加电量消耗,建议在游戏前台时用普通Thread,后台时切换到WorkManager,或者给用户提供调整更新间隔的选项。
  • 权限问题:Foreground Service需要在Android 12+申请POST_NOTIFICATIONS权限,WorkManager不需要额外权限。

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

火山引擎 最新活动