Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách tạo Android Service không bị hệ thống kill (Foreground Service)

Giới thiệu

Hướng dẫn này trình bày cách tạo một Foreground Service - loại Service hiển thị notification liên tục, được hệ thống ưu tiên không kill khi bộ nhớ thấp. Đây là cách đúng đắn thay vì cố gắng "hack" để service không bị kill (điều không thể đảm bảo 100% trên Android hiện đại).

Khái niệm quan trọng

  • Service: Thành phần chạy nền, không UI, tiếp tục sau khi Activity destroy.
  • Foreground Service: Service có notification persistent, priority cao, ít bị kill nhất.
  • START_STICKY: Hệ thống restart service nếu bị kill do thiếu bộ nhớ.
  • WakeLock: Giữ CPU chạy khi màn hình tắt (cần quyền WAKE_LOCK).

Các bước thực hiện

Bước 1: Tạo dự án mới

Tạo dự án Android Studio: File → New Project.

Bước 2: Layout activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Service"
        android:textSize="25sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Bước 3: MainActivity.java

package com.example.myapplication;

import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = findViewById(R.id.text);
        textView.setOnClickListener(v -> {
            if (isMyServiceRunning(MyForegroundService.class)) {
                textView.setText("Stopped");
                stopService(new Intent(MainActivity.this, MyForegroundService.class));
            } else {
                textView.setText("Started");
                // Android 8.0+ cần startForegroundService
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    startForegroundService(new Intent(MainActivity.this, MyForegroundService.class));
                } else {
                    startService(new Intent(MainActivity.this, MyForegroundService.class));
                }
            }
        });
    }

    private boolean isMyServiceRunning(Class serviceClass) {
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                return true;
            }
        }
        return false;
    }
}

Bước 4: Tạo Foreground Service (MyForegroundService.java)

package com.example.myapplication;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.IBinder;
import android.os.PowerManager;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;

public class MyForegroundService extends Service {

    private static final String CHANNEL_ID = "ForegroundServiceChannel";
    private static final int NOTIFICATION_ID = 1001;
    private PowerManager.WakeLock wakeLock;

    @Override
    public void onCreate() {
        super.onCreate();
        createNotificationChannel();
        
        // WakeLock để giữ CPU chạy (cần quyền WAKE_LOCK)
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::ForegroundServiceWakeLock");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Acquire wakelock
        if (wakeLock != null && !wakeLock.isHeld()) {
            wakeLock.acquire();
        }

        // Tạo notification cho Foreground Service
        Notification notification = createNotification();
        
        // Bắt đầu foreground service (bắt buộc từ Android 8.0)
        startForeground(NOTIFICATION_ID, notification);

        Toast.makeText(this, "Foreground Service đã bắt đầu", Toast.LENGTH_SHORT).show();
        Log.d("ForegroundService", "Service started");

        // QUAN TRỌNG: Trả về START_STICKY để hệ thống restart nếu bị kill
        return START_STICKY;
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                "Foreground Service Channel",
                NotificationManager.IMPORTANCE_LOW // Low importance = không âm thanh, không peek
            );
            channel.setDescription("Channel cho Foreground Service chạy nền");
            channel.enableLights(false);
            channel.enableVibration(false);
            
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
    }

    private Notification createNotification() {
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(
            this, 0, intent, PendingIntent.FLAG_IMMUTABLE);

        return new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Foreground Service đang chạy")
            .setContentText("Nhấn để mở ứng dụng")
            .setSmallIcon(R.drawable.ic_launcher_foreground) // Thay bằng icon thực tế
            .setContentIntent(pendingIntent)
            .setOngoing(true) // Không thể swipe dismiss
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setCategory(NotificationCompat.CATEGORY_SERVICE)
            .build();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (wakeLock != null && wakeLock.isHeld()) {
            wakeLock.release();
        }
        Toast.makeText(this, "Foreground Service đã dừng", Toast.LENGTH_SHORT).show();
        Log.d("ForegroundService", "Service destroyed");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null; // Không hỗ trợ binding
    }
}

Bước 5: AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.example.myapplication">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <!-- Từ Android 9 (API 28) cần quyền FOREGROUND_SERVICE -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <!-- Android 14+ cần loại foreground service cụ thể -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                

Giải thích quan trọng - Tại sao code gốc đã lỗi thời

Vấn đề code gốcGiải pháp hiện đại
Dùng WakeLock SCREEN_DIM_WAKE_LOCK (deprecated)Dùng PARTIAL_WAKE_LOCK + Foreground Service
Không có Notification (Android 8+ crash)Bắt buộc startForeground() với Notification
Không khai báo foregroundServiceType (Android 14+)Phải khai báo type: dataSync, mediaPlayback, location...
Không có NotificationChannel (Android 8+)Bắt buộc tạo channel cho notification
Service tên "service" (viết thường - không tuân convention)Đặt tên PascalCase: MyForegroundService

Các loại Foreground Service Type (Android 14+)

android:foregroundServiceType="dataSync"        // Đồng bộ dữ liệu
android:foregroundServiceType="mediaPlayback"   // Phát media
android:foregroundServiceType="location"        // Vị trí
android:foregroundServiceType="health"          // Sức khỏe
android:foregroundServiceType="camera"          // Camera
android:foregroundServiceType="microphone"      // Microphone
android:foregroundServiceType="phoneCall"       // Cuộc gọi
android:foregroundServiceType="remoteMessaging" // Tin nhắn
android:foregroundServiceType="specialUse"      // Đặc biệt (cần declare)

Lưu ý quan trọng

  • Foreground Service KHÔNG BAO GIỜ 100% không bị kill - hệ thống vẫn kill khi memory cực kỳ thấp.
  • Notification phải hiển thị liên tục (ongoing=true), user không thể dismiss.
  • Từ Android 12 (API 31), có giới hạn 20s để gọi startForeground() sau startForegroundService().
  • WorkManager là thay thế hiện đại hơn cho nhiều tác vụ nền định kỳ.
  • Không lạm dụng Foreground Service - chỉ dùng cho tác vụ thực sự cần chạy liên tục (music, tracking, sync).