Android中的Service詳解

按運行地點分類:

類別

區別

優點

缺點

應用

本地服務(Local)

該服務依附在主進程上,

服務依附在主進程上而不是獨立的進程,這樣在一定程度上節約了資源,另外Local服務因爲是在同一進程因此不需要IPC,也不需要AIDL。相應bindService會方便很多。

主進程被Kill後,服務便會終止。

非常常見的應用如:HTC的音樂播放服務,天天動聽音樂播放服務。

遠程服務(Remote)

該服務是獨立的進程,

服務爲獨立的進程,對應進程名格式爲所在包名加上你指定的android:process字符串。由於是獨立的進程,因此在Activity所在進程被Kill的時候,該服務依然在運行,不受其他進程影響,有利於爲多個進程提供服務具有較高的靈活性。

該服務是獨立的進程,會佔用一定資源,並且使用AIDL進行IPC稍微麻煩一點。

一些提供系統服務的Service,這種Service是常駐的。

其實remote服務還是很少見的,並且一般都是系統服務。

 

按運行類型分類:

類別

區別

應用

前臺服務

會在通知一欄顯示 ONGOING 的 Notification,

當服務被終止的時候,通知一欄的 Notification 也會消失,這樣對於用戶有一定的通知作用。常見的如音樂播放服務。

後臺服務

默認的服務即爲後臺服務,即不會在通知一欄顯示 ONGOING 的 Notification。

當服務被終止的時候,用戶是看不到效果的。某些不需要運行或終止提示的服務,如天氣更新,日期同步,郵件同步等。

有同學可能會問,後臺服務我們可以自己創建ONGOING 的 Notification 這樣就成爲前臺服務嗎?答案是否定的,前臺服務是在做了上述工作之後需要調用 startForeground ( android 2.0 及其以後版本)或 setForeground (android 2.0 以前的版本)使服務成爲前臺服務。這樣做的好處在於,當服務被外部強制終止掉的時候,ONGOING 的Notification 任然會移除掉。

按使用方式分類:

類別

區別

startService 啓動的服務

主要用於啓動一個服務執行後臺任務,不進行通信。停止服務使用stopService

bindService 啓動的服務

該方法啓動的服務要進行通信。停止服務使用unbindService

startService 同時也 bindService 啓動的服務

停止服務應同時使用stepService與unbindService

以上面三種方式啓動的服務其生命週期也有區別,將在隨後給出。


今天我們就來介紹一下Android中的四大組件中的服務Service,說到Service,

它分爲本地服務和遠程服務:區分這兩種服務就是看客戶端和服務端是否在同一個進程中,本地服務是在同一進程中的,遠程服務是不在同一個進程中的。

開啓服務也有兩種方式,一種是startService(),他對應的結束服務的方法是stopService(),另一種是bindService(),結束服務的是unBindService(),這兩種方式的區別就是:當客戶端Client使用startService方法開啓服務的時候,這個服務和Client之間就沒有聯繫了,Service的運行和Client是相互獨立的,想結束這個服務的話,就在服務本身中調用stopSelf()方法結束服務。而當客戶端Client使用bindService方法開始服務的時候,這個服務和Client是一種關聯的關係,他們之間使用Binder的代理對象進行交互,這個在後面會詳細說到,要是結束服務的話,需要在Client中和服務斷開,調用unBindService方法。

在這裏我們只做bindService方式的研究,而startService方式比較獨立和簡單,這裏就不做演示了。


首先來說一下本地服務:

本地服務很簡單的,就是Client和這個服務在同一個進程中:

先來看一下代碼吧:

下面這張圖是項目的結構圖:


爲了方便數據的訪問,這裏定義一個數據的訪問接口:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.nativeservice.demo;  
  2.   
  3. /** 
  4.  * 訪問接口 
  5.  * @author weijiang204321 
  6.  */  
  7. public interface IStudent {  
  8.     /** 
  9.      * 通過no訪問name 
  10.      * @param no 
  11.      * @return 
  12.      */  
  13.     public String getNameByNumber(int no);  
  14.       
  15. }  

下面再來看一下StudentService的代碼:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.nativeservice.demo;  
  2.   
  3. import android.app.Service;  
  4. import android.content.Intent;  
  5. import android.os.Binder;  
  6. import android.os.IBinder;  
  7.   
  8. /** 
  9.  * 定義的服務Service 
  10.  * @author weijiang204321 
  11.  */  
  12. public class StudentService extends Service{  
  13.   
  14.     //名稱  
  15.     public static String[] nameAry = {"張飛","李小龍","趙薇"};  
  16.       
  17.     /** 
  18.      * 通過no獲取name 
  19.      * @param no 
  20.      * @return 
  21.      */  
  22.     private String getNameByNo(int no){  
  23.         if(no>0 && no<4)  
  24.             return nameAry[no-1];  
  25.         return null;  
  26.     }  
  27.       
  28.     @Override  
  29.     public IBinder onBind(Intent arg0) {  
  30.         return new StudentBinder();  
  31.     }  
  32.       
  33.     /** 
  34.      * 自定義的Binder對象 
  35.      * @author weijiang204321 
  36.      * 
  37.      */  
  38.     private class StudentBinder extends Binder implements IStudent{  
  39.         @Override  
  40.         public String getNameByNumber(int no) {  
  41.             return getNameByNo(no);  
  42.         }  
  43.     }  
  44.   
  45.   
  46. }  


