Chào mừng trở lại với chuỗi bài viết “Android Developer Roadmap”! Sau khi đã cùng nhau tìm hiểu về lộ trình chung (Android Developer Roadmap – Lộ trình học Lập trình viên Android 2025), lựa chọn ngôn ngữ phù hợp giữa Kotlin và Java (Kotlin vs Java: Ngôn ngữ nào nên chọn cho Android vào năm 2025?), thiết lập môi trường phát triển (Lộ trình Học Lập trình viên Android 2025: Thiết lập Môi trường Phát triển Android – Hướng dẫn Chi tiết cho Người mới bắt đầu), và làm quen với cú pháp Kotlin cơ bản (Học Kotlin cho Android: Cú pháp và Khái niệm Cốt lõi Bạn Cần Nắm Vững), giờ là lúc chúng ta đào sâu vào một trong những khái niệm cốt lõi và mạnh mẽ nhất làm nền tảng cho việc xây dựng các ứng dụng Android hiện đại: Lập trình Hướng đối tượng (Object-Oriented Programming – OOP).
Nếu bạn mới bắt đầu, OOP có thể nghe có vẻ phức tạp, nhưng đừng lo lắng! OOP là một mô hình lập trình giúp chúng ta tổ chức code một cách logic, dễ hiểu, dễ bảo trì và tái sử dụng. Toàn bộ Framework Android được xây dựng dựa trên các nguyên tắc của OOP. Việc hiểu rõ OOP không chỉ giúp bạn viết code Android tốt hơn mà còn mở ra cánh cửa để hiểu sâu hơn cách các thành phần của Android hoạt động.
Trong bài viết này, chúng ta sẽ cùng nhau khám phá 4 trụ cột chính của OOP và xem cách chúng được áp dụng như thế nào trong thực tế phát triển ứng dụng Android, kèm theo các ví dụ minh họa bằng Kotlin.
Mục lục
OOP là gì và Tại sao nó Quan trọng trong Phát triển Android?
Lập trình Hướng đối tượng (OOP) là một mô hình lập trình dựa trên khái niệm “đối tượng”, có thể chứa dữ liệu (dưới dạng thuộc tính – attributes) và mã (dưới dạng phương thức – methods). Mục tiêu chính của OOP là tổ chức phần mềm xung quanh dữ liệu và các hành động trên dữ liệu đó, thay vì chỉ là logic hoặc chức năng.
Hãy nghĩ về thế giới thực. Mọi thứ xung quanh chúng ta đều là đối tượng: một chiếc điện thoại, một chiếc xe hơi, một con người. Mỗi đối tượng có các đặc điểm (màu sắc, kích thước, trạng thái pin, vận tốc) và hành động (gọi điện, chụp ảnh, lái xe, chạy). OOP cố gắng mô hình hóa thế giới thực này vào code.
Trong Android, bạn làm việc liên tục với các “đối tượng”: một `Activity` là một đối tượng, một `Button` là một đối tượng, một `ImageView` là một đối tượng. Mỗi đối tượng này có thuộc tính (ví dụ: văn bản trên Button, ảnh của ImageView) và hành động (ví dụ: Button phản ứng khi click, Activity khởi tạo khi được mở).
Việc sử dụng OOP mang lại nhiều lợi ích to lớn cho phát triển Android:
- Tái sử dụng code: Viết code một lần và sử dụng lại ở nhiều nơi khác nhau.
- Dễ bảo trì: Code được tổ chức rõ ràng thành các đối tượng riêng biệt, giúp việc sửa lỗi và cập nhật dễ dàng hơn.
- Dễ mở rộng: Dễ dàng thêm chức năng mới mà không ảnh hưởng nhiều đến code hiện có.
- Minh bạch và có cấu trúc: Code dễ đọc, dễ hiểu hơn nhờ cách tổ chức logic.
Để trở thành một lập trình viên Android giỏi, việc nắm vững OOP là điều kiện tiên quyết.
Bốn Trụ cột của Lập trình Hướng đối tượng
OOP được xây dựng dựa trên 4 nguyên tắc cốt lõi, thường được gọi là “bốn trụ cột”. Hiểu và áp dụng tốt các nguyên tắc này sẽ giúp code của bạn mạnh mẽ và linh hoạt hơn rất nhiều.
1. Tính Đóng gói (Encapsulation)
Khái niệm: Tính đóng gói là nguyên tắc kết hợp dữ liệu (thuộc tính) và các phương thức xử lý dữ liệu đó vào trong một “đơn vị” duy nhất, thường là một lớp (class). Nó giúp che giấu chi tiết triển khai bên trong của một đối tượng, chỉ cho phép truy cập hoặc sửa đổi dữ liệu thông qua các phương thức công khai (public methods). Điều này bảo vệ dữ liệu khỏi sự truy cập hoặc sửa đổi không mong muốn từ bên ngoài.
Ví dụ trong Android:
Hãy xem xét một lớp đại diện cho thông tin người dùng. Bạn muốn giữ cho dữ liệu như mật khẩu được bảo mật và chỉ cho phép thay đổi thông qua một quy trình xác thực.
class User(private val userId: String, private var passwordHash: String) {
var username: String = ""
var email: String = ""
private set // Setter is private, can only be set within the class
fun updateEmail(newEmail: String): Boolean {
// Simulate some validation logic
if (newEmail.contains("@") && newEmail.length > 5) {
this.email = newEmail
println("Email updated successfully for $username")
return true
} else {
println("Invalid email format for $username")
return false
}
}
fun changePassword(oldPasswordHash: String, newPasswordHash: String): Boolean {
// Simulate checking old password
if (this.passwordHash == oldPasswordHash) {
this.passwordHash = newPasswordHash
println("Password changed successfully for $username")
return true
} else {
println("Incorrect old password for $username")
return false
}
}
// Public getter for userId (Kotlin properties provide getters automatically)
fun getUserId(): String {
return userId
}
// We don't provide a public getter for passwordHash for security reasons
}
fun main() {
val user = User("user123", "hashed_old_password")
user.username = "Alice"
// user.email = "alice@example.com" // Error: Cannot access private setter
// user.passwordHash = "new_hash" // Error: passwordHash is private
user.updateEmail("alice@example.com") // Use public method to update email
user.changePassword("wrong_hash", "new_hashed_password") // Fails
user.changePassword("hashed_old_password", "new_hashed_password") // Succeeds
println("User ID: ${user.getUserId()}")
println("Username: ${user.username}")
// println("Email: ${user.email}") // You can access public properties (email's getter is public)
}
Trong ví dụ này:
- Dữ liệu (`userId`, `passwordHash`, `username`, `email`) và các hành động (`updateEmail`, `changePassword`, `getUserId`) được đóng gói trong lớp `User`.
- `passwordHash` được khai báo là `private`, chỉ có thể truy cập bên trong lớp `User`, bảo vệ nó khỏi bị thay đổi trực tiếp từ bên ngoài. Việc thay đổi mật khẩu phải qua phương thức `changePassword` có logic kiểm tra.
- `email` có setter là `private`, nghĩa là bạn không thể gán giá trị trực tiếp cho `email` từ bên ngoài lớp, mà phải dùng phương thức `updateEmail`.
- `username` và `userId` (thông qua getter `getUserId`) có thể truy cập công khai.
Tính đóng gói giúp kiểm soát luồng dữ liệu, đảm bảo tính toàn vẹn và bảo mật thông tin của đối tượng. Trong Android, hầu hết các thành phần UI, Controllers, Model đều áp dụng đóng gói để quản lý trạng thái và hành vi của chúng.
2. Tính Trừu tượng (Abstraction)
Khái niệm: Tính trừu tượng là nguyên tắc che giấu đi sự phức tạp không cần thiết và chỉ hiển thị những tính năng hoặc chức năng quan trọng nhất cho người dùng (ở đây là lập trình viên sử dụng lớp đó). Nó cho phép bạn tập trung vào “cái gì” một đối tượng làm được thay vì “làm như thế nào”.
Ví dụ trong Android:
Một ví dụ điển hình về tính trừu tượng trong Android là giao diện `View.OnClickListener`.
// Assume you have a Button in your layout file
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast // Import Toast
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) // Replace with your layout file
val myButton: Button = findViewById(R.id.my_button) // Replace with your Button ID
// Using an anonymous object that implements the OnClickListener interface
myButton.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
// This is the *only* method you need to implement
// You don't need to know how the Button handles the touch event internally
// or how it figures out it was clicked.
Toast.makeText(this@MyActivity, "Button clicked!", Toast.LENGTH_SHORT).show()
}
})
// In Kotlin, this is often simpler using a lambda:
myButton.setOnClickListener { v ->
Toast.makeText(this, "Button clicked (Lambda)!", Toast.LENGTH_SHORT).show()
}
// Or even simpler if the parameter is not needed:
myButton.setOnClickListener {
Toast.makeText(this, "Button clicked (Simpler Lambda)!", Toast.LENGTH_SHORT).show()
}
}
}
Khi bạn gọi `setOnClickListener` trên một `Button`, bạn chỉ cần cung cấp một đối tượng triển khai giao diện `View.OnClickListener` và định nghĩa logic cho phương thức `onClick()`. Bạn không cần biết chi tiết về cách `Button` phát hiện sự kiện chạm, xử lý tọa độ, hay xác định khi nào sự kiện “click” thực sự xảy ra. Tất cả sự phức tạp đó đã được trừu tượng hóa bên trong lớp `Button` và Framework Android. Bạn chỉ tương tác với nó qua một giao diện đơn giản là `OnClickListener`.
Các lớp trừu tượng (abstract classes) và giao diện (interfaces) là các công cụ chính để triển khai tính trừu tượng trong OOP.
3. Tính Kế thừa (Inheritance)
Khái niệm: Tính kế thừa cho phép một lớp mới (lớp con – subclass hoặc derived class) kế thừa các thuộc tính và phương thức từ một lớp hiện có (lớp cha – superclass hoặc base class). Lớp con có thể sử dụng lại các thành phần của lớp cha, mở rộng chúng hoặc ghi đè (override) một số phương thức để thay đổi hành vi. Kế thừa giúp tái sử dụng code và thiết lập mối quan hệ “là một loại của” (is-a relationship) giữa các lớp.
Ví dụ trong Android:
Đây là nguyên tắc bạn gặp hàng ngày khi lập trình Android. Hầu hết các lớp bạn tạo đều kế thừa từ một lớp khác của Framework Android.
// MyActivity extends AppCompatActivity
// AppCompatActivity extends FragmentActivity
// FragmentActivity extends Activity
// Activity extends ContextThemeWrapper
// ... and so on up the hierarchy
class MyActivity : AppCompatActivity() {
// MyActivity inherits methods like setContentView(), findViewById(), onCreate() etc.
// from its superclasses in the Android framework.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // Calls the onCreate method of the parent class
setContentView(R.layout.activity_main)
// ...
}
// You can also add new methods specific to MyActivity
fun setupViews() {
// ... setup UI elements
}
}
// Another example: Custom View
// MyCustomButton inherits from Button
// Button inherits from TextView
// TextView inherits from View
// ...
class MyCustomButton(context: Context, attrs: AttributeSet?) : Button(context, attrs) {
init {
// Initialize custom behavior
setBackgroundColor(Color.BLUE) // Override default background
setTextColor(Color.WHITE) // Override default text color
}
// You can override methods from the parent class (Button, TextView, View)
override fun setText(text: CharSequence?, type: TextView.BufferType?) {
super.setText("My Custom Button: $text", type) // Modify the text before setting
}
// Or add new methods
fun pulse() {
// Implement some animation effect
}
}
Trong ví dụ `MyActivity`:
- `MyActivity` kế thừa từ `AppCompatActivity`. Điều này có nghĩa là `MyActivity` tự động có tất cả các thuộc tính và phương thức của `AppCompatActivity` (và các lớp cha của nó), như quản lý vòng đời (`onCreate`, `onStart`, `onResume`,…), làm việc với giao diện (`setContentView`, `findViewById`), v.v.
- Bạn sử dụng từ khóa `override` để cung cấp triển khai riêng cho các phương thức đã được định nghĩa trong lớp cha (ví dụ: `onCreate`). Lệnh `super.onCreate(savedInstanceState)` gọi phương thức `onCreate` của lớp cha để đảm bảo logic cơ bản của Framework vẫn được thực thi.
Ví dụ `MyCustomButton` minh họa cách bạn tạo một loại `Button` mới với hành vi mặc định được thay đổi hoặc bổ sung. Bạn kế thừa từ `Button` và tùy chỉnh nó trong khối `init` hoặc bằng cách ghi đè các phương thức như `setText`.
Kế thừa là cách mạnh mẽ để xây dựng các lớp chuyên biệt từ các lớp tổng quát, giảm thiểu việc lặp lại code và tạo ra hệ thống phân cấp các đối tượng.
4. Tính Đa hình (Polymorphism)
Khái niệm: Tính đa hình (nghĩa là “nhiều hình thức”) cho phép các đối tượng thuộc các lớp khác nhau có thể phản hồi cùng một thông điệp (lời gọi phương thức) theo cách riêng của chúng. Nó thường được triển khai thông qua ghi đè phương thức (method overriding) khi kế thừa hoặc thông qua việc sử dụng giao diện (interfaces). Đa hình giúp code của bạn linh hoạt và dễ mở rộng hơn.
Ví dụ trong Android:
Hãy xem xét một danh sách các `View` khác nhau trong một layout. Mặc dù chúng là các loại `View` cụ thể (`Button`, `TextView`, `ImageView`), chúng đều kế thừa từ lớp cơ sở `View` và có thể được xử lý một cách thống nhất.
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class PolymorphismActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_polymorphism) // Replace with your layout file
// Assume these views exist in activity_polymorphism.xml
val button: Button = findViewById(R.id.my_button)
val textView: TextView = findViewById(R.id.my_text_view)
val imageView: ImageView = findViewById(R.id.my_image_view)
// Store different types of Views in a list of the base type View
val viewList: List<View> = listOf(button, textView, imageView)
// Iterate through the list and call a common method
for (view in viewList) {
// Although they are different types (Button, TextView, ImageView),
// they all inherit from View and have the setVisibility method.
// The exact *implementation* of setVisibility might differ slightly
// for each View type, but the method call is the same.
view.visibility = View.GONE // Hide all views
}
// Another example using an interface: Drawable
val drawables: List<Drawable> = listOf(
BitmapDrawable(resources, yourBitmap), // Assuming you have a Bitmap
ColorDrawable(Color.RED),
ShapeDrawable(...) // Assuming you create a ShapeDrawable
// Many other types implement Drawable
)
val someView = findViewById<View>(R.id.some_view)
for (drawable in drawables) {
// You can pass any object that implements the Drawable interface
// to methods expecting a Drawable. The specific type of drawable
// doesn't matter at the call site.
someView.background = drawable // View can accept any Drawable as background
}
}
}
Trong ví dụ đầu tiên:
- `button`, `textView`, và `imageView` là các đối tượng thuộc các lớp khác nhau (`Button`, `TextView`, `ImageView`).
- Tuy nhiên, vì tất cả đều kế thừa từ `View`, chúng ta có thể lưu trữ chúng trong một danh sách kiểu `List
`. - Chúng ta có thể gọi phương thức `visibility = View.GONE` trên từng phần tử của danh sách `viewList`. Dù là `Button`, `TextView`, hay `ImageView`, mỗi đối tượng sẽ thực hiện phương thức `setVisibility` theo cách riêng của nó (được định nghĩa trong lớp tương ứng hoặc lớp cha), nhưng cú pháp gọi là giống nhau. Đây là tính đa hình thông qua kế thừa và ghi đè.
Ví dụ thứ hai với `Drawable`:
- `BitmapDrawable`, `ColorDrawable`, `ShapeDrawable`,… là các lớp khác nhau.
- Tuy nhiên, tất cả đều triển khai giao diện `Drawable`.
- Bất kỳ phương thức nào mong đợi một đối tượng kiểu `Drawable` (ví dụ: `view.background = …`) đều có thể nhận bất kỳ đối tượng nào triển khai giao diện này. Đây là tính đa hình thông qua giao diện.
Đa hình giúp viết code linh hoạt, cho phép bạn làm việc với các nhóm đối tượng có liên quan thông qua một giao diện chung hoặc lớp cơ sở chung mà không cần quan tâm đến loại cụ thể của từng đối tượng tại thời điểm viết code.
OOP trong Các Thành phần Android
Framework Android là một minh chứng tuyệt vời về cách áp dụng các nguyên tắc OOP.
- Activities và Fragments: Bạn luôn tạo các lớp kế thừa từ `AppCompatActivity` hoặc `Fragment`. Đây là ví dụ rõ ràng về Kế thừa. Các lớp này cung cấp các phương thức vòng đời cơ bản mà bạn ghi đè (`onCreate`, `onResume`, etc.) để tùy chỉnh hành vi.
- Views và ViewGroups: Hệ thống View của Android là một cây phân cấp khổng lồ dựa trên Kế thừa. `TextView`, `Button`, `ImageView`, `EditText`, v.v., đều kế thừa từ `View`. `LinearLayout`, `RelativeLayout`, `ConstraintLayout`, v.v., kế thừa từ `ViewGroup` (mà `ViewGroup` lại kế thừa từ `View`). Điều này cho phép bạn coi tất cả chúng như các đối tượng `View` cơ bản (Đa hình) hoặc các đối tượng `ViewGroup` có thể chứa các `View` khác. Mỗi `View` cũng Đóng gói dữ liệu (kích thước, vị trí, trạng thái) và hành vi (vẽ, xử lý sự kiện chạm). Bạn tương tác với chúng qua các phương thức công khai, không cần biết chi tiết triển khai bên trong (Trừu tượng).
- Adapters: Khi làm việc với `RecyclerView` hoặc `ListView`, bạn tạo `Adapter` kế thừa từ các lớp Adapter cơ sở. Bạn ghi đè các phương thức như `onCreateViewHolder`, `onBindViewHolder`. Các `ViewHolder` có thể là các kiểu khác nhau nhưng đều được quản lý bởi `Adapter` (Kế thừa, Đa hình).
- Listeners và Callbacks: Android sử dụng rộng rãi các giao diện (interfaces) cho các sự kiện và callback (như `OnClickListener`, `TextWatcher`, `LifecycleObserver`). Đây là ví dụ về Trừu tượng và Đa hình. Bạn chỉ cần triển khai giao diện và cung cấp logic cho các phương thức được định nghĩa, mà không cần biết bên trong Framework xử lý sự kiện đó như thế nào.
- Intents: Một `Intent` đóng gói thông tin về một hành động cần thực hiện và dữ liệu liên quan. Framework Android sử dụng thông tin này để tìm ra thành phần phù hợp (Activity, Service, Broadcast Receiver) để xử lý `Intent`, che giấu đi quá trình tìm kiếm và khởi chạy phức tạp (Trừu tượng).
Lợi ích của OOP trong Android Development
Áp dụng tốt OOP mang lại nhiều lợi ích thiết thực:
- Tái sử dụng: Dễ dàng sử dụng lại các lớp, thành phần đã có, tiết kiệm thời gian và công sức.
- Bảo trì: Code có cấu trúc, dễ hiểu, khi có lỗi hoặc cần thay đổi, bạn biết chính xác cần tìm và sửa ở đâu.
- Mở rộng: Dễ dàng thêm chức năng mới hoặc sửa đổi chức năng hiện có mà không làm hỏng cấu trúc tổng thể.
- Linh hoạt: Nhờ tính đa hình, code có thể xử lý các loại đối tượng khác nhau một cách thống nhất.
- Hợp tác: Trong dự án nhóm, việc chia nhỏ công việc theo các đối tượng độc lập giúp các thành viên dễ dàng làm việc song song và tích hợp code.
Để củng cố kiến thức, hãy xem bảng tóm tắt các trụ cột OOP và ví dụ trong Android:
Nguyên tắc OOP | Giải thích | Ví dụ trong Android | Lợi ích chính |
---|---|---|---|
Đóng gói (Encapsulation) | Gói dữ liệu và phương thức vào một đơn vị (lớp). Che giấu chi tiết nội bộ. | Lớp User với thuộc tính private , các thành phần UI (Button , TextView ) quản lý trạng thái nội bộ. |
Kiểm soát truy cập dữ liệu, tăng bảo mật, dễ bảo trì. |
Trừu tượng (Abstraction) | Che giấu sự phức tạp, chỉ hiển thị các chức năng cần thiết. | Giao diện View.OnClickListener , các phương thức công khai của ImageView (setImageDrawable , setScaleType ) che giấu logic vẽ ảnh phức tạp. |
Giảm sự phức tạp, tăng tính dễ sử dụng API, tập trung vào “cái gì” thay vì “làm như thế nào”. |
Kế thừa (Inheritance) | Lớp con kế thừa thuộc tính và phương thức từ lớp cha. Mối quan hệ “là một loại của”. | AppCompatActivity kế thừa từ Activity , Button kế thừa từ TextView . |
Tái sử dụng code, tạo cấu trúc phân cấp, dễ mở rộng. |
Đa hình (Polymorphism) | Đối tượng thuộc các lớp khác nhau có thể phản hồi cùng một lời gọi phương thức theo cách riêng. | Danh sách các View khác nhau được xử lý chung (ví dụ: gọi setVisibility ). Sử dụng giao diện Drawable cho các loại hình ảnh khác nhau. |
Code linh hoạt, dễ mở rộng, có thể làm việc với các đối tượng liên quan thông qua giao diện chung. |
Thực hành OOP khi Lập trình Android
Để thực sự nắm vững OOP, bạn cần thực hành áp dụng các nguyên tắc này vào code hàng ngày.
- Khi tạo một lớp mới, hãy suy nghĩ về dữ liệu nó cần quản lý và các hành động nó thực hiện. Đóng gói dữ liệu bằng cách sử dụng modifier `private` và cung cấp các phương thức công khai để truy cập hoặc sửa đổi khi cần.
- Khi cần định nghĩa một hợp đồng (contract) cho các lớp khác nhau triển khai, hãy nghĩ đến việc sử dụng giao diện (interface) hoặc lớp trừu tượng (abstract class) để đạt được tính trừu tượng và đa hình.
- Khi cần tạo một biến thể của một lớp hiện có, hãy cân nhắc kế thừa để tận dụng code đã có.
- Tìm kiếm các mẫu thiết kế (design patterns) trong Android (như Adapter, Observer, Strategy…) – chúng đều dựa trên các nguyên tắc của OOP, đặc biệt là đa hình và trừu tượng.
Việc áp dụng OOP kết hợp với các kiến thức về cú pháp Kotlin/Java mà chúng ta đã học (Học Kotlin cho Android: Cú pháp và Khái niệm Cốt lõi Bạn Cần Nắm Vững) sẽ giúp code của bạn trở nên mạnh mẽ và chuyên nghiệp hơn rất nhiều. Đừng quên rằng việc cài đặt và làm quen với môi trường phát triển Android Studio (Lộ trình Học Lập trình viên Android 2025: Thiết lập Môi trường Phát triển Android – Hướng dẫn Chi tiết cho Người mới bắt đầu) là bước đầu tiên để bạn có thể viết và chạy các ví dụ code này.
Kết luận
Lập trình Hướng đối tượng không chỉ là một lý thuyết suông; nó là nền tảng vững chắc cho việc xây dựng các ứng dụng Android có cấu trúc, hiệu quả và dễ bảo trì. Bốn trụ cột – Đóng gói, Trừu tượng, Kế thừa và Đa hình – cung cấp cho bạn những công cụ mạnh mẽ để mô hình hóa vấn đề, tổ chức code và quản lý sự phức tạp.
Khi bạn tiếp tục hành trình trên Lộ trình Học Lập trình viên Android 2025, bạn sẽ thấy các nguyên tắc OOP xuất hiện ở mọi nơi, từ cách hoạt động của các thành phần UI đến cách bạn quản lý dữ liệu và tương tác với hệ thống. Hãy dành thời gian thực hành, viết code và cố gắng nhận diện cách các khái niệm OOP được áp dụng trong các thư viện và Framework bạn sử dụng.
Trong bài viết tiếp theo của chuỗi này, chúng ta sẽ bắt đầu đi sâu hơn vào các thành phần cụ thể của Android và cách chúng kết hợp lại để tạo nên một ứng dụng hoàn chỉnh.
Chúc bạn học tốt và hẹn gặp lại trong các bài viết tiếp theo!