Android的Service深入分析(全)

fmms 12年前發布 | 116K 次閱讀 Android Android開發 移動開發

1.先來看看What is Service?

A Service is not a separate process And A Service is not a thread. 一個服務不是一個獨立的進程,也不是一個線程。

那Service是什么呢?

  后臺運行 處理耗時較長的工作

  Service是一個應用程序組件

  Service沒有圖形化界面

  Service通常用來處理一些耗時比較長的操作

  可以使用Service更新ContentProvider,發送Intent以及啟動系統的通知等等

2. 接著來看看Service的整個生命周期:

Service的活動生命周期是在onStart()之后,這個方法會處理通過startServices()方法傳遞來的Intent對象。音樂Service可以通過開打intent對象來找到要播放的音樂,然后開始后臺播放。

Service停止時沒有相應的回調方法,即沒有onStop()方法。onCreate()方法和onDestroy()方法是針對所有的Services,無論它們是否啟動。通過Context.startService()Context.bindService()方法。然而,只有通過startService()方法啟動的Service才會被調用onStart()方法。如果一個Service允許別人綁定,那么需要實現以下額外的方法:

IBinder onBind(Intent intent)

boolean onUnbind(Intent intent)

void onRebind(Intent intent)

onBind()回調方法會繼續傳遞通過bindService()傳遞來的intent對像。onUnbind()會處理傳遞給unbindService()intent對象。如果Service允許綁定,onBind()會返回客戶端與服務互相聯系的通信頻道。如果建立了一個新的客戶端與服務的鏈接,onUnbind()方法可以請求調用onRebind()方法。

下面的圖表介紹了Service的回調方法,然而,它把通過startService()方法建立的服務從通過bindService()方法建立的服務分離開。記住任何服務,無果它怎樣建立,都默認客戶端可以鏈接,所以任何的Service能夠接收onBind()和onUnbind()方法。 


3. Service組件的通信方式一共有三種:(1)通過startService來啟動的Service;(2)通過bindService來啟動的Service;(3)使用AIDL方式的Service,下面我們來看看它們的具體使用方法,和它們之間有什么區別。

(1)首先來說說通過startService來啟動的Service(后臺處理工作)

          startService(Intent service),通過intent值來指定啟動哪個Service,可以直接指定目標Service的名,也可以通過Intent的action屬性來啟動設置了相應action屬性的Service

,使用這種方式啟動的Service,當啟動它的Activity被銷毀時,是不會影響到它的運行的,這時它仍然繼續在后臺運行它的工作。直至調用StopService(Intent service)方法時

時或者是當系統資源非常緊缺時,這個服務才會調用onDestory()方法停止運行。所以這種Service一般可以用做,處理一些耗時的工作。可能有人會問,如果Service不是獨立的

一個進程的話,為什么當Activity退出時,Service仍然可以進行運行呢?其實是這樣的,sdk上說了activity和service默認是運行在應用進程的主線程中,四大組件默認都是和

activity運行在同一個主線程中的,那就是說activity通過startservice方法啟動一個服務后,被啟動的服務和activity都是在同一個線程中的。所以當我主動銷毀了這個activity,但是

他所在的線程還是存在的,只不過是這個activity他所占用的資源被釋放掉了,這個activity所在的主線程只有當android內存不足才會被殺死掉,否則一般的情況下這個activity所

在的應用程序的線程始終存在,也就是這個activity所啟動的服務也會一直運行下去。

         還有一點需要注意的是,如果Service要處理一些比較耗時的工作時,因為Service和Activity默認情況都在同一個主線程中的緣故,所以要操作這些耗時的工作一般是在

Service里另起一個新線程來處理。這樣可以避免主線程的阻塞,影響用戶體驗性。

(2)然后來說說通過bindService來啟動的Service(在本地同進程內與Activity交互)

          這里舉一個實例來講:

  首選,創建一個接口,IService

         package yy.android.service
         public interface IService {
             String getName();
        }   

 接著,創建一個服務LocalService  

