Android之Service全面解析,关于Service你所需要知道的一切

目录

目录(Service)

一、前言

Service对于广大安卓开发者来说算是耳熟能详的,作为安卓四大组件之一,应用非常广泛,本文将全面总结Service定义、分类及使用,同时解析一些常见问题,如与Thread的区别,如何保证不被系统杀死等。

常见问题:
1、Service的定义及作用?
2、谈一谈Service的生命周期?
3、Service的两种启动方式有什么区别?
4、Service有哪些分类及其应用场景?
5、IntentService的作用,与Service的区别?
6、为什么使用Servie,而不是使用Thread?
7、如何保证Servie不被杀死,有哪些思路?
8、Service能否开启耗时操作?要怎么做?
9、用过哪些系统的Service?
10、定义过哪些Service?作用?能否用Thread实现?
11、使用Service要注意哪些问题,有什么适配问题?

二、Service的定义及作用

定义:一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。服务可由其他应用组件启动(如Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity)已销毁也不受影响(startService启动方式)。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。
作用:处理复杂计算、下载文件、网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互。
特点:无用户界面、在后台运行、生命周期长。

Service运行在主线程,所以不能执行耗时操作,如果需要执行耗时处理,可以启动一个工作线程处理。

三、启动方式及生命周期

3.1、Service的启动方式

  • startService:通过startService启动Service后,Service即处于“启动”状态,且在后台无限期运行,即使启动的组件(如Activity)已销毁也不受影响,除非手动调用停止服务,其生命周期与启动组件的生命周期无关。
  • bindService:通过bindService启动Service后,Service即处于“绑定”状态,绑定的组件与Service可以进行交互、发送和获取数据、甚至可以跨进程(IPC)通讯,绑定服务的生命周期与绑定的组件有关:

多个组件可以绑定到同一个服务上,如果只有一个组件绑定服务,当绑定的组件被销毁时,服务也就会停止了。如果是多个组件绑定到一个服务上,当绑定到该服务的所有组件都被销毁时,服务才会停止。

3.2、生命周期

官方图:


生命周期

3.3、startService的生命周期分析

  • onCreate:首次启动服务,再次启动不再执行,做一些初始化的操作,比如要执行耗时的操作,可以在这里创建线程,要播放音乐,可以在这里初始化音乐播放器
  • onStartCommand:通过startService启动服务,onCreate之后执行, 再次启动直接执行,可以通过startService,设置指定action,通知Service执行特定任务
  • onDestroy:服务不再使用,调用stopService或者unbindService之后触发,清理资源,退出线程、注销广播接收器等

3.4、bindService的生命周期分析

  • onCreate:首次绑定服务。做一些初始化的操作,比如要执行耗时的操作,可以在这里创建线程,要播放音乐,可以在这里初始化音乐播放器
  • onBind:onCreate之后执行,其他组件再次绑定直接执行onBind。必须实现并返回IBinder接口实例,供绑定的组件与服务通讯
  • onUnbind:当所有与该服务绑定的组件解除绑定触发。可以执行解绑后的一些事务
  • onDestroy:服务不再使用,调用stopService或者unbindService之后触发。清理资源,退出线程、注销广播接收器等

ServiceConnection
绑定服务需要调用下面的方法:

    @Override
    public boolean bindService(Intent service, ServiceConnection conn,
            int flags) {
        return mBase.bindService(service, conn, flags);
    }

解绑服务调用的方法:

    @Override
    public void unbindService(ServiceConnection conn) {
        mBase.unbindService(conn);
    }

绑定和解绑都必须参数ServiceConnection,是一个接口类,需要实现该接口,在接口回调中,能获取Binder对象,与服务通讯。

private CustomService.MyBinder mMyBinder ;

  // 绑定/解除绑定 Service 回调接口
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 绑定成功后回调
            //1 ,获取Binder接口对象
            mMyBinder = (CustomService.MyBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
           // 解除绑定后回调
            mMyBinder = null;
        }
    };

如果ServiceConnection传入空,会抛异常:

E/AndroidRuntime: FATAL EXCEPTION: main
Process: xxx, PID: 30960
java.lang.IllegalArgumentException: connection is null
    at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1782)
    at android.app.ContextImpl.bindService(ContextImpl.java:1737)
    at android.content.ContextWrapper.bindService(ContextWrapper.java:678)

3.5、onStartCommand 返回值

返回值已经定义在了 Service 基类中了,常用的有:

  • START_NOT_STICKY:如果系统在 onStartCommand 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
  • START_STICKT:如果系统在 onStartCommand 返回后终止服务,则会重建服务并调用 onStartCommand,但绝对不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务,否则系统会通过空 Intent 调用 onStartCommand。这适用于不执行命令、但无限期运行并等待作业的媒体播放器等。
  • START_REDELIVER_INTENT:如果系统在 onStartCommand 返回后终止服务,则会重建服务,并通过传递给服务等最后一个 Intent 调用 onStartCommand。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业的服务,例如下载文件。

四、Service的分类

4.1、Service分类及应用场景

Service可按照运行地点、运行类型 & 功能进行分类,具体如下:


Service分类
  • 本地服务:运行在主进程中,主进程退出后,服务也终止,依附主进程的场景,不需要IPC,如音乐播放、下载等
  • 远程服务:运行在独立进程中,不受调用进程的影响,需要IPC通讯,系统级别服务
  • 前台服务:会在通知栏显示,客户能看到,进程优先级高,不会因内存不足被杀死,音乐播放
  • 后台服务:在后台运行,客户看不到,进程优先级低,容易因内存不足被杀死,下载、I/O操作、复杂计算等

4.2、远程服务例子

远程服务是运行在独立进程里的,与调用者不在同一进程,另外,多个应用程序(客户端)可以共同调用远程服务(服务端),为了实现调用进程和服务进程之间的通讯(IPC),需要用到AIDL。

*IPC:Inter-Process Communication,即跨进程通信
*AIDL:Android Interface Definition Language,即Android接口定义语言;用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。

下面介绍远程服务的定义流程,比较简单。

4.2.1、创建服务端(Service)

1、创建AIDL文件
在需要创建远程服务的工程右键,创建AIDL文件,新建后会自动创建包目录和文件。


创建AIDL文件

AIDL文件目录

2、定义客户端-服务端之间的通信方法,

// IMyAidlInterface.aidl
package com.siu.servicedemo;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
    int cal(int a,int b);
}

定义好后,点击AS的Make Project.
3、在Service中实现AIDL定义的方法

public class RemoteService extends Service {

    IMyAidlInterface.Stub mStub = new IMyAidlInterface.Stub() {
        @Override
        public int cal(int a, int b) throws RemoteException {
            return a + b;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return mStub;
    }
}

4、注册Service到AndroidMainfest.xml

        <service
            android:name=".RemoteService"
            android:exported="true" //外部可调用
            android:process=":remote">//声明为独立进程
            <intent-filter>
                <action android:name="${applicationId}.CalService"/>//自定义action
            </intent-filter>
        </service>

到此,Service的工作已完成。

4.2.2、创建客户端(Client)

1、拷贝整个aidl目录到Client工程


aidl目录

2、定义ServiceConnection,获得binder对象,调用AIDL定义的方法

   private IMyAidlInterface mBinder;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinder = IMyAidlInterface.Stub.asInterface(service);
            try {
                int c = mBinder.cal(1, 2);
                Log.e("remoteService", "remoteService cal result=" + c);
            } catch (Exception e) {
                Log.e("remoteService", e.getMessage());
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBinder = null;
        }
    };

3、绑定服务

        findViewById(R.id.btBindService).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent it = new Intent("com.siu.servicedemo.CalService");
                it.setPackage("com.siu.servicedemo");
                bindService(it, mConnection, Context.BIND_AUTO_CREATE);
            }
        });

4、验证效果

remoteService: remoteService cal result=3

从Service端返回了计算结果,验证通过。

4.3、前台服务例子

前台服务最大的特点是会在通知栏显示通知,而后台服务没有,如下图音乐播放正是后台服务。除非服务停止或从前台删除,否则不能清除改通知。


前台服务

官方给出了启动和停止前台Service的方法:

  • startForeground(int id, Notification notification)
    该方法的作用是把当前服务设置为前台服务,其中id参数代表唯一标识通知的整型数,需要注意的是提供给 startForeground() 的整型 ID 不得为 0,而notification是一个状态栏的通知。

  • stopForeground(boolean removeNotification)
    该方法是用来从前台删除服务,此方法传入一个布尔值,指示是否也删除状态栏通知,true为删除。 注意该方法并不会停止服务。 但是,如果在服务正在前台运行时将其停止,则通知也会被删除。
    代码也比较简单:

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Notification notification;
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        int importance = NotificationManager.IMPORTANCE_HIGH;
        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        String title = "前台服务通知标题";
        String content = "前台服务通知内容";
        if (Build.VERSION.SDK_INT >= 26) {
            String id = "channel_id";  // 通知渠道的id
            CharSequence name = "channelName";   // 用户可以看到的通知渠道的名字.
            String description = "channelDesc";// 用户可以看到的通知渠道的描述
            NotificationChannel mChannel = new NotificationChannel(id, name, importance);
            mChannel.setDescription(description);  // 配置通知渠道的属性
            mChannel.enableLights(true); // 设置通知出现时的闪灯(如果 android 设备支持的话)
            if (audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE
                    || audioManager.getRingerMode() == AudioManager.MODE_NORMAL) {
                mChannel.enableVibration(true);  // 设置通知出现时的震动(如果 android 设备支持的话)
            }
            mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
            mNotificationManager.createNotificationChannel(mChannel);

            notification = new Notification.Builder(this, id)
                    .setContentTitle(title).setContentText(content)
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))
                    .setSmallIcon(R.drawable.ic_launcher)
                    .build();
        } else {
            int defaults = Notification.DEFAULT_LIGHTS | Notification.DEFAULT_SOUND;
            if (audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE 
                 || audioManager.getRingerMode() == AudioManager.MODE_NORMAL) {
                      defaults |= Notification.DEFAULT_VIBRATE;//设置Notification.DEFAULT_VIBRATE的flag后可能会在任何
                      情况下都震动,部分系统的bug,所以要判断是否开启振动
            }
            notification = new Notification.Builder(this)
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))
                    .setContentTitle(title)
                    .setSmallIcon(R.drawable.ic_launcher)
                    .setContentText(content)
                    .setAutoCancel(true)
                    .setDefaults(defaults).getNotification();
        }

        startForeground(1, notification);
        return super.onStartCommand(intent, flags, startId);
    }

测试效果:


前台服务demo

五、IntentService

5.1、定义和特点

IntentService是Service的子类,是系统封装的用于处理异步任务的类,内部默认启动一个工作线程,逐一处理任务,当执行完毕,服务自动退出,具有以下特点:
*特殊的Service,继承于Service,本身是一个抽象类;
*默认启动一个工作线程HandlerThread,逐一处理耗时异步任务,处理完后自动停止,不适于处理并行任务;
*拥有较高的优先级(Service),不容易被系统杀死,适用于执行较高优先级的异步任务;
*使用简单,只需要实现构造方法和onHandleIntent,onHandleIntent为异步方法,可执行耗时任务。

5.2、简单例子

public class MyIntenteService extends IntentService {
    public MyIntenteService() {
        super("MyIntenteService");
    }

  @Override
  protected void onHandleIntent(@Nullable Intent intent) {
        //执行耗时任务
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
        }
        Log.e("MyIntenteService", "handle task name=" + intent.getStringExtra("taskName"));
    }

    @Override
    public void onDestroy() {
        Log.e("MyIntenteService", "onDestroy");
        super.onDestroy();
    }
}

通过上面代码定义了简单的IntentService例子,只需要实现构造方法和onHandleIntent。

 findViewById(R.id.btTest).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                   for (int i = 0; i < 10; i++) {
                          Intent it = new Intent(this, MyIntenteService.class);
                          it.putExtra("taskName", "taskName" + i);
                          startService(it);//循环启动任务
                    }
             }
  }

