AndroidStudio前台服务示例程序

如果你的应用需要持续、稳定地执行后台任务(比如音乐播放、运动轨迹记录),那么将你的Service设置为前台服务是最高优先级的方案。前台服务会显示一个持续的通知,告诉用户你的应用正在运行。系统会将其视为用户正在交互的应用,从而极大地降低被杀死或限制的概率。

我的示例程序是创建一个前台服务并启动。服务里运行一个定时器,定时更新通知并与主界面进行交互。该示例程序适合于Android13及以上版本。

前台服务程序代码:

java 复制代码
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.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

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 = "通知内容";

    private boolean isRunning = false;
    private int count = 0;//定时器计数
    private Handler handler = new Handler(Looper.getMainLooper());//消息队列
    private Runnable runnable;//线程同步

    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);
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!isRunning) {
            isRunning = true;
            startTimer();
        }
        // 这里启动前台服务,在此之前不应执行任何耗时操作
        startForeground(NOTIF_ID, buildNotification(text)); // 1是通知ID,必须是唯一的正整数
        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() {
        runnable = new Runnable() {
            @Override
            public void run() {
                if (isRunning) {
                    // 执行任务,例如更新时间
                    count++;
                    updateNotification(String.format(Locale.getDefault(), "定时器已执行次%d", count));
                    // 延迟1秒后再次执行,形成循环
                    handler.postDelayed(this, 1000);
                }
            }
        };
        handler.post(runnable);
    }

    private void stopTimer() {
        if (handler != null && runnable != null) {
            handler.removeCallbacks(runnable);
        }
    }

    // 在 Activity 的 onDestroy 中务必调用 stopTimer()
    @Override
    public void onDestroy() {
        super.onDestroy();
        stopTimer();
    }

}

主程序代码:(除了启动前台服务并接收消息外,还另有段测试google TTS语音的代码)

java 复制代码
package com.example.train;

import android.Manifest;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
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.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import android.speech.tts.TextToSpeech;
import java.util.Locale;
import android.util.Log;
import android.content.Context;
import android.media.AudioManager;

public class MainActivity extends AppCompatActivity {

    android.widget.Button button = null;
    private TextToSpeech tts;

    @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;
        });

        //android 13及以上系统动态获取通知权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            checkPostNotificationPermission();
        }

        tts = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int status) {
                if (status == TextToSpeech.SUCCESS) {
                    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);
                        EditText edit = findViewById(R.id.editTextText);
                        edit.setText("语音初始化成功2");
                    }
                }
            }
        });

        button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EditText edit = findViewById(R.id.editTextText);
                //Toast.makeText(MainActivity.this,edit.getText() , Toast.LENGTH_SHORT).show();
                tts.setPitch(1.2f);// 音高调节(0.5-2.0,默认1.0)
                tts.setSpeechRate(0.9f);// 语速调节(0.5-2.0,默认1.0)
                tts.speak(edit.getText(), TextToSpeech.QUEUE_FLUSH, null, null);

            }
        });

        LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String data = intent.getStringExtra("data");
                // 处理数据
                EditText edit = findViewById(R.id.editTextText);
                edit.setText(data);
            }
        }, new IntentFilter("com.example.broadcast.MY_BROADCAST"));

        // Android 8.0+ 必须使用此方法启动前台服务
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Intent intent = new Intent(this, TimerForegroundService.class);
            startForegroundService(intent);//启动前台服务
        }
    }

    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}, 200);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 200) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //允许了通知权限
            } else {
                Toast.makeText(this, "您拒绝了通知权限", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

权限申请:

包括前台服务的申明,前台服务通知权限申请,TTS权限申请,位置服务申请(暂时用不上)

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <queries>
        <intent>
            <action android:name="android.intent.action.TTS_SERVICE" />
        </intent>
    </queries>

    <!-- 1. 声明基础及细分权限 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Train">

        <!-- 2. 在 Service 中声明类型 -->
        <service
            android:name=".TimerForegroundService"
            android:foregroundServiceType="location"
            android:enabled="true"
            android:exported="true" /><!--组件仅能被应用内部组件(如其他Activity、Service等)启动或绑定。-->

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

在模拟器中的表现:

相关推荐
李斯维3 天前
腾讯 XLog 日志框架 Android 端接入
android·android studio·android jetpack
方白羽11 天前
Android Gradle 缓存与文件目录深度解析
android·gradle·android studio
badhope14 天前
做了几年安卓开发,这些坑我帮你踩过了
android·android studio
我命由我1234519 天前
Android 开发问题:EditText 控件的 android:imeOptions=“actionDone“ 属性不生效
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
我命由我1234519 天前
Android 开发问题:获取到的 Android ID 发生了变化
android·java·开发语言·java-ee·android studio·android jetpack·android runtime
我命由我1234519 天前
Android 开发问题:Unable to find explicit activity class
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
终将老去的穷苦程序员22 天前
基于Android Studio开发的安卓图书借阅管理系统
java·sqlite·android studio·android-studio
问心无愧051322 天前
ctf show web入门107
android·前端·笔记·android studio
我命由我1234522 天前
Android 开发问题:View 的 getWidth、getHeight 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime