Android Service使用方法回顾

    Service作为Android四大组件之一,承载着重要的作用,同时,熟悉Service也会为理解Binder打下重要的基础,这里是我初学Android时做的关于Service的笔记,现在总结到这篇文章中。

概述

android中的service与Windows中的服务类似,一般没有用户界面,运行在后台,可以执行耗时的操作,是安卓四大组件之一。其他组件可以启动service,并且当用户切换另外的场景,service可以一直在后台运行。
       服务不能自己运行,需要通过调用Context.startService()或Context.bindService()方法启动服务。这两个方法都可以启动Service,但是它们的使用场合有所不同。使用startService()方法启用服务,访问(启动)者与服务之间没有关连,即使访问(启动)者退出了,服务仍然运行。采用Context.startService()方法启动服务,只能调用Context.stopService()方法结束服务,服务结束时会调用onDestroy()方法。
       使用bindService()方法启用服务,访问者与服务绑定在了一起,访问者一旦退出,服务也就终止。
       service的运行是单例模式,只会实例化一次,开启一次之后再开启还是之前的那个对象,不会再新实例化。 Service和其他的应用组件一样,运行在进程的主线程中。这就是说如果service需要很多耗时或者阻塞的操作,需要在其子线程中实现。

创建服务步骤

创建一个类,继承service
       public class MyService extends Service {
       //必须实现方法,方法返回IBinder对象,应用程序可通过该对象与Service组件通信
       public IBinder onBind(Intent intent) {
              Log.i("Other", "MyService.onBind");
              return null;
       }
      //生命周期方法,当Service第一次创建后将立即回调该方法
       public void onCreate() {
              super.onCreate();
              Log.i("Other", "MyService.onCreate");
       }
       //当Service被关闭之前将回调该方法
       public void onDestroy() {
              super.onDestroy();
              Log.i("Other", "MyService.onDestroy");
       }
       //每次客户端调用startService(Intent)方法启动该Service时都会回调该方法
       public int onStartCommand(Intent intent, int flags, int startId) {
              Log.i("Other", "MyService.onStartCommand");
              return super.onStartCommand(intent, flags, startId);
       }
       //当该Service上绑定的所有客户端都断开连接时将回调该方法
       public boolean onUnbind(Intent intent) {
              Log.i("Other", "MyService.onUnbind");
              return super.onUnbind(intent);
       }

在配置清单中注册该service

<!-- 注册服务 -->
        <service android:name=".MyService">
             <intent-filter>
                    <action android:name="cn.itcast.service.myservice.action" />
                    <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
        </service>

启动服务

服务不能自己启动,需要手动启动,有两种方法启动
1、Context.startService(Intent)
访问者与服务没有关联,即使访问者退出,服务仍然运行,
若服务没启动:onCreate()-->onStartCommand(),如服务已经启动,直接调用onStartCommand()
通过Context.stopService(Intent)关闭服务,调用此方法,此时若服务没启动:调用stopService()方法,service不做任何操作,若服务启动:调用onDestory()方法
2、Context.bindService()
访问者与服务绑定在一起,访问者一旦退出,服务终止
    以上创建了一个Service,只是重写了周期方法,若希望Service组件做某些事情,那么只要在onCreate()或onStartCommand()方法中定义相关义务代码即可

绑定本地Service并与之通信(进程内部绑定服务)

    如果service与访问者需要进行方法调用或数据交换,则应该使用bindService()和unbindService()方法绑定、解绑服务。访问者与服务通过IBinder对象联系在一起,bindService()方法调用时,需要Intent,ServiceConnection和flag参数,其中serviceConnetion对象用于监听访问者与service之间的连接情况,用于接收服务onBind()方法返回的IBinder对象,IBinder中包含service传给调用者的数据。当连接成功时,serviceConnection()将回调方法onServiceConnected(Component Name,IBinder service);IBinder对象会传入其中。图解如下:

    bindService()完整的方法签名为:bindService(Intent service,ServiceConnection conn,int flags),解释如下:
  • service:该参数通过Intent指定要启动的Service
  • conn:连接,该参数用于接收绑定服务后接收服务传递过来Binder,数据封装在IBinder类中,一般会根据service业务继承ServiceConnection类编写Connection代码,在本例中
       /**
        * 服务连接对象,是调用者和服务联系的核心
        */
       class CustomerServiceConnection implements ServiceConnection{
              /**
               * 服务连接上之后会回调该方法,服务传回来的数据在binder中
               */
              public void onServiceConnected(ComponentName name, IBinder binder) {
                     Log.i("Other","service connected");
                     ics = (ICustomerSerice) binder;
              }
              /**
               * 服务连接断开之后会回调该方法.注意,当调用者主动通过unbindService()方法断开Service的连接时,
               *  onServiceConnected()方法不会被调用。只有Service所在宿主进程由  于异常或其他原因终止,才会调用到该方法 
              */
              public void onServiceDisconnected(ComponentName name) {
              }
    }
     编写好ServiceConnection后,我们就可以通过bindService()来绑定服务了,访问者调用bindService()绑定服务后,服务代码中会调用public IBinder onbind()方法,方法返回IBinder,IBinder是一个接口,我们可以编写我们的业务代码实现该接口,这样,我们的业务方法就也返回给了访问者,访问者就可以使用这些业务方法。
   /**
    * 进程内部通信,使用bind方法开启服务.调用者和服务通过
    *IBinder对象联系在一  起.
    */
    public class CustomerService extends Service {
       //访问者绑定服务,服务调用该方法
       public IBinder onBind(Intent intent) {
              Log.i("Other", "CustomerService.onBind");
              //返回自己编写的业务Binder,该类实现IBinder接口,访问者在ServiceConnection的onServiceConnected方法中接收到该Binder对象
              return new CustomerServiceBinder();
       }
 
       public void onCreate() {
              super.onCreate();
              Log.i("Other", "CustomerService.onCreate,tid=" + Thread.currentThread().getId());
       }
 
       public void onDestroy() {
              super.onDestroy();
              Log.i("Other", "CustomerService.onDestroy");
       }
 
       public int onStartCommand(Intent intent, int flags, int startId) {
              Log.i("Other", "CustomerService.onStartCommand");
              return super.onStartCommand(intent, flags, startId);
       }
 
       public boolean onUnbind(Intent intent) {
              Log.i("Other", "CustomerService.onUnbind");
              return super.onUnbind(intent);
       }
      
       /**
        * 组合体,既继承了Binder(IBinder的实现类),同时实现自定义业务接口.
        */
       class CustomerServiceBinder extends Binder implements ICustomerSerice{
              //业务方法
              public String sayHello(String name) {
                     Log.i("Other","CustomerServiceBinder.sayHello("+name+")");
                     return "hello "+ name ;
              }
              //业务方法
              public Customer findCustomerByName(String name) {
                     Customer c = new Customer();
                     c.id = 1000;
                     c.name = name ;
                     c.age = 33;
                     return c;
              }
       }
}

图示如下:


进程间绑定服务——使用AIDL

对于进程间绑定服务,有时需要进程间共享业务或对象,当需要在不同的进程之间传递对象时,该如何实现呢? 显然, Java中是不支持跨进程内存共享的。因此要传递对象, 需要把对象解析成操作系统能够理解的数据格式, 以达到跨界对象访问的目的。在JavaEE中,采用RMI通过序列化传递对象。在Android中, 则采用AIDL(Android Interface Definition Language:接口定义语言)方式实现。
与本地绑定Service不同的是,本地Service的onBind()方法会直接把IBinder对象本身传给客户端的ServiceConnection的onServiceConnected方法的第二个参数,但远程Service的onBind()方法只是将IBinder对象的代理类传给onServiceConnected的第二个参数。当客户端获取代理以后,就可以通过该IBinder对象回调远程Service的属性或者方法了。
 AIDL是一种接口定义语言,用于约束两个进程间的通讯规则,供编译器生成代码,实现Android设备上的两个进程间通信(IPC)。AIDL的IPC机制和EJB所采用的CORBA很类似,进程之间的通信信息,首先会被转换成AIDL协议消息,然后发送给对方,对方收到AIDL协议消息后再转换成相应的对象。由于进程之间的通信信息需要双向转换,所以android采用代理类在背后实现了信息的双向转换,代理类由android编译器生成,对开发人员来说是透明的。

进程间服务绑定步骤

服务端

1)创建服务类,进程间服务绑定,同样使用onbind()方法开启服务    
2)定义AIDL服务类接口文件(xxx.aidl)
       编写AIDL需要注意:
       1>、接口名与AIDL文件名相同。
       2>、接口名和方法名前不能使用访问权限修饰符public,private,protected等,也不能用final,static
       3>、AIDL默认支持的类型包括Java的基本类型(int,long,boolean等)和(String,List,Map,CharSequence),在文件中使用这些类型时不需要import声明,对于list与map中的元素必须是AIDL支持的类型,如果使用自定义的类型作为参数或者返回值,自定义类型(Java对象)必须继承Parcelable接口
       4>、自定义类型和AIDL和AIDL生成的其他接口在aidl描述文件中,应该显示import,即便该类和aidl文件在同一个包下。
       5>、在aidl文件中所有非java基本类型的参数必须加上(in,out,inout)标记,以指明参数是输入或输出或输入输出参数
       6>、Java原始默认标记为in,不能为其他标记
       AIDL定义接口文件代码如下:
package cn.itcast.service3;
       //符合要求4>
       import cn.itcast.service3.Person;
       interface IPersonService {
       String sayHello(in String name);
       Person findPersonByName(in String name);
}
android平台会根据aidl文件自动生成Java类,保存在gen文件夹下,注意该文件名与服务类名一致。
//生成的Java类中有一个抽象类Stub,它继承Binder并实现业务接口方法,如下:
IPersionService{
              abstarct class Stub extends Binder implements IPersionService{
              class proxy{...}
    }
}
3)创建javabean
       如果要求该Javabean在进程间传递,需要实现Parcelable(邮包,类似于Java中Serializable)接口,并且增加静态字段CREATOR,用于反序列化:
package cn.itcast.service3;
 
import android.os.Parcel;
import android.os.Parcelable;
 
/**
 * Javabean
 */
public class Person implements Parcelable {
       private Integer id;
       private String name;
       private Integer age;
 
       public Integer getId() {
              return id;
       }
       public void setId(Integer id) {
              this.id = id;
       }
       public String getName() {
              return name;
       }
       public void setName(String name) {
              this.name = name;
       }
       public Integer getAge() {
              return age;
       }
       public void setAge(Integer age) {
              this.age = age;
       }
       public int describeContents() {
              // TODO Auto-generated method stub
              return 0;
       }
 