StudentService中就是定義一個訪問name的方法,在onBind方法中返回Binder對象,這個就是Client和Service之間交互的關鍵對象

下面看一下Client代碼:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.nativeservice.demo;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.ComponentName;  
  5. import android.content.Intent;  
  6. import android.content.ServiceConnection;  
  7. import android.os.Bundle;  
  8. import android.os.IBinder;  
  9. import android.os.Looper;  
  10. import android.widget.Toast;  
  11.   
  12. /** 
  13.  * 測試Service 
  14.  * @author weijiang204321 
  15.  * 
  16.  */  
  17. public class MainActivity extends Activity {  
  18.   
  19.     private IStudent student;  
  20.       
  21.     @Override  
  22.     protected void onCreate(Bundle savedInstanceState) {  
  23.         super.onCreate(savedInstanceState);  
  24.         setContentView(R.layout.activity_main);  
  25.         //開啓查詢名稱的服務  
  26.         Intent service = new Intent(this,StudentService.class);  
  27.         bindService(service,new StudentConnection(),BIND_AUTO_CREATE);  
  28.         //延遲2s在顯示查詢的內容,不然開啓服務也是需要時間的,如果不延遲一段時間的話,student對象爲null;  
  29.         new Thread(){  
  30.             @Override  
  31.             public void run(){  
  32.                 try {  
  33.                     Thread.sleep(2*1000);  
  34.                     Looper.prepare();  
  35.                     Toast.makeText(getApplicationContext(), student.getNameByNumber(1), Toast.LENGTH_LONG).show();  
  36.                     Looper.loop();  
  37.                 } catch (InterruptedException e) {  
  38.                     e.printStackTrace();  
  39.                 }  
  40.             }  
  41.         }.start();  
  42.     }  
  43.       
  44.     /** 
  45.      * 自定義的服務連接connection 
  46.      * @author weijiang204321 
  47.      * 
  48.      */  
  49.     private class StudentConnection implements ServiceConnection{  
  50.           
  51.         @Override  
  52.         public void onServiceConnected(ComponentName name, IBinder service) {  
  53.             student = (IStudent)service;  
  54.         }  
  55.         @Override  
  56.         public void onServiceDisconnected(ComponentName name) {  
  57.         }  
  58.           
  59.     }  
  60. }  


在這裏,用到了bindService方法,該方法的參數是:第一個參數是服務的intent,第二參數是一個ServiceConnection接口,第三個參數是啓動服務的方式常量,這裏最主要的就是第二個參數ServiceConnection接口,我們自己定義一個實現該接口的類。

StudentConnection,必須實現兩個方法,這兩個方法見名思議,一個是連接時調用的方法,一個是斷開連接時的方法,在開始連接的方法onServiceConnected中傳回來一個IBinder對象,這個時候需要將其轉化一下,這個就是爲什麼要在開始的時候定義一個IStudent接口,在這裏訪問數據就方便了,同時在Client代碼中要做個延遲的操作來訪問數據,因爲開啓服務,連接這個過程是需要時間的,所以在這裏就延遲了2s,這裏只是爲了能夠正常顯示數據,才這麼做的,不然student對象是爲null的,當然要根據自己的實際情況操作。最後還要在AndroidMainfest.xml中配置Service:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <service android:name=".StudentService"></service>  
這裏開啓服務用的是顯示意圖,所以沒有定義action過濾器了,運行結果很簡單這裏就不截圖了


下面在來說一下遠程服務:

在說到遠程服務的時候,我們需要先了解一些預備的知識:

首先來了解一下AIDL機制:

AIDL的作用
由於每個應用程序都運行在自己的進程空間,並且可以從應用程序UI運行另一個服務進程,而且經常會在不同的進程間傳遞對象。在Android平臺,一個進程通常不能訪問另一個進程的內存空間,所以要想對話,需要將對象分解成操作系統可以理解的基本單元,並且有序的通過進程邊界。
通過代碼來實現這個數據傳輸過程是冗長乏味的,Android提供了AIDL工具來處理這項工作。


AIDL (Android Interface Definition Language) 是一種IDL 語言,用於生成可以在Android設備上兩個進程之間進行進程間通信(interprocess communication, IPC)的代碼。如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數。
AIDL IPC機制是面向接口的,像COM或Corba一樣,但是更加輕量級。它是使用代理類在客戶端和實現端傳遞數據。

在Android中, 每個應用程序都有自己的進程,當需要在不同的進程之間傳遞對象時,該如何實現呢? 顯然, Java中是不支持跨進程內存共享的。因此要傳遞對象, 需要把對象解析成操作系統能夠理解的數據格式, 以達到跨界對象訪問的目的。在JavaEE中,採用RMI通過序列化傳遞對象。在Android中, 則採用AIDL(Android Interface Definition Language:接口描述語言)方式實現。
AIDL是一種接口定義語言,用於約束兩個進程間的通訊規則,供編譯器生成代碼,實現Android設備上的兩個進程間通信(IPC)。AIDL的IPC機制和EJB所採用的CORBA很類似,進程之間的通信信息,首先會被轉換成AIDL協議消息,然後發送給對方,對方收到AIDL協議消息後再轉換成相應的對象。由於進程之間的通信信息需要雙向轉換,所以android採用代理類在背後實現了信息的雙向轉換,代理類由android編譯器生成,對開發人員來說是透明的。


