1. 程式人生 > >Android中Service的使用方法

Android中Service的使用方法

 

目錄

  • Service 介紹
  • Service兩種啟動方式
  • 使用
  • 測試
  • IntentService
  • Activity與Service之間的通訊
  • 繼承Binder類
  • Messenger
  • AIDL

Service 介紹

A Service is an application component that can perform long-running operations in the background and does not provide a user interface. Another application component can start a service and it will continue to run in the background even if the user switches to another application. Additionally, a component can bind to a service to interact with it and even perform interprocess communication (IPC). For example, a service might handle network transactions, play music, perform file I/O, or interact with a content provider, all from the background.

Service是一個沒有使用者介面的應用程式元件,可以在後臺長時間執行。另外一個應用程式元件可以啟動一個Service,它可以在使用者切換到其他應用的時候依舊保持在後臺執行。另外,一個元件可以繫結到Service上,與它進行通訊,甚至是IPC(程序間通訊)。

Service兩種啟動方式

  • startService
  • bindService
    其生命週期官方API介紹:

     

    Paste_Image.png

     

    需要注意的點:
    1.通過startService啟動的Service,只有呼叫了stopService(外部元件呼叫)或stopSelf(Service內部自己呼叫),才會停止。
    2.通過startService啟動的Service,在Service執行中無法與Service進行互動,即外部元件只能控制其開關,無法進行互動。
    3.通過startService啟動的Service,與外部元件之間沒有關係,外部元件的生死跟它沒有聯絡。
    3.通過startService啟動的Service,啟動之後重複啟動的話不會觸發onCreate方法,但是會重複觸發onStartCommand,其實貌似也算是資料交流了吧,不過是單向的。
    4.bindService啟動的Service表示將一個Service繫結到一個元件上,其生命週期與該元件的生命週期繫結在一起,比如綁到一個Activity,Activity在Destroy後Service跟著就Destroy了。
    4.注意是不能繫結廣播的,因為廣播發完了其生命就到頭了,常用的是綁Activity,還可以是Service。
    5.bindService啟動的Service在使用完之後可以解除繫結,當一個Service上的所有繫結的元件都解綁之後,它就會被銷燬。
    6.可以同時使用兩種啟動方式,此時的生命週期就變的有些複雜了,兩種關聯到一起,總結來說的話,先startService與先bindService兩種方式達到的效果是一樣的,即此時unbindService的話,Service並不會結束,而是要等到stopService才會結束(onDestroy);若是此時stopService,也不會結束,而是要等到unbindService時才會結束(由於已經呼叫過stopService,此時會直接onDestroy)。
    7.onRebind呼叫時機:

     

    Paste_Image.png


    當舊client與service之間的關聯在onUnbind中都結束之後,新client繫結時,
    必須是onUnbind返回true,且服務在解綁之後沒有銷燬

使用

  • startService
    建立類繼承Service,在啟動元件中呼叫:

     Intent intent1 = new Intent(ServiceActivity1.this, MyService1.class);
     startService(intent1);
    

停止方法同上

  • bindService
    1.建立類繼承Service,其中預設會有一個onBind方法,返回是一個IBinder型別物件,這個返回值就是與元件之間通訊的關鍵。可以通過自定義內部類繼承Binder,在這個類中返回Service,然後在元件中通過返回的IBinder型別物件獲取到Service物件從而進行操作;
    2.在元件中新建一個ServiceConnection物件,必須重寫兩個方法,onServiceConnected(建立連線(bind_service)時呼叫)、onServiceDisconnected(一般都不呼叫,除非是意外情況,unbind_service並不呼叫這個),在onServiceConnected中獲取到Service物件進行操作。
    MyService:

     public class MyService1 extends Service {
    
         @Override
         public void onCreate() {
             super.onCreate();
             Log.i("test_out","----->onCreate");
         }
    
         @Override
         public int onStartCommand(Intent intent, int flags, int startId) {
             Log.i("test_out","----->onStartCommand");
             return super.onStartCommand(intent, flags, startId);
         }
    
         @Override
         public void onDestroy() {
             super.onDestroy();
             Log.i("test_out","----->onDestroy");
         }
    
         public class MyBinder extends Binder {
             public MyService1 getService(){
                 return MyService1.this;
             }
         }
    
         private MyBinder mBinder = new MyBinder();
    
         @Override
         public IBinder onBind(Intent intent) {
             Log.i("test_out","----->onBind");
             return mBinder;
         }
    
         @Override
         public boolean onUnbind(Intent intent) {
    
             Log.i("test_out","----->onUnbind");
             return true;
         }
    
         @Override
         public void onRebind(Intent intent) {
             super.onRebind(intent);
             Log.i("test_out","----->onRebind");
         }
    
         public int getCount(){
             return (int) (Math.random() * 10);
         }
    
     }
    

