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 按钮,就会在状态栏中看到相关通知:

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

相关推荐
长风清留扬29 分钟前
一篇文章了解何为 “大数据治理“ 理论与实践
大数据·数据库·面试·数据治理
Dnelic-32 分钟前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen3 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年10 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
周三有雨12 小时前
【面试题系列Vue07】Vuex是什么?使用Vuex的好处有哪些?
前端·vue.js·面试·typescript
爱米的前端小笔记12 小时前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
好学近乎知o12 小时前
解决sql字符串
面试
建群新人小猿13 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神14 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛14 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee