Android 活動(activity)和服務(service)進行通訊

童思宇發表於2018-02-07

本片文章還是在Android 啟動和停止服務的基礎上進行修改,通過啟動和停止服務,不知道你有沒有發現,雖然服務是在活動裡啟動的,但是在啟動了服務之後,活動與服務基本就沒有什麼關係了,確實如此,我們在活動裡呼叫了startService()方法來啟動MyService這個服務,然後MyService的onCreate()和onStartCommand()方法就會得到執行,之後服務會一直處於執行狀態,但是具體執行的是什麼邏輯,活動就控制不了了,這就類似與活動通知了服務一下:"你可以啟動了!"然後服務就去忙自己的事情了,但活動並不知道服務到底去做什麼事情,以及完成得如何,

那麼有沒有什麼辦法能讓活動和服務的關係更緊密一些呢?例如在活動中指揮服務去幹什麼,服務就去幹什麼,當然可以,這就需要藉助我們剛剛忽略的onBind()方法了.

比如說,,目前我們希望在MyService裡提供一個下載功能,然後在活動中可以決定何時開始下載,以及隨時檢視下載進度,實現這個功能的思路是建立一個專門的Binder物件來對下載功能進行管理,修改MyService中的程式碼,如下:

public class MyService extends Service {
    
    private DownloadBinder mBinder = new DownloadBinder();
    
    class DownloadBinder extends Binder{
        
        public void startDownlad(){
            Log.d(TAG, "startDownlad: executed");
        }
        
        public int getProgress(){
            Log.d(TAG, "getProgress: executed");
            return 0;
        }
    }
    ...

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }

    ...
}
可以看到,這裡我們新建了一個DownloadBinder類,並讓它繼承自Binder,然後在它的內部提供了開始下載以及檢視下載進度的方法,當然這只是兩個模擬方法,並沒有實現真正的功能,我們在這兩個方法中分別列印了一行日誌.

接著,在MyService中建立了DownloadBinder的例項,然後在onBind()方法裡返回了這個例項,這樣MyService中的工作就全部完成了.

下面就要看一看,在活動中如何去呼叫服務裡的這寫方法了,首先需要在佈局檔案裡新增兩個按鈕,修改activity_main.xml中的程式碼,如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.servicetest.MainActivity">

    ...
    
    <Button
        android:id="@+id/bind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bind Service"
        android:textAllCaps="false"
        app:layout_constraintTop_toBottomOf="@id/stop_service"/>
    
    <Button
        android:id="@+id/unbind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Unbind Service"
        android:textAllCaps="false"
        app:layout_constraintTop_toBottomOf="@id/bind_service"/>

</android.support.constraint.ConstraintLayout>
這兩個按鈕分別是用於繫結服務和取消服務的,那到底誰需要去和服務繫結呢?當然是活動了,當一個活動和服務繫結了之後,就可以呼叫該服務裡的Binder提供的方法了,修改MainActivity中的程式碼,如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private MyService.DownloadBinder downloadBinder;
    
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            downloadBinder = (MyService.DownloadBinder) iBinder;
            downloadBinder.startDownlad();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        Button bindService = findViewById(R.id.bind_service);
        Button unbindService = findViewById(R.id.unbind_service);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            ...
            case R.id.bind_service:
                Intent bindIntent = new Intent(this,MyService.class);
                bindService(bindIntent,connection,BIND_AUTO_CREATE);//繫結服務
                break;
            case R.id.unbind_service:
                unbindService(connection);//接觸繫結
                break;
                default:
                    break;
        }
    }
}
可以看到,這裡我們首先建立了一個ServiceConnection的匿名類,在裡面重寫了onServiceConnected()方法和onServiceDisConnected()方法,這兩個方法分別會在活動與服務成功繫結以及解除的時候呼叫,在onServiceConnected()方法中,我們又通過向下轉型得到了DownloadBinder的例項,有了這個例項,活動和服務之間的關係就變得非常緊密了,現在我們可以在活動中根據具體的場景來呼叫DownloadBinder中的任何public方法,即實現了指揮服務就去幹什麼的功能,這裡仍然只是做了個簡單的測試,在onServiceConnected()方法中呼叫了DownloadBinder的startDownload()和getProgress()方法.

當然,現在活動和服務其實還沒進行繫結呢,這個功能是在Bind Service按鈕的點選事件裡完成的,可以看到,這裡我們仍然是構建出了一個Intent物件,然後呼叫bindService()方法將MainActivity和MyService進行繫結,bindService()方法接收3個引數,第一個引數就是剛剛構建出的Intent物件,第二個引數是前面建立出的serviceConnection的例項,第三個引數則是一個標誌位,這裡傳入BIND_AUTO_CREATE表示在活動和服務進行繫結後自動建立服務,這會使得MyService中的onCreate()方法得到執行,但onStartCommand()方法不會執行.

然後如果我們想接觸和服務之間的繫結該怎麼辦呢?呼叫一下unbindService()方法就可以了,這也是Unbind Service按鈕的點選事件裡實現的功能.

現在重新執行一下程式,如圖;

點選一下Bind Service按鈕,觀察logcat中的列印日誌,如圖:

可以看到,首先是MyService的onCreate()方法得到了執行,然後startDownload()和getProgress()方法都得到了執行,說明我們確實已經下活動裡成功呼叫了服務裡提供的方法了.

另外需要注意,任何一個服務在整個應用程式範圍內都是通用的,即MyService不僅可以和MainActivity繫結,還可以和任何一個其他的活動進行繫結,而且繫結完成後它們都可以獲取到相同的DownloadBinder例項.

相關文章