Android四大组件之Service

什么是服务(Service)?

首先看看安卓官网的解释:
A Service is an application component representing either an application's desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use. Each service class must have a corresponding <service> declaration in its package's AndroidManifest.xml. Services can be started with Context.startService() andContext.bindService().
大概意思是说,服务是一个应用程序组件,表示应用程序希望在不与用户交互的情况下执行较长时间运行的操作,或者为其他应用程序提供功能。每个服务类必须在其包的AndroidManifest.xml中有对应的<service>标签声明。可以使用Context.startService()Context.bindService()启动服务。
需要注意的是,服务并不是运行在独立进程,而是运行在其宿主进程的主线程(即UI线程)中,所以我们如果要做一些譬如网络请求、文件IO等耗时操作时,应在Service中开启子线程。
有关多线程的使用请参考我的另一篇博文:Android多线程开发

怎么定义一个Service?

创建一个类譬如MyService,继承自Service,其中onBind()方法是Service中唯一的抽象方法,所以必须实现它。


另外要注意,Andriod四大组件都要在AndroidManifest.xml中注册,Service也不例外:

这里对MyService进行简单注册,其中enabled属性表示是否启用此服务,默认值是true,表示启用;exported表示此服务是否允许外部其他程序访问,如果我们没有指定它的值,其默认值由service中有无intent-filter决定,有intent-filter则默认值为true,否则为false。ps:当我们指定了其值为false,即使intent-filter匹配,也不能被外部程序调用。

使用服务

我们已经定义好一个服务了,怎么使用它呢?其实有两种方式,分别是‘’启动”型和“绑定型”。

  • 启动型

当应用组件(如 Activity)通过调用startService()启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁,也不影响服务运行,除非在应用组件中手动调用stopService()或在Service中调用stopSelf()才能停止服务。已启动的服务通常是执行单一操作,而且不会将结果返回给调用方,譬如我们在Activity中点了某个按钮启动Service,之后Service与Activity再无瓜葛,就好比Activity通知了服务一下:“你现在可以放飞自我了!”然后服务就飞走了,至于它飞哪去了,按什么航线飞的,Activity并不知情。代码片段如下:

        Intent intent = new Intent(this, MyService.class);
        switch (v.getId()) {
            case R.id.btn_start_service://开启服务
                startService(intent);
                break;
            case R.id.btn_stop_service://关闭服务
                stopService(intent);
                break;
  • 绑定型

当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后(通过unbindService()解绑),该服务即会被销毁。
来看看代码:首先在MyService中创建继承自Binder的内部类DownloadBinder,并定义两个方法分别模拟开启下载和获取下载进度。

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

        public int getDownloadProgress() {
            Log.e(TAG, "getDownloadProgress: ");
            return 0;
        }
    }

onBind方法返回一个DownloadBinder实例:

    /**
     * 绑定服务时才会被调用
     *
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind: ");
        return new DownloadBinder();
    }

至此MyService的工作就做完了。我们再来看看Activity中的修改:

            case R.id.btn_bind_service://绑定服务
                isBinded = true;
                bindService(intent, myServiceConnection, Context.BIND_AUTO_CREATE);
                break;
            case R.id.btn_unbind_service://解绑服务
                if (isBinded) {
                    unbindService(myServiceConnection);
                    isBinded = false;
                }
                break;

我们通过bindService()绑定服务,unbindService()解绑服务。其中“isBinded”变量用来判断服务是否解绑,myServiceConnection是ServiceConnection的一个实例,Context.BIND_AUTO_CREATE表示只要绑定存在,就会自动创建服务。
MyServiceConnection代码如下:

package com.android.basic.summary;


import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;

public class MyServiceConnection implements ServiceConnection {
    private static final String TAG = "MyServiceConnection";
    public MyService.DownloadBinder downloadBinder;

    /**
     * 绑定成功后该方法被执行。在该方法中,可以通过把IBinder对象向下转型,取得Service中onBind方法的返回值
     *
     * @param name
     * @param service
     */
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        binderAlive = service.isBinderAlive();
        downloadBinder = (MyService.DownloadBinder) service;
        downloadBinder.startDownload();
        downloadBinder.getDownloadProgress();
        Log.e(TAG, "Service Connection Success");
    }

    /**
     * 解绑后该方法被执行执行
     *
     * @param name
     */
    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.e(TAG, "Service Connection Filed");
    }
}

Service生命周期

Service生命周期示意图:

  • onCreate():首次创建服务时,系统将调用此方法。如果服务已在运行,则不会调用此方法,该方法只调用一次。
  • onStartCommand():当有组件通过调用startService()启动服务时,系统将调用此方法,且每次调用startService都会调用此方法。
  • onDestroy():当服务不再使用且将被销毁时,系统将调用此方法。
  • onBind():当有组件通过调用bindService()与服务绑定时,系统将调用此方法。
  • onUnbind():当有组件通过调用unbindService()与服务解绑时,系统将调用此方法。
  • onRebind():当旧的组件与服务解绑后,另一个新的组件与服务绑定,onUnbind()返回true时,系统将调用此方法。

PS:某一服务不管启动了多少次,我们只需要调用一次stopService或者stopSelf即可停止服务。

前台服务

后台服务在系统中的优先级是较低的,当内存不足时,可能会被系统杀死。那么如何让后台服务尽量不被杀死呢?基本的解决思路主要有以下几种:
1>既然后台服务优先级不高,那我们就提高它的优先级:

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"
            android:permission="@string/string_start_service">
            <!-- 为防止Service被系统回收,可以尝试通过提高服务的优先级解决,1000是最高优先级,数字越小,优先级越低 -->
            <intent-filter android:priority="1000">
                <action android:name="com.android.basic.summary.MyService" />
            </intent-filter>
        </service>

2>利用ANDROID的系统广播检查Service的运行状态,如果被杀掉,就再起来,系统广播是Intent.ACTION_TIME_TICK,这个广播每分钟发送一次,我们可以每分钟检查一次Service的运行状态,如果已经被结束了,就重新启动Service。代码示例:

    class TimeTickReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e(TAG, "onReceive: Intent.ACTION_TIME_TICK");
            if (!ServiceUtils.isServiceRunning(MyServiceActivity.this, "com.android.basic.summary.MyService")) {
                Intent intentStartService = new Intent(MyServiceActivity.this, MyService.class);
                startService(intentStartService);
            }
        }
    }

    private void registerBroadcast() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_TIME_TICK);
        timeTickReceiver = new TimeTickReceiver();
        //注册广播接收
        registerReceiver(timeTickReceiver, filter);
    }

 ServiceUtils.java:

package com.android.basic.summary.utils;

import java.util.List;

import android.app.ActivityManager;
import android.content.Context;
import android.util.Log;

public class ServiceUtils {
    private static final String TAG = "ServiceUtils";

    /**
     * 校验某个服务是否还存在
     */
    public static boolean isServiceRunning(Context context, String serviceName) {
        // 校验服务是否还存在
        ActivityManager am = (ActivityManager) context
                .getSystemService(Context.ACTIVITY_SERVICE);
        //关于AM的getRunningServices,Android官方解释是这样的:此方法不再适用于第三方应用程序。为了向后兼容,
        //它仍将返回调用者自己的服务。
        List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(100);
        for (ActivityManager.RunningServiceInfo info : services) {
            // 得到所有正在运行的服务的名称
            String name = info.service.getClassName();
            Log.e(TAG, "isServiceRunning: name == " + name);
            if (serviceName.equals(name)) {
                return true;
            }
        }
        return false;
    }
}

3>将服务改成前台服务foreground service,这一点是我要详细介绍的。先看代码:
在MyService中通过startForeground()即可将服务变成前台服务,其中第一参数是通知的ID,第二个参数是Notifaction对象。

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: ");
        startForeground(START_FOREGROUND_ID, getNotifaction());
    }

再来看看getNotifaction()方法:

    private Notification getNotifaction() {
        NotificationManager notificationManager;
        notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        Intent intent = new Intent(this, MyServiceActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.move_my_heart_by_xuan))
                .setContentText(getString(R.string.the_oath_of_love))
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.heart)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.heart))
                .setContentIntent(pendingIntent);
        //设置Notification的ChannelID,否则不能正常显示
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            builder.setChannelId(NOTIFICATION_ID);
        }
        Notification notification = builder.build();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(NOTIFICATION_ID, NOTIFICATION_NAME, NotificationManager.IMPORTANCE_HIGH);
            notificationManager.createNotificationChannel(channel);
        }
        return notification;
    }

注意:
1.在要开启的service中给notification添加 channelId,否则会出现如下错误:
android.app.RemoteServiceException: Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification: Notification......
2.Android 9.0及以后版本开启前台服务要在清单文件里声明FOREGROUND_SERVICE权限:

    <!-- Android 9 或更高版本并使用前台服务的应用必须请求 FOREGROUND_SERVICE 权限。 这是普通权限,因此,系统会自动为请求权限的应用授予此权限。 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

PS:前台服务开启后,会在系统通知栏展示一条类似于通知的条幅,有些产品经理很坏,非要让开发把这个通知隐藏掉,其实这个是Android平台的安全特性,在任何情况下,你都无法实现没有通知条幅的前台服务,这是因为前台服务消耗更多资源并且受到与后台服务不同的调度约束(即它没有被快速杀死),并且用户需要知道是什么应用可能正在消耗他们的电量,所以不要这样做。之前有人提高了偷偷开启服务的方案,方案在这里,但Android7.1及以上版本不再适用,我用8.0、9.0机器亲测,确实不能用。

IntentService

为了很方便地实现异步任务处理,并且在处理完任务后自动关闭服务,Android引入了IntentService,我在这里简单实现以下,代码示例如下:

package com.android.basic.summary.service;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;

public class MyIntentService extends IntentService {
    private static final String TAG = "MyIntentService";

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.e(TAG, "onHandleIntent: Thread id is " + Thread.currentThread().getId());
    }

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

首先创建一个继承自IntentService的类,然后提供一个无参构造方法,并在其中调用父类有参构造;需要实现onHandleIntent()方法,我们要做的耗时操作就放在这个方法中进行 ,为了后面验证此方法处于子线程中,我在这里打印它的线程ID。然后我们在MyServiceActivity中启动服务:

            case R.id.btn_start_intent_service://开启IntentService
                Log.e(TAG, "onClick: MainThread id is " + Thread.currentThread().getId());
                Intent intentService = new Intent(this, MyIntentService.class);
                startService(intentService);
                break;

运行项目后开启服务,有如下日志打印:

第二行日志说明了onHandleIntent确实运行在子线程中;第三行日志说明IntentService在执行完任务后就“自杀了”~
至此,关于Android中Service的使用就告一段落,后面遇到Service相关知识点再做补充,欢迎大家评论区留言与我交流~
另外,本博文源码地址:https://github.com/ZNKForSky/AndroidBasicSummary 欢迎大家start~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值