Android 四大组件之 Service

前言

Service 作为 Android 的四大组成之一,其重要性是不言而喻的,它主要用于在后台处理那些耗时但又不需要与用户发生交互的工作。例如当我们在 Activity 中开启一个下载任务时,我们可能会将应用置于后台然后去打开别的应用。这时我们的 Activity 是有可能被杀死的,如果我们将下载任务直接放在 Activity 中进行,那么就有可能导致下载中断。在这种情况下我们就最好将我们的下载任务置于 Service 中进行处理。

在介绍过 Service 的使用场景之后,我们接下来了解一下本篇文章我们要解决的问题:

  • Service 的基本使用方法。
  • Service 的两种启动方式。
  • Service 的生命周期。
  • 前台 Service
  • 系统服务

那么接下来我们就开始来学习 Service 的相关知识吧!首先我们要学习的是 Service 的基本使用方法。

Service 的基本使用方法

使用 Service 的第一步,当然就是先创建一个 Service 了,在这里要特别注意既然 ServiceAndroid 的四大基本组件之一,那么我们就需要在 Manifest.xml 中注册我们的 Service,例如我的 Service 名称为 MyService,那么我就应该在 Manifest.xmlApplication 标签中添加如下语句:

<application

    ... 省略无关代码 ...

    <service
        android:name=".MyService"
        android:enabled="true"
        android:exported="true"></service>
        
</application>

其中 exported 属性表示是否允许除当前程序之外的其他程序访问这个 Serviceenabled 属性表示是否启用这个 Service

接下来我们就来看看在 MyService 这个类中我们应当如何使用:

public class MyService extends Service {

    public MyService() {
    }

    @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();
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

可以看到,在这个类中我们重写了 4 个父类方法,分别是 onCreateonStartCommandonDestroyonBind。其中前 3 个方法属于 Service 生命周期中的方法,而 onBind 方法适用于绑定 Activity 的,我们会在后面对这个方法进行介绍,首先我们先对三个生命周期的方法打 Log,然后在 Activity 中开启和关闭 Service,然后根据打印出来的 Log 判断 Service 开启和关闭过程中调用了那些方法。代码如下所示:

public class MyService extends Service {

    private static final String TAG = MyService.class.getSimpleName();

    public MyService() {
    }

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

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

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

    ......

}

接下来就是 MainActivity 的代码了,我们在这里通过两个按钮来开启和关闭 Service,开启和关闭的方式是通过创建 Intent 对象,然后分别调用 startServicestopService 方法,将 Intent 作为参数传入完成的,代码如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button startServiceBtn;
    private Button stopServiceBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startServiceBtn = (Button)findViewById(R.id.start_service);
        stopServiceBtn = (Button)findViewById(R.id.stop_service);
        startServiceBtn.setOnClickListener(this);
        stopServiceBtn.setOnClickListener(this);
    }

    @Override
    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;
        }
    }
}

运行程序,可以观察到,当我们按下开启 Service 的按钮时,打印出了如下日志:

D/MyService: onCreate
D/MyService: onStartCommand

而如果我们重复按开启 Service 的按钮的话,将会不停地打印 onStartCommand,如下所示:

D/MyService: onCreate
D/MyService: onStartCommand
D/MyService: onStartCommand
D/MyService: onStartCommand

当我们按下关闭 Service 按钮时,打印出如下日志:

D/MyService: onDestroy

所以在这里我们可以得知,在 Activity 中通过 Intent 开启 Service 时,首先会调用 onCreate 方法,紧接着还会调用 onStartCommand 方法。

在关闭 Service 的时候,会调用 onDestroy 方法将 Service 进行销毁。

Service的两种启动方式

在上面的例子中,我们已经介绍了一种启动方式了,即在 Activity 中通过 Intent 来启动 Service,接下来我们来介绍第二种启动方式:通过 Binder 绑定 Service

是否还记得我们的 MyService 中还有一个重写方法:onBind,在第二种启动方式中,它就会派上用场了。这里你可能会有一个疑问:为什么 Android 会给我们提供两种方式来启动 Service 呢?

答案就是,在我们前面启动 Service 中,我们的 Service 是无法和 Activity 产生通信的,在 Activity 中启动 Service 后,我们只能知道 Service 已经开启了,但是它的逻辑运行到了哪里,根本无从知晓。而 Binder 就提供了一种方式,可以让 Activity 控制 Service 的行为。

