Android Service學習筆記

參考:
Android Service完全解析,關於服務你所需知道的一切(上)
Android Service完全解析,關於服務你所需知道的一切(下)
關於Android Service真正的完全詳解,你需要知道的一切
閱讀了這兩(三)篇解析之後,將其中涉及到的東西以自己能理解的方式記錄下來。


介紹

Service主要用於在後臺處理一些耗時的邏輯,或者去執行某些需要長期運行的任務。必要的時候我們甚至可以在程序退出的情況下,讓Service在後臺繼續保持運行狀態,如音樂播放器等。
根據服務在主線程或其他線程運行,可以分爲本地服務和遠程服務;

啓動與綁定

根據服務運行的兩種形式,可以分爲啓動方式和綁定方式:

  • 啓動
    當應用組件(如 Activity)通過調用 startService() 啓動服務時,服務即處於“啓動”狀態。一旦啓動,服務即可在後臺無限期運行,即使啓動服務的組件已被銷燬也不受影響,除非手動調用才能停止服務, 已啓動的服務通常是執行單一操作,而且不會將結果返回給調用方,和啓動源沒什麼聯繫。
  • 綁定
    當應用組件通過調用bindService() 綁定到服務時,服務即處於“綁定”狀態。綁定服務提供了一個客戶端-服務器接口(ServiceConnection),通過這個藉口中的相關方法可以獲取到服務的信息,執行相關方法等,允許組件與服務進行交互、發送請求、獲取結果,甚至是利用進程間通信 (IPC) 跨進程執行這些操作。 僅當與另一個應用組件綁定時,綁定服務纔會運行。 多個組件可以同時綁定到該服務,但全部取消綁定後,該服務即會被銷燬。

雖然服務的狀態有啓動和綁定兩種,但實際上一個服務可以同時是這兩種狀態,也就是說,它既可以是啓動服務(以無限期運行),也可以是綁定服務服務,問題只是在於是否實現了相應的回調方法:onStartCommand()(允許組件啓動服務)和 onBind()(允許綁定服務)。有點需要注意的是Android系統僅會爲一個Service創建一個實例對象,所以不管是啓動服務還是綁定服務,操作的是同一個Service實例,而且由於綁定服務或者啓動服務執行順序問題將會出現以下兩種情況:

  • 先綁定服務後啓動服務
    如果當前Service實例先以綁定狀態運行,然後再以啓動狀態運行,那麼綁定服務將會轉爲啓動服務運行,這時如果之前綁定的宿主(Activity)被銷燬了,也不會影響服務的運行,服務還是會一直運行下去,指定收到調用停止服務或者內存不足時纔會銷燬該服務。
  • 先啓動服務後綁定服務
    如果當前Service實例先以啓動狀態運行,然後再以綁定狀態運行,當前啓動服務並不會轉爲綁定服務,但是還是會與宿主綁定,只是即使宿主解除綁定後,服務依然按啓動服務的生命週期在後臺運行,直到有Context調用了stopService()或是服務本身調用了stopSelf()方法抑或內存不足時纔會銷燬服務。

生命週期

使用方法

Service有幾種不同的運行方式,可以在主線程或其他線程中運行,可以跨進程交互,但整體來說不外乎在Service中編寫相對應的回調方法,然後在Activity中啓動或綁定,如果只是啓動,則之後Activity只能停止,而不能做出交互,只有綁定方式,纔可以在ServiceConnection中獲取到Service定義的Binder對象(或者Service自己也是可以的)進行交互。

繼承Service編寫自己的自定義Service

public class MyService extends Service {  
  
    public static final String TAG = "MyService";  
  
    @Override  
    public void onCreate() {  
        super.onCreate();  
        Log.d(TAG, "onCreate() executed");  
    }  
  
    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        // 只在啓動模式下執行
        Log.d(TAG, "onStartCommand() executed");  
        return super.onStartCommand(intent, flags, startId);  
    }  
      
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        Log.d(TAG, "onDestroy() executed");  
    }  
  
    @Override  
    public IBinder onBind(Intent intent) {  
        // 只在綁定時執行,需要編寫Binder對象在這裏返回
        return null;  
    }  
  
}  

啓動模式

  • AndroidManifest.xml中註冊(application標籤之內)
    <service android:name="com.example.servicetest.MyService" >
  • 在需要的時機使用Intent啓動或停止Service
    Intent startIntent = new Intent(this, MyService.class);  
    startService(startIntent);  
    Intent stopIntent = new Intent(this, MyService.class);  
    stopService(stopIntent);  

因爲編寫的Service沒有編寫onBind()方法,也沒有綁定任何東西,啓動之後就和啓動它的Activity沒有什麼關係了;
啓動過程:onCreate()->onStartCommand(),在啓動之後再startService,就只有onStartCommand()
執行了。
在服務的內部可以調用stopSelf()方法停止當前服務。

綁定模式

要和Activity關聯起來就需要實現onBind()方法,首先需要編寫一個自定義的Binder繼承自Binder基類:

class MyBinder extends Binder {  
    public void startDoSth() {  
        Log.d("TAG", "do sth");  
        // do sth  
    }  
}  

這個類將在Service對象創建時實例化並用於和Service綁定,修改之前的Service代碼:

private MyBinder mBinder = new MyBinder();
...
...
@Override  
public IBinder onBind(Intent intent) {  
    // 此處把mBinder作爲一個IBinder返回,在後面被connection獲取
    return mBinder;  
}  

同時在Activity中還需要實現一個ServiceConnection來獲取Binder並執行裏面的方法完成這一過程:

private MyService.MyBinder myBinder;  
private ServiceConnection connection = new ServiceConnection() {  
    @Override  
    public void onServiceDisconnected(ComponentName name) {  
        //Android 系統會在與服務的連接意外中斷時(例如當服務崩潰或被終止時)調用該方法。注意:當客戶端取消綁定時,系統“絕對不會”調用該方法。
    }  
  
    @Override  
    public void onServiceConnected(ComponentName name, IBinder service) { 
        // 綁定之後進行的操作,應該是自動獲取到IBinder類型的service,實際上就是MyBinder,所以直接強制類型轉換
        myBinder = (MyService.MyBinder) service;  
        myBinder.startDoSth();  
        }  
    };  

到這裏只是寫好了用於綁定的準備工作和綁定完成之後的操作,而真正的綁定過程如下:

Intent bindIntent = new Intent(this, MyService.class);  
bindService(bindIntent, connection, BIND_AUTO_CREATE); 

bindService()方法接收三個參數,第一個參數就是剛剛構建出的Intent對象,第二個參數是前面創建出的ServiceConnection的實例,第三個參數是一個標誌位,這裏傳入BIND_AUTO_CREATE表示在Activity和Service建立關聯後自動創建Service,這會使得MyService中的onCreate()方法得到執行,但onStartCommand()方法不會執行。所以綁定之後的流程是onCreate()->startDoSth()
注意到這裏並沒有onStartCommand(),而是執行onServiceConnected()中的代碼。
停止Service:

unbindService(connection);
// 這裏的service只創建了,並沒有start,所以直接解綁就可以

注意:
Stop Service只會讓Service停止,Unbind Service按鈕只會讓Service和Activity解除關聯,一個Service必須要在既沒有和任何Activity關聯又處理停止狀態的時候纔會被銷燬。
宿主(Activity)解除綁定後,綁定服務就會被銷燬

遠程Service(無法綁定)

將一個普通的Service轉換成遠程Service其實非常簡單,只需要在註冊Service的時候將它的android:process屬性指定成:remote就可以了

<service  
    android:name="com.example.servicetest.MyService"  
    android:process=":remote" >  
</service>  

遠程Service不在主線程中運行,耗時操作不會阻塞進程,但是Activity和Service運行在兩個不同的進程當中,也就不能再使用傳統的建立關聯的方式,根本無法綁定了。所以這時只能start而不能使用bind方法來使用Service。

綁定遠程Service(改進版/跨進程通信)

使用AIDL

此處使用了AIDL(Android Interface Definition Language),是Android接口定義語言的意思,它可以用於讓某個Service與多個應用程序組件之間進行跨進程通信,從而可以實現多個應用程序共享同一個Service的功能。
新建aidl,之後會自動生成這樣一個java文件:

package com.example.servicetest;  
interface MyAIDLService {  
    int plus(int a, int b);  
    String toUpperCase(String str);  
}