package yy.android.service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

     public class LocalService extends Service{
        private static final String TAG="LocalService";
           private MyBind myBind=new MyBind(); 
           public IBinder onBind(Intent intent) {
            Log.d(TAG, "localService onBind");
               return myBind;
          } 
       @Override
        public void onCreate() {
            super.onCreate();
            Log.d(TAG, "localService onCreate");
       }
       @Override
       public void onDestroy() {
             super.onDestroy();
             Log.d(TAG, "localService onDestroy");
       }
       @Override
       public void onStart(Intent intent, int startId) {
           // TODO Auto-generated method stub
           super.onStart(intent, startId); 
           Log.d(TAG, "localService onStart");
       } 
      @Override
      public boolean onUnbind(Intent intent) {
           // TODO Auto-generated method stub
       Log.d(TAG, "localService onUnBind");
          return super.onUnbind(intent); 
      }
      public class MyBind extends Binder implements IService{
           public String getName() {
            // TODO Auto-generated method stub
            return "YUZHIBOYI"; 
            } 
         }  
     }

 最后就是實現ServiceActivity了:

package yy.android.service;

import android.app.Activity;
import android.os.Bundle;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder; 
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;


public class ServiceActivity extends Activity { 
     
 private static final String TAG="ServiceActivity";
    private IService iService=null;
    private EditText edit;
    private Button mBind; 
    ServiceConnection connection=new ServiceConnection() {
        public void onServiceDisconnected(ComponentName name) {
         Log.d(TAG,"DisConnection");
            System.out.println("DisConnection!!!");
        }
        public void onServiceConnected(ComponentName name, IBinder service) {
            // TODO Auto-generated method stub 
           Log.d(TAG,"Connection");
              System.out.println("Connection!!!");
              iService=(IService)service;
              edit.setText(iService.getName());
            // text.setText(iService.getName());
        }
    }; 
    @Override 
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
      //  Log.d(TAG,"Start");
        edit =(EditText)findViewById(R.id.edit); 
        mBind = (Button)findViewById(R.id.Connection);
        mBind.setOnClickListener(new OnClickListener() { 
            public void onClick(View v) {
                 Intent intent=new Intent(ServiceActivity.this,LocalService.class);
                 bindService(intent,connection, BIND_AUTO_CREATE);    
            }
        });
    }
}

AndroidManifest.xml注冊LocalService

<service android:name = ".LocalService">  
            <intent-filter>  
            </intent-filter>  
</service>  

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
     <EditText
        android:id="@+id/edit"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
     ></EditText>      
     <Button 
        android:id="@+id/Connection"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="@string/Bind"/>
</LinearLayout>
當運行程序是,日志信息是這樣的:05-10 13:46:28.353: D/LocalService(1328): localService onCreate

                                                                 05-10 13:46:28.353: D/LocalService(1328): localService onBind

                                                                 05-10 13:46:28.385: D/ServiceActivity(1328): Connection

當點擊模擬器的back按鈕時,日志信息:   05-10 13:46:57.953: D/LocalService(1328): localService onUnBind

                                                                           05-10 13:46:57.953: D/LocalService(1328): localService onDestroy

從日志信息可以分析出,整個程序的運行過程是這樣的:點用戶點擊綁定服務按鈕時Activity調用bindService()方法,然后系統就去調用onCreate創建服務,然后系統繼續調用onBind()方法向Activity傳遞一個IBinder類型的對象, 是傳遞給Activity里的ServiceConnection里的onServiceConnected(ComponentName name, IBinder service)的第二個參數,然后通過這個參數可以獲得IService的方法。進行本地的Activity和Service交互。(在同一個進程里進行的),當用戶點擊back建時,系統就調用onUnbind()再接著調用onDestory()方法銷毀服務。總結可以理解成:bindSevice()->onCreate()->onBind()->onServiceConnected();這里需要注意的一點是,啟動的LocalService是和ServiceActivity在同一個進程里的,因為在注冊服務時,沒有配置它的android:process = "xxxx" 屬性。具體android:process的用法可以自己去看其他資料。