       /**
        * 将javabean信息写入邮包,等价于序列化过程,顺序很关键(反序列化时和序列化顺序相一致)
        */
       @Override
       public void writeToParcel(Parcel dest, int flags) {
              dest.writeInt(id);
              dest.writeString(name);
              dest.writeInt(age);
       }
 
       /**
        * 静态成员,必须是CREATOR,该对象实现Parcelable.Createor接口,用于反      * 序列化
        */
       public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
              //从邮包中得到对象,反序列化过程
              public Person createFromParcel(Parcel source) {
                     Person p = new Person();
                     p.setId(source.readInt());
                     p.setName(source.readString());
                     p.setAge(source.readInt());
                     return p ;
              }
 
              public Person[] newArray(int size) {
                     return new Person[size];
              }
       };
}
同时,需要定义JavabeanAIDL文件,文件名与Javabean类名相同,并且放在同一包下,代码如下:
       package cn.itcast.service3;
       parcelable Person;

4)编写service中onBind()方法
  编写AIDL接口,gen目录下生成的IPersionService.java代码中当中,抽象类Stub  既继承了Binder类(是IBinder接口的实现类),也实现了AIDL接口文件中定义的业务方法,所以onBind方法返回Stub,符合要求。在进程内部业务绑定中,需要我们自己编写内部类来继承Binder和实现业务接口,而在进程间通信中,自动生成的Stub实现了该功能。
public IBinder onBind(Intent intent) {
              Log.i("Other", "PersonService.onBind");
              //AIDL接口文件中定义的业务方法
              return new IPersonService.Stub() {
                     public String sayHello(String name) throws RemoteException {
                            Log.i("Other","PersonService.sayHello()=" + name);
                            return "hello " + name;
                     }
              //AIDL接口文件中定义的业务方法
              public Person findPersonByName(String name) throws RemoteException {
                            Person p = new Person();
                            p.setId(2000);
                            p.setName(name);
                            p.setAge(23);
                            return p;
                     }
              };
       }

客户端部分

1、复制服务端的AIDL文件到客户端src文件夹下,(所有aidl文件和javabean,包名一致)
2、在MainActivity中创建服务连接内部类PersionConnection implements        ServiceConnetcion,并接收IBinder
   /**
        * 服务连接对象
        */
       //服务端的业务接口
       private IPersonService ips;
       class PersonConnection implements ServiceConnection{
              //通过生成的类调用其方法,返回业务实现对象,代理对象(封装了进行间的通信细节)
              public void onServiceConnected(ComponentName name, IBinder service) {
                     ips = IPersonService.Stub.asInterface(service);
              }
              public void onServiceDisconnected(ComponentName name) {
              }
       }

这里调用asInterface()方法其实返回的是stub内部类Proxy,客户端通过该代理可以返回到服务端提供的业务方法。

3、绑定服务,进程间绑定采用隐示意图
 //绑定远程服务的隐式意图
       Intent i = new Intent();
       i.setAction("cn.itcast.service.personservice.action");
       this.bindService(i, conn, Context.BIND_AUTO_CREATE);
       Toast.makeText(this, "远程绑定ok", 1).show();

4、调用业务方法

   try {
            Toast.makeText(this, ips.sayHello("kkk"),1).show();
       } catch (RemoteException e) {
             e.printStackTrace();
       }
       try {
            Person p = ips.findPersonByName("jerry");
            Toast.makeText(this,p.toString(),1).show();
          } catch (RemoteException e) {
            e.printStackTrace();
          }

tips

    如果开发者需要在Service中处理耗时任务,需要在Service中另外启动一条新线程处理耗时任务。并且不能再其他组件(Activity、BroadcastReceiver)中开启新线程进行耗时任务,Activity可能被用户退出,BroadcastReceiver的生命周期本来就很短,很可能出现的情况是在组件退出或已结束的情况下,耗时任务的进程就变成了空进程,系统内存很可能优先终止该进程,那么该进程的所有子线程也会被中止,这样很可能导致子线程无法执行完成。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章