然後修改MyService中的代碼,在裏面實現定義好的MyAIDLService接口(沒弄明白Stub哪來的,此處理解爲一個存根),此處把實例化Binder改成了實例化一個Stub:

MyAIDLService.Stub mBinder = new Stub() {  
    @Override  
    public String toUpperCase(String str) throws RemoteException {  
        if (str != null) {  
            return str.toUpperCase();  
        }  
        return null;  
    }  

    @Override  
    public int plus(int a, int b) throws RemoteException {  
        return a + b;  
    }  
};  

然後在onBind()方法中將MyAIDLService.Stub的實現返回。因爲Stub其實就是Binder的子類,所以在onBind()方法中可以直接返回Stub的實現。
修改connection:

@Override  
public void onServiceConnected(ComponentName name, IBinder service) {  
      myAIDLService = MyAIDLService.Stub.asInterface(service);  
      // 調用Service中的方法
      try {  
          int result = myAIDLService.plus(3, 5);  
          String upperStr = myAIDLService.toUpperCase("hello world");  
          Log.d("TAG", "result is " + result);  
          Log.d("TAG", "upperStr is " + upperStr);  
      } catch (RemoteException e) {  
          e.printStackTrace();  
      }  
} 

關於Stub:
j2ee裏面的stub是這樣說的..爲屏蔽客戶調用遠程主機上的對象,必須提供某種方式來模擬本地對象,這種本地對象稱爲存根(stub),存根負責接收本地方法調用,並將它們委派給各自的具體實現對象

此時依然是調用bindService()來綁定服務,在一個Activity裏調用了同一個應用程序的Service裏的方法。

跨進程

在另一個應用程序中去綁定Service的時候並沒有MyService這個類,這時就必須使用到隱式Intent了。現在修改AndroidManifest.xml中的代碼,給MyService加上一個action

<service  
    android:name="com.example.servicetest.MyService"  
    android:process=":remote" >  
    <intent-filter>  
        <action android:name="com.example.servicetest.MyAIDLService"/>  
    </intent-filter>  
</service>  

在另一個程序中,需要複製一份MyAIDLService.aidl文件,注意要將原有的包路徑一起拷貝過來,綁定的代碼修改如下:

Intent intent = new Intent("com.example.servicetest.MyAIDLService").setPakage("com.example.servicetest");;  
bindService(intent, connection, BIND_AUTO_CREATE);  

將Intent的action指定成了com.example.servicetest.MyAIDLService。

使用Messenger

  1. 服務實現一個 Handler,由其接收來自客戶端的每個調用的回調
 /**
 * 用於接收從客戶端傳遞過來的數據
 */
class IncomingHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_SAY_HELLO:
                Log.i(TAG, "thanks,Service had receiver message from client!");
                break;
            default:
                super.handleMessage(msg);
        }
    }
}
  1. Handler 用於創建 Messenger 對象(對 Handler 的引用)
/**
* 創建Messenger並傳入Handler實例對象
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
  1. Messenger 創建一個 IBinder,服務通過 onBind() 使其返回客戶端
/**
 * 當綁定Service時,該方法被調用,將通過mMessenger返回一個實現
 * IBinder接口的實例對象
 */
@Override
public IBinder onBind(Intent intent) {
    Log.i(TAG, "Service is invoke onBind");
    return mMessenger.getBinder();
}
  1. 客戶端使用 IBinder 將 Messenger(引用服務的 Handler)實例化,然後使用Messenger將 Message 對象發送給服務
public void onServiceConnected(ComponentName className, IBinder service) {
    /**
     * 通過服務端傳遞的IBinder對象,創建相應的Messenger
     * 通過該Messenger對象與服務端進行交互
     */
    mService = new Messenger(service);
    mBound = true;
}
  1. 服務在其 Handler 中(在 handleMessage() 方法中)接收每個 Message

前臺服務

