Android中Service總結

Richal發表於2018-08-20

眾所周知,Android中的Service執行在後臺,即它不依賴UI介面,所以即使開啟Service的Activity退出或者被銷燬,但是隻要程式還存在,Service就處於執行的狀態,程式被殺死,Service也就被銷燬了。那麼Service是如何執行的呢?Service只能被手動開啟,它有兩種開啟方式:一種是通過context.startService(Intent intent)方法開啟,另一種是通過context.bindService(Intent intent,ServiceConnection conn, int flags),這兩種方式的應用場景取決於是否需要與Service進行通訊,如果只是啟動Service而不需要與其通訊,那麼使用第一種方式就可以了。但如果你的需求中需要與Service保持通訊,呼叫Service中的方法,例如音樂播放器,那麼需要使用第二種啟動方式。

啟動方式不同,Service中被執行的函式也不同。使用第一種方式啟動Service,函式的執行流程如下:

08-20 16:44:16.619 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onCreate
08-20 16:44:16.632 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:44:16.632 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
複製程式碼

當多次呼叫context.startService(Intent intent)方法後,執行流程如下:

08-20 16:44:16.619 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onCreate
08-20 16:44:16.632 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:44:16.632 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
08-20 16:45:25.064 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:45:25.064 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
08-20 16:45:25.858 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:45:25.858 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
08-20 16:45:26.582 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:45:26.582 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
複製程式碼

從上面的輸出可以看出,

首次呼叫context.startService(Intent intent)方法後Service中函式執行的順序是onCreate()、onStartCommand(),多次呼叫context.startService(Intent intent)之後只會執行多次onStartCommand()方法,這裡onStart()方法被onStartCommand()方法替代,多次呼叫context.startService(Intent intent)方法後,手動呼叫context.stopService(Intent intent)方法後,Service會執行如下函式:

08-20 16:45:25.858 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:45:25.858 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
08-20 16:45:26.582 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:45:26.582 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
duo08-20 17:02:16.711 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onDestroy
複製程式碼

再次呼叫context.stopService(Intent intent)方法後無效。所以多次呼叫context.startService(Intent intent)後,只需要呼叫一次context.stopService(Intent intent)方法就可以銷燬Service物件。

使用第二種方式啟動Service後即context.bindService(Intent intent,ServiceConnection conn,int flags)後,Service內的函式執行流程如下:

08-20 17:05:45.127 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onCreate
08-20 17:05:45.127 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onBind
08-20 17:05:45.129 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onServiceConnected
複製程式碼

多次執行該函式後以上函式不會被多次呼叫,該方式主要用於外界與Service進行通訊,所以一般使用該方式繫結服務時,需要建立ServiceConnection例項物件,但因為ServiceConnection是介面,該介面裡面有如下兩個方法:

public void onServiceConnected(ComponentName name, IBinder service);

public void onServiceDisconnected(ComponentName name);
複製程式碼

所以建立ServiceConnection例項物件時需要重寫以上兩個方法,其中一旦服務被繫結即執行了Service中的onBind(Intent intent)方法後,onServiceConnected方法就會被呼叫,該方法中的第二個引數IBinder即為外界和Service通訊的通道,進而可以呼叫Service內的方法,那麼這個IBinder又是在哪裡被建立的呢?通常我們在Service中會建立一個內部類繼承Binder,該Binder是Android.os包內的類,檢視該類可以發現它實現了IBinder介面

public class Binder implements IBinder
複製程式碼

恍然大悟了吧,這裡的Binder其實和ServiceConnction裡面的onServiceConnected方法中第二個引數IBinder是一個型別呀!通常情況下我們在Service中建立Binder的子類

public class MyBinder extends Binder {
        public void startDownLoad() {
            Log.d(TAG, "start download");
        }
    }
    
複製程式碼

繫結服務後,Service中會執行onBind(Intent intent)方法,所以通常我們會在該方法中建立MyBinder的例項物件並作為返回值。

@Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        return new MyBinder();
    }
複製程式碼

繫結服務成功後,該物件會作為引數傳遞給ServiceConnection內的onServiceConnected方法

 private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("MyService","onServiceConnected");
            if (null != service) {
                MyService.MyBinder binder = (MyService.MyBinder) service;
                binder.startDownLoad();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("MyService","onServiceDisconnectd");
        }
    };

複製程式碼

進而達到了和Service進行通訊的邏輯,一般自定義的方法都會寫在MyBinder中。

