Bài 2. Các thành phần ứng dụng Android


2.1. Các thành phần ứng dụng    

Thành phần ứng dụng là các khối cơ bản để xây dựng nên một ứng dụng Android hoàn chỉnh . Các thành phần này được liên kết lỏng lẻo bởi các ứng dụng trong tập tin AndroidManifest.xml, tập tin AndroidManifest.xml mô tả mỗi thành phần của ứng dụng và cách chúng tương tác với nhau.

Có bốn thành phần chính có thể được sử dụng trong một ứng dụng Android:

Thành phầnĐặc tả
Activities Chúng gọi giao diện người dùng và xử lý các tương tác người dùng với màn hình điện thoại thông minh.
Services Chúng xử lý nền kết hợp với một ứng dụng.
Broadcast Receivers Chúng xử lý thông tin liên lạc giữa hệ điều hành Android và các ứng dụng.

Content Providers

Chúng xử lý dữ liệu và các vấn đề quản lý cơ sở dữ liệu.

2.1.1. Activity (hoạt động)

Một Activity đại diện cho một màn hình duy nhất với một giao diện người dùng. Ví dụ, một ứng dụng email có thể có một hoạt động cho thấy một danh sách các email mới, một hoạt động để soạn một email, và một hoạt động để đọc email. Nếu một ứng dụng có nhiều hơn một hoạt động, sau đó một trong số chúng được đánh dấu là hoạt động được hiển thị khi ứng dụng được khởi chạy.

Một Activity được thực hiện như một lớp con của lớp Activity như sau:

public class MainActivity extends Activity

{
 ...

}

2.1.2. Service (dịch vụ)

Một Service là một thành phần chạy trong nền để thực hiện các hoạt động lâu dài. Ví dụ, một dịch vụ có thể chơi nhạc ở chế độ nền trong khi người dùng đang ở một ứng dụng khác nhau, hoặc nó có thể lấy dữ liệu qua mạng mà không ngăn chặn người dùng tương tác với một hoạt động

Một Service được thực hiện như một lớp con của lớp Service như sau:

public class MyService extends Service

{
 ...

}

2.1.3. Broadcast Receiver

Broadcast Receiver chỉ đơn giản là phản ứng để phát các tín hiệu từ các ứng dụng khác hoặc từ hệ thống. Ví dụ, các ứng dụng cũng có thể bắt đầu chương trình phát tín hiệu để cho các ứng dụng khác biết rằng một số dữ liệu đã được tải về điện  thoại và sẵn sàng cho họ sử dụng.Một máy thu phát tín hiệu được thực hiện như một lớp con của BroadcastReceiver lớp và mỗi tín hiệu được phát đi như một đối tượng Intent.

public class MyReceiver extends BroadcastReceiver

{
 ...

}

2.1.4. Content Provider (cung cấp nội dung)

Content Providers cung cấp nội dung dữ liệu từ một ứng dụng khác theo yêu cầu. Yêu cầu đó được xử lý bằng các phương thức (methods) của lớp ContentResolver. Dữ liệu có thể được lưu trữ trong hệ thống tập tin, cơ sở dữ liệu (database) hoặc ở một nơi hoàn toàn khác.

Một Content Providers được thực hiện như một lớp con của ContentProvider lớp và phải thực hiện một bộ tiêu chuẩn API cho phép các ứng dụng khác để thực hiện các giao dịch.

public class MyContentProvider extends ContentProvider

{
 ...

}

2.1.5. Additional Components (các thành phần bổ sung)

Có thành phần bổ sung sẽ được sử dụng trong việc xây dựng của các đơn vị nêu trên. Các thành phần này là:

Thành phầnĐặc tả
Fragments Đại diện cho một hành vi hoặc một phần của giao diện người dùng trong một hoạt động.

Views

Các yếu tố giao diện người dùng được vẽ trên màn hình bao gồm các nút, danh sách các hình thức, ...

Layouts

Xem phân cấp kiểm soát định dạng màn hình và xuất hiện của các quan điểm.
Intents Tín hiệu hệ thống kết nối các thành phần với nhau.
Resources Các yếu tố bên ngoài, các hằng số và drawables hình ảnh..
Manifest Tập tin cấu hình cho ứng dụng.

2.1.5.1 Intents 

Với các thành  phần vừa nói  ở  trên thì Content providers được kích hoạt bởi một  yêu  cầu  từ  Content Resolver.  Còn  activitiesservices  và  broadcast  receivers được kích hoạt bởi tin nhắn bất đồng bộ  gọi là intents. Một intents có thể  giữ  nội dung tin nhắn. Với mỗi loại thành phần có những phương thức kích hoạt khác nhau:

Activity: được kích hoạt bởi các phương thức Context.startActivity() hoặc  Activity.startActivityForResult(). Từ  Activity  này qua  Activity  khác sẽ có thể  mang theo giá trị  và truyền qua  Activity  được khởi động giống như truyền giá trị  từ  form hiện hành qua một form khác giống như trên C#. Bên Activity  được khởi động có thể  dùng hàm getIntent() để  lấy giá trị. Nếu một Activity  khởi động một  Activity  khác và mong chờ  nó trả  về  cho nó một kết quả  thì  gọi  hàm  startAtivityForResult()  thay  vì  gọi  hàm  startActivity().  Ví dụ, một  Activity  đang cần một tấm hình là kết quả  chọn lựa từ  Activity  khác gồm danh sách các hình; kết quả trả về thông qua hàm onActivityResult().

Service:  được  khởi  động  thông  qua  hàm  Context.startService(). Tương tự, trên một intents có thể  dùng hàm  Context.bindService() để  thiết lập một kết nối giữa các thành phần và service muốn gọi.

Một đối tượng Intent có thể  đặt tên cho một thành phần mà mình muốn mởmột cách rõ ràng. Nếu làm vậy, Android tìm thấy thành phần đó và kích hoạt nó. Nhưng nếu lỡ  thành phần không được đặt tên rõ ràng và có thể  bị  nhập nhằng tên, Android  phải  chọn  thành  phần  thích  hợp  nhất  để  trả  về  cho  intents.  Nó  làm  vậy bằng cách so sánh đối tượng Intent với intent filters của thành phần sắp được mở. Intent  filters  của  thành  phần  sẽ  thông  báo  cho  Android  các  loại  intents  mà  thành phần  có  thể  quản  lý.  Tất  cả  cả  thông  tin  của  thành  phần  được  lưu  trong  tập  tin Android Manifest. Dưới đây là 2 ví dụ  mở  rộng có dùng intents filters:

<?xml version="1.0" encoding="utf-8"?>

<manifest . . . >

<application . . . >

<Activity Android:name="com.example.project.FreneticActivity"

Android:icon="@drawable/small_pic.png"

Android:label="@string/freneticLabel"

. . .  >

<intent-filter . . . >

<action Android:name="Android.intent.action.MAIN" />

<category Android:name="Android.intent.category.LAUNCHER"

/>

</intent-filter>

<intent-filter . . . >

<action Android:name="com.example.project.BOUNCE" />

<data Android:mimeType="image/jpeg" />

<category Android:name="Android.intent.category.DEFAULT"

/>

</intent-filter>

</Activity>

. . .

</application>

</manifest>

Ta thấy trong ví vụ  trong thẻ  Intents filter đầu tiên có 2 thành phần là action "Android.intent.action.MAIN"  và  category "Android.intent.category.LAUNCHER".  Nó  đánh  dấu  Activity FreneticActivity là màn hình đầu tiên khi người dùng khởi động ứng dụng trên thiết bị  Android. Nói cách  khác,  Activity  đó là đầu vào của  ứng dụng, nó là màn hình đầu tiên mà người dùng nhìn thấy khi họ khởi động ứng dụng.

Trong thẻ intents filter thứ 2 mô tả tên action mà Activity có thể thực thi trên một loại dữ liệu nào đó.

Một thành phần có thể có nhiều intents filter, mỗi cái mô tả bộ khả năng khác nhau của Activity. Nếu nó không có bất kì filter nào, nó có thể  chỉ  được kích hoạt bằng intents mà nó đặt tên rõ ràng như là một thành phần cần được mở.

Đối với broadcast receiver thì nó được tạo và khai báo bằng mã lệnh, intent filter được khởi động trực tiếp như là đối tượng IntentFilter. Tất cả  các filter khác được thiết lập trong manifest.


2.1.5.2. Tập tin AndroidManifest

Trước khi Android khởi động thành phần  ứng dụng, nó phải biết thành phần nào  đang  tồn  tại.  Vì  thế,  ứng  dụng  phải  nêu  rõ  các  thành  phần  trong  tập  tin AndroidManifest.xml  mà  được  gói  trong  tập  tin  .apk  mà  tập  tin  .apk  này  giữ  mã lệnh, tập tin, và các tài nguyên của ứng dụng.

Manifest  là  một  tập  tin  XML  và  luôn  luôn  được  đặt  tên  là AndroidManifest.xml  cho  ứng  dụng.  Trong  tập  tin  manifest  chứa  các  thành  phần sau:

  • Liệt kê các thành phần như Activity, service, . . . có trong ứng dụng.
  • Các permission cho phép dùng các phần cứng.
  • Thông số thiết lập SDK.

Ví dụ tập tin AndroidManifest.xml như sau:

<?xml version="1.0" encoding="utf-8"?>

<manifest . . . >

<application . . . >

<Activity Android:name="com.example.project.FreneticActivity"

Android:icon="@drawable/small_pic.png"

Android:label="@string/freneticLabel"

. . .  >

</Activity>

. . .

</application>

</manifest>

Các thành phần  ứng dụng có vòng đời  –  đó là sự  bắt đầu khi Android khởi động thành phần cho đến khi kết thúc  ứng dụng khi thực thể  ứng dụng bị  hủy. Các trạng thái  ứng dụng có thể  có hiệu lực hay không có hiệu lực; đối với activities, thì có tình trạng visible hoặc invisible. Trong mục này ta nói về  vòng đời của activities, services, và broadcast receiver.


2.2. Vòng đời các thành phần ứng dụng Android

2.2.1 Vòng đời của Activity

Một Activity có ba trạng thái:

  • Đang hiệu  lực  hoặc  đang  chạy:  khi  nó  trực  tiếp  giao  tác  với  người dùng.
  • Tạm dừng: khi nó không còn được hiệu lực nhưng vẫn được nhìn thấy bởi người dùng. Nói dễ hiểu hơn, có Activity này nằm trên một Activity khác mà Activity này trong suốt hoặc không che phủ  hết màn hình vì vậy  Activitykhác  được  nhìn  thấy.  Một  Activity  đang  dừng  thì  hoàn  toàn  “còn  sống” nhưng có thể bị “giết đi” bởi hệ thống trong trường hợp bộ nhớ thấp.
  • Dừng hẳn: nếu  Activity  bị  che khỏa  hoàn toàn bởi một  Activity  khác. Nó vẫn còn duy trì tất cả trạng thái và thông tin thành viên. Tuy nhiên, người dùng không còn thấy nó vì màn hình của nó ẩn đi và thường bị  “giết” bởi hệthống khi không đủ bộ nhớ.

Nếu một  Activity bị  tạm dừng  và dừng hẳn, hệ  thống có thể  loại bỏ  nó ra khỏi bộ  nhớ  bằng cách gọi hàm finish() hoặc loại bỏ  nó ra khỏi tiến trình. Khi nó thể hiện trước người dùng lần nữa, nó phải khởi động và khôi phục lại các trạng thái trước đó.

Khi một Activity chuyển từ trạng thái này qua trạng thái khác, thì Activity đó sẽ  gọi các phương thức protected sau:  void  onCreate(Bundle savedInstanceState), void onStart(), void onRestart(), void onResume(), void onPause(), void onStop(), void onDestroy(). Tất cả  các phương thức này có thể  viết lại (override) để  phù hợp với từng công việc cụ thể đối với ứng dụng của bạn khi mà trạng thái ứng dụng thay đổi. Tất cả  các Activity phải thực thi lại hàm onCreate() để  thiết lập lần đầu khi mà các đối tượng trong ứng dụng được khởi động. Nhiều activity sẽ dùng hàm onPause() để cập nhật lại dữ liệu đã thay đổi hoặc dừng hẵn việc giao tiếp với người dùng. 

