当前位置: 首页 > article >正文

Android——service使用详解

| name | 服务类的完全限定名 |

| permission | 设定组件必须具有的权限,得以启动服务或绑定服务。如果startService(),bindService()或stopService()的调用者没有被授予此权限,则该方法将不会工作,并且Intent对象不会传递到服务中 |

| process | 用来运行服务的进程的名称。通常,应用程序的所有组件都运行在应用程序创建的默认进程中,它与应用程序包名具有相同的名称。 < application >元素的process属性可以为所有组件设置不同的默认值,但组件可以使用自己的进程属性覆盖默认值,从而允许跨多个进程扩展应用程序 |

三、启动Service


启动服务由组件通过调用 startService() 启动,服务启动之后,其生命周期即独立于启动它的组件,并且可以在后台无限期地运行,即使启动服务的组件已被销毁也不受影响。因此,服务应通过调用 stopSelf() 来自行停止运行,或者由另一个组件调用 stopService() 来停止

可以通过扩展两个类来创建启动服务:

  • Service

这是所有服务的父类。扩展此类时,如果要执行耗时操作,必须创建一个用于执行操作的新线程,因为默认情况下服务将运行于UI线程

  • IntentService

这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果应用不需要同时处理多个请求,这是最好的选择。IntentService只需实现构造函数onHandleIntent() 方法即可,onHandleIntent()方法会接收每个启动请求的 Intent

3.1、继承Service

这里举一个音乐播放器的例子

继承Service类实现自定义Service,提供在后台播放音乐、暂停音乐、停止音乐的方法

public class MyService extends Service {

private final String TAG = “MyService”;

private MediaPlayer mediaPlayer;

private int startId;

public enum Control {

PLAY, PAUSE, STOP

}

public MyService() {

}

@Override

public void onCreate() {

if (mediaPlayer == null) {

mediaPlayer = MediaPlayer.create(this, R.raw.music);

mediaPlayer.setLooping(false);

}

Log.e(TAG, “onCreate”);

super.onCreate();

}

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

this.startId = startId;

Log.e(TAG, "onStartCommand—startId: " + startId);

Bundle bundle = intent.getExtras();

if (bundle != null) {

Control control = (Control) bundle.getSerializable(“Key”);

if (control != null) {

switch (control) {

case PLAY:

play();

break;

case PAUSE:

pause();

break;

case STOP:

stop();

break;

}

}

}

return super.onStartCommand(intent, flags, startId);

}

@Override

public void onDestroy() {

Log.e(TAG, “onDestroy”);

if (mediaPlayer != null) {

mediaPlayer.stop();

mediaPlayer.release();

}

super.onDestroy();

}

private void play() {

if (!mediaPlayer.isPlaying()) {

mediaPlayer.start();

}

}

private void pause() {

if (mediaPlayer != null && mediaPlayer.isPlaying()) {

mediaPlayer.pause();

}

}

private void stop() {

if (mediaPlayer != null) {

mediaPlayer.stop();

}

stopSelf(startId);

}

@Override

public IBinder onBind(Intent intent) {

Log.e(TAG, “onBind”);

throw new UnsupportedOperationException(“Not yet implemented”);

}

}

在布局中添加三个按钮,用于控制音乐播放、暂停与停止

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

}

public void playMusic(View view) {

Intent intent = new Intent(this, MyService.class);

Bundle bundle = new Bundle();

bundle.putSerializable(“Key”, MyService.Control.PLAY);

intent.putExtras(bundle);

startService(intent);

}

public void pauseMusic(View view) {

Intent intent = new Intent(this, MyService.class);

Bundle bundle = new Bundle();

bundle.putSerializable(“Key”, MyService.Control.PAUSE);

intent.putExtras(bundle);

startService(intent);

}

public void stopMusic(View view) {

Intent intent = new Intent(this, MyService.class);

Bundle bundle = new Bundle();

bundle.putSerializable(“Key”, MyService.Control.STOP);

intent.putExtras(bundle);

startService(intent);

//或者是直接如下调用

//Intent intent = new Intent(this, MyService.class);

//stopService(intent);

}

}

在清单文件中声明Service,为其添加label标签,便于在系统中识别Service

<service

android:name=“.MyService”

android:label=“@string/app_name” />

图片描述:

点击“播放音乐”按钮后,在后台将会运行着名为“Service测试”的服务

通过Log日志可以发现,多次点击“播放音乐”按钮,“onCreate()”方法只会在初始时调用一次,“onStartCommand(Intent intent, int flags, int startId)”方法会在每次点击时都被调用,点击“停止音乐”按钮,“onDestroy()”方法会被调用