如果我們同時手動呼叫了startServie(Intent intent)和bindService(Intent intent)方法Service中的函式執行屬性是如何的呢
? 情形一:先呼叫startServie(Intent intent)再呼叫 bindService(Intent intent),Service內部的函式執行順序如下

08-20 18:22:16.981 15690-15690/com.example.guoyan1.rotationdemo D/MyService: onCreate
08-20 18:22:16.992 15690-15690/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 18:22:16.992 15690-15690/com.example.guoyan1.rotationdemo D/MyService: onStart
08-20 18:22:20.114 15690-15690/com.example.guoyan1.rotationdemo D/MyService: onBind
08-20 18:22:20.120 15690-15690/com.example.guoyan1.rotationdemo D/MyService: onServiceConnected
複製程式碼

那如果先呼叫bindService(Intent intent)再呼叫startServie(Intent intent)呢?

08-20 18:24:03.250 17197-17197/com.example.guoyan1.rotationdemo D/MyService: onCreate
08-20 18:24:03.261 17197-17197/com.example.guoyan1.rotationdemo D/MyService: onBind
08-20 18:24:03.262 17197-17197/com.example.guoyan1.rotationdemo D/MyService: onServiceConnected
08-20 18:24:03.262 17197-17197/com.example.guoyan1.rotationdemo D/MyService: start download
08-20 18:24:08.047 17197-17197/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 18:24:08.048 17197-17197/com.example.guoyan1.rotationdemo D/MyService: onStart
複製程式碼

綜合上述兩種情況可以發現首次繫結/開啟服務都會首先呼叫onCreate方法,並且該方法只會被呼叫一次,如果是先呼叫的startService(Intent intent)那麼Service內會先執行onStartCommand方法,進而執行onBind方法、onServiceConnected方法。如果是先呼叫的bindService(Intent intent)方法則Service內會執行onBind方法、onServiceConnecnted方法,繼續執行onStartCommand方法,並且多次呼叫onStartService(Intent intent)方法,Service內會多次執行onStartCommand方法。

至於停止/解綁Service的方法stopService(Intent intent)和unbindService(ServiceConnection conn)的呼叫就很有講究了。

僅僅手動呼叫了startService(Intent intent)或者bindService(Intent intent)後關閉/解綁Service的方法是手動呼叫相應的stopService(Intent intent)/unbindServie(ServiceConnection conn),Service內部會執行onDestroy方法。

如果手動呼叫startService(Intent intent)和bindService(Intent intent)後,繼續呼叫unbindService的話,Service內部會執行onUnbind方法,但是此時Service還處於執行的狀態,此時必須呼叫stopService(Intent intent)之後,Service內部才會執行onDestroy方法。

如果你沒有開啟/繫結服務,手動呼叫stopService(Intent intent)不會有任何反應,但是該情況下呼叫unBindService(ServiceConnection conn)會crash,報錯如下:

Caused by: java.lang.IllegalArgumentException: Service not registered: com.example.guoyan1.rotationdemo.SixthActivity$1@bd0ebc4
                                                                                      at android.app.LoadedApk.forgetServiceDispatcher(LoadedApk.java:1477)
                                                                                      at android.app.ContextImpl.unbindService(ContextImpl.java:1632)
                                                                                      at android.content.ContextWrapper.unbindService(ContextWrapper.java:707)
複製程式碼

報錯的大致內容是Service還沒有註冊呢,所以解綁異常,需要注意的是註冊Service途徑是手動呼叫bindService方法,因為實驗發現即便手動呼叫startService(Intent intent)方法後,再手動呼叫unbindService(ServiceConnection conn)後還會出現crash,報錯同上,並且多次呼叫bindService後Service中的onBind方法僅會執行一次,並且unbindService(ServiceConnection conn)只能呼叫一次,否則會報如上錯誤。

Service和Thread的區別

Service是執行在主執行緒的,只是Service的執行不依賴任何的UI介面,可以使用context.startService(Intent intent)/contenxt.bindServie(Intent intent,ServiceConnection conn,int flags)開啟誤/繫結服務,只不過使用前者開啟服務後,開啟服務的物件不能與服務通訊。而是用後者繫結服務後可以獲取服務中的Binder物件進而與其進行通訊,所以哪個物件想和Service通訊只需要呼叫context.bindService()方法就可以啦,並且Service是依賴程式的,只要程式存在Service就會處於執行的狀態。但是Thread不同,首先Thread不依賴程式,其次建立該Thread的物件可以對其進行控制,一旦建立它的物件被銷燬,Thread就處於完全不可控的狀態,因為其他任何物件對不能操作該Thread。 好了今天的總結就到這裡吧!明天繼續。



相關文章