AndroidStudio前台服务示例程序

AndroidStudio前台服务示例程序 如果你的应用需要持续、稳定地执行后台任务比如音乐播放、运动轨迹记录那么将你的Service设置为前台服务是最高优先级的方案。前台服务会显示一个持续的通知告诉用户你的应用正在运行。系统会将其视为用户正在交互的应用从而极大地降低被杀死或限制的概率。我的示例程序里有两个前台服务程序一个是定时器服务程序一个是位置服务程序。可以选择一个并启动。该示例程序适合于Android13及以上版本。注运行程序需要手机上安装有google play服务、google TTS并在手机设置中授予示例程序后台省电优化白名单各品牌手机设置方法有异、自启动权限、位置权限始终允许、通知权限。如果全部设置到位那么程序中的权限申请就会成功不会弹出对话框。注在手机上定时器最短响应时间为1分钟设置小于1分钟的间隔是没用的测试机为小米。对于位置服务只有在手机位置变化情况下才会触发回调。手机放桌上不动位置服务回调基本上不会触发。定时器服务程序代码package com.example.train; 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.media.AudioAttributes; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import androidx.core.app.NotificationCompat; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.app.AlarmManager; import android.os.SystemClock; import android.speech.tts.TextToSpeech; import android.util.Log; import org.greenrobot.eventbus.EventBus; import java.util.Locale; public class TimerForegroundService extends Service { private static final int NOTIF_ID 1; // 全局唯一的通知标识 String NOTIFICATION_CHANNEL_ID foreground_service_channel; String CHANNEL_NAME 前台服务通知; String text 定时器正在运行; //用于定时器 AlarmManager alarmManager null; PendingIntent pendingIntent null; public static int count 0;//定时器计数 public static float vol 0.5f;//音量 private TextToSpeech tts; public TimerForegroundService() { } Override public void onCreate() { super.onCreate(); // 初始化代码 //创建通知模板 if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { NotificationChannel channel new NotificationChannel(NOTIFICATION_CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT); NotificationManager manager getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } tts new TextToSpeech(this, new TextToSpeech.OnInitListener() { Override public void onInit(int status) { if (status TextToSpeech.SUCCESS) { // 关键设置音频属性为闹钟模式强制可听 if (Build.VERSION.SDK_INT Build.VERSION_CODES.LOLLIPOP) { AudioAttributes attrs new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ALARM) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED) .build(); tts.setAudioAttributes(attrs); } int result tts.setLanguage(Locale.CHINA); // 设置中文 if (result TextToSpeech.LANG_MISSING_DATA || result TextToSpeech.LANG_NOT_SUPPORTED) { Log.e(TTS, 中文语言包不可用); } else{ //AudioManager am (AudioManager)getSystemService(Context.AUDIO_SERVICE); //am.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(am.getStreamMaxVolume(AudioManager.STREAM_MUSIC) * 0.8), 0); EventBus.getDefault().post(new MyEvent(1,TTS语音初始化完成)); } } } }); } Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent ! null) { int code intent.getIntExtra(code, 0); String text intent.getStringExtra(text); switch(code){ case 0: // 这里启动前台服务 startForeground(NOTIF_ID, buildNotification(text)); // 1是通知ID必须是唯一的正整数 startTimer(); EventBus.getDefault().post(new MyEvent(1,定时器服务已启动)); break; case 1: updateNotification(text); tts.setPitch(1.2f);// 音高调节0.5-2.0默认1.0 tts.setSpeechRate(0.9f);// 语速调节0.5-2.0默认1.0 Bundle params new Bundle(); //因为TTS被设为闹钟模式(便于熄屏后播放)所以手机音量键无法控制语音大小需要代码来控制 params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, TimerForegroundService.vol); // 0.0(静音) ~ 1.0(最大) tts.speak(text, TextToSpeech.QUEUE_FLUSH, params, null); break; case 2: stopForeground(true); stopSelf(); EventBus.getDefault().post(new MyEvent(1,定时器服务已停止)); break; } } return START_STICKY; } Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. //throw new UnsupportedOperationException(Not yet implemented); return null; } private Notification buildNotification(String contentText) { //Notification 点击跳转需通过 PendingIntent 封装 Intent 并传入 Notification //Android 12 必须显式指定 FLAG_IMMUTABLE 或 FLAG_MUTABLE否则应用崩溃 。 Intent intent new Intent(this, MainActivity.class); int flag Build.VERSION.SDK_INT Build.VERSION_CODES.S ? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT; PendingIntent pendingIntent PendingIntent.getActivity(this, 0, intent, flag); return new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.train) .setContentTitle(前台服务) .setContentText(contentText) .setContentIntent(pendingIntent)// 绑定点击事件 .setAutoCancel(true)// 点击后自动消失 .setOngoing(true) .build(); } private void updateNotification(String newContent) { Notification updatedNotification buildNotification(newContent); NotificationManager manager (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(NOTIF_ID, updatedNotification); Intent intent new Intent(com.example.broadcast.MY_BROADCAST); intent.putExtra(data, newContent); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } private void startTimer() { alarmManager (AlarmManager) getSystemService(Context.ALARM_SERVICE); Intent intent new Intent(this, YourBroadcastReceiver.class); pendingIntent PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); long interval 10000; // // 设置AlarmManager每分触发一次 alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() interval, pendingIntent); alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), interval, pendingIntent); } private void stopTimer() { alarmManager.cancel(pendingIntent); pendingIntent.cancel(); // API level 19 (KitKat) required for this line of code to work correctly if FLAG_NO_CREATE is used in getBroadcast() method call for PendingIntent creation. Otherwise, it can be safely omitted for older versions of Android or if FLAG_CANCEL_CURRENT is used instead of FLAG_NO_CREATE or FLAG_UPDATE_CURRENT in getBroadcast() method call for PendingIntent creation. } // 在 Activity 的 onDestroy 中务必调用 stopTimer() Override public void onDestroy() { super.onDestroy(); stopTimer(); tts.stop(); // 停止当前播报 tts.shutdown(); // 释放底层资源关键步骤 tts null; // 避免悬空引用 } }定时器回调package com.example.train; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Build; import java.time.LocalTime; public class YourBroadcastReceiver extends BroadcastReceiver { Override public void onReceive(Context context, Intent intent) { // 处理每秒触发的事件 TimerForegroundService.count; LocalTime now null; // 获取当前时间 if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { now LocalTime.now(); int hour now.getHour(); // 获取小时 int minute now.getMinute(); // 获取分钟 int second now.getSecond(); String text String.format(%d点%d分%d秒,hour,minute,second); Intent d new Intent(context, TimerForegroundService.class); d.putExtra(code, 1); d.putExtra(text, text); context.startForegroundService(d); } } }位置服务程序代码package com.example.train; import android.Manifest; 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.content.pm.PackageManager; import android.media.AudioAttributes; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; import android.location.Location; import android.speech.tts.TextToSpeech; import java.util.Locale; import android.util.Log; import android.content.Context; import android.media.AudioManager; import com.google.android.gms.location.FusedLocationProviderClient; import com.google.android.gms.location.LocationCallback; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationResult; import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.Priority; import org.greenrobot.eventbus.EventBus; public class LocationForegroundService extends Service { private FusedLocationProviderClient fusedLocationClient; private LocationCallback locationCallback; private static final int NOTIF_ID 2; // 全局唯一的通知标识 String NOTIFICATION_CHANNEL_ID location_service_channel; String CHANNEL_NAME 前台位置服务通知; String text 位置服务正在运行; public static int count 0; public static float vol 0.5f;//音量 private TextToSpeech tts; public LocationForegroundService() { } Override public void onCreate() { super.onCreate(); fusedLocationClient LocationServices.getFusedLocationProviderClient(this); //创建通知模板 if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { NotificationChannel channel new NotificationChannel(NOTIFICATION_CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT); NotificationManager manager getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } tts new TextToSpeech(this, new TextToSpeech.OnInitListener() { Override public void onInit(int status) { if (status TextToSpeech.SUCCESS) { // 关键设置音频属性为闹钟模式强制可听 if (Build.VERSION.SDK_INT Build.VERSION_CODES.LOLLIPOP) { AudioAttributes attrs new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ALARM) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED) .build(); tts.setAudioAttributes(attrs); } int result tts.setLanguage(Locale.CHINA); // 设置中文 if (result TextToSpeech.LANG_MISSING_DATA || result TextToSpeech.LANG_NOT_SUPPORTED) { Log.e(TTS, 中文语言包不可用); } else{ //AudioManager am (AudioManager)getSystemService(Context.AUDIO_SERVICE); //am.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(am.getStreamMaxVolume(AudioManager.STREAM_MUSIC) * 0.8), 0); EventBus.getDefault().post(new MyEvent(1,TTS语音初始化完成)); } } } }); } Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent ! null) { int code intent.getIntExtra(code, 0); String text intent.getStringExtra(text); switch(code){ case 0: // 这里启动前台服务 startForeground(NOTIF_ID, buildNotification(text)); // 1是通知ID必须是唯一的正整数 requestLocationUpdates(); EventBus.getDefault().post(new MyEvent(1,位置服务已启动)); break; /* case 1: updateNotification(text); tts.setPitch(1.2f);// 音高调节0.5-2.0默认1.0 tts.setSpeechRate(0.9f);// 语速调节0.5-2.0默认1.0 tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, null); break;*/ case 2: stopForeground(true); stopSelf(); EventBus.getDefault().post(new MyEvent(1,位置服务已停止)); break; } } return START_STICKY; } Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return null; } Override public void onDestroy() { super.onDestroy(); fusedLocationClient.removeLocationUpdates(locationCallback); tts.stop(); // 停止当前播报 tts.shutdown(); // 释放底层资源关键步骤 tts null; // 避免悬空引用 } private Notification buildNotification(String contentText) { //Notification 点击跳转需通过 PendingIntent 封装 Intent 并传入 Notification //Android 12 必须显式指定 FLAG_IMMUTABLE 或 FLAG_MUTABLE否则应用崩溃 。 Intent intent new Intent(this, MainActivity.class); int flag Build.VERSION.SDK_INT Build.VERSION_CODES.S ? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT; PendingIntent pendingIntent PendingIntent.getActivity(this, 0, intent, flag); return new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.train) .setContentTitle(前台服务) .setContentText(contentText) .setContentIntent(pendingIntent)// 绑定点击事件 .setAutoCancel(true)// 点击后自动消失 .setOngoing(true) .build(); } private void updateNotification(String newContent) { Notification updatedNotification buildNotification(newContent); NotificationManager manager (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(NOTIF_ID, updatedNotification); } private void requestLocationUpdates() { // 1. 创建 Builder设置基础间隔为 10 秒 (10000 毫秒) LocationRequest.Builder builder new LocationRequest.Builder(10000); // 2. 配置其他参数 LocationRequest locationRequest builder .setPriority(Priority.PRIORITY_HIGH_ACCURACY ) // 高精度 .setMinUpdateIntervalMillis(10000) // 最快 10 秒更新一次 .setMaxUpdateDelayMillis(20000) // 最长延迟 20 秒必须更新一次 .build(); // 3. 构建最终的 LocationRequest 对象 if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) PackageManager.PERMISSION_GRANTED) { fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback new LocationCallback() { Override public void onLocationResult(LocationResult locationResult) { if (locationResult null) { return; } for (Location location : locationResult.getLocations()) { // 处理接收到的位置更新 //Log.d(LocationService, Location: location.getLatitude() , location.getLongitude()); } count; text String.format(位置服务已执行%d次,LocationForegroundService.count); updateNotification(text); tts.setPitch(1.2f);// 音高调节0.5-2.0默认1.0 tts.setSpeechRate(0.9f);// 语速调节0.5-2.0默认1.0 Bundle params new Bundle(); params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, LocationForegroundService.vol); // 0.0(静音) ~ 1.0(最大) tts.speak(text, TextToSpeech.QUEUE_FLUSH, params, null); } }, Looper.getMainLooper()); } } }主程序代码package com.example.train; import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.*; import androidx.activity.EdgeToEdge; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; public class MainActivity extends AppCompatActivity { //用于‌标识位置权限请求‌的自定义整数常量通常在调用 requestPermissions() 方法时作为第一个参数传入 //以便在 onRequestPermissionsResult() 回调中识别该请求。 private static final int LOCATION_PERMISSION_REQUEST_CODE 201; private static final int NOTICE_PERMISSION_REQUEST_CODE 200; android.widget.Button button null; android.widget.Button button2 null; //用于标记程序要启动哪一个前台服务 private enum ServiceType{None,Timer,Location} ServiceType type ServiceType.None; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) - { Insets systemBars insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); // 注册事件监听器 EventBus.getDefault().register(this); //android 13及以上系统动态获取通知权限 if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q) { checkPostNotificationPermission(); checkLocationNotificationPermission(); } button findViewById(R.id.button);//启动服务 button.setOnClickListener(new View.OnClickListener() { Override public void onClick(View v) { startService(); } }); button2 findViewById(R.id.button2);//停止服务 button2.setOnClickListener(new View.OnClickListener() { Override public void onClick(View v) { stopService(); } }); RadioGroup radioGroup findViewById(R.id.radioGroup2); radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { Override public void onCheckedChanged(RadioGroup group, int checkedId) { float vol 0; // 检查哪个RadioButton被选中 if (checkedId R.id.radioButton3) { vol 0.3f; } else if (checkedId R.id.radioButton4) { vol 0.5f; }else if (checkedId R.id.radioButton5) { vol 0.8f; } else if (checkedId R.id.radioButton6) { vol 0; } TimerForegroundService.vol vol; LocationForegroundService.vol vol; } }); } Override protected void onResume() { super.onResume(); EditText edit findViewById(R.id.editTextText); if(TimerForegroundService.count 0){ String val String.format(定时器已执行%d次,TimerForegroundService.count); edit.setText(val); } else if(LocationForegroundService.count 0){ String val String.format(位置服务已执行%d次,LocationForegroundService.count); edit.setText(val); } } Override protected void onDestroy() { super.onDestroy(); stopService(); // 注销事件监听器 EventBus.getDefault().unregister(this); } Subscribe(threadMode ThreadMode.MAIN) // 确保在主线程中更新UI public void onMessageEvent(MyEvent event) { // MyEvent是你要接收的事件类型 // 处理事件 switch(event.code){ case 1: EditText edit findViewById(R.id.editTextText); edit.append(event.text\n);//添加初始化信息 break; } } private void checkPostNotificationPermission() { if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions((Activity) this, new String[]{ Manifest.permission.POST_NOTIFICATIONS}, NOTICE_PERMISSION_REQUEST_CODE); } else{ EditText edit findViewById(R.id.editTextText); edit.append(通知权限已开启\n); } } private void checkLocationNotificationPermission(){ if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); } else{ EditText edit findViewById(R.id.editTextText); edit.append(位置权限已开启\n); } } Override public void onRequestPermissionsResult(int requestCode, NonNull String[] permissions, NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode NOTICE_PERMISSION_REQUEST_CODE) { if (grantResults.length 0 grantResults[0] PackageManager.PERMISSION_GRANTED) { //允许了通知权限 EditText edit findViewById(R.id.editTextText); edit.append(通知权限申请成功\n); } else { Toast.makeText(this, 您拒绝了通知权限, Toast.LENGTH_SHORT).show(); } } else if (requestCode LOCATION_PERMISSION_REQUEST_CODE) { if (grantResults.length 0 grantResults[0] PackageManager.PERMISSION_GRANTED) { //允许了位置权限 EditText edit findViewById(R.id.editTextText); edit.append(位置权限申请成功\n); } else { Toast.makeText(this, 您拒绝了位置权限, Toast.LENGTH_SHORT).show(); } } } private void startService(){ // Android 8.0 必须使用此方法启动前台服务 if (type ServiceType.None Build.VERSION.SDK_INT Build.VERSION_CODES.O) { RadioGroup radioGroup findViewById(R.id.radioGroup); int selectedId radioGroup.getCheckedRadioButtonId(); if(selectedId R.id.radioButton1){ type ServiceType.Timer; } else{ type ServiceType.Location; } Intent intent (type ServiceType.Timer) ? new Intent(this, TimerForegroundService.class) : new Intent(this, LocationForegroundService.class); intent.putExtra(code, 0); intent.putExtra(text, 0);//初始化启动无文本内容 startForegroundService(intent); } } private void stopService(){ if (type ! ServiceType.None Build.VERSION.SDK_INT Build.VERSION_CODES.O) { Intent intent (type ServiceType.Timer) ? new Intent(this, TimerForegroundService.class) : new Intent(this, LocationForegroundService.class); intent.putExtra(code, 2); intent.putExtra(text, 0);//初始化启动无文本内容 startForegroundService(intent); } } }权限申请包括前台服务的申明前台服务通知权限申请TTS权限申请位置服务申请?xml version1.0 encodingutf-8? manifest xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:toolshttp://schemas.android.com/tools queries intent action android:nameandroid.intent.action.TTS_SERVICE / /intent /queries !-- 1. 声明基础及细分权限 -- uses-permission android:nameandroid.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_BACKGROUND_LOCATION / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION / uses-permission android:nameandroid.permission.POST_NOTIFICATIONS / uses-permission android:nameandroid.permission.SCHEDULE_EXACT_ALARM / uses-permission android:nameandroid.permission.USE_EXACT_ALARM / application android:allowBackuptrue android:dataExtractionRulesxml/data_extraction_rules android:fullBackupContentxml/backup_rules android:iconmipmap/ic_launcher android:labelstring/app_name android:roundIconmipmap/ic_launcher_round android:supportsRtltrue android:themestyle/Theme.Train service android:name.LocationForegroundService android:enabledtrue android:exportedtrue android:foregroundServiceTypelocation/service receiver android:name.YourBroadcastReceiver / !-- 2. 在 Service 中声明类型 -- service android:name.TimerForegroundService android:enabledtrue android:exportedtrue android:foregroundServiceTypelocation / !-- 组件仅能被应用内部组件如其他Activity、Service等启动或绑定。 -- activity android:name.MainActivity android:exportedtrue intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity /application /manifest在手机中的表现