当中,每次回调onStartCommand()方法时,参数“startId”的值都是递增的,startId用于唯一标识每次对Service发起的处理请求

如果服务同时处理多个 onStartCommand() 请求,则不应在处理完一个启动请求之后立即销毁服务,因为此时可能已经收到了新的启动请求,在第一个请求结束时停止服务会导致第二个请求被终止。为了避免这一问题,可以使用 stopSelf(int) 确保服务停止请求始终基于最新一次的启动请求。也就是说,如果调用 stopSelf(int) 方法的参数值与onStartCommand()接受到的最新的startId值不相符的话,stopSelf()方法就会失效,从而避免终止尚未处理的请求

如果服务没有提供绑定,则使用 startService() 传递的 Intent 是应用组件与服务之间唯一的通信模式。如果希望服务返回结果,则启动服务的客户端可以为广播创建一个 PendingIntent (使用 getBroadcast()),并通过启动服务的 Intent 传递给服务。然后,服务就可以使用广播传递结果

当中,onStartCommand() 方法必须返回一个整数,用于描述系统应该如何应对服务被杀死的情况,返回值必须是以下常量之一:

  • START_NOT_STICKY

如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务

  • START_STICKY

如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)

  • START_REDELIVER_INTENT

如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务

3.2、IntentService

由于大多数启动服务都不必同时处理多个请求,因此使用 IntentService 类实现服务也许是最好的选择

IntentService 执行以下操作:

  • 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent

  • 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样就不必担心多线程问题

  • 在处理完所有启动请求后停止服务,因此不必自己调用 stopSelf()方法

  • 提供 onBind() 的默认实现(返回 null)

  • 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent()

因此,只需实现构造函数与 onHandleIntent() 方法即可

这里举一个关于输出日志的例子

public class MyIntentService extends IntentService {

private final String TAG = “MyIntentService”;

public MyIntentService() {

super(“MyIntentService”);

}

@Override

protected void onHandleIntent(Intent intent) {

Bundle bundle = intent.getExtras();

if (bundle != null) {

for (int i = 0; i < 5; i++) {

try {

Thread.sleep(200);

} catch (InterruptedException e) {

e.printStackTrace();

}

Log.e(TAG, bundle.getString(“key”, “默认值”));

}

}

}

}

public class StartIntentServiceActivity extends AppCompatActivity {

private int i = 1;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_start_intent_service);

}

public void startService(View view) {

Intent intent = new Intent(this, MyIntentService.class);

Bundle bundle = new Bundle();

bundle.putString(“key”, “当前值:” + i++);

intent.putExtras(bundle);

startService(intent);

}

}

当中,startService(View view)方法与一个Button绑定,连续快速地多次点击Button,验证IntentService当中的日志是否依次输出,还是交叉着输出

可以看到是依次输出的,即IntentService的工作线程是逐一处理所有启动请求的

此外,查看后台,可以看到当前后台应用程序进程中有两个服务

四、绑定Service


应用组件(客户端)通过调用 bindService() 绑定到服务,绑定是异步的,系统随后调用服务的 onBind() 方法,该方法返回用于与服务交互的 IBinder。要接收 IBinder,客户端必须提供一个 ServiceConnection 实例用于监控与服务的连接,并将其传递给 bindService()。当 Android 系统创建了客户端与服务之间的连接时,会回调ServiceConnection 对象的**onServiceConnected()**方法,向客户端传递用来与服务通信的 IBinder

多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder 传递至其他绑定的客户端。当所有客户端都取消了与服务的绑定后,系统会将服务销毁(除非 startService() 也启动了该服务)

另外,只有 Activity、服务和内容提供者可以绑定到服务,无法从广播接收器绑定到服务

可以通过以下三种方法定义IBinder接口:

  • 扩展 Binder 类

如果服务是供本应用专用,并且运行在与客户端相同的进程中,则应通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建接口。客户端收到 Binder 后,可利用它直接访问 Service 中可用的公共方法

  • 使用 Messenger

如需让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口。服务可以这种方式定义对应于不同类型 Message 对象的 Handler。此 Handler 是 Messenger 的基础,后者随后可与客户端分享一个 IBinder,从而让客户端能利用 Message 对象向服务发送命令。此外,客户端还可定义自有 Messenger,以便服务回传消息。这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,这样就不必对服务进行线程安全设计

  • 使用 AIDL

