Hướng dẫn sử dụng Service trong Android
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Các loại dịch vụ trong Android

Dịch vụ là gì?

Một dịch vụ (Service) là một thành phần chạy ngầm bên trên hệ điều hành để thực hiện các hoạt động dài hạn mà không cần phải tương tác với người sử dụng và nó hoạt động ngay cả khi ứng dụng bị phá hủy. Một dịch vụ cơ bản có thể có hai loại.
Trạng thái Mô tả

Started

(Được khởi động)
Một dịch vụ được gọi là started (được khởi động) khi một thành phần ứng dụng, chẳng hạn như Activity chạy nó bằng cách gọi startService(). Một khi được gọi, dịch vụ này có thể chạy ở chế độ nền vô thời hạn, thậm chí cả khi thành phần start nó bị phá hủy.

Dịch vụ này còn được gọi là dịch vụ không bị giàng buộc (Un Bounded Service).

Bound

(Giàng buộc)
Một dịch vụ được giàng buộc (bound) khi một thành phần ứng dụng giàng buộc nó bằng cách gọi bindService().
Một dịch vụ ràng buộc cung cấp một giao diện client-server cho phép các thành phần tương tác với dịch vụ, gửi các yêu cầu, nhận kết quả, và thậm chí làm như vậy xuyên qua nhiều tiến trình với Inter-Process communication (IPC)

Trong khoa học máy tính, inter-process communication (IPC) là hoạt động chia sẻ dữ liệu qua nhiều tiến trình chuyên dụng thông thường sử dụng giao thức truyền thông.  Cụ thể ứng dụng sử dụng IPC được phân ra như clients và servers, khi các clients yêu cầu dữ liệu, và server đáp ứng yêu cầu của client.

Một dịch vụ có vòng đời, các phương thức gọi lại (callback methods) mà bạn có thi hành (implement) để theo dõi những thay đổi trong trạng thái của dịch vụ và bạn có thể thực hiện công việc ở giai đoạn thích hợp. Sơ đồ dưới đây về bên trái cho thấy vòng đời khi dịch vụ được tạo ra với startService(), sơ đồ bên phải cho thấy vòng đời của dịch vụ được tạo ra bởi bindService().
Để tạo ra một dịch vụ, bạn tạo một lớp Java mở rộng class Service hoặc một trong các lớp con của nó. Class Service định nghĩa các phương thức callback khác nhau và quan trọng nhất được đưa ra dưới đây. Bạn không cần phải thi hành tất cả các phương thức callbacks. Tuy nhiên, điều quan trọng là bạn hiểu mỗi một và thực hiện những điều gì, đảm bảo ứng dụng của bạn cư xử theo cách người dùng mong đợi.
Ngoài 2 loại dịch vụ trên, có một dịch vụ khác gọi là IntentService. Intent Service được sử dụng để thực hiện các nhiệm vụ tại một thời điểm, nghĩa là khi nhiệm vụ hoàn thành dịch vụ tự hủy.
So sánh các loại dịch vụ:
Unbound Service
(Không giàng buộc)
Bound Service
(Giàng buộc)
Intent 
Service
Unbounded Service được sử dụng để thực hiện nhiệm vụ lâu dài và lặp đi lặp lại. Bounded Service được sử dụng để thực hiện nhiệm vụ ở nền (background) và giàng buộc với thành phần giao diện.
Intent Service được sử dụng để thực hiện các nhiệm vụ tại một thời điểm, nghĩa là khi nhiệm vụ hoàn thành dịch vụ tự hủy.

Unbound Service được start bởi gọi startService().

Bounded Service được start bởi gọi bindService().
Intent Service được start bởi gọi startService().

Unbound Service bị dừng lại hoặc bị hủy bởi gọi một cách tường minh phương thức stopService().
Bounded Service bị gỡ giàng buộc hoặc bị hủy bởi gọi unbindService(). IntentService gọi một cách không tường minh phương thức stopself() để hủy

Unbound Service độc lập với thành phần đã start nó.

Bound Service phụ thuộc vào thành phần giao diện đã start nó.

Intent Service độc lập với thành phần đã start nó.
Các phương thức callback và mô tả:
Callback Description
onStartCommand() Hệ thống gọi phương thức này khi một thành phần khác, chẳng hạn như một Activity, yêu cầu các khởi động dịch vụ, bằng cách gọi startService(). Nếu bạn thực thi phương pháp này, trách nhiệm của bạn là ngừng dịch vụ khi nó hoàn thành công việc, bằng cách gọi phương thức stopSelf() hoặc stopService().
onBind() Hệ thống gọi phương thức này khi thành phần khác muốn liên kết với các dịch vụ bằng cách gọi bindService(). Nếu bạn thi hành phương pháp này, bạn phải cung cấp một giao diện mà khách hàng sử dụng để giao tiếp với các dịch vụ, bằng cách trả lại một đối tượng IBinder. Bạn phải luôn luôn thi hành phương thức này, nhưng nếu bạn không muốn cho phép ràng buộc, bạn có thể trả về null.
onUnbind() Hệ thống gọi phương thức này khi tất cả các clients đã bị ngắt kết nối từ một giao diện cụ thể được công bố bởi các dịch vụ.
onRebind() Hệ thống gọi phương thức này khi khách hàng mới đã kết nối với dịch vụ, sau khi trước đó đã được thông báo rằng tất cả đã bị ngắt kết nối trong onUnbind(Intent).
onCreate() Hệ thống gọi phương thức này khi dịch vụ được tạo ra sử dụng đầu tiên onStartCommand() hoặc onBind(). Gọi một lần tại thời điểm thiết lập.
onDestroy() Hệ thống gọi phương thức này khi dịch vụ không còn được sử dụng và đang bị hủy (destroy). Dịch vụ của bạn nên thi hành điều này để dọn dẹp các dữ liệu rác...
 

2- Dịch vụ không giàng buộc (Un bounded Service)

Unbound Service (hoặc còn gọi là Started Service): Trong trường hợp này, một thành phần ứng dụng khởi động dịch vụ bằng cách gọi startService(), và dịch vụ sẽ tiếp tục chạy trong nền, ngay cả khi các thành phần khởi tạo nó bị phá hủy. Ví dụ, khi được bắt đầu, một dịch vụ sẽ tiếp tục chơi nhạc trong nền vô thời hạn.
Phương thức onStartCommand() trả về kiểu integer, và là một trong các giá trị sau:
  • START_STICKY
  • START_NOT_STICKY
  • TART_REDELIVER_INTENT

START_STICKY & START_NOT_STICKY

  • Cả hai giá trị này chỉ thích hợp khi điện thoại hết bộ nhớ và giết các dịch vụ trước khi kết thúc thực hiện. START_STICKY nói với các hệ điều hành để tạo lại các dịch vụ sau khi đã có đủ bộ nhớ và gọi onStartCommand() một lần nữa với một Intent null. START_NOT_STICKY nói với các hệ điều hành để không bận tâm tái tạo các dịch vụ một lần nữa.
Ngoài ra còn có một START_REDELIVER_INTENT giá trị thứ ba mà nói với các hệ điều hành để tạo lại các dịch vụ và truyền một Intent tương tự cho onStartCommand().

Ví dụ dịch vụ chơi nhạc (Chạy ngầm)

Tạo mới một "Empty Activity" project với tên PlaySongService
Project đã được tạo ra.
Kéo thả 2 Button vào màn hình.
Click kép chuột vào các Button để thay đổi ID và text cho các Button.
Button 1:
  • ID: button_play
  • Text: Play
  • Properties
    • onClick: playSong

Button 2:
  • ID: button_stop
  • Text: Stop
  • Properties
    • onClick: stopSong

Chuẩn bị file nhạc mp3:

Nhấn phải chuột vào thư mục res chọn:
  • New/Android resource directory
Copy và Paste một file nhạc mp3 vào thư mục raw bạn vừa tạo ra.

Tạo class Service

Nhấn phải chuột vào một java package, chọn:
  • New/Service/Service
Nhập vào tên class:
  • PlaySongService
PlaySongService.java
package org.o7planning.playsongservice;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;

public class PlaySongService extends Service {

   private MediaPlayer mediaPlayer;

   public PlaySongService() {
   }


   @Override
   public IBinder onBind(Intent intent){
       // Service này là loại không giàng buộc (Un bounded)
       // Vì vậy method này ko bao giờ được gọi.
       return null;
   }


