Android Service 使用详解

1. Service 是什么?

服务(Service) 是 Android 的四大组件之一,是 Android 中实现程序后台运行的解决方案,适合去执行那些不需要和用户交互而且还要求长期运行的任务。

Service 的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,Service 仍然能够保持正常运行。

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

Service 并不会自动开启线程,所有代码都是默认运行在主线程中 。我们需要在服务内部手动创建子线程,并在这里执行具体的任务。

2. Service 的基本用法

2.1 定义一个 Service

如下图,如同创建 Activity 一样创建一个 Service:

有两个属性:

  • Exported:表示是否将这个 Service 暴露给外部其他程序访问。
  • Enabled:表示是否启用这个 Service。

创建之后的 MyService 继承 Service

java 复制代码
// Android API 34
public class MyService extends Service {  
    public MyService() {  
    }  
  
    @Override  
    public IBinder onBind(Intent intent) {  
        // TODO: Return the communication channel to the service.  
        throw new UnsupportedOperationException("Not yet implemented");  
    }  
}

onBind()Service 类中唯一的抽象方法:

java 复制代码
// Service.java
@Nullable  
public abstract IBinder onBind(Intent intent);

定义一个 Service 不仅实现 onBind() 方法,一般还会实现 onCreate()onStartCommend()onDestroy()

java 复制代码
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) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }
}
  1. onCreate() :服务第一次创建时调用。
  2. onStartCommend() :每次启动服务时调用。服务一旦启动就立即去执行某个操作的代码逻辑现在这里。
  3. onDestroy() :服务销毁时调用。回收不再使用的资源。

Android 四大组件在 AndroidManifest.xml 中进行注册才能生效:

java 复制代码
// AndroidManifest.xml
<service  
    android:name=".MyService"  
    android:enabled="true"  
    android:exported="true" />

2.2 startService() 显式开启服务

主要通过 Intent 去完成服务的启动和停止。

开启 Service 的两种方式:显示开启绑定开启

Activity 中可以直接调用 startService()stopService() 这两个方法显示开启和停止服务:

java 复制代码
if (id == R.id.start_service) {  
    Intent startIntent = new Intent(this, MyService.class);  
    startService(startIntent);// 通过 startService() 方法启动 MyService 服务  
} else if (id == R.id.stop_service) {  
    Intent stopIntent = new Intent(this, MyService.class);  
    stopService(stopIntent);// 由 Activity 来决定服务何时停止。若想服务自己停止,在 MyService 调用stopSelf()方法  
}

点击开启服务按钮开启服务之后,在手机设置中正在运行的服务中会看见我们开启的服务,也可以通过 log 看看服务是否开启,停止服务同理:

这里,完全是 Activity 决定服务何时停止的,如果没有点击按钮,Service 会一直处于运行状态。

如果想让 Service 自己停下来,只需要在 MyService 任何一个位置调用 stopSelf()

显示开启服务流程:

2.3 绑定开启

通过 startService() 方法显示开启 Service 后,调用者(Activity)就和 service 没有关联了。

这里一个很重要的问题:Activity 无法拿到 Service引用

举个例子,如何在 MyService 中提供一个下载功能,并且在 Activity 中可以决定何时开始下载,以及查看下载进度呢?

通过 bindService() 方法绑定开 启服务,可以借助 onBind() 方法,创建一个专门的 Binder 对象来对下载功能进行管理。

新建 DownloadBinder 继承自 Binder,并在其内部提供开始下载和查看进度方法:

java 复制代码
public class MyService extends Service {

    private DownloadBinder mDownloadBinder = new DownloadBinder();

    public class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d("MyService", "Start");
        }
        public int getProgress(){
            Log.d("MyService","getProgress");
            return 0;
        }
        public void stopDownload(){
            Log.d("MyService", "Stop");
        }

    }

    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) {
        // TODO: Return the communication channel to the service.
        return mDownloadBinder;
    }
}

通过 startService() 开启 Service 时,我们重写了 onBind(),直接返回的是null,此时该方法并没有调用。

第31~34行,当通过 bindService() 开启 Service 时,需要返回 IBiner 的引用给绑定者使用。

返回的是 DownloadBinder 对象的引用,该对象持有了 MyService 引用。绑定者又是在哪里接收 IBinder 的引用呢?

在 Activity 中定义的 ServiceConnection 匿名内部类:

创建一个 ServiceConnection 的匿名类,重写了 onServiceConnected()onServiceDisconnected(),这两个方法分别在 Activity 和 Service 成功绑定以及连接断开时时调用:

java 复制代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private MyService.DownloadBinder downloadBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        if (id == R.id.start_service) {
           ...
        } else if (id == R.id.stop_service) {
            ...
        } else if (id == R.id.bind_service) {
            Intent bindIntent = new Intent(this, MyService.class);
            // service 和 activity 绑定后自动创建服务 BIND_AUTO_CREATE,这会使得 MyService 中 OnCreate() 方法得到执行
            bindService(bindIntent,connection,BIND_AUTO_CREATE);
        } else if (id == R.id.unbind_service) {
            unbindService(connection);
        }
    }

   
    private ServiceConnection connection = new ServiceConnection() {
        // service 和 activity 绑定时调用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // service 就是从 onBind() 方法返回的
            // 向下转型得到 DownloadBinder 的实例
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        // Service 被销毁时调用,内存不足等。
        }
    };
}

有了 ServiceConnection 引用,接着就需要和 Service 建立联系,建立联系的过程即是绑定开启 Service 的过程:

java 复制代码
private void bindService() {
    Intent intent = new Intent(this, MyService.class);
    bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}

从上面可以看出,绑定开启的流程:

  1. 新建 DownloadBinder 类持有 Service 对象。
  2. 在 Service 的 onBind() 方法中返回 IBinder 引用(mDownloadBinder),该引用持有 Service 引用。
  3. Activity 调用 bindService() 方法成功后,会调用 ServiceConnection 的 onServiceConnected() 方法,Service 和 ServiceConnection 也会建立联系,onServiceConnected() 方法会返回 IBinder 引用(mDownloadBinder)。
  4. Activity 通过 IBinder 引用(mDownloadBinder)拿到 Service 引用,进而操作 Service。

以上回答了上面的问题:绑定者(Activity)如何拿到 Service 引用。

那么如何进行解绑呢?

手动调用 unbindService(serviceConnection) 方法进行解绑,解绑时也需要传入 ServiceConnection 引用,不然无法确定解绑哪个 Service。

手动调用该方法即可解绑 Service。 当 Activity 通过 bindService() 绑定开启 Service 后,若是 Activity 销毁了,那么相应的 Service 也会被销毁掉。

值得注意的是:

  1. 若是 startService() 开启 Service,则无法用 unbindService() 方法关闭 Service。
  2. 若是 bindService() 开启 Service,则无法用 stopService() 关闭 Service。
  3. 既调用了 startService() 方法,又调用了 bindService() 方法的 Service ,这种情况下要同时调用 stopService()unbindService() 方法,onDestroy() 方法才会执行。

绑定开启服务的流程:

3. 前台服务

系统内存不足时可能会回收服务。若想要服务一直保持运行状态,可以考虑使用前台服务,避免服务被回收。

前台服务会一直有一个正在运行的图标在系统的状态栏显示,类似于通知的效果。相当于 Service 以这种形式在前台运行。

Android 9.0开始,需进行权限声明:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

java 复制代码
// MyService.java
@Override
public void onCreate() {
    super.onCreate();
    Log.d("MyService","onCreate executed");
    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        NotificationChannel notificationChannel = new NotificationChannel("my_service","前台Service通知 ", NotificationManager.IMPORTANCE_DEFAULT);
        notificationManager.createNotificationChannel(notificationChannel);
    }
    Intent intent = new Intent(this, MyServiceActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
    Notification notification =  new NotificationCompat.Builder(this,"my_service")
            .setContentTitle("This is content title")
            .setContentText("真正的平静,不是避开车马喧嚣,而是在心中修篱种菊。尽管如流往事,每一天都涛声依旧,只要我们消除执念,便可寂静安然。愿每个人,在纷呈世相中不会迷失荒径,可以端坐磐石上,醉倒落花前。")
            .setWhen(System.currentTimeMillis())
            .setSmallIcon(R.drawable.a)
            .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.a))
            .setContentIntent(pi)
            .build();
    startForeground(3,notification);
}

第5行,NotificationManager 类用于向用户展示通知,对通知进行管理。

第7行,NotificationChannel 类用于构建一个通知渠道,并使用 NotificationManager 的 createNotificationChannel() 方法进行创建。

通知渠道(Notification Channels),也被称为通知类别,是Android 8.0(API 级别 26)引入的一个功能,它为开发者提供了更好的管理通知的方式,也使用户能够有更好地控制他们想要接收的通知类型。

NotificationChannel 类和 createNotificationChannel() 方法都是 Android 8.0 系统中新增的 API,因此我们在使用的时候进行版本判断。

创建一个通知渠道至少需要渠道ID渠道名称 以及重要等级这三个参数:

  • 渠道ID:可以随便定义,只要保证全局唯一性就可以。
  • 渠道名称:给用户看的,需清楚地表达这个渠道的用途。
  • 重要等级 :主要有 IMPORTANCE_HIGHIMPORTANCE_DEFAULTIMPORTANCE_LOWIMPORTANCE_MIN。用户可以随时手动更改某个通知渠道的重要等级。

第12行,使用 NotificationCompat Builder 对 Notification 进行构造。由于通知 API 的不稳定性,使用 NotificationCompat 类对通知相关操作进行兼容实现。

NotificationCompat.Builder(this,"my_service") 第二个参数为渠道ID,需要与创建时的 ID 保持一致。

第11行,PendingIntent 可以在某个合适的时机执行某个动作,可以理解为延迟执行的Intent。通过第18行设置,在点击状态栏中的通知时会进行跳转。

运行程序,点击 START SERVICE 按钮,就会在状态栏中看到相关通知:

若没有出现,可能通知权限还未开启:

相关推荐
帅得不敢出门1 分钟前
Android Framework预装traceroute执行文件到system/bin下
android
xzkyd outpaper3 分钟前
从面试角度回答Android中ContentProvider启动原理
android·面试·计算机八股
编程乐学9 分钟前
基于Android 开发完成的购物商城App--前后端分离项目
android·android studio·springboot·前后端分离·大作业·购物商城
yours_Gabriel3 小时前
【java面试】微服务篇
java·微服务·中间件·面试·kafka·rabbitmq
这个家伙很笨5 小时前
了解Android studio 初学者零基础推荐(4)
android·ide·android studio
alexhilton6 小时前
在Android应用中实战Repository模式
android·kotlin·android jetpack
天涯学馆7 小时前
工厂模式在 JavaScript 中的深度应用
前端·javascript·面试
巛、7 小时前
ES6面试题
前端·面试·es6
汪子熙8 小时前
走进 Fundamental NGX Platform:从 SAP 设计体系到高生产力组件层
前端·javascript·面试