Android 系统开发_四大组件篇 -- Service 解析(用法)

开篇

服务是什么?

服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还需要长期进行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。

不过需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。

Service 使用方法

定义一个服务

首先,我们定义一个 MyService.java 类,当然作为一个服务类,必须要继承 Service(android.app.Service),看代码:

// 源码路径:frameworks/base/core/java/android/app/Service.java
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
    private static final String TAG = "Service";
    ... ...

    @Nullable
    public abstract IBinder onBind(Intent intent);
    ... ...
}

Service 定义了一个抽象方法 onBind,子类继承它,必须复写此方法。

public class MyService extends Service{
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {        // 这是一个抽象方法,那么子类是必须要重写的
        return null;
  }
}

服务既然已经定义好了,自然应该在服务中去处理一些事情,那处理事情的逻辑应该写在哪里?我们需要在服务里重写 Service 中的另一些常用的方法:

public class MyService extends Service{
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {        // 这是一个抽象方法,那么子类是必须要重写的
        return null;
  }

    @Override
    public void onCreate() {                                                // 服务创建时调用
      super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {      // 服务启动时调用
      return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {                                               // 服务销毁时调用
      super.onDestroy();
    }
}

和添加 Activity 一样,我们添加了一个服务,那么在 AndroidManifest.xml 文件中必须进行注册才能生效!

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.xin02ma.myapplication">
  <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    ......
    <activity android:name=".MainActivity">
      ......
    </activity>
    <service android:name=".MyService"
                 android:enabled="true"
                 android:exported="true" />
  </application>
</manifest>

启动和停止服务

服务定义好了,接下来就应该考虑如何去启动以及停止这个服务了。

(1)先添加两个Button(activity_main.xml)

<Button
  android:id="@+id/start_service"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/start_service" />

<Button
  android:id="@+id/stop_service"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/stop_service" />

<center>《Android 系统开发_四大组件篇 -- Service 解析(用法)》</center>

  (2)接下来,修改主函数 MainActivity 的代码:

public class MainActivity extends Activity implements View.OnClickListener{
  private Button startService;                                              
  private Button stopService;

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);                               // 采用布局
    startService = (Button) super.findViewById(R.id.start_service);       // 取得Button实例
    stopService = (Button) super.findViewById(R.id.stop_service);         // 取得Button实例
    startService.setOnClickListener(this);                                // 监控Button,注册按钮事件
    stopService.setOnClickListener(this);                                 // 监控Button,注册按钮时间
  }

  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.start_service:
        Intent startIntent = new Intent(this, MyService.class);
        startService(startIntent);                                          // 启动服务
        break;
      case R.id.stop_service:
        Intent stopIntent = new Intent(this, MyService.class);
        stopService(stopIntent);                                            // 停止服务
        break;
      default:
        break;
    }
  }
}

上面的代码很简单,主要作了以下工作:

(1)取得 startService 和 stopService 两个按钮实例,并且注册了点击事件;

(2)通过 Intent 对象,调用 Activity 的 startService() 和 stopService() 方法来启动和停止服务。

【Notice】

这里的活动的启动和停止完全是由活动本身控制的,如果我们 start 了服务,但是没有点击 stop,那么服务会一直处于运行状态,此时服务如何让自己停止下来?

只需要在 MyService 的任何一个位置调用 stopSelf() 这个方法就能让服务停下来!

Log测试

添加Log,查看Service是如何运作的:

public class MyService extends Service{

    private static final String TAG = "MyService";
    
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {        // 这是一个抽象方法,那么子类是必须要重写的
        return null;
  }
 
    public void onCreate() {                                                // 服务创建时调用
      super.onCreate();
      Log.d(TAG, "onCreate executed");
    }

    public int onStartCommand(Intent intent, int flags, int startId) {      // 服务启动时调用
      Log.d(TAG, "onStartCommand executed");
      return super.onStartCommand(intent, flags, startId);
    }

    public void onDestroy() {                                               // 服务销毁时调用
      super.onDestroy();
      Log.d(TAG, "onDestroy executed");
    }
}

添加了3行Log,目的就是看:在我们点击两个按钮的时候,整个Service什么时候创建,什么时候启动,什么时候毁灭!

我们来看一下执行结果,运行程序,查看Logcat中的打印日志:
《Android 系统开发_四大组件篇 -- Service 解析(用法)》

(1)第一次点击 StartService按钮 后,MyService中的 onCreate() onStartCommand() 方法都执行了,图中黄色箭头所示!
此时,我们可以在 手机 –> 设置 –> 应用 –> 运行中 可以看到这个服务:
《Android 系统开发_四大组件篇 -- Service 解析(用法)》

(2)然后我们点击 stopService按钮 后,MyService中的 onDestory() 方法被执行,图中蓝色箭头所示!

(3)此时可能你会有一个疑问?当我们点击了 startService按钮 以后, onCreate() onStartCommand() 方法同时被执行,这两个方法有什么区别?

图中的 红色箭头 给了我们答案: onCreat() 方法是在服务第一次创建的时候调用的,而 onStartCommand() 方法则在每次启动服务的时候都会被调用。

当我们在 服务未启动 的时候,点击 startService 按钮 ,则此时会 执行两个方法

但是 服务启动完成 之后,再次点击(随便你点几次) startService按钮 ,你会发现 只有onStartCommand()方法被执行

Service生命周期

上面介绍完 Service 的使用方法,接下来看看 Service 的 生命周期 :跟Activity相比,Service的生命周期很简单: onCreate()->onStart()->onDestroy()

我们以如下的方式展开这章节的讨论工作!

【主题】 :Activity 与 Service之间的 Communication

【问题】 :由上贴我们知道,当我们点击 START SERVICE 按钮后,服务的 onCreate() 和 onStartCommand() 方法会得到执行,此后 Service 是一直存在于后台运行的,Activity 无法控制 Service 中具体的逻辑运行,那么这样 Activity 只相当于起到一个通知的作用,除了告诉 Service 你可以开始工作了。那么这样显然会分离两者之间的关联性,这也不是我们需要的结果!

【后果】 :如果出现以上的问题,那么在我们平时的项目开发过程中,一直存在的 Service 很有可能会引起功耗的问题,可能影响手机的运行效率!

【要求】 :我们能否将 Activity 与 Service 建立一种联系,当 Activity 终结之时,Service 也销毁,也就是有没有办法让 Activity 和 Service 能够 “不求同生,但求共死”

答案是肯定的! 这就涉及到 Service 的另一个重要知识点: 绑定 解绑

还是以代码为例:

MyService

MyService.java

public class MyService extends Service{

  private static final String TAG = "MyService";

  private DownloadBinder mBinder = new DownloadBinder();              // 定义一个 DownloadBinder 类

  class DownloadBinder extends Binder {                               // 让 DownloadBinder 成为 Binder 的子类
        public void startDownload() {                                   // 定义开始下载的方法
      Log.d(TAG, "startDownload executed");
    }
    public int getProgress() {                                     // 定义一个查看下载进度的方法
      Log.d(TAG, "getProgress executed");
      return 0;                                                 
    }
  }

  @Nullable
  @Override
  public IBinder onBind(Intent intent) {      // onBind()方法,这个方法将在绑定后调用
    return mBinder;                         // 返回 IBinder 的实例 --> DownloadBinder 类的实例
  }

    public void onCreate() {                                               
      super.onCreate();
      Log.d(TAG, "onCreate executed");
    }

    public int onStartCommand(Intent intent, int flags, int startId) {      
      Log.d(TAG, "onStartCommand executed");
      return super.onStartCommand(intent, flags, startId);
    }

    public void onDestroy() {                                               
      super.onDestroy();
      Log.d(TAG, "onDestroy executed");
    }
}

BIND SERVICE / UNBIND SERVICE

我们在Layout中添加两个按钮  BIND SERVICE  和  UNBIND SERVICE

《Android 系统开发_四大组件篇 -- Service 解析(用法)》

MainActivity.java

public class MainActivity extends Activity implements View.OnClickListener{

  private ServiceConnection connection = new ServiceConnection() {                       // 创建一个 ServiceConnection 的匿名内部类
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {              // 重写 onServiceConnected() 方法
      MyService.DownloadBinder downloadBinder = (MyService.DownloadBinder) service;  // 向下转型取得 downloadBinder 实例
      downloadBinder.startDownload();                                                // 在 Activity 中调用 Service 的方法
      downloadBinder.getProgress();                                                  // 在 Activity 中调用 Service 的方法
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {       // 重写onServiceDisconnected()方法
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Button startService = (Button) super.findViewById(R.id.start_service);
    Button stopService = (Button) super.findViewById(R.id.stop_service);
    Button bindService = (Button) super.findViewById(R.id.bind_service);
    Button unbindService = (Button) super.findViewById(R.id.unbind_service);        
    startService.setOnClickListener(this);
    stopService.setOnClickListener(this);
    bindService.setOnClickListener(this);
    unbindService.setOnClickListener(this);
  }

  @Override
  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.start_service:
        Intent startIntent = new Intent(this, MyService.class);        // START 服务 --> onCreate() --> onStartCommand()
        startService(startIntent);
        break;
      case R.id.stop_service:
        Intent stopIntent = new Intent(this, MyService.class);         // STOP 服务 --> onDestroy()
        stopService(stopIntent);
        break;
      case R.id.bind_service:                                            // 绑定 --> ?
        Intent bindIntent = new Intent(this, MyService.class);
        bindService(bindIntent, connection, BIND_AUTO_CREATE);         // 💥 💥 💥 💥 重点分析
        break;
      case R.id.unbind_service:                                          // 解绑 --> ?
        unbindService(connection);
        break;
      default:
        break;
    }
  }
}

bindService

看一下 bindService(bindIntent, connection, BIND_AUTO_CREATE) 这个方法:

bindService 接收了 3 个参数:

bindIntent :这个参数传入的就是我们的 intent,目的就是调用 MyService 这个服务。

connection :这个参数传入的就是创建好的 ServiceConnection 的实例,这个参数代表着我们的 Activity 是要和 Service 绑定在一起的!

BIND_AUTO_CREATE :这是一个 FLAG,表示在活动和服务进行绑定后 自动创建服务 。注意!是自动创建服务,也就是说 MyService 会执行 onCreate() 方法,但是不会执行 onStartCommand() 方法!

接下来,直接看代码最终的效果:
《Android 系统开发_四大组件篇 -- Service 解析(用法)》   《Android 系统开发_四大组件篇 -- Service 解析(用法)》

通过排列组合,对按钮进行点击,Log分 3 种情况:

START SERVICE + STOP SERVICE:

1、当我们先点击 START SERVICE :此时服务启动,调用 onCreat() 和 onStartCommand() 方法;

2、当我们后点击 STOP SERVICE :此时,服务被销毁,调用 onDestroy() 方法。

BIND SERVICE + UNBIND SERVICE:

1、当我们先点击 BIND SERVICE :此时服务仅仅是创建,并未启动!所以调用的只是 onCreate() 方法。此时 Activity 与 Service 绑定,会同时调用 onBind() 方法,此时 onServiceConnected() 方法会被执行,还记的 onBind() 方法的返回类型不?我们通过 Log 可以很明显发现,Activity 调用了服务内部的两个自定义方法。

2、当我们后点击 UNBIND SERVICE :由于服务还未启动,而 BIND SERVICE 只是将服务创建好并与活动进行绑定,那么解绑后,势必会销毁这个 Service,所以 onDestroy() 被执行!

START SERVICE + BIND SERVICE + UNBIND SERVICE + STOP SERVICE:

1、我们先点击 START SERVICE :onCreat() 和 onStartCommand() 方法被执行,这个就不用多说了;

2、然后点击 BIND SERVICE :这个时候其实活动已经在后台运行了,我们此时将活动和服务绑定,那么 onCreate() 不会再执行,只会执行 onServiceConnected() 方法,Log 里面打出来看的很清楚。

3、此时你如果手贱,想 STOP SERVICE:那么恭喜你,毫无反应!为什么?因为你都没解绑,你怎么销毁?

4、OK,那我们先解绑,我们点击 UNBIND SERVICE :此时一个奇怪的现象发生了,LOG 日志没有打印出 Destroy() 这个方法啊?没有被执行啊!不是说 bind 了 Service 之后,unbind 就会销毁这个服务吗?这跟我们之前分析的不符合啊。

5、好吧,我们来看看为什么。其实原因很简单:我们先 start 了 Service,那么此时服务已经在后台运行了,这个时候你 bind,让 Service 和 Activity 绑定,其实是没有什么意义的。但是既然绑定了,你如果不解绑,那么 Destroy() 毫无用武,所以,这种情况和(2)中分析的还是有区别的,此是解绑完后,服务还是舒舒服服的在后台运行,所以,要想干掉这个服务,你必须要 STOP SERVICE。

6、那我们解绑后,再 STOP SERVICE :这个时候 Service 就被枪毙了!

Service 两个实用小技巧

Forground Service

服务几乎都是在后台运行的,一直一来它都是默默地做着辛苦的工作。但是服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服务。如果你希望服务可以一直保持运行状态,而不是由于系统内存不足的原因导致被回收掉,就可以考虑使用前台服务。前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。当然有时候你也可能不仅仅是为了防止服务被回收掉才使用前台服务,有些项目由于特殊的需求会要求必须使用前台服务,比如说天气类软件,它的服务在后台更新天气数据的同时,还会在系统状态栏一直显示当前的天气信息。

【问题】 :我们都知道服务是运行在后台的,如果系统出现内存不足的情况,那么此时,系统就可能回收后台的服务,那么我们如何保证服务可以一直运行?

