Android开发之低调的Service

2023-12-27 10:50
文章标签 android 开发 service 低调

本文主要是介绍Android开发之低调的Service,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

锲而舍之,朽木不折;锲而不舍,金石可镂。——荀况

    今天学习了一下Service的用法就和大家一起来讨论Android中Service的相关知识点,如有谬误,欢迎批评指正,如有疑问欢迎留言。

一、Service用途

     Service在Android中和Activity是属于同一级别上的组件,Android中的Service,其意思是“服务”,它在后台运行不可交互。Service自己不能运行,需要通过某一个Activity或者其它Context对象来调用,Context.startService()和Context.bindService()两种方式启动 Service 。
       Android 中的服务,它与 Activity不同,它是不能与用户交互的,不能自己启动的,运行在后台的程序(干着重的工作,却连个界面也没有,因此我说它低调),那我们什么时候会用到Service呢?比如我们播放音乐的时候,有可能想边听音乐边干些其他事情,当我们退出播放音乐的应用,如果不用 Service,我们就听不到歌了,所以这时候就得用到Service了,又比如当我们一个应用的数据是通过网络获取的,不同时间(一段时间)的数据是不同的,这时候我们可以用 Service在后台定时更新,而不用每打开应用的时候在去获取。如果在 Service的onCreate或者 onStart方法中做一些很耗时的动作,最好是启动一个新线程来运行这个 Service,因为,如果 Service运行在主线程中,会影响到程序的 UI操作或者阻塞主线程中的其它事情。 

二、Service的生命周期

首先来看官网给出的Service的生命周期图


1.onCreate() 创建Service
2.onStart(Intent intent, int startId) 启动Service
3.onStartCommand(Intent intent, int flags, int startId)启动Service
4.onDestroy() 销毁Service
5.onBind() 返回一个IBinder接口对象给Service

从这个生命周期图中我们可以看到有两种启动Service的方法Context.startService和Context.bindService,对应的生命周期也分为两种情况:
(1)startService启动模式
从图中可以看出当我们采用Context.startService(intent)这种方式时,系统会实例化一个服务,依次调用onCreate()和onStartCommand()方法,之后服务就进入了运行状态,如果服务已经运行了我们再次启动时不会重新创建服务,系统会自动调用刚才我们启动的服务,并调用其onStart()方法,如果我们想销毁一个服务可以使用stopService(intent)方法,使用stopService()方法会调用onDestroy()方法此时服务就被销毁了,

(2)bindService启动模式
在这种模式下,调用bindService(Intent service, ServiceConnection conn, int flags)来绑定一个Service,这时Service会调用自身的onCreate()方法(前提是该Service未创建),系统会实例化一个服务,接着调用onBind(intent)方法调用onBind方法后调用者就可以和服务进行交互了,当我们采用bindService这种方法创建服务时,如果已经创建好了一个如果再进行创建,系统不会创建新的Service实例,也不会调用onBind方法,这种方式启动的服务的销毁方法是使用unbindService方法,此时onUnbind方法和onDestroy方法都会被调用。
关于bindService(Intent service, ServiceConnection conn, int flags)参数的说明
参数①service: Intent 对象
参数②conn:  ServiceConnection对象,实现其onServiceConnected()和onServiceDisconnected()在连接成功和断开连接时处理。后面有实例来进行说明
参数③flags:Service创建的方式,一般用Service.BIND_AUTO_CREATE表示绑定时自动创建。

可能有的人会问这两种启动模式有什么不同?
strarService和bindService的不同之处:startService模式下调用者与服务无必然联系,即使调用者结束了自己的生命周期,只要没有使用stopService方法停止这个服务,服务仍会运行;然而通常情况下,bindService模式下服务是与调用者同生共死的,在绑定结束之后,一旦调用者被销毁,服务就会终止,我们通常用一句话来形容bindService:不求同生,但求同死。

另外需要注意的是在Android2.0之前我们使用startService启动服务时都是习惯重写onStart方法,在Android2.0时系统引进了onStartCommand方法取代了onStart方法,但是为了兼容以前的程序,在onStartCommand方法中其实是调用了onStart方法,我们之后会做验证,不过我们最好还是重写onStartCommand方法。

小平同志曾经说过,实践是检验真理的唯一标准,下面我们就结合实例对上面的理论来做验证,以便加深印象
布局如下:


代码如下:
1.Service的代码
package com.example.servicepractice;import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;public class MyService extends Service {private static final String TAG = "MyService";@Overridepublic void onCreate() {super.onCreate();Log. i(TAG,"onCreate called" );}@Overridepublic void onStart(Intent intent, int startId) {super. onStart(intent, startId);Log. i(TAG,"onStart called" );}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log. i(TAG,"onStartCommand called" );return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();Log. i(TAG,"onDestroy called" );}@Overridepublic IBinder onBind(Intent intent) {Log. i(TAG,"onBind called" );return null;}@Overridepublic boolean onUnbind(Intent intent) {Log. i(TAG,"onUnbind called" );return super.onUnbind(intent);}
}
2.MainActivity的代码
package com.example.servicepractice;import android.os.Bundle;
import android.os.IBinder;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;public class MainActivity extends Activity {protected static final String TAG = "MyService";private Button btn_start;private Button btn_stop;private Button btn_bind;private Button btn_unbind;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout. activity_main);findViews();setClick();}private void findViews() {btn_start=(Button) findViewById(R.id. btn_start);btn_stop=(Button) findViewById(R.id. btn_stop);btn_bind=(Button) findViewById(R.id. btn_bind);btn_unbind=(Button) findViewById(R.id. btn_unbind);}private void setClick() {//采用startService启动服务btn_start.setOnClickListener( new OnClickListener() {public void onClick(View v) {Intent intent= new Intent(MainActivity.this,MyService.class );startService(intent);}});//销毁服务btn_stop.setOnClickListener( new OnClickListener() {public void onClick(View v) {Intent intent= new Intent(MainActivity.this,MyService.class );stopService(intent);}});//绑定服务btn_bind.setOnClickListener( new OnClickListener() {public void onClick(View v) {Intent intent = new Intent(MainActivity.this,MyService.class );bindService(intent, conn,Context. BIND_AUTO_CREATE);}});//解除绑定btn_unbind.setOnClickListener( new OnClickListener() {public void onClick(View v) {unbindService( conn);}});}private ServiceConnection conn=new ServiceConnection() {public void onServiceConnected(ComponentName name, IBinder service) {//connectedLog. i(TAG,"onServiceConnection called." );}public void onServiceDisconnected(ComponentName name) {} };   
}

因为服务是四大组件之一所以我们要在清单文件中配置,注意:如果启动的服务没有在清单文件中配置并不会报错(这种错误很难发现,切记要配置),只是启动时不启动这个服务,所以在开发时一定要注意养成一个好的习惯--->对于四大组件一定要声明好之后就去配置否则 如果忘记了还得花好长时间去寻找错误
配置代码如下:

   <serviceandroid:name="com.example.servicepractice.MyService" ><intent-filter><action android:name="com.example.service" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></service>

如果我们的服务只在本应用中调用可以去掉<intent-filter>这个属性是其他应用调用本应用中的服务时所配置的属性
从布局中我们可以看到一共有四个按钮我们首先来看前面两个按钮
1、"启动服务"和"销毁服务"学习
            //采用startService启动服务btn_start.setOnClickListener( new OnClickListener() {public void onClick(View v) {Intent intent= new Intent(MainActivity.this,MyService.class );startService(intent);}});//销毁服务btn_stop.setOnClickListener( new OnClickListener() {public void onClick(View v) {Intent intent=new Intent(MainActivity.this,MyService. class);stopService(intent);}});
首先我们点击启动按钮打印日志如下


我们发现它调用的方法的顺序是onCreate->onStartCommand->onStart
然后我们接着点击启动服务按钮打印结果如下

我们发现再次启动时系统并没有重新实例化这个Service,因为系统发现这个服务已经启动了,此时它会直接调用onStartCommand方法,在onStartCommand方法中会调用onStart方法

onStartCommand方法的源码:

     /* @param intent The Intent supplied to {@link android.content.Context#startService},* as given.  This may be null if the service is being restarted after* its process has gone away, and it had previously returned anything* except {@link #START_STICKY_COMPATIBILITY}.* @param flags Additional data about this start request.  Currently either* 0, {@link #START_FLAG_REDELIVERY}, or {@link #START_FLAG_RETRY}.* @param startId A unique integer representing this specific request to* start.  Use with {@link #stopSelfResult(int)}.** @return The return value indicates what semantics the system should* use for the service's current started state.  It may be one of the* constants associated with the {@link #START_CONTINUATION_MASK} bits.** @see #stopSelfResult(int)*/public int onStartCommand(Intent intent, int flags, int startId) {onStart(intent , startId);return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;}