我们先来看根据第二种启动方式修改的 MyService 的代码:

public class MyService extends Service {

    private static final String TAG = MyService.class.getSimpleName();

    private DownloadBinder mBinder;

    public MyService() {
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate");
        super.onCreate();
        mBinder = new DownloadBinder();
    }

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

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

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        return mBinder;
    }

    public class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d(TAG, "startDownload");
        }
    }

}

在这里我们定义了一个内部类 DownloadBinder,它是 Binder 的子类,我们在这个类中定义了一个方法 startDownload。然后在 MyService 的开头,我们维护了一个私有成员 mBinder,它在 onCreate 方法中初始化,然后在 onBind 中直接返回。分析完了 MyService 的代码,我们接下来再来看看 MainActivity 中的代码,我们在我们之前的基础上再添加 2 个按钮,分别用于绑定和解绑 Service,代码如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button startServiceBtn;
    private Button stopServiceBtn;
    private Button bindServiceBtn;
    private Button unbindServiceBtn;

    private MyService.DownloadBinder mBinder;  // DownloadBinder
	
	// 关键点
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinder = (MyService.DownloadBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startServiceBtn = (Button)findViewById(R.id.start_service);
        stopServiceBtn = (Button)findViewById(R.id.stop_service);
        bindServiceBtn = (Button)findViewById(R.id.bind_service);
        unbindServiceBtn = (Button)findViewById(R.id.unbind_service);
        startServiceBtn.setOnClickListener(this);
        stopServiceBtn.setOnClickListener(this);
        bindServiceBtn.setOnClickListener(this);
        unbindServiceBtn.setOnClickListener(this);
    }

    @Override
    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;
            case R.id.bind_service:
                Intent intent = new Intent(this, MyService.class);
                bindService(intent, connection, BIND_AUTO_CREATE);
                break;
            case R.id.unbind_service:
                unbindService(connection);
                break;
        }
    }
}

我们在 MainActivity 中维护了一个 DownloadBinder 类型的成员 mBinder,然后还创建了一个匿名内部类 ServiceConnection,重写了它的两个方法 onServiceConnectedonServiceDisconnected,在 onServiceConnected 方法中,我们就可以获得 Service 中的 DownloadBinder 实例,然后赋值给 mBinder。这样,我们就能够通过 mBinder 获取到它的 startDownload 方法了,然后我们就可以在 Activity 中通过该方法的调用控制 Service 的行为。

接下来我们看到 onClick 方法中是如何绑定和解绑 Service 的,首先依然是要创建 Intent 对象,然后调用 bindService 方法绑定 ServicebindService 有 3 个参数,第一个是 Intent,第 2 个是 ServiceConnection,第 3 个则是一个标志位,这里我们传入 BIND_AUTO_CREATE 表示绑定之后自动创建 Service

而解除绑定同样也需要 Intent,然后调用 unbindService 方法,传入我们的 ServiceConnection 对象即可。

接下来我们同样运行一下这段代码,看看打印出来的 Log 是什么样子的。首先按下绑定服务的按钮,打印出来的是如下信息:

D/MyService: onCreate
D/MyService: onBind

可以看到打印出了如上信息,调用的是 onCreate 方法和 onBind 方法,而我们无论再怎么按绑定按钮,这些信息都不会再打印了。接下来我们按下解绑按钮,打印出如下信息:

D/MyService: onDestroy

解绑后它就调用了 onDestroy 方法销毁了 Service

而在这里,我们还有一点需要注意的是,如果我们既按下了开启 Service 按钮来开启服务(即第 1 中启动方式),又按下绑定按钮对 Service 进行了绑定,那么我们在单独按下关闭 Service 的按钮或者解除绑定的按钮时,我们会发现 Log 中并没有打印出 onDestroy 的信息,也就是说我们的 Service 并没有执行 onDestroy 方法销毁 Service

在这种情况下,我们既要关闭服务,又要解除绑定(也就是两个销毁的按钮都按下),最终才会调用 onDestroy 方法销毁我们的 Service