在Activity中建立連線:
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = ((MyService1.MyBinder)service).getService();
textView.setText("" + mService.getCount());
}

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i("test_out","----->onServiceDisconnected");
        }
    };

啟動與停止:

    bindService(intent3, serviceConnection, Service.BIND_AUTO_CREATE);

第一個引數是intent,第二個是上面的serviceConnection,第三個表示繫結方式,Service.BIND_AUTO_CREATE表示繫結時不存在的話就自動建立。
解綁:unbindService(serviceConnection);

測試

startService -> stopService

Paste_Image.png


startService -> startService -> startService -> stopService

Paste_Image.png


果然重複start會呼叫onStartCommand,那麼就相當於可以發指令。
bindService -> unbindService

Paste_Image.png


bindService -> 按下返回鍵

Paste_Image.png


startService -> bindService -> unbindService

Paste_Image.png


startService -> bindService -> unbindService -> bindService -> unbindService

Paste_Image.png


startService -> bindService -> stopService

Paste_Image.png


此時unbindService :

Paste_Image.png

 

IntentService

Android封裝好的Service,在其中的onHandleIntent(Intent intent)中處理需要在子執行緒中處理的邏輯,在處理完畢後,會自動onDestroy。

Activity與Service之間的通訊

一般來講,我們將繫結到Service上的元件稱為客戶端,Service稱為服務端,他們之間的通訊可以在同一個程序,也可以在不同的程序。
要進行通訊,那個前面已經提到,就要在客戶端獲取到一個IBinder物件,而獲取這個物件的方式有三種:
繼承Binder類,使用Messenger類,使用AIDL
第一種上面已經用過了,不過這種方式是適用於同進程內通訊,因為不同的程序使用的是不同的記憶體區域。

Messenger

其實現是通過Message以及Handler來進行通訊,Handler應該都有所瞭解,一般使用最多的都是用來解決非UI執行緒更新UI的問題,Handler機制有待仔細研究一下,後面寫一篇。
其實其底層實現也是通過AIDL。
服務端(Service):
基本流程跟上面的繼承Binder類的方式差不多,如下:
首先建立一個Service,在其中實現一個Handler,用於接受訊息。
然後通過Handler建立一個Messenger例項,在onBind中返回Messager例項的Binder。

public class MyService2 extends Service {

    class ServiceHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == 1){
                Log.i("test_out","------>receive data from client!");
            }
        }
    }

    final Messenger messager = new Messenger(new ServiceHandler());

    @Override
    public IBinder onBind(Intent intent) {
        Log.i("test_out","----->onBind");
        return messager.getBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i("test_out","----->onUnbind");
        return true;
    }

}

客戶端:
通過serviceConnection中的onServiceConnected獲取到服務端傳來的Binder物件,例項化Client端的Messenger。
建立Message,通過Messager傳送訊息。

ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mMessager = new Messenger(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i("test_out","----->onServiceDisconnected");
    }
};

public void sendToMyService(){
    Message msg = new Message();
    msg.what = 1;
    try {
        mMessager.send(msg);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

@Override
protected void onStart() {
    super.onStart();
    Intent intent = new Intent();
    intent.setAction("com.gsq.service2");
    intent.setClassName("com.example.gsq.servicetest", "com.example.gsq.servicetest.service.MyService2");
    bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}

@Override
protected void onStop() {
    super.onStop();
    unbindService(serviceConnection);
}

對了,這裡我的客戶端是另外一個APK,驗證程序間通訊,那麼就要在Service註冊的時候指定其action,在Intent跳轉的時候設定完整包名和action,這個在前面學習Intent中有介紹的。

    <service android:name=".service.MyService2"
        android:exported="true"
        android:permission="com.gsq.permission.service2">
        <intent-filter>
            <action android:name="com.gsq.service2" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </service>

注意,這裡寫了android:exported之後,AS會提示你:

Exported services (services which either set exported=true or contain an intent-filter and do not specify exported=false) should define a permission that an entity must have in order to launch the service or bind to it. Without this, any application can use this service.

大意就是,最好是加一個許可權,android:permission。
自定義許可權過程:在被呼叫的應用中先定義

<permission
    android:name="com.gsq.permission.service2"
    android:protectionLevel="normal">
</permission>

然後使用,android:permission="com.gsq.permission.service2
然後在呼叫者(客戶端)宣告:

<uses-permission android:name="com.gsq.permission.service2" />

坑:自定義許可權名稱一定要是 *.permission.*
好了,這樣子Messenger的使用流程就說完了,下面就可以看一下結果了。
啟動第二個應用:

Paste_Image.png


點選button:

Paste_Image.png


OK ! 大功告成!
再點一下:

Paste_Image.png


關閉:

Paste_Image.png


上面就實現了客戶端向服務端傳送訊息。
下面就來實現一下服務端在受到訊息之後給一個response。
流程跟上面類似,要想傳送訊息就要有一個Messenger,可是我們前面不是分別在服務端建立了一個Messenger了麼,然後將這個Messenger返回給客戶端,這樣客戶端就可以通過這個Messenger向伺服器端傳送訊息,那麼既然已經通道已經存在了,我們能不能在服務端使用這個Messenger來想客戶端傳送訊息呢?
答案是否定的。因為這個Messenger是在服務端的,而且是通過服務端的handler例項化的,然後通過binder傳到客戶端,也就是說告訴客戶端你就用這個給我發訊息就行了,我就能收到,但是服務端並不知道客戶端是誰,誰拿到了這個Messenger,只知道處理通過這個Messenger傳遞來的訊息。
那麼按照這個分析的話,要想服務端向客戶端傳送訊息方式就顯而易見了,就是在客戶端例項化一個Messenger,然後傳遞給服務端,告訴它用這個Messenger給我發訊息就行了,那麼客戶端可以通過binder獲取到服務端的Messenger例項,那麼服務端怎麼獲取到客戶端的例項呢?這個就很簡單了,因為前面通道已經建立了,只需要在客戶端向服務端傳送訊息的時候將客戶端自己的Messenger傳遞過去就行了。而Message類剛好有一個引數replyTo,這個引數就是Messenger型別的,那麼我們只需要在客戶端建一個handler,然後例項化一個Messenger傳遞過去就ok了。
分析完畢,下面是實現:
首先是客戶端程式碼:

 

//建立一個處理服務端發來的訊息的handler
private class MyHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        switch(msg.what){
            case 2:
                Log.i("test_out", "------>receive response from server!");
                break;
            default:
                super.handleMessage(msg);
        }
    }
}

然後在向服務端傳送資料的時候給msg指定replyTo:

//在client向服務端傳送訊息的時候告訴服務端通過這個messenger進行回覆
msg.replyTo = new Messenger(new MyHandler());

接下來是服務端程式碼,收到訊息後獲取到messenger併發送訊息即可:

class ServiceHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        if(msg.what == 1){
            Log.i("test_out","------>receive data from client!");
            Messenger messengerFromClient = msg.replyTo;
            Message replyMessage = new Message();
            replyMessage.what = 2;
            try {
                messengerFromClient.send(replyMessage);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}

接下來就是測試了:
先啟動服務端,按下home,再啟動客戶端:
此時服務端:

Paste_Image.png


然後在客戶端點擊發送訊息按鈕:

Paste_Image.png


再看一下客戶端:

Paste_Image.png


這樣就完成了雙向通訊。
小結:要實現雙向通訊需要首先在服務端建立一個handler,通過這個handler例項化一個Messenger,通過binder將這個Messenger返回給客戶端,客戶端收到Messenger之後即可向服務端發訊息,此時在客戶端建立一個handler,並例項化一個Messenger,在向服務端發訊息時將這個Messenger傳遞過去,服務端收到訊息的同時也得到了客戶端的Messenger,就可以發訊息給客戶端。

 

Messenger是使用序列的方式來進行通訊,資料量比較大時顯然就有點吃力了,而且主要是用來傳遞訊息,但是我們還會出現在客戶端呼叫服務端的方法的情況,這種情況下Messenger就無能為力了,這時候就需要AIDL了,而且Messenger底層實現的本質上也是AIDL。

AIDL

由於水平很菜,感覺AIDL太高深了,後面單獨寫一個。

參考:
http://www.jianshu.com/p/a8e43ad5d7d2
http://blog.csdn.net/luoyanglizi/article/details/51586437
http://blog.csdn.net/luoyanglizi/article/details/51594016
非常感謝這位大神。

 

 

轉自https://www.jianshu.com/p/37c8b6901e22