Android四大组件之Service

一、 Service简介


service继承树
service继承树

Service 是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。

其他应用组件启动服务,而且即使用户切换到其他应用,服务仍将在后台继续运行。 组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。

服务在其托管进程(创建服务的应用程序所在的进程)的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。 这意味着,如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应在服务内创建新线程来完成这项工作。通过使用单独的线程,可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。Service的ANR时间是20s

仅当内存过低且必须回收系统资源以供具有用户焦点的 Activity 使用时,Android 系统才会强制停止服务。如果将服务绑定到具有用户焦点的 Activity,则它不太可能会终止;如果将服务声明为在前台运行(前台服务),则它几乎永远不会终止。或者,如果服务已启动并要长时间运行,则系统会随着时间的推移降低服务在后台任务列表中的位置,而服务也将随之变得非常容易被终止;如果服务是启动服务,则您必须将其设计为能够妥善处理系统对它的重启。 如果系统终止服务,那么一旦资源变得再次可用,系统便会重启服务(不过这还取决于从 onStartCommand() 返回的值)

当某个进程被杀死时,所有依赖于它的服务也都会销毁停止运行。

为了确保应用的安全性,请始终使用显式 Intent 启动或绑定 Service,且不要为服务声明 Intent 过滤器。

二、使用


1. 创建Service

public class ExampleService extends Service {
    int mStartMode;       // indicates how to behave if the service is killed
    IBinder mBinder;      // interface for clients that bind
    boolean mAllowRebind; // indicates whether onRebind should be used

    /**
     * 生命周期方法:首次创建服务时回调
     */
    @Override
    public void onCreate() {
        // The service is being created
    }
    
    /**
     * 生命周期方法:startService()启动服务时回调,首次启动先调用onCreate()
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        
         return mStartMode;
    }
    
    /**
     * 生命周期方法:bindService()绑定服务时回调,首次绑定先调用onCreate()
     */
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return mBinder;
    }
    
    /**
     * 生命周期方法:绑定者调用unbindService()时回调
     */
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return mAllowRebind;
    }
   
   /**
     * 生命周期方法:服务被销毁前回调
     * 1. startService()后调用stopService()或stopSelf();
     * 2. bindService()后调用unbindService(), 调用了N次bindService确立了N次绑定关系,就要调用N次unbindService()将N个绑定关系解除,才会回调onDestory()
     * 3. startService() + bindService(),调用1次stopService()或stopSelf() + N次unbindService(),才会回调onDestory()
     */
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
        
    }
}

与 Activity 生命周期回调方法不同,不需要调用这些回调方法的超类实现。

2. 清单文件注册

<service
    android:name=".service.ExampleService"
    android:enabled="true"
    android:exported="true">
    <!--隐式启动需添加filter-->
    <intent-filter>
        <!--自定义action,字符串-->
        <action android:name="com.test.service" />
        <!--必须添加此category-->
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

3. 启动Service

  • startService()
// startService
Intent intent = new Intent(this,ExampleService.class);
startService(intent);
    • 一旦启动,Service即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响;

    • 必须通过调用 stopSelf() 来自行停止运行或通过其他组件调用 stopService() 来停止服务。服务停止后,系统会将其销毁;

    • Service未创建,调用startService(),回调onCreate() > onStartCommand() ,再次调用startService(),只回调onStartCommand(),并不会开启多个服务。

  • bindService()

private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName className,IBinder service) {
        Log.e(TAG,"连接成功");
    }

    @Override
    public void onServiceDisconnected(ComponentName arg0) {
    }
};
Intent intent3 = new Intent(this,HelloService.class);
bindService(intent3,mConnection,BIND_AUTO_CREATE);
    • 一旦绑定,Service的生命周期会跟绑定的组件关联起来,多个组件可以绑定同一个Service,当全部绑定取消,服务才会销毁;

    • 组件也可以通过调用unbindService()关闭连接,取消绑定;调用了N次bindService()就需要调用N次unbindService(),再多次调用会报错Service not registered

    • Service未创建,调用bindService(),回调onCreate() > onBind(), 创建后,已绑定的客户端再次调用bindService(),不会再回调onBind() ;不同的客户端再去绑定时会回调onBind() ; 也就是每一个客户端绑定Service只会使Service回调一次onBind(),用来返回IBinder通信