選擇AIDL的使用場合
官方文檔特別提醒我們何時使用AIDL是必要的:只有你允許客戶端從不同的應用程序爲了進程間的通信而去訪問你的service,以及想在你的service處理多線程。


瞭解了AIDL之後下面來看一下項目的結構:

我們這個遠程服務是想在將服務端和客戶端分別放到一個應用中,所以這裏要建立兩個Android項目一個是remoteService,一個是remoteClient

首先來看一下Service端的項目結構:


在這裏我們需要定義一個aidl文件,具體步驟很簡單的:

因爲AIDL相當於是一個接口,所以它的定義和interface的定義很類似的,使用interface關鍵字,有一點不同的是,AIDL中不能有修飾符(public,protected,private),不然報錯,這個你們可以自己嘗試一下,然後將定義好的AIDL文件的後綴名.java改成.aidl,此時在gen文件夾下面就會多出一個與之對應的java文件,這個是編譯器乾的事情。這個AIDL接口中定義的一般都是Client和Service交互的接口。

下面來看一下StudentQuery.aidl文件:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package cn.itcast.aidl;  
  2. //注意沒有任何的訪問權限修飾符  
  3. interface StudentQuery {  
  4.     //通過number來訪問學生的name  
  5.     String queryStudent(int number);  
  6. }  
代碼結構和接口是大同小異的。

再來看一下服務端的代碼:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package cn.itcast.remote.service;  
  2.   
  3. import cn.itcast.aidl.StudentQuery;  
  4. import android.app.Service;  
  5. import android.content.Intent;  
  6. import android.os.IBinder;  
  7. import android.os.RemoteException;  
  8. /** 
  9.  * 遠程服務端 
  10.  */  
  11. public class StudentQueryService extends Service {  
  12.     //姓名名稱  
  13.     private String[] names = {"張飛""李靜""趙薇"};  
  14.       
  15.     private IBinder binder = new StudentQueryBinder();  
  16.       
  17.     @Override  
  18.     public IBinder onBind(Intent intent) {  
  19.         return binder;  
  20.     }  
  21.     /** 
  22.      * 服務中定義的訪問方法 
  23.      * @param number 
  24.      * @return 
  25.      */  
  26.     private String query(int number){  
  27.         if(number > 0 && number < 4){  
  28.             return names[number - 1];  
  29.         }  
  30.         return null;  
  31.     }  
  32.       
  33.     /** 
  34.      * 定義Binder,這裏需要繼承StudentQuery.Stub 
  35.      * StudentQuery是我們定義的AIDL 
  36.      * @author weijiang204321 
  37.      * 
  38.      */  
  39.     private final class StudentQueryBinder extends StudentQuery.Stub{  
  40.         public String queryStudent(int number) throws RemoteException {           
  41.             return query(number);  
  42.         }         
  43.     }  
  44.   
  45. }  
