Android四大组件之Service总结

1、定义

Service 中文称 服务,是Android四大组件之一
Service简单说一种即使用户没有与应用交互也可以在后台运行的组件
作用:提供需要后台运行的任务(如:复杂计算、下载、音乐播放)
特点:后台运行、无界面、生命周期长
注意:在默认情况下仍会在应用的主线程中运行,因此,如果该服务执行密集型或阻塞性操作,您仍应在该服务中创建新线程。

2、生命周期

在这里插入图片描述
4个手动调用的方法
手动调用方法 作用
startService() 启动服务
stopService() 关闭服务
bindService() 绑定服务
unbindService() 解绑服务

5个自动调用的方法
内部自动调用的方法 作用
onCreat() 创建服务
onStartCommand() 开始服务
onDestroy() 销毁服务
onBind() 绑定服务
onUnbind() 解绑服务

启动或绑定服务的流程
启动和绑定一个服务,在无解绑前提下调用stopService是无法停止服务的。
常用场景
注意:
关于操作Service
1)startService()/stopService()只能启动/关闭Service,无法操作Service;
2)bindService()/unbindService()能绑定/解锁绑定Service,还能操作Service;

关于Service何时销毁
startService()启动Service,调用者退出后Service还存在;
bindService启动的Service,调用者退出后Service也会退出销毁;

注意:只有 Activity、Service和ContentProvider可以绑定到服务,您无法从广播接收器绑定到服务。

创建绑定服务方式
1)通过扩展Binder
如果只有本地应用使用您的服务,并且不需要跨进程工作,那么您可以实现自己的 Binder 类,该类可让客户端直接访问服务中的公共方法。

2)使用Messenger
如果您需要让服务与远程进程通信,则可使用 Messenger 为您的服务提供接口。借助此方法,您无需使用 AIDL 即可执行进程间通信 (IPC)。
对于大多数应用,服务不需要执行多线程处理,因此使用 Messenger 可让服务一次处理一个调用。如果您的服务必须执行多线程处理,请使用 AIDL 定义接口。

3)使用AIDL

  • 来自本地进程的调用在发出调用的同一线程中执行。如果该线程是您的主界面线程,则该线程会继续在 AIDL接口中执行。如果该线程是其他线程,则其便是在服务中执行您的代码的线程。因此,如果只有本地线程访问服务,您可以完全控制在其中执行的线程。但如果遇到这种情况,请不要使用AIDL,而应通过扩展 Binder 来创建接口。
  • 远程进程的调用分派自平台在您自己的进程内维护的线程池。准备好接收来自未知线程且同时发生多个调用的传入调用。换言之,AIDL接口的实现必须是完全线程安全的。从同一远程对象上的一个线程发出的调用会按顺序到达接收器端
  • oneway关键字用于修改远程调用的行为。使用它时,远程调用不会阻塞。它会发送交易数据并立即返回。该接口的实现最终会收到此调用,它是来自 Binder线程池的常规调用,是正常远程调用。如果将 oneway 用于本地调用,则不会产生任何影响,调用仍是同步调用。

3、类型

3.1 按运行地点分

3.1.1 本地服务

调用者和服务提供者在同一个进程

3.1.2 远程服务

调用者和服务提供者在不同进程,远程服务常用在跨进程通信运用。

在多进程通信中,存在两个进程角色(以最简单的为例):服务器端和客户端

以下是两个进程角色的具体使用步骤:
服务器端(Service)
步骤1:新建定义AIDL文件,并声明该服务需要向客户端提供的接口
步骤2:在Service子类中实现AIDL中定义的接口方法,并定义生命周期的方法(onCreat、onBind())
步骤3:在AndroidMainfest.xml中注册服务 & 声明为远程服务

客户端(Client)
步骤1:拷贝服务端的AIDL文件到目录下
步骤2:使用Stub.asInterface接口获取服务器的Binder,根据需要调用服务提供的接口方法
步骤3:通过Intent指定服务端的服务名称和所在包,绑定远程Service

3.2 按运行类型分

3.2.1 前台服务

在Service中使用startForeground()方法启动服务

3.2.2 后台服务

普通的startService()启动的服务

3.3 按功能分

3.3.1 可通信服务

使用bindService()启动的服务

3.3.2 不可通信服务

用startService()启动,调用者退出后Service还存在,不需要与Activity或Service通信

4、使用和应用场景

在这里插入图片描述

5、IntentService服务

Service子类,其使用工作线程逐一处理所有启动请求,不适合并行处理多个请求的场景。
IntentService=Service+HandlerThread+Handler

5.1 IntentService工作原理

在这里插入图片描述
若启动IntentService 多次,那么 每个耗时操作 则 以队列的方式 在 IntentService的 onHandleIntent回调方法中依次执行,执行完自动结束。

5.2 IntentService如何单独开启1个新的工作线程

@Override
public void onCreate() {
    super.onCreate();
    
    // 1. 通过实例化andlerThread新建线程 & 启动;故 使用IntentService时,不需额外新建线程
    // HandlerThread继承自Thread,内部封装了 Looper
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
  
    // 2. 获得工作线程的 Looper & 维护自己的工作队列
    mServiceLooper = thread.getLooper();

    // 3. 新建mServiceHandler & 绑定上述获得Looper
    // 新建的Handler 属于工作线程 ->>分析1
    mServiceHandler = new ServiceHandler(mServiceLooper); 
}


   /** 
     * 分析1:ServiceHandler源码分析
     **/ 
     private final class ServiceHandler extends Handler {

         // 构造函数
         public ServiceHandler(Looper looper) {
         super(looper);
       }

        // IntentService的handleMessage()把接收的消息交给onHandleIntent()处理
        @Override
         public void handleMessage(Message msg) {
  
          // onHandleIntent 方法在工作线程中执行
          // onHandleIntent() = 抽象方法,使用时需重写 ->>分析2
          onHandleIntent((Intent)msg.obj);
          // 执行完调用 stopSelf() 结束服务
          stopSelf(msg.arg1);

    }
}

   /** 
     * 分析2: onHandleIntent()源码分析
     * onHandleIntent() = 抽象方法,使用时需重写
     **/ 
      @WorkerThread
      protected abstract void onHandleIntent(Intent intent);

5.2 IntentService如何通过onStartCommand()将Intent传递给服务依次插入到工作队列

/** 
  * onStartCommand()源码分析
  * onHandleIntent() = 抽象方法,使用时需重写
  **/ 
  public int onStartCommand(Intent intent, int flags, int startId) {

    // 调用onStart()->>分析1
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

/** 
  * 分析1:onStart(intent, startId)
  **/ 
  public void onStart(Intent intent, int startId) {

    // 1. 获得ServiceHandler消息的引用
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;

    // 2. 把 Intent参数 包装到 message 的 obj 发送消息中,
    //这里的Intent  = 启动服务时startService(Intent) 里传入的 Intent
    msg.obj = intent;

    // 3. 发送消息,即 添加到消息队列里
    mServiceHandler.sendMessage(msg);
}

5.3 IntentService总结

IntentService本质=Handler+HandlerThread+Service
1)通过HandlerThread单独开启一个工作线程;
2)利用此工作线程,创建一个内部Handler;ServiceHandler
3)将ServiceHandler 与 IntentService绑定;
4)通过onStartCommand()传递服务intent到ServiceHandler,依次插入到IntentService工作队列中,然后逐个发送给onHandleIntent()

通过复写onHandleIntent(),在里面 根据Intent的不同进行不同操作即可。

两个注意事项需要关注的:
工作任务队列 = 顺序执行(原因:onCreate只会调用一次,所以工作线程只会创建1次)
不建议通过 bindService() 启动 IntentService(原因:bindService不会执行onStartCommand(),无法将任务分配到工作线程中)

5.4 对比

5.4.1 IntentService与Service区别

在这里插入图片描述

5.4.2 IntentService与其它线程区别

在这里插入图片描述

Service、IntentService、JobIntentService的区别

使用场景:‌
Service适用于需要长时间运行且不需要与用户交互的后台任务,‌如音乐播放、‌数据同步等。‌
JobIntentService则更适合处理不需要即时完成的任务,‌或者需要在特定条件下(‌如网络可用时)‌执行的任务,‌因为它可以更好地管理资源和避免应用被系统终止的风险

Service中启动Activity

service.startActivity()->ContextWrapper.startActivity()->ContextImpl.startActivity()

1) 在Service里面启动Activity必须要有FLAG_ACTIVITY_NEW_TASK参数,那么我们添加上不就可以了?

如下:

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

那么这样会带来什么问题呢?

这样带来的问题就是在最近任务列表里面会出现两个相同的应用程序,比如你是在电话本里面启动的,那么最近任务列表就会出现两个电话本;因为有两个Task嘛!

那怎么解决呢?

其实也非常好解决,只要在新的Task里面的Activity里面配置android:excludeFromRecents=”true”就可以了。表示这个Activity不会显示在最近列表里面。

2) 为什么Android在Service 里面启动Activity要强制规定使用参数FLAG_ACTIVITY_NEW_TASK呢?

我们可以来做这样一个假设,我们有这样一个需求:

我们在电话本里面启动一个Service,然后它执行5分钟后,启动一个Activity

那么很有可能用户在5分钟后已经不在电话本程序里面操作了,有可能去上网,打开浏览器程序了。

5分钟后,此时当前的Task是浏览器的task,那么弹出Activity,如果这个Activity在当前Task的话,也就是浏览器的Task;那么用户就会觉得莫名其妙;因为弹出的Activity和浏览器在一个Task,本来这个Activity应该属于电话本的。

所以,对于Service而言,干脆强制定义启动的Activity要创建一个新的Task.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值