讲解到这里,我们来分别对两种方式进行一个总结:

  1. 第一种启动方式是通过 startService 方法进行启动,在我们的 Activity 中调用该方法就会使我们的服务进入运行状态,此时会调用 onCreateonStartCommand 方法,onCreate 方法只会调用一次,而在重复调用 startService 方法时,onStartCommand 方法会重复执行。
  2. startService 相对应的方法是 stopService,它用于关闭服务,它会调用 onDestroy 方法销毁 Service
  3. 第二种启动方式是通过 bindService 方法进行启动,即绑定我们的 Service,它可用于 Activity 操控 Service 的行为,在 Activity 中我们通过 ServiceConnectiononServiceConnected 方法获得 Binder 的实例,然后通过该实例调用相对应的方法来操纵 Service 的行为。调用 bindService 会调用 onCreateonBind 方法,这两个方法均只会执行一次,即重复调用 bindService 不会重复调用这两个方法。
  4. bindService 方法相对应的是 unbindService 方法,它用于解除绑定,它会调用 onDestroy 方法销毁 Service
  5. 如果即调用了 startService,又调用了 bindService,那么单独调用 stopServiceunbindService 并不会销毁 Service,需要这两个方法同时调用才能销毁 Service

在讲解完 Service 的两种启动方式之后,接下来我们来了解一下 Service 的生命周期。

Service 的生命周期

官方文档提供的 Service 生命周期图如下所示:
Service 的生命周期图
可以很清晰的看到,在通过 startService 方法启动 Service 的方式中,生命周期是 onCreate - onStartCommand - onDestroy,而通过 bindService 方法启动 Service 时,生命周期为 onCreate - onBind - onUnbind - onDestroy。接下来我们来了解一下这么方法的具体含义:

  • onCreate:第一次启动 Service 由系统调用。
  • onStartCommand:通过 startService 启动服务时由系统调用,它的调用位于 onCreate 之后。
  • onBind:绑定 Service 时调用,会返回一个 Binder 实例。
  • onUnbind:解绑 Service 时调用。
  • onDestroy:停止 Service 时调用,用于销毁 Service

在了解完 Service 的生命周期之后,我们接下来讲解下如何创建前台 Service

前台 Service

在我们之前的例子中,我们的 Service 都是在后台运行的。但是 Service 的系统优先级并不高,在内存不足的时候,它还是有被回收掉的风险,所以我们可以通过一个前台 Service 来提高它的系统优先级,这样子它就可以一直保持运行状态。

要实现前台 Service,其实非常简单,只要在 onCreate 中添加一些代码即可,onCreate 的代码如下所示:

@Override
public void onCreate() {
    ......
    Notification notification;
    Intent intent = new Intent(this, MainActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        NotificationChannel channel = new NotificationChannel("service",
                "fore_service", NotificationManager.IMPORTANCE_DEFAULT);
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        manager.createNotificationChannel(channel);
        notification = new NotificationCompat
                .Builder(this, "service")
                .setContentTitle("Foreground Service")
                .setContentText("This is Foreground Service's test")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setWhen(System.currentTimeMillis())
                .setContentIntent(pi)
                .build();
    } else {
        notification = new NotificationCompat
                .Builder(this)
                .setContentTitle("Foreground Service")
                .setContentText("This is Foreground Service's test")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setWhen(System.currentTimeMillis())
                .setContentIntent(pi)
                .build();
    }
    startForeground(1, notification);  // 启动前台服务
}

在这段代码中,其实主要的逻辑就是创建 Notification 对象,然后通过 startForeground 方法进行调用产生前台服务。而我们创建 PendingIntent 对象是用于点击前台服务后可以返回我们的 MainActivity

这里还有一个需要注意的点,我们在这里对于不同版本进行了适配,因为从 Android 8.0 开始对于 Notification 的创建做了很大的改动,所以有必要对不同版本进行适配,Notification 的适配相关问题可以查看这篇文章:https://blog.csdn.net/guolin_blog/article/details/79854070 ,讲的非常详细,由于与本主题无关,故不做介绍。

我们来看看它实现的效果:
在这里插入图片描述

Service 的其他注意事项

我们前面一起提到,在 Service 中我们常用于处理一些耗时的任务,耗时的任务自然是放在子线程里面去做的,那么我们的 Service 是运行在哪个线程中的呢?

事实上 Service 默认是运行在主线程中的,它并不会为我们开启一个子线程,所以在 Service 中我们的耗时操作需要自己开启子线程来进行处理,否则可能会出现 ANR 问题。当然还有其他的解决方案如使用 IntentService,在 IntentService 是默认为我们开启了子线程的。