這個服務端的代碼和我們之前的本地服務代碼差不多,不同的是我們定義的Binder類是繼承了StudentQuery.Stub類,其中StudentQuery是我們定義的AIDL文件,編譯器幫我們生成的StudentQuery.java(在gen文件夾中)這個類,下面來看一下這個類吧:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. /* 
  2.  * This file is auto-generated.  DO NOT MODIFY. 
  3.  * Original file: C:\\Users\\weijiang204321\\Desktop\\傳智播客Android視頻教程_源代碼\\remoteService\\src\\cn\\itcast\\aidl\\StudentQuery.aidl 
  4.  */  
  5. package cn.itcast.aidl;  
  6. //注意沒有任何的訪問權限修飾符  
  7.   
  8. public interface StudentQuery extends android.os.IInterface  
  9. {  
  10. /** Local-side IPC implementation stub class. */  
  11. public static abstract class Stub extends android.os.Binder implements cn.itcast.aidl.StudentQuery  
  12. {  
  13. private static final java.lang.String DESCRIPTOR = "cn.itcast.aidl.StudentQuery";  
  14. /** Construct the stub at attach it to the interface. */  
  15. public Stub()  
  16. {  
  17. this.attachInterface(this, DESCRIPTOR);  
  18. }  
  19. /** 
  20.  * Cast an IBinder object into an cn.itcast.aidl.StudentQuery interface, 
  21.  * generating a proxy if needed. 
  22.  */  
  23. public static cn.itcast.aidl.StudentQuery asInterface(android.os.IBinder obj)  
  24. {  
  25. if ((obj==null)) {  
  26. return null;  
  27. }  
  28. android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);  
  29. if (((iin!=null)&&(iin instanceof cn.itcast.aidl.StudentQuery))) {  
  30. return ((cn.itcast.aidl.StudentQuery)iin);  
  31. }  
  32. return new cn.itcast.aidl.StudentQuery.Stub.Proxy(obj);  
  33. }  
  34. @Override public android.os.IBinder asBinder()  
  35. {  
  36. return this;  
  37. }  
  38. @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException  
  39. {  
  40. switch (code)  
  41. {  
  42. case INTERFACE_TRANSACTION:  
  43. {  
  44. reply.writeString(DESCRIPTOR);  
  45. return true;  
  46. }  
  47. case TRANSACTION_queryStudent:  
  48. {  
  49. data.enforceInterface(DESCRIPTOR);  
  50. int _arg0;  
  51. _arg0 = data.readInt();  
  52. java.lang.String _result = this.queryStudent(_arg0);  
  53. reply.writeNoException();  
  54. reply.writeString(_result);  
  55. return true;  
  56. }  
  57. }  
  58. return super.onTransact(code, data, reply, flags);  
  59. }  
  60. private static class Proxy implements cn.itcast.aidl.StudentQuery  
  61. {  
  62. private android.os.IBinder mRemote;  
  63. Proxy(android.os.IBinder remote)  
  64. {  
  65. mRemote = remote;  
  66. }  
  67. @Override public android.os.IBinder asBinder()  
  68. {  
  69. return mRemote;  
  70. }  
  71. public java.lang.String getInterfaceDescriptor()  
  72. {  
  73. return DESCRIPTOR;  
  74. }  
  75. //通過number來訪問學生的name  
  76.   
  77. @Override public java.lang.String queryStudent(int number) throws android.os.RemoteException  
  78. {  
  79. android.os.Parcel _data = android.os.Parcel.obtain();  
  80. android.os.Parcel _reply = android.os.Parcel.obtain();  
  81. java.lang.String _result;  
  82. try {  
  83. _data.writeInterfaceToken(DESCRIPTOR);  
  84. _data.writeInt(number);  
  85. mRemote.transact(Stub.TRANSACTION_queryStudent, _data, _reply, 0);  
  86. _reply.readException();  
  87. _result = _reply.readString();  
  88. }  
  89. finally {  
  90. _reply.recycle();  
  91. _data.recycle();  
  92. }  
  93. return _result;  
  94. }  
  95. }  
  96. static final int TRANSACTION_queryStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);  
  97. }  
  98. //通過number來訪問學生的name  
  99.   
  100. public java.lang.String queryStudent(int number) throws android.os.RemoteException;  
  101. }  
感覺好複雜,其實我們沒必要看懂的,這個是Android內部實現的,我們在這裏可以瞭解一下,看一下那個Stub抽象類,他實現了Binder接口,所以我們需要繼承這個類就可以了,還有一個問題就是我們注意到,就是返回StudentQuery接口對象的問題:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public static cn.itcast.aidl.StudentQuery asInterface(android.os.IBinder obj)  
  2. {  
  3. if ((obj==null)) {  
  4. return null;  
  5. }  
  6. //如果bindService綁定的是同一進程的service,返回的是服務端Stub對象本身,那麼在客戶端是直接操作Stub對象,並不進行進程通信了  
  7. android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);  
  8. if (((iin!=null)&&(iin instanceof cn.itcast.aidl.StudentQuery))) {  
  9. return ((cn.itcast.aidl.StudentQuery)iin);  
  10. }  
  11. //bindService綁定的不是同一進程的service,返回的是代理對象,obj==android.os.BinderProxy對象,被包裝成一個AIDLService.Stub.Proxy代理對象  
  12. //不過AIDLService.Stub.Proxy進程間通信通過android.os.BinderProxy實現  
  13. return new cn.itcast.aidl.StudentQuery.Stub.Proxy(obj);  
  14. }  

  1. /* 
  2.  * Copyright (C) 2006 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.   
  17. package android.os;  
  18.   
  19. /** 
  20.  * Base class for Binder interfaces.  When defining a new interface, 
  21.  * you must derive it from IInterface. 
  22.  */  
  23. public interface IInterface  
  24. {  
  25.     /** 
  26.      * Retrieve the Binder object associated with this interface. 
  27.      * You must use this instead of a plain cast, so that proxy objects 
  28.      * can return the correct result. 
  29.      */  
  30.     public IBinder asBinder();  
  31. }  
上面的是Interface接口,他只有一個方法asBinder()這個方法就是返回一個IBinder對象,而我們的AIDL接口需要實現這個接口的,所以說這個asBinder()方法的功能就是將AIDL接口轉化成IBinder對象,這個是內部實現的,在asInterface()方法中可以看到:

  1. private static class Proxy implements com.demo.aidl.StudentAIDL  
  2.         {  
  3.             private android.os.IBinder mRemote;  
  4.             Proxy(android.os.IBinder remote)  
  5.             {  
  6.                 mRemote = remote;  
  7.             }  
  8.             @Override public android.os.IBinder asBinder()  
  9.             {  
  10.                 return mRemote;  
  11.             }  
  12.             public java.lang.String getInterfaceDescriptor()  
  13.             {  
  14.                 return DESCRIPTOR;  
  15.             }  
  16.             @Override public java.lang.String queryNameByNo(int no) throws android.os.RemoteException  
  17.             {  
  18.                 android.os.Parcel _data = android.os.Parcel.obtain();  
  19.                 android.os.Parcel _reply = android.os.Parcel.obtain();  
  20.                 java.lang.String _result;  
  21.                 try {  
  22.                     _data.writeInterfaceToken(DESCRIPTOR);  
  23.                     _data.writeInt(no);  
  24.                     mRemote.transact(Stub.TRANSACTION_queryNameByNo, _data, _reply, 0);  
  25.                     _reply.readException();  
  26.                     _result = _reply.readString();  
  27.                 }  
  28.                 finally {  
  29.                     _reply.recycle();  
  30.                     _data.recycle();  
  31.                 }  
  32.                 return _result;  
  33.             }  
  34.         }  
這個是代理生成類,我們可以看到這個類中是返回的mRemote對象就是IBinder的一個引用,同時也返回了一個StudentQuery實現對象。

在StudentQuery.Stub中有一個asInterface方法,這個方法中我們可以看到,如果這個Service和Client是在同一個進程中,則在Client中的StudentConnection類中返回的是IBinder就是實際的對象,如果不是在同一個進程中的話,返回的是IBinder的代理對象。

其他的問題我們暫時不看了,也不去做深入的瞭解了,


再來看一下AndroidMainfest.xml文件中配置Service:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <service android:name=".StudentQueryService">  
  2.             <intent-filter >  
  3.                 <action android:name="cn.itcast.student.query"/>                  
  4.             </intent-filter>              
  5. </service>  
這裏我們開啓服務的話,需要用到隱式意圖了,而不能直接用Service類了,因爲是在不同的應用中,這樣服務端的代碼就差不多了,我們現在來看一下客戶端的代碼結構:


首先來看一下,客戶端肯定要有aidl文件,所以我們將服務端的aidl的文件拷到Client中(包括AIDL所在的包也要拷過來,刷新一下,在gen文件夾中出現了StudentQuery.java類),下面來看一下Client的代碼(MainActivity.java):

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package cn.itcast.remoteservice.client;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.ComponentName;  
  5. import android.content.Intent;  
  6. import android.content.ServiceConnection;  
  7. import android.os.Bundle;  
  8. import android.os.IBinder;  
  9. import android.os.RemoteException;  
  10. import android.util.Log;  
  11. import android.view.View;  
  12. import android.widget.EditText;  
  13. import android.widget.TextView;  
  14. import cn.itcast.aidl.StudentQuery;  
  15.   
  16. /** 
  17.  * 客戶端的測試代碼 
  18.  * @author weijiang204321 
  19.  * 
  20.  */  
  21. public class MainActivity extends Activity {  
  22.     //定義控件  
  23.     private EditText numberText;  
  24.     private TextView resultView;  
  25.     private StudentQuery studentQuery;  
  26.       
  27.     //定義一個連接  
  28.     private StudentConnection conn = new StudentConnection();  
  29.   
  30.     @Override  
  31.     public void onCreate(Bundle savedInstanceState) {  
  32.         super.onCreate(savedInstanceState);  
  33.         setContentView(R.layout.main);  
  34.   
  35.         numberText = (EditText) this.findViewById(R.id.number);  
  36.         resultView = (TextView) this.findViewById(R.id.resultView);  
  37.         //這裏開啓一個Service使用隱式意圖action的名稱必須和remoteService中AndroidMainfest.xml中定義的服務的action的name一樣  
  38.         Intent service = new Intent("cn.itcast.student.query");  
  39.         bindService(service, conn, BIND_AUTO_CREATE);  
  40.     }  
  41.       
  42.     /** 
  43.      * 給按鈕定義點擊事件 
  44.      * @param v 
  45.      */  
  46.     public void queryStudent(View v) {  
  47.         String number = numberText.getText().toString();  
  48.         int num = Integer.valueOf(number);  
  49.         try {  
  50.             resultView.setText(studentQuery.queryStudent(num));  
  51.         } catch (RemoteException e) {  
  52.             e.printStackTrace();  
  53.         }  
  54.     }  
  55.   
  56.     @Override  
  57.     protected void onDestroy() {  
  58.         unbindService(conn);  
  59.         super.onDestroy();  
  60.     }  
  61.   
  62.     /** 
  63.      * 自定義StudentConnection實現了ServiceConnection 
  64.      * @author weijiang204321 
  65.      * 
  66.      */  
  67.     private final class StudentConnection implements ServiceConnection {  
  68.         public void onServiceConnected(ComponentName name, IBinder service) {  
  69.             //這裏就用到了Stub類中的asInterface方法,將IBinder對象轉化成接口  
  70.             studentQuery = StudentQuery.Stub.asInterface(service);  
  71.         }  
  72.         public void onServiceDisconnected(ComponentName name) {  
  73.             studentQuery = null;  
  74.         }  
  75.     }  
  76. }  
Client端的代碼結構和我們之前的本地服務的代碼結構差不多,有幾處不同,第一處不同就是那個開啓服務的方式,這裏使用的是隱式的方式開啓服務的,因爲是跨應用訪問的,還有一處不同的是返回的StudentQuery接口對象不同,本地服務的話是通過強制轉化的,而遠程服務這裏是用asInterface方法進行轉化的。

客戶端和服務端的代碼結構看完了,下面我們來運行一下,首先安裝remoteService包,然後運行remoteClient包,在文本框中輸入學號,查詢到名稱了,運行成功。在不同的應用中,一定是跨進程的,不行我們可以查看一下系統的進程:

這裏我們使用adb命令查看:

http://blog.csdn.net/jiangwei0910410003/article/details/17114491 

在這篇blog中我說到了怎麼使用這條命令

我們可以看到有兩個進程Proc #9和Proc #12

到這裏我們就介紹了本地服務和遠程服務的使用了,下面我們再來看一下額外的知識,就是怎麼在一個應用中進行遠程服務的訪問,我們之前涉及到的是跨應用的訪問,這裏其實很簡單,只需要改動一處就可以:下面是我改過的一個項目,在同一個應用中進行遠程服務的訪問,代碼都是一樣的,這裏就不做介紹了;


這裏需要修改的地方就是要在AndroidManifest.xml中修改一下Service的定義屬性:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <service   
  2.             android:name="com.demo.remoteservice.StudentService"  
  3.             android:process=":remote">  
  4.             <intent-filter >  
  5.                 <action android:name="com.demo.remoteservice.studentservice"/>  
  6.             </intent-filter>  
  7. </service>  
這裏我們添加了一個屬性就是android:process=":remote",這個屬性就是將該服務設置成遠程的,就是和Activity不在同一個進程中,具體的Service屬性說明請看我的另外的一篇blog:

http://blog.csdn.net/jiangwei0910410003/article/details/18794945

運行結果是一樣的,爲了證明服務是遠程服務,我們在使用上面的命令打印一下當前系統中的進程信息:

系統中的Proc #9和Proc #10兩個進程,這裏就介紹了怎麼在同一個應用中跨進程訪問服務。


下面在來看一下AIDL:

我們上面說到的AIDL是說他怎麼用,而且很是簡單,我現在來具體看一下AIDL文件的定義:

Aidl默認支持的類型包話java基本類型(int、long、boolean等)和(String、List、Map、CharSequence),如果要傳遞自定義的類型該如何實現呢?

要傳遞自定義類型,首先要讓自定義類型支持parcelable協議,實現步驟如下:

1>自定義類型必須實現Parcelable接口,並且實現Parcelable接口的publicvoid writeToParcel(Parcel dest, int flags)方法 。

2>自定義類型中必須含有一個名稱爲CREATOR的靜態成員,該成員對象要求實現Parcelable.Creator接口及其方法。

3> 創建一個aidl文件聲明你的自定義類型。

Parcelable接口的作用:實現了Parcelable接口的實例可以將自身的狀態信息(狀態信息通常指的是各成員變量的值)寫入Parcel,也可以從Parcel中恢復其狀態。Parcel用來完成數據的序列化傳遞。

下面來看一下例子:

1> 創建自定義類型,並實現Parcelable接口,使其支持parcelable協議。如:在cn.itcast.domain包下創建Person.java:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package cn.itcast.domain;  
  2. import android.os.Parcel;  
  3. import android.os.Parcelable;  
  4. public class Person implements Parcelable  
  5.   privateInteger id;  
  6.   private Stringname;  
  7.    
  8.   public Person(){}  
  9.   publicPerson(Integer id, String name) {  
  10.   this.id = id;  
  11.   this.name = name;  
  12.   }  
  13.   public IntegergetId() {  
  14.   return id;  
  15.   }  
  16.   public voidsetId(Integer id) {  
  17.   this.id = id;  
  18.   }  
  19.   public StringgetName() {  
  20.   return name;  
  21.   }  
  22.   public voidsetName(String name) {  
  23.   this.name = name;  
  24.   }   
  25.   @Override  
  26.   public intdescribeContents() {  
  27.   return 0;  
  28.   }  
  29.   @Override  
  30.   public voidwriteToParcel(Parcel dest, int flags) {//把javanbean中的數據寫到Parcel  
  31.   dest.writeInt(this.id);  
  32.   dest.writeString(this.name);  
  33.   }  
  34.   //添加一個靜態成員,名爲CREATOR,該對象實現了Parcelable.Creator接口  
  35.   publicstatic final Parcelable.Creator<Person> CREATOR = newParcelable.Creator<Person>(){  
  36.   @Override  
  37.   public PersoncreateFromParcel(Parcel source) {//從Parcel中讀取數據,返回person對象  
  38.   return newPerson(source.readInt(), source.readString());  
  39.   }  
  40.   @Override  
  41.   public Person[]newArray(int size) {  
  42.   return newPerson[size];  
  43.   }  
  44.   };  
  45. }  

2> 在自定義類型所在包下創建一個aidl文件對自定義類型進行聲明,文件的名稱與自定義類型同名。

3> 在接口aidl文件中使用自定義類型,需要使用import顯式導入,本例在cn.itcast.aidl包下創建IPersonService.aidl文件,內容如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package cn.itcast.aidl;  
  2. import cn.itcast.domain.Person;  
  3. interface IPersonService {  
  4.       void save(inPerson person);  
  5. }  

4> 在實現aidl文件生成的接口(本例是IPersonService),但並非直接實現接口,而是通過繼承接口的Stub來實現(Stub抽象類內部實現了aidl接口),並且實現接口方法的代碼。內容如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class ServiceBinder extends IPersonService.Stub {  
  2.        @Override  
  3.        public voidsave(Person person) throws RemoteException {  
  4.   Log.i("PersonService",person.getId()+"="+ person.getName());  
  5.        }   
  6. }  

5> 創建一個Service(服務),在服務的onBind(Intent intent)方法中返回實現了aidl接口的對象(本例是ServiceBinder)。內容如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class PersonService extends Service {  
  2.   privateServiceBinder serviceBinder = new ServiceBinder();  
  3.   @Override  
  4.   public IBinderonBind(Intent intent) {  
  5.   return serviceBinder;  
  6.   }  
  7. public class ServiceBinder extends IPersonService.Stub {  
  8.        @Override  
  9.        public void save(Person person) throwsRemoteException {  
  10.   Log.i("PersonService",person.getId()+"="+ person.getName());  
  11.        }  
  12. }  
  13. }  

其他應用可以通過隱式意圖訪問服務,意圖的動作可以自定義,AndroidManifest.xml配置代碼如下:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <service android:name=".PersonService" >  
  2.   <intent-filter>  
  3.   <actionandroid:nameactionandroid:name="cn.itcast.process.aidl.PersonService " />  
  4.   </intent-filter>  
  5. </service>  

6> 把應用中的aidl文件和所在package一起拷貝到客戶端應用的src目錄下,eclipse會自動在客戶端應用的gen目錄中爲aidl文件同步生成IPersonService.java接口文件,接下來再把自定義類型文件和類型聲明aidl文件及所在package一起拷貝到客戶端應用的src目錄下。

最後就可以在客戶端應用中實現與遠程服務的通信,代碼如下:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class ClientActivity extends Activity {  
  2.   private IPersonService personService;  
  3.   @Override  
  4.   public void onCreate(Bundle savedInstanceState) {  
  5.   super.onCreate(savedInstanceState);  
  6.   setContentView(R.layout.main);  
  7.   this.bindService(newIntent("cn.itcast.process.aidl.PersonService"),this.serviceConnection, BIND_AUTO_CREATE);//綁定到服務  
  8.   }  
  9.   @Override  
  10.   protected voidonDestroy() {  
  11.   super.onDestroy();  
  12.   this.unbindService(serviceConnection);//解除服務  
  13.   }   
  14.    
  15.   privateServiceConnection serviceConnection = new ServiceConnection() {  
  16.   @Override  
  17.   public void onServiceConnected(ComponentName name, IBinder service) {  
  18.   personService =IPersonService.Stub.asInterface(service);  
  19.   try {  
  20.   personService.save(new Person(56,"liming"));  
  21.   } catch(RemoteException e) {  
  22.   Log.e("ClientActivity",e.toString());  
  23.   }  
  24.   }  
  25.   @Override  
  26.   public void onServiceDisconnected(ComponentName name) {  
  27.   personService =null;  
  28.   }  
  29.   };  
  30. }  
這樣就可以訪問我們自己定義的類型了。


下面來總結一下定義一個遠程服務的步驟:

假設A應用需要與B應用進行通信,調用B應用中的download(String path)方法,B應用以Service方式向A應用提供服務。需要下面四個步驟:
1> 在B應用中創建*.aidl文件,aidl文件的定義和接口的定義很相類,如:在cn.itcast.aidl包下創建IDownloadService.aidl文件,內容如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package cn.itcast.aidl;  
  2. interfaceIDownloadService {  
  3.   void download(String path);  
  4. }  

當完成aidl文件創建後,eclipse會自動在項目的gen目錄中同步生成IDownloadService.java接口文件。接口文件中生成一個Stub的抽象類,裏面包括aidl定義的方法,還包括一些其它輔助方法。值得關注的是asInterface(IBinder iBinder),它返回接口類型的實例,對於遠程服務調用,遠程服務返回給客戶端的對象爲代理對象,客戶端在onServiceConnected(ComponentName name, IBinder service)方法引用該對象時不能直接強轉成接口類型的實例,而應該使用asInterface(IBinder iBinder)進行類型轉換。


編寫Aidl文件時,需要注意下面幾點:
  1.接口名和aidl文件名相同。
  2.接口和方法前不用加訪問權限修飾符public,private,protected等,也不能用final,static。
  3.Aidl默認支持的類型包話java基本類型(int、long、boolean等)和(String、List、Map、CharSequence),使用這些類型時不需要import聲明。對於List和Map中的元素類型必須是Aidl支持的類型。如果使用自定義類型作爲參數或返回值,自定義類型必須實現Parcelable接口。
  4.自定義類型和AIDL生成的其它接口類型在aidl描述文件中,應該顯式import,即便在該類和定義的包在同一個包中。
  5.在aidl文件中所有非Java基本類型參數必須加上in、out、inout標記,以指明參數是輸入參數、輸出參數還是輸入輸出參數。
  6.Java原始類型默認的標記爲in,不能爲其它標記。


2> 在B應用中實現aidl文件生成的接口(本例是IDownloadService),但並非直接實現接口,而是通過繼承接口的Stub來實現(Stub抽象類內部實現了aidl接口),並且實現接口方法的代碼。內容如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class ServiceBinder extends IDownloadService.Stub {  
  2.   @Override  
  3.   public void download(String path) throws RemoteException {  
  4.   Log.i("DownloadService",path);  
  5.   }   
  6. }  