Chú ý gọi siêu lớp (superclass): Việc thực thi bất kì phương thức vòng đời ứng dụng nào thì nên luôn luôn gọi hàm tương ứng của siêu lớp. Ví dụ:

Với 7 phương thức định nghĩa cho toàn bộ  vòng đời  ứng dụng của Activity. Nếu chú ý, ta thấy có những vòng khép kín như sau:

  • Vòng khép kín toàn bộ của ứng dụng:  Activity  bắt đầu từ  lần gọi hàm đầu tiên onCreate() đến hàm cuối cùng onDestroy(). Một  Activity  làm tất cả các thiết lập đầu tiên cho chương trình  trong hàm onCreate() và giải phóng tất cả  các tài nguyên trong hàm onDestroy(). Ví dụ như ứng dụng có 1 luồng chạy  ngầm  để  tải  dữ  liệu  từ  máy  chủ  về  thì  nó  tạo  luồng  trong  hàm onCreate() và sau đó dừng hẵn luồng trong hàm onDestroy().
  • Vòng đời  nhìn  thấy  được  bởi  người  dùng:  trong  Activity  từ  hàm onStart() cho đến hàm onStop(). Trong suốt thời gian này, người dùng có thểthấy  Activity  trên màn hình. Giữa 2 phương thức  này, bạn có thể  duy trì các tài nguyên mà cần thiết để  trình bày  Activity  cho người dùng.  Ví dụ, bạn có thể  khai báo BroadcastReceiver trong hàm onStart()  để  giám sát tự  thay đổi ảnh  hưởng  đến  giao  diện  và  giải  phóng  nó  trong  hàm  onStop()  khi  người dùng  không  còn nhìn thấy những  gì bạn đang trình bày. Hàm onStart() và onStop() được gọi nhiều lần bởi vì acitivity thay đổi nhiều lần giữa trạng thái nhìn thấy được và ẩn đi đối với người dùng.
  • Vòng đời chạy ngầm: từ hàm onResume() đến hàm onPause(). Trong suốt  thời  gian  này,  Activity  chuyển  từ  trạng  thái  resume  sang  pause  hay ngược lại. onPause() được gọi khi thiết bị  “ngủ” hoặc khi  Activity  mới được khởi động, hàm onResume() được gọi khi kết quả  của  một  Activity hay một intent mới được đưa ra. Vì thế mã lệnh trong hai phương thức này nên ít thôi.

Hình sau mô tả  những vòng khép kín và con đường mà  activty phải trải qua giữa các trạng thái. Hình ô van thể  hiện trạng thái của Activity, hình chữ  nhật thể hiện các phương thức

Vòng đời của một Activity

Bảng sau mô tả mỗi hàm chi tiết hơn về cách sử dụng và đặt nó ở trong vòng đời ứng dụng của Activity:

Bảng mô tả lại ý nghĩa các phương thức trong vòng đời của một Activity

Phương thứcMô tảCó  thể hủy (killable)Theo  sau hàm
onCreate()   Được  gọi  khi  Activity  lần  đầu  tiên  được tạo. Đây là nơi đầu tiên bạn nên làm tất cảcác  thiết  lập  như  tạo  Views,  gắn  dữ  liệu vào danh sách hay đại loại vậy. Luôn theo sau là hàm onStart(). Không onStart()
onRestart()   Được gọi sau khi  Activity  đã bị  dừng trước đó và được được khởi động lại. Luôn theo sau hàm onStart(). Không onStart()
onStart()   Được  gọi  chỉ  trước  khi  acitivity  trở  nên nhìn thấy được với người dùng. Luôn theo sau là hàm onResume() nếu  Activity  chạy

ngầm hoặc onStop() nếu nó bị ẩn đi.

Không onResume() hay onStop()
onResume()   Chỉ  được gọi trước  Activity  bắt đầu tươngtác  với  người  dùng.  Tại  thời  điểm  này, Activity  nằm  trên  đầu  của  ngăn  xếp Activity  và người dùng chuẩn bị  tương tác với  chúng.  Luôn  theo  sau  là  hàm onPause(). Không onPause()
onPause()  

