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的生命週期本來就很短,很可能出現的情況是在組件退出或已結束的情況下,耗時任務的進程就變成了空進程,系統內存很可能優先終止該進程,那麼該進程的所有子線程也會被中止,這樣很可能導致子線程無法執行完成。



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