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ốc | Giả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).