3> 在B應用中創建一個Service(服務),在服務的onBind(Intent intent)方法中返回實現了aidl接口的對象(本例是ServiceBinder)。內容如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class DownloadService extendsService {  
  2.   private ServiceBinder serviceBinder = new ServiceBinder();  
  3.   @Override  
  4.   public IBinder onBind(Intent intent) {  
  5.   return serviceBinder;  
  6.   }  
  7.   public class ServiceBinder extends IDownloadService.Stub {  
  8.   @Override  
  9.   public void download(String path) throws RemoteException {  
  10.   Log.i("DownloadService",path);  
  11.   }   
  12.   }  
  13. }  

其他應用可以通過隱式意圖訪問服務,意圖的動作可以自定義,AndroidManifest.xml配置代碼如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <serviceandroid:name=".DownloadService">  
  2.   <intent-filter>  
  3.   <action android:name="cn.itcast.process.aidl.DownloadService"/>  
  4.   </intent-filter>  
  5. </service>  

4> 把B應用中aidl文件所在package連同aidl文件一起拷貝到客戶端A應用,eclipse會自動在A應用的gen目錄中爲aidl文件同步生成IDownloadService.java接口文件,接下來就可以在A應用中實現與B應用通信,代碼如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class ClientActivity extendsActivity {  
  2.   private IDownloadService downloadService;  
  3.   @Override  
  4.   public void onCreate(Bundle savedInstanceState) {  
  5.   super.onCreate(savedInstanceState);  
  6.   setContentView(R.layout.main);  
  7.   this.bindService(newIntent("cn.itcast.process.aidl.DownloadService"), this.serviceConnection,BIND_AUTO_CREATE);//綁定到服務  
  8.   }  
  9.   @Override  
  10.   protected void onDestroy() {  
  11.   super.onDestroy();  
  12.   this.unbindService(serviceConnection);//解除服務  
  13.   }   
  14.    
  15.   private ServiceConnection serviceConnection = new ServiceConnection() {  
  16.   @Override  
  17.   public void onServiceConnected(ComponentName name, IBinder service){  
  18.   downloadService = IDownloadService.Stub.asInterface(service);  
  19.   try {  
  20.   downloadService.download("http://www.itcast.cn");  
  21.   } catch (RemoteException e) {  
  22.   Log.e("ClientActivity", e.toString());  
  23.   }  
  24.   }  
  25.   @Override  
  26.   public void onServiceDisconnected(ComponentName name) {  
  27.   downloadService = null;  
  28.   }  
  29.   };  
  30. }  

最後看一下Service的生命週期以及和生命週期相關的方法:

與採用Context.startService()方法啓動服務有關的生命週期方法

onCreate()->onStart()->onDestroy()

onCreate()該方法在服務被創建時調用,該方法只會被調用一次,無論調用多少次startService()或bindService()方法,服務也只被創建一次。

onStart()只有採用Context.startService()方法啓動服務時纔會回調該方法。該方法在服務開始運行時被調用。多次調用startService()方法儘管不會多次創建服務,但onStart() 方法會被多次調用。

onDestroy()該方法在服務被終止時調用。

l 與採用Context.bindService()方法啓動服務有關的生命週期方法

onCreate()->onBind()->onUnbind()->onDestroy()

onBind()只有採用Context.bindService()方法啓動服務時纔會回調該方法。該方法在調用者與服務綁定時被調用,當調用者與服務已經綁定,多次調用Context.bindService()方法並不會導致該方法被多次調用。

onUnbind()只有採用Context.bindService()方法啓動服務時纔會回調該方法。該方法在調用者與服務解除綁定時被調用。

如果先採用startService()方法啓動服務,然後調用bindService()方法綁定到服務,再調用unbindService()方法解除綁定,最後調用bindService()方法再次綁定到服務,觸發的生命週期方法如下:

onCreate()->onStart()->onBind()->onUnbind()[重載後的方法需返回true]->onRebind()


總結:

Android中的服務和windows中的服務是類似的東西,服務一般沒有用戶操作界面,它運行於系統中不容易被用戶發覺,可以使用它開發如監控之類的程序。服務的開發比較簡單,如下:
第一步:繼承Service類

public class SMSService extends Service { }


第二步:在AndroidManifest.xml文件中的<application>節點裏對服務進行配置:
<service android:name=".SMSService" />


服務不能自己運行,需要通過調用Context.startService()或Context.bindService()方法啓動服務。這兩個方法都可以啓動Service,但是它們的使用場合有所不同。使用startService()方法啓用服務,調用者與服務之間沒有關連,即使調用者退出了,服務仍然運行。使用bindService()方法啓用服務,調用者與服務綁定在了一起,調用者一旦退出,服務也就終止,大有“不求同時生,必須同時死”的特點。
採用Context.startService()方法啓動服務,在服務未被創建時,系統會先調用服務的onCreate()方法,接着調用onStart()方法。如果調用startService()方法前服務已經被創建,多次調用startService()方法並不會導致多次創建服務,但會導致多次調用onStart()方法。採用startService()方法啓動的服務,只能調用Context.stopService()方法結束服務,服務結束時會調用onDestroy()方法。

下面兩張圖是Service的生命週期:


圖一



圖二


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