(3)最后要說的是使用AIDL方式的Service(進行跨進程的通信)

AIDL(Android Interface Definition Language)  IPC機制是面向對象的,輕量級的。通過AIDL定義的接口可以實現服務器端與客戶端的IPC通信。在Android上,一個進程不能簡單的像訪問本進程內存一樣訪問其他進程的內存。所以,進程間想要對話,需要將對象拆解為操作系統可以理解的基本數據單元,并且有序的通過進程邊界。通過代碼來實現這個數據傳輸過程是冗長乏味的,所幸的是android提供了AIDL工具來幫我們完成了此項工作。

注意:僅僅在你需要A應用程序的客戶端訪問B應用程序的服務器端來實現 IPC通信,并且在服務器端需要處理多線程(客戶端)訪問的情況下使用AIDL。如果不需要使用到進程間的IPC通信,那么通過Binder接口實現將更為合適,如果需要實現進程間的IPC通信,但不需要處理多線程(多客戶端),通過Messager接口來實現將更為合適。不管怎樣,在使用AIDL之前,應先確保已理解了Bound Service。

AIDL接口的調用采用的是直接的函數調用方式,但你無法預知哪個進程(或線程)將調用該接口。同進程的線程調用和其他進程調用該接口之間是有所區別的:

  • 在同進程中調用AIDL接口,AIDL接口代碼的執行將在調用該AIDL接口的線程中完成,如果在主UI線程中調用AIDL接口,那么AIDL接口代碼的執行將會在這個主UI線程中完成。如果是其他線程,AIDL接口代碼的執行將在service中完成。因此,如果僅僅是本進程中的線程訪問該服務,你完全可以控制哪些線程將訪問這個服務(但是如果是這樣,那就完全沒必要使用AIDL了,而采取Binder接口的方式更為合適)。
  • 遠程進程(其他線程)調用AIDL接口時,將會在AIDL所屬的進程的線程池中分派一個線程來執行該AIDL代碼,所以編寫AIDL時,你必須準備好可能有未知線程訪問、同一時間可能有多個調用發生(多個線程的訪問),所以ADIL接口的實現必須是線程安全的。
  • 可以用關鍵字oneway來標明遠程調用的行為屬性,如果使用了該關鍵字,那么遠程調用將僅僅是調用所需的數據傳輸過來并立即返回,而不會等待結果的返回,也即是說不會阻塞遠程線程的運行。AIDL接口將最終將獲得一個從Binder線程池中產生的調用(和普通的遠程調用類似)。如果關鍵字oneway在本地調用中被使用,將不會對函數調用有任何影響。  

定義AIDL接口

AIDL接口使用后綴名位.aidl的文件來定義,.aidl文件使用java語法編寫,并且將該.aidl文件保存在 src/目錄下(無論是服務端還是客戶端都得保存同樣的一份拷貝,也就是說只要是需要使用到該AIDL接口的應用程序都得在其src目錄下擁有一份.aidl文件的拷貝)。

編譯時,Android sdk 工具將會為 src/目錄下的.aidl文件在 gen/ 目錄下產生一個IBinder接口。服務端必須相應的實現該IBinder接口。客戶端可以綁定該服務、調用其中的方法實現IPC通信。

 

創建一個用AIDL實現的服務端,需要以下幾個步驟: 
    1. 創建.aidl文件:

        該文件(YourInterface.aidl)定義了客戶端可用的方法和數據的接口

    2. 實現這個接口:

        Android SDK將會根據你的.aidl文件產生AIDL接口。生成的接口包含一個名為Stub的抽象內部類,該類聲明了所有.aidl中描述的方法,你必須在代碼里繼承該Stub類并且實現.aidl中定義的方法。

    3.向客戶端公開服務端的接口:

        實現一個Service,并且在onBinder方法中返回第2步中實現的那個Stub類的子類(實現類)。

注意:

服務端AIDL的任何修改都必須的同步到所有的客戶端,否則客戶端調用服務端得接口可能會導致程序異常(因為此時客戶端此時可能會調用到服務端已不再支持的接口

1. 創建.aidl文件

AIDL使用簡單的語法來聲明接口,描述其方法以及方法的參數和返回值。這些參數和返回值可以是任何類型,甚至是其他AIDL生成的接口。重要的是必須導入所有非內置類型,哪怕是這些類型是在與接口相同的包中。

默認的AIDL支持一下的數據類型(這些類型不需要通過import導入):

  • java語言的原始數據類型(包括 int, long, char, boolen 等等)
  • String
  • CharSequence:該類是被TextView和其他控件對象使用的字符序列
  • List:列表中的所有元素必須是在此列出的類型,包括其他AIDL生成的接口和可打包類型。List可以像一般的類(例如List<String>)那樣使用,另一邊接收的具體類一般是一個ArrayList,這些方法會使用List接口
  • Map:Map中的所有元素必須是在此列出的類型,包括其他AIDL生成的接口和可打包類型。一般的maps(例如Map<String,Integer>)不被支持,另一邊接收的具體類一般是一個HashMap,這些方法會使用Map接口。

對于其他的類型,在aidl中必須使用import導入,即使該類型和aidl處于同一包內。

定義一個服務端接口時,注意一下幾點:

  • 方法可以有0個或多個參數,可以使空返回值也可以返回所需的數據。
  • 所有非原始數據類型的參數必須指定參數方向(是傳入參數,還是傳出參數),傳入參數使用in關鍵字標記,傳出參數使用out,傳入傳出參數使用inout。如果沒有顯示的指定,那么將缺省使用in。
  • 在aidl文件中所有的注釋都將會包含在生成的IBinder接口中(在Import和pacakge語句之上的注釋除外)。
  • aidl中只支持成員方法,不支持成員變量。 
我們通過一個例子來說明:
(服務端)YAIDLService工程里的包yy.service.aidl有以下三個文件
IAIDLService.aidl
package yy.service.aidl;  
interface IAIDLService {  
    String getName();  
}

將該.aidl文件保存在工程目錄中的 src/目錄下,當編譯生成apk時,sdk 工具將會在 gen/ 目錄下生成一個對應的IBiner接口的.java文件。

如果使用eclipse編寫app,那么這個IBinder接口文件將會瞬間生成。

生成的接口包含一個名為Stub的抽象的內部類,該類聲明了所有.aidl中描述的方法,

注意:

Stub還定義了少量的輔助方法,尤其是asInterface(),通過它或以獲得IBinder(當applicationContext.bindService()成功調用時傳遞到客戶端的 onServiceConnected())并且返回用于調用IPC方法的接口實例

YAIDLService.java
package yy.service.aidl;
import yy.service.aidl.IAIDLService.Stub;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
public class YAIDLService extends Service{
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return mBinder;
}
    private IAIDLService.Stub mBinder = new Stub() {  
  
        public String getName() throws RemoteException {  
            return "YUZHIBOYI";  
        }  
};
}
在服務端里的服務程序里實現接口。這樣,mBinder就是一個Stub類得對象,該對象為service提供了IPC接口,并將會向客戶端公開,這樣客戶端就可以通過該對象與該service進行交互了。現在,如果客戶端(比如一個Activity)調用bindService()來連接該服務端(YAIDLService) ,客戶端的onServiceConnected()回調函數將會獲得從服務端(YAIDLService )的onBind()返回的mBinder對象

實現ADIL接口時需要注意一下幾點:

  • 不能保證所有對aidl接口的調用都在主線程中執行,所以必須考慮多線程調用的情況,也就是必須考慮線程安全。
  • 默認IPC調用是同步的。如果已知IPC服務端會花費很多毫秒才能完成,那就不要在Activity或View線程中調用,否則會引起應用程序掛起(Android可能會顯示“應用程序未響應”對話框),可以試著在獨立的線程中調用。
  • 不會將異常返回給調用方 
YAIDLServiceActivity.java(這個文件是創建Activity自動生成的可以不用)

(客戶端)YAIDLClient工程里的yy.client.aidl包里有
文件YAIDLClientActivity.java
package yy.client.aidl;
import yy.service.aidl.IAIDLService;
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.os.RemoteException;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class YAIDLClientActivity extends Activity {
    private IAIDLService mAIDLService;  
    private TextView mName;  
    private Button mMessage;  
    private Button mPerson;      
    private ServiceConnection mServiceConnection = new ServiceConnection() {     
        public void onServiceConnected(ComponentName name, IBinder service) {  
            mAIDLService = IAIDLService.Stub.asInterface(service); 
            System.out.println("OnService!!!");
        }
        public void onServiceDisconnected(ComponentName name) {  
            mAIDLService = null;  
            System.out.println("DisService!!!");
        }
    };  
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        findViewById(R.id.connection).setOnClickListener(new OnClickListener() {  
            public void onClick(View view) {            
                /** 
                 * 第一步,單擊"連接"按鈕后用mServiceConnection去bind服務器端創建的Service。 
                 */  
                Intent service = new Intent("yy.service.aidl.IAIDLService");  
                bindService(service, mServiceConnection, BIND_AUTO_CREATE);  
            }  
        });  
        mName = (TextView)findViewById(R.id.name);
        mMessage = (Button) findViewById(R.id.message);  
        mMessage.setOnClickListener(new OnClickListener() {  
            public void onClick(View view) {  
                /** 
                 * 第二步,從服務器端獲取字符串。 
                 */  
                try {  
                    mName.setText(mAIDLService.getName());  
                } catch (RemoteException e) {  
                    e.printStackTrace();  
                }  
            }  
        });  
    }
}
在包yy.service.aidl包里有文件
IAIDLService.aidl
package yy.service.aidl;  
  
interface IAIDLService {  
    String getName();  
}

客戶端同樣得訪問該接口類(這里指YAIDLService),所以,如果服務端和客戶端不在同一進程(應用程序)中,那么客戶端也必須在 src/ 目錄下擁有和服務端同樣的一份.aidl文件的拷貝(同樣是指,包名、類名、內容完全一模一樣),客戶端將會通過這個.aidl文件生成android.os.Binder接口——以此來實現客戶端訪問AIDL中的方法。當客戶端在onServiceConnected()回調方法中獲得IBinder對象后,必須通過調用YourServiceInterface.Stub.asInterface(service)將其轉化成為YourServiceInterface類型如上程序。


服務端的AndroidMainfest.xml里注冊Service
<service android:name = ".YAIDLService" android:process = ":remote">  
          <intent-filter>  
              <action android:name = "yy.service.aidl.IAIDLService" />  
          </intent-filter>  
       </service> 

總結:這里給出調用遠端AIDL接口的步驟:

    1. 在 src/ 目錄下包含.adil文件。

    2. 聲明一個IBinder接口(通過.aidl文件生成的)的實例。

    3. 實現ServiceConnection.

    4. 調用Context.bindService()綁定你的ServiceConnection實現類的對象(也就是遠程服務端)。

    5. 在onServiceConnected()方法中會接收到IBinder對象(也就是服務端),調用YourInterfaceName.Stub.asInterface((IBinder)service)將返回值轉換為YourInterface類型。

    6. 進行跨進程交互。

好了,Service大概就介紹完了,歡迎大家拍磚,一起討論!!!

 本文由用戶 fmms 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!