在调用的地方循环多次启动Service,执行多个任务,输入的日志如下:

E/MyIntenteService: onCreate
E/MyIntenteService: handle task name=taskName0
E/MyIntenteService: handle task name=taskName1
E/MyIntenteService: handle task name=taskName2
E/MyIntenteService: handle task name=taskName3
E/MyIntenteService: handle task name=taskName4
E/MyIntenteService: handle task name=taskName5
E/MyIntenteService: handle task name=taskName6
E/MyIntenteService: handle task name=taskName7
E/MyIntenteService: handle task name=taskName8
E/MyIntenteService: handle task name=taskName9
E/MyIntenteService: onDestroy

通过日志发现,即使启动了多次MyIntenteService,但实例只有1个,任务都添加到消息队列,执行完所有任务后自动销毁。

5.3、源码分析

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;

    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent) msg.obj);
            stopSelf(msg.arg1);
        }
    }

    public IntentService(String name) {
        super();
        mName = name;
    }

    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @WorkerThread
    protected abstract void onHandleIntent(Intent intent);
}

源码比较容易理解,分析下主要的方法:
1、onCreate
第一次启动服务,会调用onCreate方法,内部会开启一个异步线程HandlerThread,HandlerThread带有消息循环对象Looper,然后创建ServiceHandler(继承Handler),传入HandlerThread持有的Looper对象,这样ServiceHandler就能与异步线程的Looper进行绑定,就能处理异步任务了。
2、ServiceHandler
继承于Handler,需要传入Looper对象,在handleMessage中处理消息队列中的任务,然后回调抽象方法onHandleIntent,这就是创建IntentService需要实现的方法,在方法内执行异步任务。执行完onHandleIntent后,调用stopSelf(msg.arg1),不是stopSelf(),因为stopSelf()会立即停止服务,而stopSelf(int startId)会等所有任务执行完才停止。
3、onStartCommand、onStart
每次启动Service会执行onStartCommand,在方法内再执行onStart方法,而onStart则通过mServiceHandler对象发送一个消息,该消息将在HandlerThread中执行,会触发2中的handleMessage方法,执行异步任务。

六、Service和Thread的区别

不同 Thread Service
概念不同 程序执行的最小单元,分配CPU的基本单位 Android的一种机制,没有UI的后台组件,与其他组件的关系类似CS,通过IPC通信
执行任务 可执行耗时任务 运作在主线程,不可执行耗时任务,如果需要,在内部启动Thread
使用场景 执行可能会影响UI主线程的耗时任务 不需要UI的长时间后台任务,如播放音乐、下载

七、如何保证 Service 不被杀死

7.1、开启不死服务的途径

在一些特定的场景,Service需要保持运行不能被系统杀死,有以下途径:

  • 1、onStartCommand返回START_STICKY或START_REDELIVER_INTENT
    针对内存资源不足系统可能会杀非前台Service的情况,可以将onStartCommand的返回值设为START_STICKY或START_REDELIVER_INTENT,Service被杀死后,当资源足够时会自动恢复该服务。
  • 2、提升Service优先级
    将Service设置为前台服务,拥有前台进程的优先级,这样系统需要释放资源时不会优先杀死该进程。
  • 3、Service互拉
    同时启动A、B两个服务,互相监听对方的广播,在各自的onDestroy方法中,发送广播,收到广播后重新启动对方,如A被杀死触发onDestroy方法,A发出广播,B监听到后马上重新启动A,方法有效的前提是进程仍在。

对于内存不足系统回收,方法1和3都有效,而在内存不足系统回收、应用管理中杀死正在运行服务,都会触发onDestroy,方法3都有效,但是如果是用户强行杀死进程,所有方法都无效。

  • 4、监听系统静态广播
    监听系统广播,如网络变化、解锁屏,然后重启服务,不过当进程被杀,方法也无效。
  • 5、第三方APP唤醒
    极光、个推等第三方推送平台sdk的做法,为了保证推送服务常驻,如果应用A、B、C都用了该sdk,当用户打开应用A时,同时拉起B、C的推送服务。6.0以后系统为了省电,提高续航能力,引入了Doze模式,该方法开始不理想。
  • 6、守护进程
    一般采用Native进程,5.0以下拉活没问题,5.0以上由于改为杀死进程的同时干掉了进程组,所以父子型守护进程将无法保证重启。
  • 7、双Native独立进程
    双Native独立进程,互相守护,对上面守护进程的补充,非父子关系,这种方式在github上看到过开源项目MarsDaemon ,但仅兼容到Android6.0。
  • 8、使用AlarmManager
    利用AlarmManager定时唤起(killBackgroundProcess可以唤起但force-stop后无法唤起)。
  • 9、使用JobSheduler机制
    Android5.x、6.x有效,但AndroidN失效,Android N可以了解下scheduleAsPackage。

7.2、到底要不要开启永不退出的服务

Android service 不被杀死“永不退出的服务”(双进程,服务,多进程,微信)在文中引入了一篇文献,总结得比较好。总的原则来说就是:

android开发者在可能的情况下尽量避免开发长时间运行的服务,只有服务真的在做事情的时候才开启服务,不要让服务坐着干等。是否要停止服务,应该遵循用户的意愿和系统的调度。如果服务消耗资源比较多,又强制持续运行,将会严重占用系统资源和加速电量消耗,影响用户的使用,还可能被用户认为是流氓应用,随时被卸载掉。

八、Service的适配

8.1、Android 5.0 以上不支持隐式启动服务

官方说明:绑定到服务

Context.bindService()方法现在需要显式Intent,如果提供隐式 intent,将引发异常。为确保应用的安全性,请使用显式 intent 启动或绑定Service,且不要为服务声明 intent 过滤器。
例如下面代码:

Intent it = new Intent();
it.setAction("test_demo_service");
bindService(it, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        }
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
         }
}, Service.BIND_AUTO_CREATE);

无论是startService还是bindService,启动后报错如下:

  E/AndroidRuntime: FATAL EXCEPTION: main
  Process: xxx, PID: 30060
  java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=test_demo_service }
        at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1644)
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1685)
        at android.app.ContextImpl.startService(ContextImpl.java:1657)
        at android.content.ContextWrapper.startService(ContextWrapper.java:644)

解决方案:

  • 改为显式启动
  • 指定包名

8.2 、Android 8.0 以上禁止后台启动服务

官方说明:后台 Service 限制
处于前台时,应用可以自由创建和运行前台与后台 Service。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用 Service。 在该时间窗结束后,应用将被视为处于空闲状态。 此时,系统将停止应用的后台 Service,就像应用已经调用 Service 的Service.stopSelf()方法一样。
在这些情况下,后台应用将被置于一个临时白名单中并持续数分钟。 位于白名单中时,应用可以无限制地启动 Service,并且其后台 Service 也可以运行。 处理对用户可见的任务时,应用将被置于白名单中,例如:

  • 处理一条高优先级Firebase 云消息传递 (FCM)消息。
  • 接收广播,例如短信/彩信消息。
  • 从通知执行PendingIntent
  • 在 VPN 应用将自己提升为前台进程前开启VpnService

请注意: IntentService 是一项 Service,因此其遵守针对后台 Service 的新限制。 因此,许多依赖 IntentService 的应用在适配 Android 8.0 或更高版本时无法正常工作。 出于这一原因,Android 支持库 26.0.0 引入了一个新的JobIntentService类,该类提供与 IntentService 相同的功能,但在 Android 8.0 或更高版本上运行时使用作业而非 Service。

在 Android 8.0 之前,创建前台 Service 的方式通常是先创建一个后台 Service,然后将该 Service 推到前台。 Android 8.0 有一项复杂功能:系统不允许后台应用创建后台 Service。 因此,Android 8.0 引入了一种全新的方法,即 startForegroundService(),以在前台启动新 Service。 在系统创建 Service 后,应用有五秒的时间来调用该 Service 的 startForeground() 方法以显示新 Service 的用户可见通知。 如果应用在此时间限制内未调用 startForeground(),则系统将停止此 Service 并声明此应用为 ANR。

8.3、源码分析(api 26)