三、Service 生命周期


1. 生命周期图

Service生命周期
Service生命周期

2. 2种生命周期阶段

  • 完整生命周期
    调用 onCreate() 开始起,到 onDestroy() 返回时结束。
    与 Activity 类似,服务也在 onCreate() 中完成初始设置,并在 onDestroy() 中释放所有剩余资源。

  • 有效生命周期
    startService(): onStartCommand() —— onDestory()
    bindService(): onBind() —— onUnbind()

四、扩展


1. 使用Service还是Thread?

简单地说,服务是一种即使用户未与应用交互也可在后台运行的组件。 因此,您应仅在必要时才创建服务。

如需在主线程外部执行工作,不过只是在用户正在与应用交互时才有此需要,则应创建新线程而非服务。 例如,如果您只是想在 Activity 运行的同时播放一些音乐,则可在 onCreate() 中创建线程,在 onStart() 中启动线程,然后在 onStop() 中停止线程。您还可以考虑使用 AsyncTask 或 HandlerThread,而非传统的 Thread 类。

请记住,如果您确实要使用服务,则默认情况下,它仍会在应用的主线程中运行,因此,如果服务执行的是密集型或阻止性操作,则您仍应在服务内创建新线程。

2. 开启前台服务

通过Service.startForeground()。此方法采用两个参数:唯一标识通知的整型数和状态栏的 Notification。例如:

Intent intent = new Intent(this, MainActivity.class);

PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);

Notification notification = new NotificationCompat.Builder(this)
        .setContentTitle("This is content title")
        .setContentText("This is content text")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.mipmap.ic_launcher)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
        .setContentIntent(pi)
        .build();
        
startForeground(1, notification);

注意:

1.提供给 startForeground() 的整型 ID 不得为 0。

2.要从前台移除服务,请调用 stopForeground()。此方法采用一个布尔值,指示是否也移除状态栏通知。 此方法不会停止服务。 但是,如果您在服务正在前台运行时将其停止,则通知也会被移除。

3. intentService

这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,这是最好的选择。 您只需实现 onHandleIntent() 方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。

由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 IntentService 类实现服务也许是最好的选择。

IntentService 执行以下操作:

  • 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
  • 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。
  • 在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。
  • 提供 onBind() 的默认实现(返回 null)。
  • 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。

综上所述,您只需实现 onHandleIntent() 来完成客户端提供的工作即可。(不过,您还需要为服务提供小型构造函数。)

public class HelloIntentService extends IntentService {

  /**
   * 构造函数是必需的,并且必须调用超级IntentService(String)构造函数,其中包含工作线程的名称。
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * IntentService从默认工作线程调用此方法,并带有启动服务的意图。
   * 当该方法返回时,IntentService 将适当地停止服务。
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // do sth 
      try {
          Thread.sleep(5000);
      } catch (InterruptedException e) {
          // Restore interrupt status.
          Thread.currentThread().interrupt();
      }
  }
}

4、你有注意到onStartCommand 方法的返回值吗?不同返回值有什么区别?

  • START_STICKY
    当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。
  • START_NOT_STICKY
    当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
  • START_REDELIVER_INTENT
    当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务

5. 5.0隐式启动Service正确方式

从 Android 5.0(API 级别 21)开始,如果使用以前的隐式 Intent 启动Service,系统会引发异常(Service Intent must be explicit)。

正确的隐式启动方式:

Intent intent = new Intent();
intent.setPackage("Service所在包名"); // 这是关键,其余都一样
intent.setAction("Service的IntentFilter中的action");
…… 
startService(intent);

个人总结,水平有限,如果有错误,希望大家能给留言指正!如果对您有所帮助,可以帮忙点个赞!如果转载,希望可以留言告知并在显著位置保留草帽团长的署名和标明文章出处!最后,非常感谢您的阅读!

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

推荐阅读更多精彩内容