ภาพรวม
ตัวอย่างนี้สาธิตการสร้าง Service ที่ทำงานในพื้นหลัง (Background) และ Restart อัตโนมัติเมื่อถูกระบบ Kill ด้วย START_STICKY และ onTaskRemoved
⚠️ ข้อจำกัด Android 8.0+ (API 26+)
Background Service มีข้อจำกัดเข้มงวด:
- ไม่สามารถรัน Background Service ได้นานเกิน 1-2 นาที (ยกเว้น Foreground Service)
- ต้องใช้ Foreground Service พร้อม Notification สำหรับงานที่ต้องรันนาน
START_STICKYและonTaskRemovedไม่รับประกันการ Restart ใน Doze Mode / App Standby- ใช้ WorkManager สำหรับงานที่ต้องการความน่าเชื่อถือและ Deferrable
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่
Android Studio → File → New Project → Empty Activity
ขั้นตอนที่ 2: จัดรูปแบบหน้าจอ (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Start Background Service"
android:textSize="16sp"
android:textStyle="bold"/>
</RelativeLayout>
ขั้นตอนที่ 3: สร้าง Service (MyService.java)
package app.com.sample;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// แสดง Toast ว่า Service เริ่มทำงาน
Toast.makeText(getApplicationContext(),
"Service running in background", Toast.LENGTH_SHORT).show();
// ทำงาน Background ที่นี่ (Download, Sync, Music Player, etc.)
// START_STICKY: ระบบจะ Restart Service หากถูก Kill (เมื่อหน่วยความจำพอ)
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// ไม่รองรับ Binding
return null;
}
@Override
public void onTaskRemoved(Intent rootIntent) {
// เรียกเมื่อ User สวายปิดแอปจาก Recent Apps
// Restart Service เอง
Intent restartIntent = new Intent(getApplicationContext(), this.getClass());
restartIntent.setPackage(getPackageName());
startService(restartIntent);
super.onTaskRemoved(rootIntent);
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(getApplicationContext(), "Service destroyed", Toast.LENGTH_SHORT).show();
}
}
ขั้นตอนที่ 4: เขียนโค้ด MainActivity.java
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// เริ่ม Service
startService(new Intent(getApplicationContext(), MyService.class));
}
});
}
}
ขั้นตอนที่ 5: AndroidManifest.xml (สำคัญ: ต้องประกาศ Service)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="app.com.sample">
<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">
<!-- ประกาศ Service ที่นี่ -->
<service
android:name=".MyService"
android:enabled="true"
android:exported="false"> <!-- exported="false" ปลอดภัยกว่า -->
</service>
<activity android:name=".MainActivity">
<intent-filter>
</intent-filter>
</activity>
</application>
</manifest>
การทดสอบ
รันแอป → กดปุ่ม "Start Background Service" → ดู Toast → สวายปิดแอปจาก Recent Apps → Service จะ Restart เอง (ใน Android เก่า)
แนวทางปัจจุบันที่แนะนำ (Best Practice 2024+)
| Use Case | Solution ที่แนะนำ |
|---|---|
| Sync ข้อมูลเป็นระยะ | WorkManager (PeriodicWorkRequest) |
| Download/Upload ไฟล์ใหญ่ | WorkManager + Foreground Service |
| Play Music / Video | MediaSession + Foreground Service |
| Location Tracking | Foreground Service + FusedLocationProvider |
| Real-time Chat | Firebase Cloud Messaging (FCM) |
| Sensor Monitoring | Foreground Service |
Foreground Service Template (Android 8.0+)
public class MyForegroundService extends Service {
private static final int NOTIFICATION_ID = 1;
private static final String CHANNEL_ID = "ForegroundServiceChannel";
@Override
public void onCreate() {
super.onCreate();
createNotificationChannel();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// สร้าง Notification
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Service Running")
.setContentText("Background task in progress...")
.setSmallIcon(R.drawable.ic_notification)
.setOngoing(true)
.build();
// เริ่ม Foreground Service
startForeground(NOTIFICATION_ID, notification);
// ทำงานจริงที่นี่...
return START_NOT_STICKY; // หรือ START_STICKY
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, "Foreground Service", NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
}
}
WorkManager Example (แทน Background Service)
// build.gradle
implementation "androidx.work:work-runtime-ktx:2.8.1"
// Worker Class
class SyncWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// ทำงาน Background ที่นี่
return Result.success()
}
}
// Schedule งาน
val request = PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES)
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"sync_work", ExistingPeriodicWorkPolicy.KEEP, request)