调用Activity的startService实际是调用ContextWrapper.startService。

public class ContextWrapper extends Context {
    Context mBase;
    ...
    @Override
    public ComponentName startService(Intent service) {
        return mBase.startService(service);
    }
    ...
}

上面方法中的mBase的对象类型是Context,其实现类是ContextImpl,接着看ContextImpl.startService

@Override
    public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, mUser);
    }
private ComponentName startServiceCommon(Intent service, UserHandle user) {
        try {
            validateServiceIntent(service);
            service.prepareToLeaveProcess(this);
            ComponentName cn = ActivityManagerNative.getDefault().startService(
                mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                            getContentResolver()), getOpPackageName(), user.getIdentifier());
            if (cn != null) {
                if (cn.getPackageName().equals("!")) {
                    throw new SecurityException(
                            "Not allowed to start service " + service
                            + " without permission " + cn.getClassName());
                } else if (cn.getPackageName().equals("!!")) {
                    throw new SecurityException(
                            "Unable to start service " + service
                            + ": " + cn.getClassName());
                }
            }
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

其中,有个方法验证Intent的方法,针对非显式启动且没指定包名的,5.0以上直接抛异常,低版本则输出警告日志。

private void validateServiceIntent(Intent service) {
    if (service.getComponent() == null && service.getPackage() == null) {
        if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
            IllegalArgumentException ex = new IllegalArgumentException(
                    "Service Intent must be explicit: " + service);
            throw ex;
        } else {
            Log.w(TAG, "Implicit intents with startService are not safe: " + service
                    + " " + Debug.getCallers(2, 3));
        }
    }
}

这里不展开分析更多的源码,感兴趣的可以阅读startService方法源码.

8.4、Service应用中遇到的坑

项目中有不少场景用了Service(其实有些地方根据Google推荐可以改为JobScheduler),其中有个Service是在进入首页的onPostCreate启动,是显式启动,然后收到了不少崩溃日志,主要分为两种:
崩溃一:java.lang.SecurityException: Unable to start service Intent

Caused by: java.lang.SecurityException: Unable to start service Intent { act=start_for_mainpage cmp=xxx/.xxxService (has extras) }: 
Unable to launch app xxx/10098 for service Intent { act=start_for_mainpage cmp=xxx/.xxxService }: 
user 0 is restricted\n\tat android.app.ContextImpl.startServiceCommon(ContextImpl.java:1729)\n\tat
android.app.ContextImpl.startService(ContextImpl.java:1702)\n\tat
android.content.ContextWrapper.startService(ContextWrapper.java:494)\n\tat

崩溃二:java.lang.IllegalStateException: Not allowed to start service Intent

java.lang.IllegalStateException: Not allowed to start service Intent { act=start_for_mainpage cmp=xxx/.xxxService (has extras) }: 
app is in background uid UidRecord{70cfbb u0a159 TPSL idle procs:1 seq(0,0,0)}

第一个类型崩溃率比第二个高,接近百万分一,都是OPPO手机(系统版本为4.4.4和5.1.1,机型有A31c,A31,A33,A53m,A33m),第二个崩溃都是8.0系统,品牌有小米和华为,但是两个类型崩溃我都重现不了,模拟在后台启动也重现不了。
解决方案
后来修改为启动服务的时机,在首页的onResume启动,并且加了try catch,如果抛异常,再重试。后面也收到这样的崩溃日志了,可能是加了try catch的原因,也没收到功能上有问题的反馈。
另外,对于第一个异常,也有开发者碰到,主要是OPPO手机针对电量优化,息屏后一段时间禁止自启动导致。
bug--service--Caused by java.lang.SecurityException: Unable to start service Intent { }:user 0 is restricted

参考

Android Service和IntentService知识点详细总结
Android 四大组件:一份全面 & 简洁的 Service 知识讲解攻略
关于Android Service真正的完全详解,你需要知道的一切
Android service 不被杀死“永不退出的服务”(双进程,服务,多进程,微信)
Android进程保活/拉活的常见套路

!(https://upload-images.jianshu.io/upload_images/13079595-2eb5f4317f356afa.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容