Android 后台启动startService()相关问题的解决

需求

有一个用户需要这样一个功能,要求是APP能在充电的时候自动进入APP的一个界面
我寻思着,这玩意用普通权限做不了呀,不过APP有root权限倒也无妨,于是便决定采用Service去做后台服务

问题

某天,在Bugly看到如下的错误报告

java.lang.IllegalStateException:
   Not allowed to start service Intent  
   app is in background uid UidRecord

在这里插入图片描述
提示我无法在后台启动这个服务
于是便开始着手解决这个问题

解决问题

使用startForegroundService()

根据不同的SDK版本采用不同的启动方案

  /**
   * 开启用户服务 需要在目标Service的 onCreate方法中加入startForeground(1, new Notication())
   *
   * @param cls class
   * @param context 上下文
   */
  public static void startUseService(Context context, Class cls) {
    Intent intent = new Intent(context, cls);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      context.startForegroundService(intent);
    } else {
      context.startService(intent);
    }
  }

解决方法:使用startForegroundService()

必须在5秒内调用该服务的 startForeground(int id, Notification notification) 方法,否则将停止服务并抛出 *android.app.RemoteServiceException:Context.startForegroundService() did not then call Service.startForeground()*异常。

Bad notification for startForeground

结果发现调用了**startForeground(int id, Notification notification)**依旧不起作用,提示

FATAL EXCEPTION: main
    android.app.RemoteServiceException: Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification: Notification(channel=null pri=0 contentView=null vibrate=null sound=null defaults=0x0 flags=0x40 color=0x00000000 vis=PRIVATE)

解决方法 :自定义notification

Android 8.0 以上不能用空的通知了 , 必须自己创建通知通道

/** 启动前台服务 */
  private void startForeground() {
    
    
    Log.d(TAG, "startForeground: ");
    String channelId = null;
    // 8.0 以上需要特殊处理
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    
    
      channelId = createNotificationChannel("fuck.foreground", "ForegroundService");
    } else {
    
    
      channelId = "";
    }
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId);
    Notification notification =
        builder
            .setContentText("充电控制服务已启动!")
            .setSmallIcon(R.mipmap.fuck)
            .setPriority(PRIORITY_MIN)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build();
     // 设置 ID 为 0 , 就不显示已通知了 , 但是 oom_adj 值会变成后台进程 11
    // 设置 ID 为 大于0 , 会在通知栏显示该前台服务
    startForeground(0, notification);
  }
/**
   * 创建通知通道
   *
   * @param channelId
   * @param channelName
   * @return
   */
  @RequiresApi(Build.VERSION_CODES.O)
  private String createNotificationChannel(String channelId, String channelName) {
    
    
    NotificationChannel chan =
        new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE);
    chan.setLightColor(Color.BLUE);
    chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
    NotificationManager service =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    service.createNotificationChannel(chan);
    return channelId;
  }

以为这样就可以在不弹出通知的情况下去启动前台服务

解决通知问题

由于上述的startForeground(0, notification)
会不显示通知,结果出现了新的问题

android.app.ForegroundServiceDidNotStartInTimeException: 
		Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord

原来是转为了后台服务,前台服务启动了个寂寞
这个时候就必须使用startForeground(大于零, notification)
通知会影响到用户的体验,如何去避免呢

这个时候在网上看到有人在说可以删除管道的方式
本人实际测试并不能解决问题

失败方案1:

 NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        manager.cancel(1);

失败方案2

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    
    
        NotificationChannel channel = new NotificationChannel(channelId,
                    "Channel for  notification",
                    NotificationManager.IMPORTANCE_NONE);
        mNotificationManager.createNotificationChannel(channel);
}

会出现以下之类的问题,无法去操作前台服务的channel
Not allowed to delete channel fuck.foreground with a foreground service

解决方案 :stopForeground(true)

用定时线程池之类的去定一个延迟任务删除前台通知
由于通知发出是有延迟的,基本上在发出去之前删除掉,
可以实现调用了startForeground(???, notification)而又不会弹出通知消息烦恼用户的情况

 //    延迟2s删除前台通知
      executorService.schedule(() -> 
      stopForeground(true), 
      2, 
      TimeUnit.SECONDS);

就这样,问题愉快的解决了!

总结

  1. 使用startForegroundService(intent)并且在目标service中自己去创建notification
  2. 在自己创建的channel中去使用startForeground(大于零, notification)
  3. 如果需要没有通知,使用stopForeground(true)去停止前台服务

猜你喜欢

转载自blog.csdn.net/qq_29687271/article/details/128187571
今日推荐