操作完启动服务按钮后我们接着点击销毁服务按钮打印日志如下

发现当调用 stopService(intent)这个方法是会调用Service的onDestroy方法从而销毁服务。

2."绑定服务"和"解除绑定"的学习

          //绑定服务btn_bind.setOnClickListener( new OnClickListener() {public void onClick(View v) {Intent intent = new Intent(MainActivity.this,MyService.class );bindService(intent, conn,Context. BIND_AUTO_CREATE);}});//解除绑定btn_unbind.setOnClickListener( new OnClickListener() {public void onClick(View v) {unbindService( conn);}});
通过上面的 bindService(intent,conn,Context. BIND_AUTO_CREATE)这个方法我们发现通过需要一个ServiceConnection对象,ServiceConnection代表与服务的连接,它只有两个方法,onServiceConnected和onServiceDisconnected,前者是在操作者在连接一个服务成功时被调用,而后者是在服务崩溃或被杀死导致的连接中断时被调用,而如果我们自己解除绑定时则不会被调用,所以我们这里只研究onServiceConnected这个方法。
我们首先来看下ServiceConnection的代码
  private ServiceConnection conn= new ServiceConnection() {/*** Called when a connection to the Service has been established, with* the {@link android.os.IBinder} of the communication channel to the* Service.** @param name The concrete component name of the service that has* been connected.** @param service The IBinder of the Service's communication channel,* which you can now make calls on.*/public void onServiceConnected(ComponentName name, IBinder service) {//connectedLog. i( TAG,"onServiceConnection called." );}/*** Called when a connection to the Service has been lost.  This typically* happens when the process hosting the service has crashed or been killed.* This does <em>not </em> remove the ServiceConnection itself -- this* binding to the service will remain active, and you will receive a call* to {@link #onServiceConnected} when the Service is next running.** @param name The concrete component name of the service whose* connection has been lost.*/public void onServiceDisconnected(ComponentName name) {}
};
到这我们还差一步就可以绑定服务了,因为在前面服务中的onBind方法返回值为null,这样是不行的,要想实现绑定操作,必须返回一个实现了IBinder接口类型的实例,该接口描述了与远程对象进行交互的抽象协议,有了它我们才能与服务进行交互。所以我们要修改代码
     @Overridepublic IBinder onBind(Intent intent) {Log. i(TAG,"onBind called" );return new Binder(){};}
上面的代码中返回了一个Binder的实例,而这个Binder恰恰是实现了IBinder接口,所以这样就可以实现绑定服务的操作了

首先我们点击一下”绑定服务“按钮打印日志如下
我们发现onCreate方法、onBind方法和onServiceConnection方法被调用了,此时服务已经进入运行状态,如果此时我们再次点击”绑定服务“按钮,这三个方法都不会被调用然后点击”解除绑定“按钮打印日志如下
可以看到onUnbind方法和onDestroy方法被调用了,此时Service已经被销毁,整个生命周期结束。
由于bindService启动的服务是和应用绑定到一起的所以当MainActivity退出程序时,服务也会随之结束。
下面我们来说一种情况当我们点击"绑定服务"按钮之后我们点击两次“解除绑定”按钮会发现程序崩溃了,日志如下
还有一种情况,当我们点击“绑定服务”按钮后此时服务已经运行了,此时我们直接按“返回”键程序退出了,我们看到日志会报一个错
我们要解决这两种问题,我们可以定义一个变量,然后在Activity的onDestroy方法中解除绑定就ok了,也就是做如下修改  
     //定义一个变量,标识这个服务是否处于绑定状态private boolean binded; //定义一个绑定服务的方法private void unbindService(){if( binded){unbindService( conn);binded= false;}}//解除绑定btn_unbind.setOnClickListener( new OnClickListener() {public void onClick(View v) {unbindService();}});//在onDestroy方法中调用解除绑定的方法protected void onDestroy() {unbindService();};
 这样上面两种错误就解决了,也体现了我们写代码的严谨性。 

3.以上两种启动方法混合使用的学习
在上面我们讨论的都是一对相匹配的启动和销毁方式,可能有的人会问,那我这四个按钮混合着点击会有什么效果呢,接着我们来验证一下
首先我们点击"启动服务"按钮打印日志如下
这个和我们前面讨论的匹配没有问题,接着我们按下绑定服务按钮打印日志如下

调用了onBind方法接着我们点击解除绑定按钮日志如下

此时调用了onUbind方法,值得注意的是此时服务虽然解除了但是没有终止,而是继续运行,这时我们再次点击绑定服务按钮和解除服务按钮发现onbind和onUnbind方法都不会被调用,那么是不是没有绑定成功呢?答案是虽然没有调用onBind方法但是还是绑定成功了,我们可以从如下日志验证


但是我们怎么销毁掉这个服务呢?答案是:如果我们同时使用startService与bindService,Service终止需要unbindService与stopService同时调用,才能终止Service,不管startService与bindServicede的调用顺序,如果先调用unbindService此时服务不会自动终止,再调用stopService之后服务才会停止,如果先调用stopService此时服务也不会终止,而再调用unbindService或者之前调用bindService的context不在了(如Activity被finish的时候)之后服务才会自动停止;

总结+特别注意:

1、在调用 bindService 绑定到Service的时候,你就应当保证在某处调用 unbindService 解除绑定(尽管 Activity 被 finish 的时候绑定会自动解除,并且Service会自动停止);

2、在使用 startService 启动服务之后,一定要使用 stopService停止服务,不管你是否使用bindService;

3、同时使用 startService 与 bindService 要注意到,Service 的终止,需要unbindService与stopService同时调用,才能终止 Service,不管 startService 与 bindService 的调用顺序,如果先调用 unbindService 此时服务不会自动终止,再调用 stopService 之后服务才会停止,如果先调用 stopService 此时服务也不会终止,而再调用 unbindService 或者 之前调用 bindService 的 Context 不存在了(如Activity 被 finish 的时候)之后服务才会自动停止;

4、当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了),对应服务的生命周期与上述相同。

5、在 sdk 2.0 及其以后的版本中,对应的 onStart 已经被否决变为了 onStartCommand,不过之前的 onStart 任然有效。这意味着,如果你开发的应用程序用的 sdk 为 2.0 及其以后的版本,那么你应当使用 onStartCommand 而不是 onStart。

6、startService 启动服务想要用startService启动服务,不管Local (本地)还是 Remote(远程) 我们需要做的工作都是一样简单。当然要记得在Androidmanifest.xml 中注册 service。

好了这一篇就到这里了,欢迎大家留言交流,批评指正。






这篇关于Android开发之低调的Service的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/542878

相关文章

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做

解决systemctl reload nginx重启Nginx服务报错:Job for nginx.service invalid问题

《解决systemctlreloadnginx重启Nginx服务报错:Jobfornginx.serviceinvalid问题》文章描述了通过`systemctlstatusnginx.se... 目录systemctl reload nginx重启Nginx服务报错:Job for nginx.javas

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C#图表开发之Chart详解

《C#图表开发之Chart详解》C#中的Chart控件用于开发图表功能,具有Series和ChartArea两个重要属性,Series属性是SeriesCollection类型,包含多个Series对... 目录OverviChina编程ewSeries类总结OverviewC#中,开发图表功能的控件是Char

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

鸿蒙开发搭建flutter适配的开发环境

《鸿蒙开发搭建flutter适配的开发环境》文章详细介绍了在Windows系统上如何创建和运行鸿蒙Flutter项目,包括使用flutterdoctor检测环境、创建项目、编译HAP包以及在真机上运... 目录环境搭建创建运行项目打包项目总结环境搭建1.安装 DevEco Studio NEXT IDE

Python开发围棋游戏的实例代码(实现全部功能)

《Python开发围棋游戏的实例代码(实现全部功能)》围棋是一种古老而复杂的策略棋类游戏,起源于中国,已有超过2500年的历史,本文介绍了如何用Python开发一个简单的围棋游戏,实例代码涵盖了游戏的... 目录1. 围棋游戏概述1.1 游戏规则1.2 游戏设计思路2. 环境准备3. 创建棋盘3.1 棋盘类