Android14新特性 开启前台service服务

1. Android14新特性

1.1. 场景

  在Android14(targetSDK=34)系统手机开启前台service服务崩溃

ATAL EXCEPTION: main
                 Process: com.inspur.lbrd, PID: 15634
                 java.lang.RuntimeException: Unable to create service com.inspur.lbrd.service.KeepAliveService: android.app.MissingForegroundServiceTypeException: Starting FGS without a type  callerApp=ProcessRecord{957facf 15634:com.inspur.lbrd/u0a352} targetSDK=34
                 	at android.app.ActivityThread.handleCreateService(ActivityThread.java:5182)
                 	at android.app.ActivityThread.-$$Nest$mhandleCreateService(Unknown Source:0)
                 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2638)
                 	at android.os.Handler.dispatchMessage(Handler.java:108)
                 	at android.os.Looper.loopOnce(Looper.java:226)
                 	at android.os.Looper.loop(Looper.java:328)
                 	at android.app.ActivityThread.main(ActivityThread.java:9128)
                 	at java.lang.reflect.Method.invoke(Native Method)
                 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:586)
                 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
                 Caused by: android.app.MissingForegroundServiceTypeException: Starting FGS without a type  callerApp=ProcessRecord{957facf 15634:com.inspur.lbrd/u0a352} targetSDK=34
                 	at android.app.MissingForegroundServiceTypeException$1.createFromParcel(MissingForegroundServiceTypeException.java:53)
                 	at android.app.MissingForegroundServiceTypeException$1.createFromParcel(MissingForegroundServiceTypeException.java:49)
                 	at android.os.Parcel.readParcelableInternal(Parcel.java:4884)
                 	at android.os.Parcel.readParcelable(Parcel.java:4866)
                 	at android.os.Parcel.createExceptionOrNull(Parcel.java:3066)
                 	at android.os.Parcel.createException(Parcel.java:3055)
                 	at android.os.Parcel.readException(Parcel.java:3038)
                 	at android.os.Parcel.readException(Parcel.java:2980)
                 	at android.app.IActivityManager$Stub$Proxy.setServiceForeground(IActivityManager.java:7415)
                 	at android.app.Service.startForeground(Service.java:775)
                 	at com.inspur.lbrd.service.KeepAliveService.setForeground(SourceFile:118)
                 	at com.inspur.lbrd.service.KeepAliveService.onCreate(SourceFile:32)
                 	at android.app.ActivityThread.handleCreateService(ActivityThread.java:5169)
                 	at android.app.ActivityThread.-$$Nest$mhandleCreateService(Unknown Source:0) 
                 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2638) 
                 	at android.os.Handler.dispatchMessage(Handler.java:108) 
                 	at android.os.Looper.loopOnce(Looper.java:226) 
                 	at android.os.Looper.loop(Looper.java:328) 
                 	at android.app.ActivityThread.main(ActivityThread.java:9128) 
                 	at java.lang.reflect.Method.invoke(Native Method) 
                 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:586) 
                 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099) 

在这里插入图片描述

1.2. 解决方案

1.2.1. 在清单文件AndroidManifest.xml添加权限和配置

 <!-- android14前台常住服务权限-->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
  <service
      android:name=".service.KeepAliveService"
      android:foregroundServiceType="location" />

1.2.2. 授权

      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // 定位权限
            requestPermissionLauncher
                    .launch(Manifest.permission.ACCESS_COARSE_LOCATION);
            requestPermissionLauncher
                    .launch(Manifest.permission.ACCESS_FINE_LOCATION);
            requestPermissionLauncher
                    .launch(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
        }
    ActivityResultLauncher<String> requestPermissionLauncher
            = registerForActivityResult(
            new ActivityResultContracts.RequestPermission(),
            result -> {
                if (result.equals(true)) {
                    //权限获取到之后的动作
                } else {
                    //权限没有获取到的动作
                }
            });

1.2.3. service服务

public class KeepAliveService extends Service {
    private final String TAG = "szyj_GridTraceS-";

    public KeepAliveService() {
    }
    @Override
    public void onCreate() {
        super.onCreate();
        // 添加常驻通知栏
        setForeground();
       // startXcService();
    }
    private void startXcService() {
        try {
            String patrolStatus = SpUtil.getInstance(this).getString(
                    GridTraceConstant.SP_PATROL_STATUS,
                    GridTraceConstant.SP_PATROL_STATUS_FALSE);
            //巡查服务已开启
            if (TextUtils.equals(patrolStatus, GridTraceConstant.SP_PATROL_STATUS_TRUE)) {
                if (!ServiceUtil.isServiceRunning(this, GridTraceService.class.getName())) {
                    startService(new Intent(this, GridTraceService.class));
                }
            } else {//未开启巡查服务
                if (ServiceUtil.isServiceRunning(this, GridTraceService.class.getName())) {
                    stopService(new Intent(this, GridTraceService.class));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //可将onStartCommand() 方法的返回值设为 START_STICKY或START_REDELIVER_INTENT ,
        //该值表示服务在内存资源紧张时被杀死后,在内存资源足够时再恢复。
        //也可将Service设置为前台服务,这样就有比较高的优先级,在内存资源紧张时也不会被杀掉。
        return START_STICKY;
        //return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        // 删除图标
        stopForeground(true);
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    /**
     * 添加常驻通知栏
     */
    private void setForeground() {
        NotificationManager notificationManager = (NotificationManager) getSystemService
                (Context.NOTIFICATION_SERVICE);
        String notificationId = "serviceid";
        String notificationName = "servicename";
        int noticeId = 2;
        Notification.Builder builder = new Notification.Builder(this);
        //创建NotificationChannel
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(notificationId,
                    notificationName, NotificationManager.IMPORTANCE_HIGH);
            channel.enableLights(true);//设置高亮(选填)
            channel.setShowBadge(true);//设置角标(选填)
            //设置锁屏可见(选填)
            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
            notificationManager.createNotificationChannel(channel);
            builder.setChannelId(notificationId);
        }
        Intent intent = new Intent(KeepAliveService.this, MainActivity.class);
        PendingIntent pendingIntent;
        //Android12
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
            pendingIntent = PendingIntent.getActivity(this,
                    123, intent, PendingIntent.FLAG_IMMUTABLE);
        } else {
            pendingIntent = PendingIntent.getActivity(this,
                    123, intent, PendingIntent.FLAG_ONE_SHOT
                            | PendingIntent.FLAG_MUTABLE);
        }
        builder.setSmallIcon(R.mipmap.icon_app)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),
                        R.mipmap.icon_app))
                .setContentTitle(getString(R.string.app_name))//选填
                .setContentText(getString(R.string.app_name))//选填
                .setWhen(System.currentTimeMillis())
                .setContentIntent(pendingIntent);
        Notification notification = builder.build();
        startForeground(noticeId, notification);
    }
}

1.2.4. 启动service服务

  if (!ServiceUtil.isServiceRunning(this, KeepAliveService.class.getName())) {
            startService(new Intent(this, KeepAliveService.class));
        }

&emsp;&emsp;判断服务是否开启

public class ServiceUtil {
    /**
     * @param context
     * @param className service后台服务名称
     * @return
     * @desc 查询service是否在运行
     */
    public static boolean isServiceRunning(Context context, String className) {
        ActivityManager activityManager = (ActivityManager) context
                .getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> serviceList = activityManager
                .getRunningServices(Integer.MAX_VALUE);

        if (!(serviceList.size() > 0)) {
            return false;
        }

        for (int i = 0; i < serviceList.size(); i++) {
            ActivityManager.RunningServiceInfo serviceInfo = serviceList.get(i);
            ComponentName serviceName = serviceInfo.service;

            if (serviceName.getClassName().equals(className)) {
                return true;
            }
        }
        return false;
    }
}