最近开始学习Android四大组件之一的Service组件,所以想把自己的一些总结写成博客,共同学习。
下面开始主要内容:
Service:长期在后台运行的没有界面的组件,而且是配合Intent意图一起使用的。下面先介绍一下器两种启动方式
启动方式有两种:开启和绑定
1.对于开启:有两个对应的方法
startService()调用onCreate方法
stopService() 调用onDestory方法
2.对于绑定:也有两个对用的方法
bindService() 直接调用onBind()方法 同时还会检测服务是否已被 如果没有会先调用onCreate()方法
unBindService() 直接调用onUnbind()方法 如果该解绑是最后一个绑定 同时还会调用onDestory()方法
其中bindService()绑定服务 可以得到服务的代理人对象,间接地调用服务里面的方法
另外要注意下面两点:
绑定服务:可以间接调用服务里的方法。
如果调用者activity被销毁了,服务也会跟着被销毁
开启服务:不可以调用服务里的方法。
如果调用者activity退出了,服务还会长期的在后台运行
下面再简单介绍一下service的生命周期:
1.单独调用
startService()-----onCreate();
...中间还有startCommand()
start()
stopService () -----ondestory();
-------------------------------------------------------
bindService() -----onCreate() -> onBind();
...中间还有startCommand()
start()
unBindService() -----onUnbind() -> onDestory();
2.混合调用
需求:既要保证服务长期的在后台运行,又想要去调用服务里的里面方法
技巧:1.先开启服务(实现长期运行) 2.绑定服务(可实现调用服务中的方法)
3.解除绑定 4.停止服务
步骤:1.开启服务 startService() -onCreate();
2.绑定服务 bindService() -onBind();
3.解绑服务 unBindService() -onUnbind();
4.停止服务 stopService() -onDestory();
如果在混合使用是不是按照这样的顺序去执行 ,可能会出现不是上面顺序的生命周期
比如说:如果先绑定服务,再开启服务,会发现如果这个时候点击stopService的动作按钮不会有任何
反应,但如果接着点击解除绑定服务,这是会打印出onUnbind 接着是onDestory
又如如果在开启服务,绑定服务后,在没有解绑之前就按了模拟器的返回键,这时抛出表示
还有与服务之间的绑定存在的异常,之后会有onUnbind 和onDestory出现
总之,综合上面的现象,在以后实际应用中,最好是start与stop 和 bind与unbind 分别都是成对出现。
通过上面简单的讲解,下面给出具体的测试代码,测试代码通过使用服务主activity——MainActivity和一个模拟唱歌的服务——SingService分别实现了服务的两种开启方式,另外在测试代码中还实现了通过绑定调用服务中方法的功能;这里是调用了调用了SingService中的changeSong()方法。
下面详细说明各部分代码的功能。
首先是布局文件,可以看到这里是定义了五个按钮,分别对用于开启服务,关闭服务,绑定服务,解绑服务以及调用服务中的方法的五个按钮,代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.testservice.MainActivity" >
<Button
android:onClick="start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开启服务" />
<Button
android:onClick="stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="关闭服务" />
<Button
android:onClick="bind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="绑定服务" />
<Button
android:onClick="unbind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="解绑服务" />
<Button
android:onClick="change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="调用服务里的方法" />
</LinearLayout>
下面再来看一下MainActivity的代码,首先可以清楚的看到,代码中分别实现了五个按钮事件start(),stop(),bind(),unbind()以及change();前四个事件分别条用系统提供的api来实现服务的开启,停止,绑定与解绑,具体实现代码大家应该也能很容易就看懂,不过要注意的是绑定和解绑使用的conn一定要是同一个,不然会报错。对于change()方法的实现,就涉及到SingService的实现,下面通过SingService的代码一起讲解
package com.wang.testservice1;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
/**
* 调用唱歌服务的activity
* 同时通过按钮事件来控制服务的生命周期
* @author Administrator
*
*/
public class MainActivity extends Activity {
//步骤四:在activity中得到IBinder的引用对象
//private SingService.MyBinder mybinder;
private IService mybinder;
private MyConn conn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 开始服务
* @param v
*/
public void start(View v){
Intent intent = new Intent(this,SingService.class);
//采用 api 创建服务 服务是被系统(框架)创建出来的
startService(intent);
}
/**
* 停止服务
* @param v
*/
public void stop(View v){
Intent intent = new Intent(this,SingService.class);
stopService(intent);
}
public void bind(View v){
Intent intent = new Intent(this,SingService.class);
//intent:激活服务的意图
//conn:获取服务的中间人 一定不能为空
//flags( BIND_AUTO_CREATE) 如果绑定的服务没有被创建 系统会创建服务
//步骤一:采用绑定的的方式取开启服务
conn = new MyConn();
bindService(intent, conn, BIND_AUTO_CREATE);
}
public void unbind(View v){
//注意解除绑定的连接一定要是和绑定时的是同一个
unbindService(conn);
}
private class MyConn implements ServiceConnection{
//步骤三:IBinder对象传递到conn接口的回调方法onServiceConnected
//在服务成功绑定的时候 执行的方法
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
System.out.println("唱歌服务的中间人(Ibinder service)被返回回来了....");
//mybinder = (MyBinder) service;
mybinder = (IService) service;
}
//在服务被解除绑定时执行的方法 只有程序异常终止或进程被杀死时才会被调用
@Override
public void onServiceDisconnected(ComponentName name) {
System.out.println("绑定被异常解除了.....");
}
}
/**
* 调用服务的方法,换首歌
* @param v
*/
public void change(View v){
//由于系统框架在创建服务的时候 会创建与之对应的上下文
//而下面的操作是直接new 对象 是没有上下文的 所以changeSong()方法中的toast会抛出空指针异常
/*SingService service = new SingService();
service.changeSong("我爱你!");*/
//步骤五:利用IBinder间接地条用服务里的方法
mybinder.callChangeSong("我爱你");
}
@Override
protected void onDestroy() {
//这里是一个小技巧,防止在activity退出时还有绑定着的服务没解绑而抛出异常
try {
unbindService(conn);
} catch (Exception e) {
e.printStackTrace();
}
super.onDestroy();
}
}
在讲解change()之前,我们先来看一下如何实现主activity中调用服务中的方法,在前面我们简要说过,直接通过startService开启服务,是无法实现的,在下面的代码中我们也可以看出,在change()试图通过new 一个SingService实例对象来调用callChangeSong()是不可行的,具体原因在代码注释中已经说的很清楚了,这里也不再赘述。下面主要通过绑定服务来讲解如何实现,我归纳出了五个步骤:
1.采用绑定的的方式取开启服务
2.在service中发现有activity通过绑定的方式开启服务,调用onBind()方法,并返回一个中间人IBinder
3.在主activity中IBinder对象传递到conn接口的回调方法onServiceConnected()中
4.在activity中得到IBinder的引用对象
5.利用IBinder间接地调用服务里的方法
同时每个步骤也在代码中明确的标了出来。下面是SingService的代码
package com.wang.testservice1;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.widget.Toast;
/**
* 模拟一个唱歌服务
* 同时实现了通过中间人提供供外界调用服务的方法
* @author Administrator
*
*/
public class SingService extends Service {
//步骤二:有activity通过绑定的方式开启服务,调用onBind()方法,并返回一个中间人IBinder
//当采用绑定方式开启服务时 被调用,同时会返回一个Ibinder对象(中间人)供启动服务的activity使用
@Override
public IBinder onBind(Intent intent) {
System.out.println("onBind 服务被绑定了....");
//返回自定义的代理人
return new Mybinder();
}
@Override
public boolean onUnbind(Intent intent) {
System.out.println("onUnbind 所有的绑定都被解除了");
return super.onUnbind(intent);
}
//这里把中间人定义成共有的话,在绑定服务的activity中可以直接拿到中间人的对象
//这样就可以直接调用中间人中的方法
//但实际应用中是不会把中间人直接暴露出来的,而是定义成服务的私有类
//但是这样的话,其他的activity就无法利用中间人去调用服务中的方法
//我们会采用让中间人继承一个接口,同时让中间人中的一些方法是实现了接口中的方法
//这样我们就抽取出来了中间人中的几个方法去供外界调用
/*public class Mybinder extends Binder implements IService{
//间接利用中间人去调用更换歌曲的方法
public void callChangeSong(String songName){
changeSong(songName);
}
public void callDeleteSong(String songName){
deleteSong(songName);
}
}*/
//这里是将中间人的访问权限定义成private,这样外界就无法直接得到中间人的对象
//所以这里就让中间人去继承IService接口,同时实现接口中的callChangeSong()方法
//这样外界就可以通过返回的IBinder和接口去调用callChangeSong()方法
//而其他的方法像callDeleteSong()还是私有的,无法被外界访问
private class Mybinder extends Binder implements IService{
//间接利用中间人去调用更换歌曲的方法
public void callChangeSong(String songName){
changeSong(songName);
}
public void callDeleteSong(String songName){
deleteSong(songName);
}
}
@Override
public void onCreate() {
System.out.println("onCreate 服务开始 开始唱歌了.....");
super.onCreate();
}
/**
* 更改唱的歌曲
* @param songName
*/
public void changeSong(String songName){
Toast.makeText(getApplicationContext(), "开始唱"+songName, 0).show();
}
public void deleteSong(String songName){
Toast.makeText(getApplicationContext(), "删除"+songName, 0).show();
}
@Override
public void onDestroy() {
System.out.println("onDestory 服务销毁 停止唱歌了.....");
super.onDestroy();
}
}
从上面的代码可以看出,通过两种方式都可以实现调用服务中的方法,
一种是将service中的IBinder类(中间人)做成public,这样在其他类中可以直接生成其实例对象,就可以调用任何一个方法;
另一种是也是实际中使用的将IBinder类做成private,这样实现了更好的封装性,但是在访问上却更加麻烦了,这时就需要借助接口来实现,具体实现代码中也给出了,下面给出接口IService的实现;
package com.wang.testservice1;
/**
* 一个接口
* 配合服务中的私有类MyBinder实现外界间接调用服务的方法
* @author Administrator
*
*/
public interface IService {
public void callChangeSong(String songName);
}
由于每个人的理解不同,在表达上可能不是太清晰,大家将就着看吧!
另外,这里附上源码下载路径http://download.youkuaiyun.com/detail/wangwenan1993/8696711