【解决】 :在服务中,有一个 前台服务 的概念,调用 startForground() 方法可以实现。

如何创建一个前台服务,看代码:

public class MyService extends Service{

  ......

  @Override
  public void onCreate() {
    super.onCreate();
        Log.d("MyService", "onCreate executed");
    Intent intent = new Intent(this, MainActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
    Notification notification = new Notification.Builder(this)     // 启动服务后,在前台添加一个Notification
        .setContentTitle("This is a 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);
  }
    ... ...
}

以上的代码是在 Service 的创建中添加了一个 Notification,调用 startForground() 就可以保证:只要服务一直存在,那么在前台就会一直显示这个 Notification。

如果我们在 onDestroy() 中调用 stopForground() 方法,会销毁这个 Notification,但是 Service 还是存活的,此时 Service 就会面临被 System 干掉的风险。

如果直接 STOP SERVICE,那么 Notification 和 Service 都会销毁。 

IntentService

【问题】 :我们知道服务的代码逻辑是在主线程中执行的,如果我们在主线程中需要执行一些耗时的操作,那么很有可能出现ANR(程序暂无响应)的状况。

这个时候,我们可以采用 Android 的多线程编程的方式,我们应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。所以,一个比较标准的服务就可以写成如下形式:

public class MyService extends Service{
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(new Runnable() {      // 开启一个线程处理耗时操作
      @Override
      public void run() {
        // 处理具体的逻辑                     
      }
    }).start();
    return super.onStartCommand(intent, flags, startId);
  }
}

现在,服务可以启动起来了。但是如果不调用 StopService() stopSelf() 方法,服务会一直运行,所以我们需要修改一下代码:

public class MyService extends Service{
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(new Runnable() {
      @Override
      public void run() {
        // 处理具体的逻辑                  // 开启一个线程处理耗时操作
        stopSelf();                        // 让服务执行完逻辑后自行停止
      }
    }).start();
    return super.onStartCommand(intent, flags, startId);
  }
}

上面的代码就是一个标准的 Service 的书写形式,主要包含两个知识点:Thread子线程的创建 和 stopSelf() 方法的调用。

虽说这种写法并不复杂,但是总会有人忘记开启线程,或者忘记调用 stopSelf(),那么有没有更好的办法能够实现上面两个需求呢?

【解决】: 在 Android 中,专门提供了一个 IntentService 类(android.app.IntentService),这个类就能很好的满足我们的需求!我们直接通过代码来看:

新建一个 MyIntentService 类继承自 IntentService,代码如下:

public class MyIntentService extends IntentService{

  public MyIntentService() {
    super("MyIntentService");     // 调用父类的有参构造函数
  }

  @Override
  protected void onHandleIntent(Intent intent) {
        // 打印当前线程的 id
    Log.d("MyIntentService", "MyIntentServiceThread id is " + Thread.currentThread().getId());
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    Log.d("MyIntentService", "onDestroy executed");
  }
}

以上代码做了几件事:

1、提供了一个无参的构造方法,并且调用了父类的有参构造函数;

2、子类实现父类的 onHandleIntent() 抽象方法,这个方法好就好在,它是一个已经运行在子线程中的方法。也就是说,服务调用了它,那么执行的逻辑就如同 Thread 子线程;

3、根据 IntentService 的特性,这个服务在运行结束后应该是会自动停止的,所以我们又重写了 onDestroy()方法,在这里也打印一行日志,以证实服务是不是停止掉了。

我们在 xml 文件中,创建一个 MyIntentService 服务按钮:

<Button
  android:id="@+id/start_intent_service"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/intent_service"/>

然后修改 MainActivity 中的代码:

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
        ... ...
        
    Button startIntentService = (Button) super.findViewById(R.id.start_intent_service);

    startIntentService.setOnClickListener(new View.OnClickListener() {

      @Override
      public void onClick(View v) {
        Log.d("MyIntentService", "MainActivity Thread id is " + Thread.currentThread().getId());            // 查看主线程的id
        Intent intentService = new Intent(getBaseContext(), MyIntentService.class);                   
        startService(intentService);
      }
    });
  }
}

最后,在AndroidMainfest中注册服务:

<service android:name=".MyIntentService" />

【结果】

《Android 系统开发_四大组件篇 -- Service 解析(用法)》

从打出的LOG可以看出:

1、MyIntentService 和 MainActivity 所在进程的 id是不一样的

2、onHandleIntent() 方法在执行完逻辑后确实销毁了服务,效果等同于 stopSelf()。

从上面的分析可以看出 onHandleIntent() 方法确实相当的好用!

    原文作者:Android
    原文地址: https://segmentfault.com/a/1190000016535484
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