Được  gọi  khi  hệ  thống  sắp  bắt  đầu  hồi phục  lại  một  Activity  khác.  Phương  thức này  được  dùng  để  lưu  những  dữ  liệu  mới vừa  thay  đổi,  dừng  hẳn  hiệu  ứng  động  và và  dừng  hẳn  những  thứ  khác  có  thể  ngốn tài  nguyên  CPU  hay  đại  loại  vậy. 

Ứng dụng làm bất cứ  cái gì để  sao cho nó chạy nhanh bởi vì  Activity  kế  tiếp sẽ  không hồi phục lại cho đến khi acitvity hiện tại dừng lại.  Nó  theo  sau  hàm  onResume()  nếu Activity  hiện  tại  sắp  được  thực  thi  hoặc theo sau là hàm onStop() nếu nó trở  nên ẩn đi đối với người dùng.

onResume() hay onStop()
onStop()  

Được gọi khi người dùng không nhìn thấy được  Activity  nữa. Điều này có thể  xảy ra bởi vì nó đang được hủy hoặc một  Activitykhác  (có  thể  Activity  đang  tồn  tại  hoặc Activity  mới) được hồi phục lại và đang đè lên  Activity  hiện tại. Vừa theo sau là hàm onRestart()  nếu  Activity  đang  quay  lại  đểtương  tác  với  người  dùng  hoặc  hàm onDestroy() nếu Activity dự định hủy đi.

onRestart() hay onDestroy()

onDestroy()  

Được gọi trước khi Activity bị hủy đi. Hàm này  được  gọi  lần  cuối  cùng  khi  mà  một ativity đang kết thúc hay hệ  thống chỉ  đang hủy  những  thực  thể  của  Activity  để  tiết kiệm không gian. Có thể  phân biệt 2 trạng thái này bằng hàm isFinishing().

Không

 

Có thể  hủy (Killable): Ý nghĩa của nó là dù muốn dù không thì hệ thống có thể  “giết” tiến trình đang giữ  Activity tại thời điểm sau khi phương thức kết thúc mà không thực thi dòng mã lệnh cũa một Activity khác. Ba phương thức (onPause(), onStop(), onDestroy()) có giá trị là Có. Bởi vì onPause() là phương thức đầu tiên của ba phương thức, nó là phương thức duy nhất bảo đảm được gọi trước khi tiến trình kết thúc trong khi đó onStop()onDestroy() thì không. Vì thế, bạn nên dùng onPause() để cập nhật tất cả dữ liệu mới vừa được thay đổi. 

Còn  những  phương  thức  được  đánh  dấu  là  Không  trong  cột  Có  thể  hủy (Killable) bảo đảm tiến trình giữ  acitivity khỏi bị  “giết” khỏi thời điểm mà chúng được  gọi.  Mặc  dù  vậy  một  ativity  trong  tình  trạng  có  thể  hủy  bắt  đầu  từ  khi onPause() đến onResume(). Nó  không  thể  bị  hủy cho đến  khi hàm onPause() kết thúc.

- Tiết  kiệm  tình  trạng  của  Activity:  Khi  hệ  thống  tắt  Activity  để  bảo toàn bộ  nhớ, người dùng mong muốn  Activity  giữ  được trạng thái  Activity như lúc trước khi tắt. Để  giữ  lại trạng thái trước khi  Activity  bị  tắt, bạn nên dùng  hàm  onSaveInstanceState()  cho  Activity.  Android  gọi  hàm  này  trước khi hủy acvitity. Tiếp theo sau đó, để  lấy lại trạng thái của  Activity  thì phải thực thi hàm onRestoreInstanceState()  sau hàm onStart() và hàm onCreate() đã  chạy  trước  đó.  Vì  thế  ta  có  thể  vừa  tái  tạo  và  lưu  vết  lại  trạng  thái Activity. Không  giống như hàm onPause() hay những hàm trong  vòng đời ứng  dụng,  hàm  onSaveInstanceState() hay hàm onRestoreInstanceState() không phải phương thức  vòng  đời của ứng dụng và không phải lúc nào cũng được gọi vì không phải lúc nào người dùng  cũng muốn lưu vết lại trạng thái của Activity.

