Android的Service深入分析


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. 進行跨進程交互。

 

    from:http://www.open-open.com/lib/view/open1336702234921.html


向博主發送郵件

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