App 掛載背景服務,監聽 WiFi 連線廣播,連線成功後自動下載 3 支視頻,播放完畢自動刪除。
┌─────────────────────────────────────┐
│ Foreground Service │ ← 保持存活、顯示通知
├─────────────────────────────────────┤
│ BroadcastReceiver (WiFi 狀態) │ ← CONNECTIVITY_CHANGE / WIFI_STATE_CHANGED
├─────────────────────────────────────┤
│ DownloadManager / WorkManager │ ← 可靠下載、支援斷點續傳
├─────────────────────────────────────┤
│ MediaPlayer / ExoPlayer │ ← 播放完成回調
├─────────────────────────────────────┤
│ File 清理邏輯 │ ← 播放完成刪除快取
└─────────────────────────────────────┘
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<service android:name=".VideoDownloadService"
android:foregroundServiceType="dataSync" />
<receiver android:name=".WifiStateReceiver" android:exported="true">
<intent-filter>
<action android:name="android.net.wifi.STATE_CHANGE" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
public class WifiStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action) ||
ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo wifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (wifi != null && wifi.isConnected()) {
// 啟動前台服務開始下載
Intent serviceIntent = new Intent(context, VideoDownloadService.class);
ContextCompat.startForegroundService(context, serviceIntent);
}
}
}
}
public class VideoDownloadService extends Service {
private static final String[] VIDEO_URLS = {
"https://example.com/video1.mp4",
"https://example.com/video2.mp4",
"https://example.com/video3.mp4"
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 建立前台通知
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("視頻預載中")
.setContentText("正在下載 3 支視頻...")
.setSmallIcon(R.drawable.ic_download)
.setProgress(3, 0, false)
.build();
startForeground(1, notification);
// 使用 WorkManager 可靠下載
downloadVideos();
return START_NOT_STICKY;
}
private void downloadVideos() {
for (int i = 0; i < VIDEO_URLS.length; i++) {
final int index = i;
WorkRequest request = new OneTimeWorkRequest.Builder(VideoDownloadWorker.class)
.setInputData(new Data.Builder()
.putString("url", VIDEO_URLS[i])
.putInt("index", i)
.build())
.build();
WorkManager.getInstance(this).enqueue(request);
}
}
}
public class VideoDownloadWorker extends Worker {
@NonNull
@Override
public Result doWork() {
String url = getInputData().getString("url");
int index = getInputData().getInt("index", 0);
File dest = new File(getApplicationContext().getCacheDir(), "video_" + index + ".mp4");
try (InputStream in = new URL(url).openStream();
FileOutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) > 0) out.write(buffer, 0, len);
} catch (IOException e) {
return Result.retry();
}
// 更新通知進度
// ...
return Result.success();
}
}
// 播放器完成監聽
player.setOnCompletionListener(mp -> {
File videoFile = new File(getCacheDir(), "video_" + currentIndex + ".mp4");
if (videoFile.exists()) videoFile.delete();
});
| 版本 | 關鍵變更 | 應對 |
|---|---|---|
| Android 8.0+ | 背景執行限制 | Foreground Service + Notification Channel |
| Android 10+ | Scoped Storage | 使用 getCacheDir() 或 MediaStore |
| Android 12+ | 精確鬧鐘/前台服務類型 | 宣告 foregroundServiceType="dataSync" |
// 直接用 WorkManager 監聽網路約束
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // WiFi
.build();
PeriodicWorkRequest work = new PeriodicWorkRequest.Builder(
VideoSyncWorker.class, 15, TimeUnit.MINUTES)
.setConstraints(constraints)
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"video_sync", ExistingPeriodicWorkPolicy.KEEP, work);