适配8.0以上系统开启前台服务

后台执行限制

Android 8.0 为提高电池续航时间而引入的变更之一是,当您的应用进入已缓存状态时,如果没有活动的组件,系统将解除应用具有的所有唤醒锁。

此外,为提高设备性能,系统会限制未在前台运行的应用的某些行为。具体而言:

现在,在后台运行的应用对后台服务的访问受到限制。
应用无法使用其清单注册大部分隐式广播(即,并非专门针对此应用的广播)。
默认情况下,这些限制仅适用于针对 O 的应用。不过,用户可以从 Settings 屏幕为任意应用启用这些限制,即使应用并不是以 O 为目标平台。

Android 8.0 还对特定函数做出了以下变更:

如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用 startService() 函数,则该函数将引发一个 IllegalStateException。(对应于堆栈一的报错)

新的 Context.startForegroundService() 函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数。(对应于堆栈二的报错,需要补充)

后台服务限制

在后台中运行的服务会消耗设备资源,这可能降低用户体验。 为了缓解这一问题,系统对这些服务施加了一些限制。

系统可以区分 前台 和 后台 应用。 (用于服务限制目的的后台定义与内存管理使用的定义不同;一个应用按照内存管理的定义可能处于后台,但按照能够启动服务的定义又处于前台。)如果满足以下任意条件,应用将被视为处于前台:

具有可见 Activity(不管该 Activity 已启动还是已暂停)。
具有前台服务。
另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的服务,那么该应用处于前台:

IME
壁纸服务
通知侦听器
语音或文本服务
如果以上条件均不满足,应用将被视为处于后台。

绑定服务不受影响
这些规则不会对绑定服务产生任何影响。 如果您的应用定义了绑定服务,则不管应用是否处于前台,其他组件都可以绑定到该服务。

处于前台时,应用可以自由创建和运行前台服务与后台服务。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用服务。

在该时间窗结束后,应用将被视为处于 空闲 状态。 此时,系统将停止应用的后台服务,就像应用已经调用服务的“Service.stopSelf()”方法。

在这些情况下,后台应用将被置于一个临时白名单中并持续数分钟。 位于白名单中时,应用可以无限制地启动服务,并且其后台服务也可以运行。

处理对用户可见的任务时,应用将被置于白名单中,例如:

处理一条高优先级 Firebase 云消息传递 (FCM) 消息。

接收广播,例如短信/彩信消息。

从通知执行 PendingIntent。

在很多情况下,您的应用都可以使用 JobScheduler 作业替换后台服务。 例如,CoolPhotoApp 需要检查用户是否已经从朋友那里收到共享的照片,即使该应用未在前台运行。

之前,应用使用一种会检查其云存储的后台服务。 为了迁移到 Android 8.0,开发者使用一个计划作业替换了这种后台服务,该作业将按一定周期启动,查询服务器,然后退出。

在 Android 8.0 之前,创建前台服务的方式通常是先创建一个后台服务,然后将该服务推到前台。

Android 8.0 有一项复杂功能;系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService(),以在前台启动新服务。

在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground() 方法以显示新服务的用户可见通知。
如果应用在此时间限制内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR。

我们知道在android 8.0 禁止启动后台服务。提出通过startForegroundService() 启动前台服务。但是必须要配合在service 中调用Service.startForeground(),不然就会出现ANR 或者crash。
官方指南:https://developer.android.com/about/versions/oreo/background
ANR log 如下:

11-06 02:01:59.616  3877  3893 E ActivityManager: ANR in com.shift.phonemanager.permission.accesslog
11-06 02:01:59.616  3877  3893 E ActivityManager: PID: 1369
11-06 02:01:59.616  3877  3893 E ActivityManager: Reason: Context.startForegroundService() did not then call Service.startForeground()
11-06 02:01:59.616  3877  3893 E ActivityManager: Load: 0.0 / 0.0 / 0.0
11-06 02:01:59.616  3877  3893 E ActivityManager: CPU usage from 7945ms to 0ms ago (2007-11-06 02:01:51.418 to 2007-11-06 02:01:59.363):
11-06 02:01:59.616  3877  3893 E ActivityManager:   60% 3877/system_server: 35% user + 25% kernel / faults: 3744 minor 6 major
11-06 02:01:59.616  3877  3893 E ActivityManager:   25% 1042/com.android.launcher3: 20% user + 4.9% kernel / faults: 11190 minor 9 major
11-06 02:01:59.616  3877  3893 E ActivityManager:   18% 1149/android.process.media: 13% user + 5.3% kernel / faults: 6130 minor
11-06 02:01:59.616  3877  3893 E ActivityManager:   15% 1420/adbd: 3.6% user + 11% kernel / faults: 5074 minor
11-06 02:01:59.616  3877  3893 E ActivityManager:   9.7% 255/logd: 2.7% user + 6.9% kernel / faults: 5 minor
11-06 02:01:59.616  3877  3893 E ActivityManager:   9.2% 3814/surfaceflinger: 4.4% user + 4.8% kernel / faults: 658 minor

crash log 如下:

--------- beginning of crash
11-06 02:06:05.307  3106  3106 E AndroidRuntime: FATAL EXCEPTION: main
11-06 02:06:05.307  3106  3106 E AndroidRuntime: Process: com.shift.phonemanager.permission.accesslog, PID: 3106
11-06 02:06:05.307  3106  3106 E AndroidRuntime: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1771)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:106)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:164)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:6518)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
11-06 02:06:05.320  3118  3118 D ExtensionsFactory: No custom extensions.

解决办法

代码如下:

package com.example.demo_42_startforegroundservice;
 
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
 
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "jiatai";
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = findViewById(R.id.btn);
        Button btn2 = findViewById(R.id.btn2);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "start service");
                Intent intent = new Intent(MainActivity.this,MyService.class);
                intent.putExtra("type",1);
                startForegroundService(intent);
            }
        });
 
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "start service");
                Intent intent = new Intent(MainActivity.this,MyService.class);
                intent.putExtra("type",2);
                startForegroundService(intent);
            }
        });
    }
}

Service代码如下:

package com.example.demo_42_startforegroundservice;
 
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
 
public class MyService extends Service {
    private static final String TAG = "jiatai";
    private MyHandler handler;
    public MyService() {
    }
 
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
 
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "service oncreate");
        handler = new MyHandler();
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, final int startId) {
        int type = intent.getIntExtra("type",1);
        Log.d(TAG, "the create notification type is " + type + "----" + (type == 1 ? "true" : "false"));
        if(type == 1){
            createNotificationChannel();
        }else{
            createErrorNotification();
        }
        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendEmptyMessage(startId);
            }
        }.start();
        return super.onStartCommand(intent, flags, startId);
    }
 
    private void createErrorNotification() {
        Notification notification = new Notification.Builder(this).build();
        startForeground(0, notification);
    }
 
    private void createNotificationChannel() {
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        // 通知渠道的id
        String id = "my_channel_01";
        // 用户可以看到的通知渠道的名字.
        CharSequence name = getString(R.string.channel_name);
//         用户可以看到的通知渠道的描述
        String description = getString(R.string.channel_description);
        int importance = NotificationManager.IMPORTANCE_HIGH;
        NotificationChannel mChannel = new NotificationChannel(id, name, importance);
//         配置通知渠道的属性
        mChannel.setDescription(description);
//         设置通知出现时的闪灯(如果 android 设备支持的话)
        mChannel.enableLights(true); mChannel.setLightColor(Color.RED);
//         设置通知出现时的震动(如果 android 设备支持的话)
        mChannel.enableVibration(true);
        mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
//         最后在notificationmanager中创建该通知渠道 //
        mNotificationManager.createNotificationChannel(mChannel);
 
        // 为该通知设置一个id
        int notifyID = 1;
        // 通知渠道的id
        String CHANNEL_ID = "my_channel_01";
        // Create a notification and set the notification channel.
        Notification notification = new Notification.Builder(this)
                .setContentTitle("New Message") .setContentText("You've received new messages.")
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setChannelId(CHANNEL_ID)
                .build();
        startForeground(1,notification);
    }
 
    private class MyHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            stopSelf(msg.what);
        }
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "5s onDestroy");
        Toast.makeText(this, "this service destroy", 1).show();
        stopForeground(true);
    }
}

注意: 需要在清单文件中,添加权限,普通权限,不需要动态申请

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

这样就完美的解决的启动前台服务导致的anr和crash套餐了

采坑记录1:
使用谷歌官网的音频播放组件中的MediaBrowserServiceCompat类,开启前台服务,会报错,引起crash,可能是谷歌本身的一个bug,特此记录

private MediaBrowserServiceCompat mMediaBrowserService;
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mMediaBrowserService.startForegroundService(new Intent(mContext, MusicService.class));
        }else {
            mMediaBrowserService.startService(new Intent(mContext, MusicService.class));
        }

这样写会引起崩溃,不能开启前台服务,只能使用普通的开启服务
参考网址:https://stackoverflow.com/questions/52120360/service-startforeground-in-mediabrowserservicecompat

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值