[Android 08] Service và những vấn đề liên quan

1. Service là gì?
Service chạy ở background, thi hành tiến trình tốn nhiều thời gian hoặc thi hành các process từ xa. Một service không cung cấp bất kì user interface nào cả, nó chỉ hoạt động ở background với các input từ user. Ví dụ, một service có thể chơi nhạc ở background trong khi người dùng đang surfing ở 1 app khác, hoặc có thể download data từ internet mà không block user interaction với thiết bị.
2. LifeCycle của Service.
Một vòng đời của Service có những callbacks sau:
  • onCreate()
    Nó được thực thi khi service được khởi tạo để cài đặt các cấu hình cần thiết. Lưu ý rằng nó không được gọi khi service đang hoạt động, tức là nó chỉ được thực thi nếu service chưa chạy.
  • onStartCommand()
    Phương thức này được gọi mỗi khi ủy quyền cho startService() bằng component, Activity hoặc Fragment, hoặc BroadcastReceiver. Khi sử dụng phương thức này, Service sẽ chạy cho tới khi sử dụng stopSelf() hoặc stopService(). Lưu ý rằng bất kể bao nhiêu lần phương thức onStartCommand được gọi, stopSelf() hoặc stopService() được ủy quyền chỉ một lần duy nhất để dừng service lại mà thôi!
  • onBind()
    Phương thức này thực thi khi có component gọi hàm bindService() và trả về 1 instance của IBinder, cung cấp cách giao tiếp tới Service. Khi sử dụng bindService(), nó sẽ giữ service luôn chạy trong khi client vẫn còn bound đến chính nó.
  • onDestroy()
    Hàm này được thực thi khi service không còn sử dụng nữa, và cho phép giải phóng, nhường lại resources đã chiếm dụng.
  • onConfigurationChanged() onLowMemory()
    Hai hàm này quan trọng khi được ủy quyền để là một listener theo dõi configuration và khi bộ nhớ bị sụt giảm nghiêm trọng.
3. Định nghĩa quy trình cho một service.
Trường android:process định nghĩa tên của quy trình để service có thể chạy. Thông thường, tất cả components của ứng dụng chạy theo quy trình mặc định được tạo bởi ứng dụng, nhưng mà, component có thể ghi đè các attribute của quy trình chính nó, cho phép tách ứng dụng ra thành các multiple processes.

1
2
3
4
<service
android:name="com.ngoctan.project
android:process=":externalProcess">
</service>
Nếu tên của process được bắt đầu với chữ thường (không viết hoa), service sẽ chạy trong quy trình global của tên process đó, và cung cấp quyền (permission) để làm việc đó. Nó cho phép component ở ứng dụng khác có thể chia sẻ quy trình sử dụng lại tài nguyên.
4. Khởi tạo unbound service.

1
2
3
4
5
6
7
8
<service
            android:name=".RecordingService"
            android:enabled="true"
            android:exported="false"
        <!--Sử dụng enabled=true nếu service được khởi tạo bởi hệ thống -->
        <!--Sử dụng exported =true nếu components của ứng dụng khác có thể ủy quyền đến service này, tức là tương tác chéo.
            Nếu là false, chỉ component trong ứng dụng với cùng userId mới startService hoặc bind nó mà thôi-->
        />
Khởi tạo lớp RecordingService, kế thừa từ Service:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class RecordingService extends Service {
    public static RecordingService recordingServiceInstance = null;
    private static boolean isRunning = false;
    private int NOTIFICATIONID = 1;
    private NotificationManager notificationManager = null;

    /**
     * Sử dụng singleton để tạo instance duy nhất cho RecordingService.
     */
    @Override
    public void onCreate() {
        recordingServiceInstance = this;
        isRunning = true;
        notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        super.onCreate();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //PendingIntent để khởi chạy activity nếu user select notification.
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
        //Thiết lập thông tin cho notification
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("Notification")
                .setContentText("Service is running")
                .setContentIntent(pendingIntent)
                .setOngoing(true)
                .build();
        //Show notification trong chế độ foreground
        startForeground(NOTIFICATIONID, notification);
        return START_STICKY;

    }

    @Override
    public void onDestroy() {
        isRunning = false;
        recordingServiceInstance = null;
        //Cancel notification
        notificationManager.cancel(NOTIFICATIONID);
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void doSomething() {
        Log.d("Do what?", "Follow me");
    }
}
Sử dụng Service, khởi chạy nó từ MainActivity.class:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public void startOrStopService(){
if( RecordingService.isRunning ){
        // Stop service
Intent intent = new Intent(this, RecordingService.class);
        stopService(intent);
    }
else {
// Start service
Intent intent = new Intent(this, RecordingService.class);
        startService(intent);
    }
}
Như đã thể hiện bên trên, lớp RecordingService implement Singleton, và do đó nó giữ instanceinstance nhưng không có static singleton factory bởi vì service là singleton tự nhiên và được khởi tạo bằng intent từ MainActivity.class. Instance rất hữu dụng cho các thành phần bên ngoài muốn xử lý service khi chúng đang hoạt động.
Khi muốn sử dụng phương thức doSomething() trong Service, uỷ quyền khởi chạy phương thức đó từ activity:
1
2
3
4
5
public void makeServiceDoSomething(){ 
if( RecordingService.isRunning ){
RecordingService.instance.doSomething();
  }
}
5. Khởi tạo BoundService với Binder.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MainActivity extends AppCompatActivity {
    private LocalService localService;
    boolean mBound = false;

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

    }

    @Override
    protected void onStart() {
        super.onStart();
        bindService(new Intent(this, LocalService.class), serviceConnection, Context.BIND_AUTO_CREATE);

    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(serviceConnection);
            mBound = false;
        }
    }

    /**
     * Xác định callbacks cho việc ràng buộc service, ném nó vào trong bindService.
     */
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //Bởi vì đã bound đến LocalService ở onStart
            //nên chỉ cần cast IBinder và lấy instance của LocalService.
            LocalService.LocalBinder localBinder = (LocalService.LocalBinder) serviceConnection;
            serviceConnection = (ServiceConnection) localBinder.getLocalService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mBound = false;
        }
    };
}
/* *Hôm nay là một ngày mà tất cả những "service" đều outOfMemoryException. */
HappyCoding!

Nhận xét

Bài đăng phổ biến từ blog này

[Android-02] LayoutParams trong Android