前臺服務被認爲是用戶主動意識到的一種服務,因此在內存不足時,系統也不會考慮將其終止。 前臺服務必須爲狀態欄提供通知,狀態欄位於“正在進行”標題下方,這意味着除非服務停止或從前臺刪除,否則不能清除通知。
Android官方給我們提供了兩個方法,分別是startForeground()和stopForeground(),這兩個方式解析如下:

  • startForeground(int id, Notification notification)
    該方法的作用是把當前服務設置爲前臺服務,其中id參數代表唯一標識通知的整型數,需要注意的是提供給 startForeground() 的整型 ID 不得爲 0,而notification是一個狀態欄的通知。
  • stopForeground(boolean removeNotification)
    該方法是用來從前臺刪除服務,此方法傳入一個布爾值,指示是否也刪除狀態欄通知,true爲刪除。 注意該方法並不會停止服務。 但是,如果在服務正在前臺運行時將其停止,則通知也會被刪除。
    Myservice.java
 /**
 * Notification
 */
public void createNotification(){
    //使用兼容版本
    NotificationCompat.Builder builder=new NotificationCompat.Builder(this);
    //設置狀態欄的通知圖標
    builder.setSmallIcon(R.mipmap.ic_launcher);
    //設置通知欄橫條的圖標
    builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.screenflash_logo));
    //禁止用戶點擊刪除按鈕刪除
    builder.setAutoCancel(false);
    //禁止滑動刪除
    builder.setOngoing(true);
    //右上角的時間顯示
    builder.setShowWhen(true);
    //設置通知欄的標題內容
    builder.setContentTitle("I am Foreground Service!!!");
    //創建通知
    Notification notification = builder.build();
    //設置爲前臺服務
    startForeground(NOTIFICATION_DOWNLOAD_PROGRESS_ID,notification);
}

 @Override
public int onStartCommand(Intent intent, int flags, int startId) {
    int i=intent.getExtras().getInt("cmd");
    if(i==0){
        if(!isRemove) {
            createNotification();
        }
        isRemove=true;
    }else {
        //移除前臺服務
        if (isRemove) {
            stopForeground(true);
        }
        isRemove=false;
    }

    return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
    //移除前臺服務
    if (isRemove) {
        stopForeground(true);
    }
    isRemove=false;
    super.onDestroy();
}

onStartCommand詳解

onStartCommand(tent intent, int flags, int startId)

參數

  • intent :啓動時,啓動組件傳遞過來的Intent,如Activity可利用Intent封裝所需要的參數並傳遞給Service
  • flags:表示啓動請求時是否有額外數據,可選值有 0,START_FLAG_REDELIVERYSTART_FLAG_RETRY,0代表沒有,它們具體含義如下:
  • START_FLAG_REDELIVERY
    這個值代表了onStartCommand方法的返回值爲
    START_REDELIVER_INTENT,而且在上一次服務被殺死前會去調用stopSelf方法停止服務。其中START_REDELIVER_INTENT意味着當Service因內存不足而被系統kill後,則會重建服務,並通過傳遞給服務的最後一個 Intent 調用 onStartCommand(),此時Intent時有值的。
  • START_FLAG_RETRY
    該flag代表當onStartCommand調用後一直沒有返回值時,會嘗試重新去調用onStartCommand()
  • startId : 指明當前服務的唯一ID,與stopSelfResult(int startId)配合使用,stopSelfResult 可以更安全地根據ID停止服務。

返回值

實際上onStartCommand的返回值int類型纔是最最值得注意的,它有三種可選值, START_STICKYSTART_NOT_STICKYSTART_REDELIVER_INTENT,它們具體含義如下:

  • START_STICKY
    當Service因內存不足而被系統kill後,一段時間後內存再次空閒時,系統將會嘗試重新創建此Service,一旦創建成功後將回調onStartCommand方法,但其中的Intent將是null,除非有掛起的Intent,如pendingintent,這個狀態下比較適用於不執行命令、但無限期運行並等待作業的媒體播放器或類似服務。
  • START_NOT_STICKY
    當Service因內存不足而被系統kill後,即使系統內存再次空閒時,系統也不會嘗試重新創建此Service。除非程序中再次調用startService啓動此Service,這是最安全的選項,可以避免在不必要時以及應用能夠輕鬆重啓所有未完成的作業時運行服務。
  • START_REDELIVER_INTENT
    當Service因內存不足而被系統kill後,則會重建服務,並通過傳遞給服務的最後一個 Intent 調用 onStartCommand(),任何掛起 Intent均依次傳遞。與START_STICKY不同的是,其中的傳遞的Intent將是非空,是最後一次調用startService中的intent。這個值適用於主動執行應該立即恢復的作業(例如下載文件)的服務。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章