- Phối hợp các activities: khi một Activity  khởi động một Activity khác, cả  hai  Activity  sẽ  thay đổi trạng thái của  vòng đời của  mình.  Activity  này tạm dừng  và có thể  dừng  hẳn  trong khi cái khác  thì mới khởi động. Trong trường hợp bạn có thể  cần phối hợp các  Activity  này khi mà 2  Activity  cùng nằm trong cùng một tiến trình.


2.2.1 Vòng đời Service

Một service có thể dùng trong 2 cách:

Cách 1: Nó có thể  được  khởi động và được cho phép  dùng  cho đến khi ai đó dừng  hẳn  nó  hoặc  nó  tự  dừng  hẳn.  Để  khởi  động  dùng  hàm Context.startService() và dừng hẳn bằng hàm Context.stopService() hoặc tựdừng  bằng  hàm  Service.stopSelf()  hoặc  Service.stopSelfResult(). Chú  ý  là hàm stopService() chỉ  được gọi  một  lần duy nhất để  dừng service trong khi đó gọi hàm startService() bao nhiêu lần tùy thích.

Cách 2: Nó có thể  được vận hành  bằng  mã lệnh dùng lớp interface  mà nó đã định nghĩa. Client thiết lập kết nối và dùng kết nối ấy để  gọi dịch vụ. Kết nối được  thiết lập bằng cách  gọi Context.bindService()  và được  đóng bởi hàm Context.unbindService().

Giống như Activity, Service có những phương ứng vòng đời của riêng mình mà có thể  thực thi để  giám sát sự thay đổi trong trạng thái của nó. Nhưng service có ít hơn  và gồm có 3 phương thức public: void onCreate(), void onStart (Intent intent), void onDestroy().

Bằng cách thực thi những phương thức này, bạn có thể giám sát vòng đời của service:

  • Toàn bộ vòng đời của service xảy ra khi gọi hàm onCreate()  và  kết thúc  khi  gọi  hàm  onDestroy().  Giống  như  một  Activity,  một  service  khởi động  trong  hàm  onCreate() và  giải  phóng  mọi  tài  nguyên  trong  hàm onDestroy().
  • Vòng đời “có hiệu lực” của service bắt đầu khi gọi hàm onStart(), và không được kết thúc bằng onStop() vì hàm này không có thực.

Phương thức onCreate()onDestroy() được gọi cho tất cả  dịch vụ  cho dù service khởi động bằng hàm Context.startService() hay hàm Context.bindService(). Tuy nhiên, onStart() được gọi  khi service đã khởi động bằng hàm startService(). Nếu  một  service  cho  phép  những  service  khác  liên  kết  với  nó  thì  phải  dùng  các phương  thức  sau:  IBinder  onBind (Intent  intent)boolean  onUnbind (Intent  intent), void onRebind(Intent intent).

Hình  sau  mô  tả  các  phương  thức  cho  service,  Mặc  dù,  hình  chia  ra  2  loại service dựa trên tiêu chí khởi động bởi hàm startService() và hàm bindService(). Cho dù nó khởi động bằng cách nào đi chăng nữa, thì nó vẫn cho client kết nối tới nó và bất kì service nào cũng có thể gọi 2 hàm onBind()onUnbind()

Vòng đời của một Service


2.2.3 Vòng đời BroadcastReceiver

Broadcast Receiver có một hàm cho phép người dùng viết lại là hàm void onCreate(Context curContext, Intent broadcastMsg).

Khi một tin nhắn phát đi cho người nhận, Android gọi phương thức onReceive() và chuyển nó cho đối tượng Intent đang chứa tin nhắn. Broadcast receiver được xem là có hiệu lực chỉ  khi nào có đang thực thi phương thức này. Khi phương thức onReceive() kết thúc thì broadcast receiver không còn hiệu lực nữa. 

Một  tiến  trình  với  một  broadcast  receiver thì được  bảo vệ  khỏi  bị “giết”. Nhưng một tiến trình với các thành phần không còn hiệu lực (Activity, service,…) thì bị hệ thống “giết” bất kì lúc nào khi mà bộ  nhớ không đủ cho các tiến trình khác.