Android--Service

什么是Service

Service是一个后台运行的组件,执行长时间运行且不需要用户交互的任务。即使应用被销毁也依然可以工作。
Service并不是在单独进程中运行,也是运行在应用程序进程的主线程中,在执行具体耗时任务过程中要手动开启子线程,应用程序进程被杀死,所有依赖该进程的服务也会停止运行。



Service状态

状态描述
StartedAndroid的应用程序组件,如活动,通过startService()启动了服务,则服务是Started状态。一旦启动,服务可以在后台无限期运行,即使启动它的组件已经被销毁。
Bound当Android的应用程序组件通过bindService()绑定了服务,则服务是Bound状态。Bound状态的服务提供了一个客户服务器接口来允许组件与服务进行交互,如发送请求,获取结果,甚至通过IPC来进行跨进程通信。



Service生命周期

服务拥有生命周期方法,可以实现监控服务状态的变化,可以在合适的阶段执行工作。下面的左图展示了当服务通过startService()被创建时的生命周期,右图则显示了当服务通过bindService()被创建时的生命周期:
在这里插入图片描述
要创建服务,你需要创建一个继承自Service基类或者它的已知子类的Java类。Service基类定义了不同的回调方法和多数重要方法。你不需要实现所有的回调方法。虽然如此,理解所有的方法还是非常重要的。实现这些回调能确保你的应用以用户期望的方式实现。

回调描述
onStartCommand()其他组件(如活动)通过调用startService()来请求启动服务时,系统调用该方法。如果你实现该方法,你有责任在工作完成时通过stopSelf()或者stopService()方法来停止服务。
onBind()当其他组件想要通过bindService()来绑定服务时,系统调用该方法。如果你实现该方法,你需要返回IBinder对象来提供一个接口,以便客户来与服务通信。你必须实现该方法,如果你不允许绑定,则直接返回null。
onUnbind()当客户中断所有服务发布的特殊接口时,系统调用该方法。
onRebind()当新的客户端与服务连接,且此前它已经通过onUnbind(Intent)通知断开连接时,系统调用该方法。
onCreate()当服务通过onStartCommand()和onBind()被第一次创建的时候,系统调用该方法。该调用要求执行一次性安装。
onDestroy()当服务不再有用或者被销毁时,系统调用该方法。你的服务需要实现该方法来清理任何资源,如线程,已注册的监听器,接收器等。

onCreate→onStartCommand()/onBind→onDestroy
创建,运行,停止

Service可以同时进行普通启动和绑定,启动和绑定的都是Service的唯一实例
在停止时要同时解绑和停止,才能使Service进入停止调用onDestroy



Service启动方式

1 普通启动:startService()

生命周期

回调方法简述
onCreate()服务创建时调用
onStartCommand()服务启动时调用
onDestroy()服务销毁时调用

onCreate()只在服务创建时调用,服务只创建唯一实例,重复启动服务,只会调用 onStartCommand()

启动和停止的代码

Intent intent=new Intent(this,MyService.class);
context.startService(intent);//启动Service
context.stopService(intent);//停止Service.服务自身stopSelf()方法也可停止服务。多次启动只需一次停止


2 绑定启动:bindService()

一种Activity和Service可以进行通信/交互的启动方式

Service还有一个回调方法用于绑定服务:

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

下面是绑定启动的代码例子:

Service代码:自定义了一个Binder内部类,包含具体执行操作(这里用打印日志代替),然后在onBind()中返回Binder对象

public class MyService extends Service {
    private static final String TAG = "MyService";
 
    //初始化自定义的Binder对象
    private MyCustomBinder mBinder=new MyCustomBinder();
 
    public MyService() {
    }
    //自定义的内部Binder类
    class MyCustomBinder extends Binder{
        public void startDoSomething(){
            Log.i(TAG, "startDoSomething: ");
        }
        public void getProgress(){
            Log.i(TAG, "getProgress: ");
        }
    }
 
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        Log.i(TAG, "onBind: ");
        return mBinder;
    }
}

进行绑定操作的Activity中的代码:初始化MyService.MyCustomBinder对象和ServiceConnection对象,然后绑定启动.

ServiceConnection的onServiceConnected()在绑定Service时调用,在onBind()回调之后,onServiceDisconnected()只有在异常解绑停止服务时调用,正常解绑不会调用

BIND_AUTO_CREATE参数表示绑定Service时创建Service,解绑Service时停止Service

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity";
    private MyService.MyCustomBinder myCustomBinder;
    private ServiceConnection serviceConnection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            myCustomBinder= (MyService.MyCustomBinder) service;
            myCustomBinder.startDoSomething();
            myCustomBinder.getProgress();
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: ");
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        Button button3=findViewById(R.id.button3);
        Button button4=findViewById(R.id.button4);
 
        button3.setOnClickListener(this);
        button4.setOnClickListener(this);
 
    }
 
    @Override
    public void onClick(View v) {
        int id=v.getId();
        switch (id){
            case R.id.button3:
                //绑定启动Service
                Intent intent3=new Intent(MainActivity.this,MyService.class);
                bindService(intent3,serviceConnection,BIND_AUTO_CREATE);
                break;
            case R.id.button4:
                //解绑Service
                unbindService(serviceConnection);
                break;
        }
    }
}

多次点击绑定,然后解绑,查看打印日志:在这里插入图片描述
绑定启动不会调用 onStartCommand(),而是调用onBind(),并且多次点击绑定并不会重复绑定也不会重复调用onBind()和onServiceConnected()

(与Service绑定的Activity出栈时(finish),Service自动解绑并且停止)



前台服务

服务在后台运行,优先级比较低,当内存不足时可以会被系统回收,可以使用前台服务。
前台服务会在状态栏中显示图标,并且下拉状态栏,可以在通知栏中看到详细信息,类似于通知。
(API26开始使用新的方法startForegroundService()来启动前台服务,和startService一样的效果。要求Service里有startForeground()方法,否则startForegroundService()启动前台服务会报ANR错误)。

示例代码:(这段代码是写在Service里而不是Activity里,你也可以写在onStartCommand方法中)
这里增加了Android8.0新特性,通知渠道Channel的设置

public class MyServiceForeground extends Service {
    public MyServiceForeground() {
    }
 
    @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();
        Intent intent8=new Intent(this,MainActivity.class);
        PendingIntent pi=PendingIntent.getActivity(this,0,intent8,0);
        //后面一个参数为渠道参数,Android8.0新增要求
        Notification notification=new NotificationCompat.Builder(this,"foreground")
                .setContentTitle("这是标题")
                .setContentText("这是内容这是内容这是内容")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(pi)
                .build();
        NotificationManager notificationManager= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel channel = null;
        //Android8.0要求设置通知渠道
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            channel = new NotificationChannel("foreground", "foregroundName", NotificationManager.IMPORTANCE_HIGH);
            notificationManager.createNotificationChannel(channel);
        }
        startForeground(1,notification);
    }
......
}



IntentService

我们使用Service往往需要手动开启子线程,执行耗时操作,执行完毕后可以选择stopSelf()停止服务。

IntentService的作用就和上面一样,它的onHandleIntent()回调方法就是在子线程里面执行耗时操作,并且onHandleIntent()执行完毕之后会自动停止服务。



清单文件申明service其属性要点

属性说明
android:descriptionstring resource向用户描述服务的字符串。您应将此标签设置为对字符串资源的引用,以便可以像对界面中的其他字符串那样对其进行本地化。
android:directBootAware“true” / “false”服务是否支持直接启动,即其是否可以在用户解锁设备之前运行。默认值为 “false”。
android:enabled“true” / “false”系统是否可实例化服务 —“true”表示可以,“false”表示不可以。默认值为“true”。<application>元素拥有自己的 enabled 属性,该属性适用于所有应用组件,包括服务。只有在 <application><service>属性都为“true”(因为它们都默认使用该值)时,系统才能启用服务。任何一项为“false”都会造成服务停用,从而使系统无法将其实例化。
android:exported“true” / “false”其他应用的组件是否能调用服务或与之交互 —“true”表示可以,“false”表示不可以。当该值为“false”时,只有同一个应用或具有相同用户 ID 的应用的组件可以启动服务或绑定到服务。默认值取决于服务是否包含 Intent 过滤器。没有任何过滤器意味着服务只能通过指定其确切的类名称进行调用。这意味着服务专供应用内部使用(因为其他应用不知晓其类名称)。因此,在这种情况下,默认值为“false”。另一方面,至少存在一个过滤器意味着服务专供外部使用,因此默认值为“true”。此属性并非是唯一限制向其他应用披露服务的方式。您还可使用权限来限制哪些外部实体可以与服务交互。
android:foregroundServiceType“connectedDevice” / “dataSync” / “location” / “mediaPlayback” / “mediaProjection” / “phoneCall”阐明服务是满足特定用例要求的前台服务。例如,“location” 类型的前台服务表示应用正在获取设备的当前位置,目的通常是继续用户发起的操作,且该操作与设备位置相关。您可以将多个前台服务类型分配给特定服务。
android:icondrawable resource表示服务的图标。必须将该属性设置为对包含图像定义的可绘制资源的引用。如果未设置该属性,则转而使用为应用整体指定的图标(请参阅 <application>元素的 icon 属性)。服务的图标(无论是在此处设置,还是由 <application>元素设置)同时也是服务所有 Intent 过滤器的默认图标。
android:isolatedProcess“true” / “false”如果设置为 true,则此服务将在与系统其余部分隔离的特殊进程下运行。此服务自身没有权限,只能通过 Service API 与其进行通信(绑定和启动)。
android:labelstring resource可向用户显示的服务名称。如果未设置该属性,则转而使用为应用整体设置的标签(请参阅 <application>元素的 label 属性)。服务的标签(无论是在此处设置,还是由 <application>元素设置)同时也是服务所有 Intent 过滤器的默认标签(请参阅 <intent-filter>元素的 label 属性)。您应将此标签设置为对字符串资源的引用,以便可以像对界面中的其他字符串那样对其进行本地化。不过,为便于开发应用,您也可将其设置为原始字符串。
android:namestring实现服务的 Service 子类的名称。此名称应为完全限定类名称(例如“com.example.project.RoomService”)。不过,为简便起见,如果名称的第一个字符是句点(例如,“.RoomService”),则名称将追加至 <manifest>元素中指定的软件包名称。一旦发布应用,即不应更改该名称(除非您设置了 android:exported=“false”)。没有默认值。必须指定该名称。
android:permissionstring实体启动服务或绑定到服务所必需的权限的名称。如果 startService()、bindService() 或 stopService() 的调用者尚未获得此权限,该方法将不起作用,且系统不会将 Intent 对象传送给服务。如果未设置该属性,则对服务应用由 <application>元素的 permission 属性所设置的权限。如果二者均未设置,则服务不受权限保护。
android:processstring将运行服务的进程的名称。正常情况下,应用的所有组件都会在为应用创建的默认进程中运行。该名称与应用软件包的名称相同。<application>元素的 process 属性可为所有组件设置不同的默认进程名称。不过,组件可以使用自己的 process 属性替换默认值,让您可以将应用散布到多个进程中。如果为此属性分配的名称以冒号(“:”)开头,则系统会在需要时创建应用专用的新进程,并且服务会在该进程中运行。如果进程名称以小写字符开头,则服务将在使用该名称的全局进程中运行,前提是它拥有相应的权限。如此一来,不同应用中的组件便可共享进程,从而减少资源使用。
<service android:description="string resource"//向用户描述服务的字符串。您应将此标签设置为对字符串资源的引用,以便可以像对界面中的其他字符串那样对其进行本地化。
         android:directBootAware=["true" | "false"]//服务是否支持直接启动,即其是否可以在用户解锁设备之前运行。
         android:enabled=["true" | "false"]//系统是否可实例化服务,默认值为“true”。
         android:exported=["true" | "false"]//其他应用的组件是否能调用服务或与之交互
         android:foregroundServiceType=["connectedDevice" | "dataSync" |
                                        "location" | "mediaPlayback" | "mediaProjection" |
                                        "phoneCall"]//阐明服务是满足特定用例要求的前台服务。例如,"location" 类型的前台服务表示应用正在获取设备的当前位置,目的通常是继续用户发起的操作,且该操作与设备位置相关。您可以将多个前台服务类型分配给特定服务。
         android:icon="drawable resource"//表示服务的图标。必须将该属性设置为对包含图像定义的可绘制资源的引用。如果未设置该属性,则转而使用为应用整体指定的图标(请参阅 <application> 元素的 icon 属性)。服务的图标(无论是在此处设置,还是由 <application> 元素设置)同时也是服务所有 Intent 过滤器的默认图标(请参阅 <intent-filter> 元素的 icon 属性)。
         android:isolatedProcess=["true" | "false"]//如果设置为 true,则此服务将在与系统其余部分隔离的特殊进程下运行。此服务自身没有权限,只能通过 Service API 与其进行通信(绑定和启动)。
         android:label="string resource"//可向用户显示的服务名称。如果未设置该属性,则转而使用为应用整体设置的标签(请参阅 <application> 元素的 label 属性)。服务的标签(无论是在此处设置,还是由 <application> 元素设置)同时也是服务所有 Intent 过滤器的默认标签(请参阅 <intent-filter> 元素的 label 属性)。您应将此标签设置为对字符串资源的引用,以便可以像对界面中的其他字符串那样对其进行本地化。不过,为便于开发应用,您也可将其设置为原始字符串。
         android:name="string"//实现服务的 Service 子类的名称。//实体启动服务或绑定到服务所必需的权限的名称。如果 startService()、bindService() 或 stopService() 的调用者尚未获得此权限,该方法将不起作用,且系统不会将 Intent 对象传送给服务。如果未设置该属性,则对服务应用由 <application> 元素的 permission 属性所设置的权限。如果二者均未设置,则服务不受权限保护。
         android:permission="string"//实体启动服务或绑定到服务所必需的权限的名称。如果 startService()、bindService() 或 stopService() 的调用者尚未获得此权限,该方法将不起作用,且系统不会将 Intent 对象传送给服务。如果未设置该属性,则对服务应用由 <application> 元素的 permission 属性所设置的权限。如果二者均未设置,则服务不受权限保护。
         android:process="string" >//将运行服务的进程的名称。正常情况下,应用的所有组件都会在为应用创建的默认进程中运行。该名称与应用软件包的名称相同。<application> 元素的 process 属性可为所有组件设置不同的默认进程名称。不过,组件可以使用自己的 process 属性替换默认值,让您可以将应用散布到多个进程中。如果为此属性分配的名称以冒号(“:”)开头,则系统会在需要时创建应用专用的新进程,并且服务会在该进程中运行。如果进程名称以小写字符开头,则服务将在使用该名称的全局进程中运行,前提是它拥有相应的权限。如此一来,不同应用中的组件便可共享进程,从而减少资源使用。
    . . .
</service>

包含它的文件:
<application>

可包含:
<intent-filter>
<meta-data>
说明:
将服务(Service 子类)声明为应用的一个组件。与 Activity 不同,服务缺少可视化界面。服务用于实现长时间运行的后台操作,或可由其他应用调用的富通信 API。
必须用清单文件中的 元素表示所有服务。系统不会识别和运行任何未进行声明的服务。



如何保证Service不被杀死?

  • onStartCommand方式中,返回START_STICKY
    调用Context.startService方式启动Service时,如果Android面临内存匮乏,可能会销毁当前运行的Service,待内存充足时可以重建Service。而Service被Android系统强制销毁并再次重建的行为依赖于Service的onStartCommand()方法的返回值。

START_NOT_STICKY
如果返回START_NOT_STICKY,表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service。当然如果在其被杀掉之后一段时间又调用了startService,那么该Service又将被实例化。那什么情境下返回该值比较恰当呢?
如果我们某个Service执行的工作被中断几次无关紧要或者对Android内存紧张的情况下需要被杀掉且不会立即重新创建这种行为也可接受,那么我们便可将 onStartCommand的返回值设置为START_NOT_STICKY。
举个例子,某个Service需要定时从服务器获取最新数据:通过一个定时器每隔指定的N分钟让定时器启动Service去获取服务端的最新数据。当执行到Service的onStartCommand时,在该方法内再规划一个N分钟后的定时器用于再次启动该Service并开辟一个新的线程去执行网络操作。假设Service在从服务器获取最新数据的过程中被Android系统强制杀掉,Service不会再重新创建,这也没关系,因为再过N分钟定时器就会再次启动该Service并重新获取数据。
START_STICKY
如果返回START_STICKY,表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象,然后Android系统会尝试再次重新创建该Service,并执行onStartCommand回调方法,但是onStartCommand回调方法的Intent参数为null,也就是onStartCommand方法虽然会执行但是获取不到intent信息。如果你的Service可以在任意时刻运行或结束都没什么问题,而且不需要intent信息,那么就可以在onStartCommand方法中返回START_STICKY,比如一个用来播放背景音乐功能的Service就适合返回该值。
START_REDELIVER_INTENT
如果返回START_REDELIVER_INTENT,表示Service运行的进程被Android系统强制杀掉之后,与返回START_STICKY的情况类似,Android系统会将再次重新创建该Service,并执行onStartCommand回调方法,但是不同的是,Android系统会再次将Service在被杀掉之前最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到重新创建后的Service的onStartCommand方法中,这样我们就能读取到intent参数。只要返回START_REDELIVER_INTENT,那么onStartCommand重的intent一定不是null。如果我们的Service需要依赖具体的Intent才能运行(需要从Intent中读取相关数据信息等),并且在强制销毁后有必要重新创建运行,那么这样的Service就适合返回START_REDELIVER_INTENT。

  • 提高Service的优先级
    在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。

  • 提升Service进程的优先级
    当系统进程空间紧张时,会依照优先级自动进行进程的回收。
    Android将进程分为6个等级,按照优先级由高到低依次为:
    1.前台进程foreground_app
    2.可视进程visible_app
    3.次要服务进程secondary_server
    4.后台进程hiddena_app
    5.内容供应节点content_provider
    6.空进程empty_app
    可以使用startForeground将service放到前台状态,这样低内存时,被杀死的概率会低一些。

  • 在onDestroy方法里重启Service
    当service走到onDestroy()时,发送一个自定义广播,当收到广播时,重新启动service。

  • 系统广播监听Service状态

  • 将APK安装到/system/app,升级系统级应用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值