Android学习之服务初体验
•概念
Service(服务)是一个长期运行在后台,没有用户界面的应用组件,即使切换到另一个应用程序或者后台,服务也可以正常运行;
因此,服务适合执行一些不需要显示界面的后台耗时操作,比如下载网络数据,播放音乐等。
•定义一个服务
新建一个 ServiceTest 项目,然后右击 com.example.servicetest->New->Service->Service ;
会弹出如下图所示的窗口:
可以看到,这里我们将服务命名为 MyService(由于我之前创建过,所以左下角提示名字重复):
exported :表示是否允许除了当前程序之外的其他程序访问这个服务
- 如果设置为 true,则能够被调用或交互,通常如果一个服务需要跨进程使用需要这么设置,否则不能
- 设置为 false 时,只有同一个应用程序的组件或带有相同用户 ID 的应用程序才能启动或绑定该服务
enabled :指该服务是否能够被实例化
- 如果设置为 true,则能够被实例化,否则不能被实例化,默认值是 true
- 一般情况下,我们都会需要实例化,所以也可以选择不设置
需要注意的是,每一个服务都需要在 AndroidManifest.xml 文件中进行注册,
因为我们是通过 Android Studio New 的 Service ,
所以 Android Studio 默认帮我们在清单文件中添加对该 Service 的注册;
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.servicetest"> <application ......> <service android:name=".MyService" android:enabled="true" android:exported="true" /> <activity android:name=".MainActivity"> ...... </activity> </application> </manifest>现在观察 MyService 中的代码,如下所示:
public class MyService2 extends Service { public MyService2() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } }可以看到,MyService 是继承自 Service 类的,说明这是一个服务;
目前,MyService 中可以算是空空如也,但有一个 onBind() 方法特别醒目,
这个方法是 Service 中唯一的一个抽象方法,所以必须要在子类里实现,后面会提及该方法的用法;
既然是定义一个服务,自然应该在服务中去处理一些事情,这时就要重写 Service 中的另外一些方法了;
MyService.java
public class MyService extends Service { public final static String TAG = "MyService"; public MyService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate: "); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand: "); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy: "); } }可以看到,在该代码中重写了 onCreate(),onStartCommand() , onDestroy() 方法,并分别在它们的方法体中打印Log日志;
•启动和停止服务
启动和停止服务主要是借助 Intent 来实现的,下面就让我们在 ServiceTest 项目中尝试去启动以及停止 MyService;
首先修改 activity_main.xml 中的代码;
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="10dp"> <Button android:id="@+id/start_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start Service" android:textAllCaps="false" /> <Button android:id="@+id/stop_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Stop Service" android:textAllCaps="false" /> </LinearLayout>在该代码中,仅仅添加了两个按钮,分别是用于启动服务和停止服务的;
然后,修改 MainActivity.java 中的代码;
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private Button startService; private Button stopService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startService = findViewById(R.id.start_service); startService.setOnClickListener(this); stopService = findViewById(R.id.stop_service); stopService.setOnClickListener(this); } @Override public void onClick(View v) { Intent intent = new Intent(this,MyService.class); switch(v.getId()){ case R.id.start_service: startService(intent); break; case R.id.stop_service: stopService(intent); break; default: break; } } }可以看到,这里在 onCreate() 方法中分别获取到了 startService 按钮 和 stopService 按钮 的实例,
并给他们注册了点击事件;
然后在点击事件里构建出了一个 Intent 对象,并通过调用 startService(intent) 方法来启动 MyService 服务,
调用 stopService(intent) 来停止 MyService 服务;
startService() 和 stopService() 方法都是定义在 Context 类中的,所以我们在活动里可以直接调用这两个方法。
运行效果
通过观察日志文件可以看到:
- onCreate() :服务第一次被创建的时候调用,只执行一次
- onStartCommand() :每次服务启动的时候调用
- 在第二次点击 Start Service 按钮的时候,只打印了 onStartCommand: 语句
- 如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在 onStartCommand() 中
- onDestory() :服务被销毁的时候调用,只执行一次
需要注意的是,如果在活动中只点击了 startService 按钮,而没有点击 stopService 按钮,那么服务会一直处于运行状态,
如果想要 MyService 服务停止运行,除了点击 stopService 按钮外,
还可以通过在 MyService.java 中任何一个方法中调用 stopSelf() 方法,即可将该服务停止;
例如,我在 onStartCommand() 中调用 stopSelf() 方法,观察 Logcat 的打印语句;
运行效果
果不其然,打印完 onStartCommand: 语句后紧跟着打印了 onDestroy: 语句。
•活动和服务进行通信
在上面的介绍中,我们只是实现了在 Activity 中开启和关闭一个服务,
然而服务开启后就和这个 Activity 没什么联系了;
其实二者是可以继续保持联系的,还记得前面提到的一个 onBind() 方法吧,其实它就是 Service 与 Activity 之间建立通信的桥梁;
比如说,我们希望 MyService 里提供一个下载功能,然后在活动中可以决定何时开始下载,以及随时查看下载进度;
实现这个功能的思路是创建一个专门的 Binder 对象来对下载功能进行管理;
修改 MyService.java 中的代码;
MyService.java
public class MyService extends Service { public static final String TAG = "MyService"; private DownloadBinder mBinder = new DownloadBinder(); class DownloadBinder extends Binder { public void startDownload(){//开始下载 Log.d(TAG, "startDownload: "); } public int getProgress(){//获取下载进度 Log.d(TAG, "getProgress: "); return 0; } } @Override public IBinder onBind(Intent intent) { return mBinder; } public MyService() {} @Override public void onCreate() {...} @Override public int onStartCommand(Intent intent, int flags, int startId) {...} @Override public void onDestroy() {...} }可以看到,这里我们新建了一个 DownloadBinder 类,并让他继承自 Binder;
然后在他的内部提供了开始下载以及查看下载进度的方法,
当然,这只是两个模拟方法,仅仅是在这两个方法中打印了一行日志;
接着在 MyService.java 中创建了 DownloadBinder 实例,然后再 onBind() 方法中返回了这个实例;
这样,MyService 中的工作就全部完成了;
下面就要看一看,在活动中如何去调用服务里的这些方法;
首先在布局文件中新增两个按钮;
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="10dp"> <Button .../> <Button .../> <Button android:id="@+id/bind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Bind Service" android:textAllCaps="false" /> <Button android:id="@+id/unbind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Unbind Service" android:textAllCaps="false" /> </LinearLayout>这两个按钮分别是用于绑定服务和取消绑定服务的;
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ ...... private Button bindService; private Button unbindService; private MyService.DownloadBinder downloadBinder; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { downloadBinder = (MyService.DownloadBinder) service; downloadBinder.startDownload(); downloadBinder.getProgress(); } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { ...... bindService = findViewById(R.id.bind_service); bindService.setOnClickListener(this); unbindService = findViewById(R.id.unbind_service); unbindService.setOnClickListener(this); } @Override public void onClick(View v) { Intent intent = new Intent(this,MyService.class); switch(v.getId()){ ...... case R.id.bind_service: bindService(intent,connection,BIND_AUTO_CREATE); break; case R.id.unbind_service: unbindService(connection); break; default: break; } } }可以看到,这里我们首先创建了一个 ServiceConnection 的匿名类,
在里面重写了 onServiceConnected() 方法和 onServiceDisconnected() 方法,
这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用;
在 onServiceConnected() 方法中,我们又通过向下转型得到了 DownloadBinder 实例;
然后调用了 DownloadBinder 的 startDownload() 和 getProgress() 方法。
当然,现在活动和服务还没进行绑定呢,这个功能是在 bindService 按钮 的点击事件里完成的;
在点击事件里,通过调用 bindService() 方法将 MainActivity 和 MyService 进行绑定,
调用 unbindService() 方法解除绑定;
其中, bindService(Intent service,ServiceConnection conn,int flags) 传递了三个参数:
Intent :用于指定要启动的 Service
ServiceConnection :用于监听调用者与 Service 之间的连接状态
- 当调用者与Service连接成功时,将回调该对象的 onServiceConnected() 方法
- 断开连接时,将回调该对象的 onServiceDisconnected() 方法
flag :指绑定时是否自动创建 Service(如果 Service 还未创建)
- 可指定为 0,即不自动创建
- 也可指定为 BIND_AUTO_CREATE ,即自动创建
现在,让我们重新运行一下程序;
运行效果
可以看到,再点击 Bind Service 按钮 的时候,首先是 MyService 的 onCreate() 方法得到了执行,
然后 startDownload() 和 getProgress() 方法都得到了执行;
另外需要注意的是,任何一个服务在整个应用程序范围内都是通用的,即 MyService 不仅可以和 MainActivity 绑定,
还可以和任何一个其他的活动进行绑定,而且在绑定完成后,他们都可以获取到相同的 DownloadBinder 实例。
•服务的生命周期
服务的启动方式
通过上面的学习,可以看到服务有两种启动方式:
通过 startService(Intent intent) 方式启动服务
- 通过 startService 方式启动服务,服务会长期在后台运行
- 并且服务的状态与开启者的状态没有关系,即便启动服务的组件已经被销毁,服务也依旧会运行
通过 bindService 方式启动服务
- 通过 bindService 方式启动服务,服务会与组件进行绑定
- 一个被绑定的服务提供一个客户端与服务器接口,允许组件与服务进行交互,发送请求,得到结果
- 多个组件可以绑定一个服务,当调用 onUnbind() 方法时,这个服务就被销毁了,即调用 onDestory() 方法
图示
文字描述
——《第一行代码》
•声明
参考资料