AIDL(Android 接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行 IPC。 之前采用 Messenger 的方法实际上是以 AIDL 作为其底层结构。 如上所述,Messenger 会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。 不过,如果想让服务同时处理多个请求,则可直接使用 AIDL。 在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。如需直接使用 AIDL,必须创建一个定义编程接口的 .aidl 文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可在服务内对其进行扩展

4.1、绑定服务的具体步骤:

4.1.1、扩展 Binder 类

如果服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder 类,让客户端通过该类直接访问服务中的公共方法。此方法只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效

以下是具体的设置方法:

  • 在服务中创建一个可满足下列任一要求的 Binder 实例:

  • 包含客户端可调用的公共方法

  • 返回当前 Service 实例,其中包含客户端可调用的公共方法

  • 或返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法

  • 从 onBind() 回调方法返回此 Binder 实例

  • 在客户端中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定服务

4.1.2、实现 ServiceConnection接口

重写两个回调方法:

  • onServiceConnected()

系统会调用该方法以传递服务的onBind() 方法返回的 IBinder

  • onServiceDisconnected()

Android 系统会在与服务的连接意外中断时,例如当服务崩溃或被终止时调用该方法。当客户端取消绑定时,系统不会调用该方法

4.1.3、调用 bindService(),传递 ServiceConnection 对象

4.1.4、当系统调用了 onServiceConnected() 的回调方法时,就可以通过IBinder对象操作服务了

4.1.5、要断开与服务的连接需调用 unbindService()方法。如果应用在客户端仍处于绑定状态时销毁客户端,会导致客户端取消绑定,更好的做法是在客户端与服务交互完成后立即取消绑定客户端,这样可以关闭空闲服务

示例代码:

public class MyBindService extends Service {

private IBinder myBinder;

private Random mGenerator;

private final String TAG = “MyBindService”;

public class MyBinder extends Binder {

MyBindService getService() {

return MyBindService.this;

}

}

@Override

public void onCreate() {

Log.e(TAG, “onCreate”);

myBinder = new MyBinder();

mGenerator = new Random();

super.onCreate();

}

@Override

public IBinder onBind(Intent intent) {

Log.e(TAG, “onBind”);

return myBinder;

}

@Override

public void onDestroy() {

Log.e(TAG, “onDestroy”);

super.onDestroy();

}

@Override

public boolean onUnbind(Intent intent) {

Log.e(TAG, “onUnbind”);

return super.onUnbind(intent);

}

@Override

public void onRebind(Intent intent) {

Log.e(TAG, “onRebind”);

super.onRebind(intent);

}

public int getRandomNumber() {

return mGenerator.nextInt(100);

}

}

public class BindServiceActivity extends AppCompatActivity {

private MyBindService mService;

private boolean mBound = false;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_bind_service);

}

private ServiceConnection mConnection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

MyBindService.MyBinder binder = (MyBindService.MyBinder) service;

mService = binder.getService();

mBound = true;

}

@Override

public void onServiceDisconnected(ComponentName name) {

mBound = false;

}

};


http://www.kler.cn/a/301401.html

相关文章:

  • SQL MID() 函数详解
  • 蜀道山CTF<最高的山最长的河>出题记录
  • Redis知识分享(三)
  • Ubuntu 18.04 配置sources.list源文件(无法安全地用该源进行更新,所以默认禁用该源)
  • 学了Arcgis的水文分析——捕捉倾泻点,河流提取与河网分级,3D图层转要素失败的解决方法,测量学综合实习网站存着
  • 面试经典 150 题:20、2、228、122
  • 快速上手Spring Boot应用
  • Python语言开发学习之使用Python预测天气
  • 二十三种设计模式之建造者模式(类比汽车制造厂好理解一些)
  • sqlite3 相关知识
  • 嵌入式边缘计算:融合创新与未来展望
  • 有关WSL和docker的介绍
  • Qt-常用控件(3)-多元素控件、容器类控件和布局管理器
  • 在 ArkTS 中,如何有效地进行内存管理和避免内存泄漏?
  • Android12_13左上角状态栏数字时间显示右移动
  • 文档大模型,能否真正解决非结构化数据难题
  • 【kubernetes】配置管理中心Configmap运用
  • vue2实现歌曲播放和歌词滚动效果
  • 【鸿蒙】HarmonyOS NEXT星河入门到实战5-基础语法
  • 【F的领地】项目拆解:小学教辅资料
  • 海外云手机——跨国业务的高效工具
  • Python中如何判断一个变量是否为None
  • Ubuntu快速安装Python3
  • 【C++】——vector
  • 数据库的介绍:关系型数据库和非关系型数据库究竟是什么?
  • 基于vue框架的城市体育运动交流平台15s43(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。