另外,停止 Service 除了在 Activity 中调用 stopService 方法外,我们还可以在 Service 内部合适的地方调用 stopSelf 方法,同样也可以实现停止 Service 的效果,这里我们在 onStartCommand 方法中开启子线程写入相应逻辑,代码如下所示:

@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 之外,我们还可以使用现成的系统 Service,例如我们在上面前台 Service 的例子中创建 NotificationManager 对象就是使用的系统 Service,它的使用方式也相当简单,就是通过 getSystemService 方法,传入一个标志位,然后将返回值进行类型强转就可以了,接下来我们通过一个例子,来充分感受下调用系统服务的用法。

例子:通过调用系统的 AlarmManager,在后台执行一个模拟的定时任务,定时时间为 5s。

我们接下来看看 AlarmManager 的一些参数说明:

工作类型

参数 含义
ELAPSED_REALTIME 让定时任务的触发时间从系统开机算起,但不会唤醒 CPU
ELAPSED_REALTIME_WAKEUP 让定时任务的触发时间从系统开机算起,并且会唤醒 CPU
RTC 让定时任务的触发时间从1970年1月0日0时开始算起,但不会唤醒 CPU
RTC_WAKEUP 让定时任务的触发时间从1970年1月0日0时开始算起,并且会唤醒 CPU

定时任务触发的时间计算

如果工作类型为 ELAPSED_REALTIMEELAPSED_REALTIME_WAKEUP,那么定时时间应该为: SystemClock.elapsedRealtime() + delayTimes,单位为 ms。

如果工作类型为 RTCRTC_WAKEUP,那么定时时间应该为: System.currentTimeMillis() + delayTimes,单位为 ms。

在了解这些参数以及如何计算定时时间之后,我们定时 5s 就可以写成如下代码:

long time = SystemClock.elapsedRealtime() + 5000;   // 延时 5s
AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, pi);

可以看到我们调用的是 AlarmManagersetExtra 方法来执行定时任务,它接收 3 个参数,第 1 个是一个标志位,代表工作类型,第 2 个就是传入我们的定时时间,而第 3 个参数是一个 PendingIntent 对象,在这里我们通过定时开启一个广播,所以 PendingIntent 的代码如下:

Intent receiverIntent = new Intent(this, MyReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, receiverIntent, 0);

因为是要开启一个广播,所以这里我们要通过 getBroadcast 方法获取到广播,这里我们的广播命名为 MyReceiver,它的代码如下所示:

public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent i = new Intent(context, MyService.class);
        context.startService(i);
    }
}

不要忘了在 Manifest.xml 中对它进行注册,我们一旦开启了广播,就会调用 startService 方法开启 Service,如此 Service 就会再次执行定时任务,再次打开广播,然后广播又会打开 Service…如此循环反复,我们就成功建立起了一个定时任务。

我们在前面提到过,重复调用 startService 会重复调用 Service 中的 onStartCommand 方法,所以我们的定时任务逻辑应该放在 onStartCommand 中执行,所以完整的 MyService 代码如下所示:

public class MyService extends Service {

    private static final String TAG = MyService.class.getSimpleName();

    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        Intent receiverIntent = new Intent(this, MyReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(this, 0, receiverIntent, 0);
        long time = SystemClock.elapsedRealtime() + 5000;   // 延时 5s
        AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
        manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, pi);
        return super.onStartCommand(intent, flags, startId);
    }

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

}

在这里我们依然通过打印 Log 来进行验证,运行该程序,结果如下所示:

2019-02-23 10:41:56.013 3226-3226/com.marck.servicetest D/MyService: onStartCommand
2019-02-23 10:42:01.048 3226-3226/com.marck.servicetest D/MyService: onStartCommand
2019-02-23 10:42:06.085 3226-3226/com.marck.servicetest D/MyService: onStartCommand
2019-02-23 10:42:11.120 3226-3226/com.marck.servicetest D/MyService: onStartCommand
2019-02-23 10:42:16.160 3226-3226/com.marck.servicetest D/MyService: onStartCommand

在通过 Activity 开启 Service 后,可以看到确实是每过 5s,我们就会执行一次 onStartCommand 方法。

空格空格空格

到这里,Service 的内容基本上就讲解完了,ServiceAndroid 的基础和重要的组成部分,希望大家能够通过本篇文章有所收获,有问题可以在评论区下方给我留言!

猜你喜欢

转载自blog.csdn.net/qq_38182125/article/details/87880081