Android 初级学习路线

Android四大组件之Service

2018-12-17  本文已影响0人  草帽团长

一、 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
Intent intent = new Intent(this,ExampleService.class);
startService(intent);
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 生命周期


1. 生命周期图

Service生命周期Service生命周期

2. 2种生命周期阶段

四、扩展


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。

综上所述,您只需实现 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);

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

上一篇 下一篇

猜你喜欢

热点阅读