   @Override
   public void onCreate(){
       super.onCreate();
       // Tạo đối tượng MediaPlayer, chơi file nhạc của bạn.
       mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.mysong);
   }

   @Override
   public int onStartCommand(Intent intent, int flags, int startId){
       // Chơi nhạc.
       mediaPlayer.start();

       return START_STICKY;
   }

   // Hủy bỏ dịch vụ.
   @Override
   public void onDestroy() {
       // Giải phóng nguồn dữ nguồn phát nhạc.
       mediaPlayer.release();
       super.onDestroy();
   }
}
MainActivity.java
package org.o7planning.playsongservice;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

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

   // Method này được gọi khi người dùng Click vào nút Start.
   public void playSong(View view)  {
       // Tạo ra một đối tượng Intent cho một dịch vụ (PlaySongService).
       Intent myIntent = new Intent(MainActivity.this, PlaySongService.class);
       // Gọi phương thức startService (Truyền vào đối tượng Intent)
       this.startService(myIntent);
   }

   // Method này được gọi khi người dùng Click vào nút Stop.
   public void stopSong(View view)  {
       // Tạo ra một đối tượng Intent.
       Intent myIntent = new Intent(MainActivity.this, PlaySongService.class);
       this.stopService(myIntent);
   }



}
OK bây giờ bạn có thể chạy ứng dụng của mình và thưởng thức bài hát.

3- Dịch vụ bị giàng buộc (Bouned Service)

Ở đây tôi mô phỏng một dịch vụ cung cấp thông tin thời tiết cho ngày hiện tại, với đầu vào là vị trí địa lý (Hanoi, Chicago, ...), kết quả trả về là mưa, nắng,...
Tạo một project có tên WeatherService.
Kéo thả một số Widget vào màn hình.
Bằng cách nhấn kép chuột vào các widget, bạn có thể sét đặt text và ID cho các widget:
TextView 1:
  • ID: text_location
  • Text: Location
TextView 2:
  • ID: text_weather
  • Text: <Weather>
Button:
  • ID: button_weather
  • Text: How is the Weather today?
Sét đặt các ID và thuộc tính cho đối tượng EditText:
EditText
  • ID: text_input_location
  • Text: Hanoi
Properties
  • layout:width: fill_parent
  • gravity
    • center: checked
Sét đặt thuộc tính onClick cho Button là showWeather, điều này có nghĩa là khi click vào Button phương thức showWeather sẽ được gọi, method này chúng ta sẽ viết sau.

Tạo Service:

Nhấn phải chuột vào một java package chọn:
  • New/Service/Service
Nhập vào:
  • Class name: WeatherService
Class WeatherService đã được tạo ra, đây là class mở rộng từ class android.app.Service.
WeatherService.java
package org.o7planning.weatherservice;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;


import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;


public class WeatherService extends Service {

   private static String LOG_TAG = "WeatherService";

   // Lưu trữ dữ liệu thời tiết.
   private static final Map<String, String> weatherData = new HashMap<String,String>();

   private final IBinder binder = new LocalWeatherBinder();

   public class LocalWeatherBinder extends Binder {

       public WeatherService getService()  {
           return WeatherService.this;
       }
   }

   public WeatherService() {
   }

   @Override
   public IBinder onBind(Intent intent) {
       Log.i(LOG_TAG,"onBind");
       return this.binder;
   }

   @Override
   public void onRebind(Intent intent) {
       Log.i(LOG_TAG, "onRebind");
       super.onRebind(intent);
   }

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

   @Override
   public void onDestroy() {
       super.onDestroy();
       Log.i(LOG_TAG, "onDestroy");
   }

   // Trả về thông tin thời tiết ứng với địa điểm của ngày hiện tại.
   public String getWeatherToday(String location) {
       Date now= new Date();
       DateFormat df= new SimpleDateFormat("dd-MM-yyyy");

       String dayString = df.format(now);
       String keyLocAndDay = location + "$"+ dayString;

       String weather=  weatherData.get(keyLocAndDay);
       //
       if(weather != null)  {
           return weather;
       }

       //
       String[] weathers = new String[]{"Rainy", "Hot", "Cool", "Warm" ,"Snowy"};

       // Giá trị ngẫu nhiên từ 0 tới 4
       int i= new Random().nextInt(5);

       weather =weathers[i];
       weatherData.put(keyLocAndDay, weather);
       //
       return weather;
   }



}
MainActivity.java
package org.o7planning.weatherservice;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {


   private boolean binded=false;
   private WeatherService weatherService;

   private TextView weatherText;
   private EditText locationText;

   ServiceConnection weatherServiceConnection = new ServiceConnection() {

       @Override
       public void onServiceConnected(ComponentName name, IBinder service) {
           WeatherService.LocalWeatherBinder binder = (WeatherService.LocalWeatherBinder) service;
           weatherService = binder.getService();
           binded = true;
       }

       @Override
       public void onServiceDisconnected(ComponentName name) {
           binded = false;
       }
   };

   // Khi Activity tạo giao diện của nó.
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);


       weatherText = (TextView) this.findViewById(R.id.text_weather);
       locationText = (EditText)this.findViewById(R.id.text_input_location);
   }

   // Khi Activity start.
   @Override
   protected void onStart() {
       super.onStart();

       // Tạo đối tượng Intent cho WeatherService.
       Intent intent = new Intent(this, WeatherService.class);

       // Gọi method bindService(..) để giàng buộc dịch vụ với giao diện.
       this.bindService(intent, weatherServiceConnection, Context.BIND_AUTO_CREATE);
   }

   // Khi Activity ngừng hoạt động.
   @Override
   protected void onStop() {
       super.onStop();
       if (binded) {
           // Hủy giàng buộc kết nối với dịch vụ.
           this.unbindService(weatherServiceConnection);
           binded = false;
       }
   }

   // Khi click vào Button xem thời tiết.
   public void showWeather(View view)  {
       String location = locationText.getText().toString();

       String weather= this.weatherService.getWeatherToday(location);

       weatherText.setText(weather);
   }
}
OK, giờ bạn có thể chạy ứng dụng.

4- Dịch vụ IntentService

Ví dụ IntentService:

Hình ảnh dưới đây minh họa cách giao tiếp giữa Client (Activity)IntentService, Client khởi động dịch vụ, nó gửi yêu cầu của nó thông qua một đối tượng Intent, dịch vụ chạy và làm các công việc của mình, đồng thời nó có thể gửi thông tin liên quan tới tình trạng công việc của nó, chẳng hạn làm được bao nhiêu phần trăm. Tại client có thể sử dụng ProgressBar để hiển thị phần trăm công việc đã làm được.
Tạo mới một project SimpleIntentService.
Kéo thả một số đối thành phần vào giao diện:
Nháy kép chuột vào ProgressBar để thay đổi ID và các giá trị của nó.
Sét đặt ID và Text cho các thành phần trên giao diện.
ProgressBar:
  • ID: progressBar
  • Properties:
    • layout:width: fill_parent

TextView
  • ID: text_percel
  • Text: <Percel>

Button 1
  • ID: button_start
  • Text: Start
  • Properties
    • onClick: startButtonClicked
Button 2
  • ID: button_stop
  • Text: Stop
  • Properties
    • onClick: stopButtonClicked
Sét đặt method sẽ được gọi khi người dùng click vào button Start.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
   android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
   android:paddingRight="@dimen/activity_horizontal_margin"
   android:paddingTop="@dimen/activity_vertical_margin"
   android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

   <ProgressBar
       style="?android:attr/progressBarStyleHorizontal"
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       android:id="@+id/progressBar"
       android:layout_alignParentTop="true"
       android:layout_centerHorizontal="true"
       android:layout_marginTop="66dp"
       android:indeterminate="false"
       android:max="100"
       android:progress="0" />

   <TextView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textAppearance="?android:attr/textAppearanceLarge"
       android:text="&lt;Percel>"
       android:id="@+id/text_percel"
       android:layout_below="@+id/progressBar"
       android:layout_centerHorizontal="true"
       android:layout_marginTop="40dp" />

   <Button
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Start"
       android:id="@+id/button_start"
       android:layout_centerVertical="true"
       android:layout_centerHorizontal="true"
       android:onClick="startButtonClicked" />

   <Button
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Stop"
       android:id="@+id/button_stop"
       android:layout_below="@+id/button_start"
       android:layout_centerHorizontal="true"
       android:layout_marginTop="47dp"
       android:onClick="stopButtonClicked" />
   
</RelativeLayout>
Tạo một IntentService bằng cách nháy phải chuột vào một package, chọn:
  • New/Service/IntentService
Class SimpleIntentService đã được tạo ra, nó cũng đã được đăng ký với AndroidManifest.xml, code được tạo ra là một gợi ý cho bạn viết một IntentService, bạn có thể xóa hết các code được tạo ra.
SimpleIntentService.java
package org.o7planning.simpleintentservice;

import android.app.IntentService;
import android.content.Intent;
import android.os.SystemClock;


public class SimpleIntentService extends IntentService {

   public static final String ACTION_1 ="MY_ACTION_1";

   public SimpleIntentService() {
       super("SimpleIntentService");
   }

   @Override
   protected void onHandleIntent(Intent intent) {

       // Tạo một đối tượng Intent (Một đối tượng phát sóng).        
       Intent broadcastIntent = new Intent();

       // Sét tên hành động (Action) của Intent này.
       // Một Intent có thể thực hiện nhiều hành động khác nhau.
       // (Có thể coi là nhiều nhiệm vụ).
       broadcastIntent.setAction(SimpleIntentService.ACTION_1);



       // Vòng lặp 100 lần phát sóng của Intent.
       for (int i = 0; i <= 100; i++) {

           // Sét đặt giá trị cho dữ liệu gửi đi.
           // (Phần trăm của công việc).
           broadcastIntent.putExtra("percel", i);

           // Send broadcast
           // Phát sóng gửi đi.
           sendBroadcast(broadcastIntent);

           // Sleep 100 Milliseconds.
           // Tạm dừng 100 Mili giây.
           SystemClock.sleep(100);
       }

   }
}
MainActivity.java
package org.o7planning.simpleintentservice;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

   private Button startButton;
   private Button stopButton;
   private TextView percelText;

   private ProgressBar progressBar;

   private Intent serviceIntent;

   private ResponseReceiver receiver = new ResponseReceiver();


   // Broadcast component
   // Class mô phỏng một bộ thu sóng
   // (Thu tín hiệu gửi từ Service).
   public class ResponseReceiver extends BroadcastReceiver {

       // on broadcast received
       @Override
       public void onReceive(Context context, Intent intent) {

           // Kiểm tra nhiệm vụ của Intent gửi đến.
           if(intent.getAction().equals(SimpleIntentService.ACTION_1)) {
               int value = intent.getIntExtra("percel", -1);

               new ShowProgressBarTask().execute(value);
           }
       }
   }

   // Class làm nhiệm vụ hiển thị giá trị cho ProgressBar.
   class ShowProgressBarTask extends AsyncTask<Integer, Integer, Integer> {

       @Override
       protected Integer doInBackground(Integer... args) {

           return args[0];
       }

       @Override
       protected void onPostExecute(Integer result) {
           super.onPostExecute(result);

           progressBar.setProgress(result);

           percelText.setText(result + " % Loaded");

           if (result == 100) {
               percelText.setText("Completed");
               startButton.setEnabled(true);
           }

       }
   }

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

       this.startButton= (Button) this.findViewById(R.id.button_start);
       this.stopButton = (Button)this.findViewById(R.id.button_stop);
       this.percelText = (TextView) this.findViewById(R.id.text_percel);
       this.progressBar = (ProgressBar) this.findViewById(R.id.progressBar);
   }


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

       // Đăng ký bộ thu sóng với Activity.
       registerReceiver(receiver, new IntentFilter(
               SimpleIntentService.ACTION_1));
   }

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

       // Hủy đăng ký bộ thu sóng với Activity.
       unregisterReceiver(receiver);
   }

   // Phương thức được gọi khi người dùng nhấn vào nút Start.
   public void startButtonClicked(View view)  {
       startButton.setEnabled(false);

       // Intent yêu cầu gửi đến Service.
       serviceIntent = new Intent(this, SimpleIntentService.class);

       // Chạy dịch vụ.
       startService(serviceIntent);
   }


   public void stopButtonClicked(View view)  {
       if(serviceIntent!= null)  {
           // serviceIntent.get
       }
   }

}
  • Chạy ứng dụng (Xem slider):

Và bạn có thể xem nguyên tắc hoạt động của ví dụ trên theo hình minh họa dưới đây: