技術學習記錄

[Android]四大元件之Service

Service介紹

先前在[Android]四大元件簡介中有簡單介紹過,Service是一個沒有使用者介面的程式,一般是拿來做在背景處理一些需要長時間監控的事情。

Service和Activity最大的差別是,他可以在背景進行長時間的工作。

在這裡還是拿音樂播放器為例:

音樂播放器的特色是,就算當Activity已經退到背景去,但音樂仍在繼續播放,且當一首歌播放完之後還會繼續播放下一首,直到你退出這個應用程式。

能夠達到這樣的功能,都是歸功於Service在背景控制音樂播放器的功勞。

Service生命週期

Service如同Activity一樣,也是具備生命週期的。

由於Service沒有畫面,因此不需要onStart()、onStop()、onPause()、onResume()等等callback做相應的處理。

生命週期如下圖:

由上圖我們可以看到,Service具有兩種啟動方式,分別是startServide()和bindService()

startService()

這種方式是透過intent來啟動Service,系統會先呼叫onCreate(),然後將intent帶入onStartCommand方法中。

透過startService()方式啟動的Service會一直執行,直到呼叫了stopService()或是內部呼叫stopSelf()才會被停止。

另外,不論呼叫多少次startService(),只有在Service第一次啟動時才會呼叫onCreate(),其他時間都是直接呼叫onStartCommand()的,因此我們可以利用intent中夾帶一些參數讓Service去做相對應的事情。

生命週期參考左半邊的流程。

startService範例

啟動Service

1.建立一個TestService.java,並繼承自android.app.Service

public class TestService extends Service {

    private static final String TAG = "TestService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "Service is call onCreate()");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "Service is call onStartCommand()");
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "Service is call onBind()");
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "Service is call onDestroy()");
    }
}

2.在MainActivity的onCreate(),透過intent啟動TestService,並在onDestroy(),關閉Service

public class MainActivity extends AppCompatActivity {

    private Intent serviceIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        serviceIntent = new Intent(MainActivity.this, TestService.class);

        startService(serviceIntent);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        stopService(serviceIntent);
    }
}

傳送指令給Service

回到TestService.java,並在onStartCommand()中,加入下列程式碼,像這樣:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.i(TAG, "Service is call onStartCommand()");

    // 接收由 Activity 傳送過來的指令
    String message = intent.getStringExtra("SHOW_MESSAGE");
    if (message != null) {
        Log.i(TAG, "Message is: " + message);
    }

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

在MainActivity中,加入一個按鈕,並透過先前建立的serviceIntent物件,發送一個String Extra,像這樣:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        serviceIntent.putExtra("SHOW_MESSAGE", "Hello");
        startService(serviceIntent);
    }
});

執行應用程式,即可在Logcat看到Service被啟動。

按下按鈕後,我們可以在Logcat看到Service接收到SHOW_MESSAGE,並顯示Hello

2021-05-19 17:53:37.751 5586-5586/? I/TestService: Service is call onCreate()
 2021-05-19 17:53:37.751 5586-5586/? I/TestService: Service is call onStartCommand()
 2021-05-19 17:53:42.070 5586-5586/? I/TestService: Service is call onStartCommand()
 2021-05-19 17:53:42.070 5586-5586/? I/TestService: Message is: Hello

bindService()

相對於startService()的另一種啟動方式,則是bindService()。

bindService()的啟動方式是在Activity建立一個與Service綁定的連線(ServiceConnection),讓與之綁定的Activity可以直接呼叫這個Service中的公開方法。

另外,bindService()方式啟動的Service,會隨著Activity與之解綁時被銷毀。因此在設計時必須考慮Service是需要長時間運作而決定要使用startService()還是bindService()。

生命週期參考右半邊的流程。

bindService範例

啟動Service

1.建立一個TestBindService.java,並繼承自android.app.Service。

此外,在TestBindService中,建立一個MyTestBinder類別。

接著再將onBind()覆寫,把MyTestBinder作為回傳的物件傳出。

public class TestBindService extends Service {

    private static final String TAG = "TestBindService";

    private final IBinder binder = new MyTestBinder();

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "Service is call onCreate()");
    }


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "Service is call onBind()");
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "Service is call onUnbind()");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "Service is call onDestroy()");
    }

    public void showMessage(String message) {
        Log.i(TAG, "Message is: " + message);
    }

    public class MyTestBinder extends Binder {
        public TestBindService getService() {
            return TestBindService.this;
        }
    }
}

2.在MainActivity的onCreate(),新增connect、mService物件。

並在connect的onServiceConnected()中,透過IBinder取得我們要啟動的Service,並帶到mService。

private TestBindService mService;
private ServiceConnection connection = new ServiceConnection() {

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mService = null;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mService = ((TestBindService.MyTestBinder) service).getService();
        Log.i(TAG, "Service is connected");
    }
};

之後我們就可以直接呼叫Service中公開的function,讓Service做事。

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        mService.showMessage("Hello");
    }
});

執行APP,按下APP上的按鈕,即可在Logcat看到我們呼叫Service所做的事情了

2021-05-19 18:11:48.490 6303-6303/com.ray650128.startservicetest I/MainActivity: Service is connected
 2021-05-19 18:11:52.806 6303-6303/com.ray650128.startservicetest I/TestBindService: Message is: Hello

使用Service時可能會遇到的疑問

1.Service可以長時間運作,代表它可以執行耗時工作嗎?

答案是不行,因為Service基本的三個生命周期都是在主執行緒上執行的,因此我們如果要執行耗時工作,還是必須切換到其他執行緒來執行耗時工作。

其他待後續補充

以上是繼四大元件簡介之後,對於Service更詳細一點的介紹,如果需要更詳細的資料,請參考Android Developers網站

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *