Chào mừng bạn trở lại với loạt bài viết “Android Developer Roadmap“! Sau khi đã làm quen với ứng dụng “Hello World” đầu tiên, thiết lập môi trường phát triển, hiểu về Kotlin (hoặc lựa chọn giữa Kotlin và Java), nắm vững OOP, cấu trúc dữ liệu, Gradle, và Git, cùng với việc tìm hiểu sâu về vòng đời Activity và quản lý trạng thái, chúng ta đã sẵn sàng đào sâu hơn vào các cơ chế cốt lõi làm nên một ứng dụng Android. Ở bài viết gần đây, chúng ta đã “Giải Mã Giao Tiếp Giữa Các Thành Phần Android” thông qua Implicit vs Explicit Intents. Hôm nay, chúng ta sẽ tập trung vào một khái niệm đi liền với Implicit Intent và là xương sống cho khả năng tương tác giữa các ứng dụng: **Intent Filter**.
Nếu Intent là “ý định” muốn thực hiện một hành động nào đó, thì Intent Filter chính là “bảng kê khai khả năng” của các thành phần (Activity, Service, Broadcast Receiver). Chúng cho hệ thống Android biết rằng: “Tôi (thành phần này) có thể xử lý những Intent có đặc điểm A, B, C”. Việc hiểu và sử dụng Intent Filter một cách hiệu quả là cực kỳ quan trọng, không chỉ để ứng dụng của bạn có thể hoạt động mượt mà với các thành phần nội bộ, mà còn để nó có thể tương tác với các ứng dụng khác trên thiết bị.
Bài viết này sẽ đưa bạn đi từ định nghĩa cơ bản đến cách cấu hình và sử dụng Intent Filter một cách hiệu quả trong các tình huống thực tế. Hãy cùng bắt đầu!
Mục lục
Intent Filter Là Gì?
Trong Android, Intent Filter là một phần tử trong file cấu hình `AndroidManifest.xml` dùng để khai báo khả năng của một thành phần ứng dụng (Activity, Service, hoặc Broadcast Receiver). Nó cho hệ thống biết loại Implicit Intent nào mà thành phần đó sẵn sàng nhận và xử lý. Khi một Implicit Intent được gửi đi, hệ thống Android sẽ quét qua tất cả các Intent Filter được khai báo trong file manifest của các ứng dụng đã cài đặt để tìm ra thành phần (hoặc nhiều thành phần) phù hợp nhất để xử lý Intent đó.
Hãy tưởng tượng bạn có một Intent với hành động là “xem” một bức ảnh. Hệ thống Android sẽ tìm tất cả các Activity hoặc các thành phần khác đã đăng ký một Intent Filter cho phép xử lý hành động “xem” (ACTION_VIEW
) đối với dữ liệu có kiểu là hình ảnh (image/*
). Nếu tìm thấy, Android sẽ hiển thị một hộp thoại cho phép người dùng chọn ứng dụng nào sẽ mở bức ảnh đó (trừ khi người dùng đã đặt ứng dụng mặc định), hoặc tự động mở nếu chỉ có một ứng dụng phù hợp.
Về mặt kỹ thuật, Intent Filter được khai báo bên trong thẻ của thành phần tương ứng trong `AndroidManifest.xml`. Ví dụ:
<manifest ...>
<application ...>
<activity android:name=".MyActivity">
<!-- Đây là một Intent Filter -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Có thể có nhiều Intent Filter cho một thành phần -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<!-- Các thành phần khác cũng có thể có Intent Filter -->
<service android:name=".MyService">
<intent-filter>
<action android:name="com.example.app.START_MY_SERVICE" />
</intent-filter>
</service>
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
</application>
</manifest>
Mỗi thẻ `
Các Phần Tử Của Intent Filter
Một Intent Filter được tạo nên từ ba phần tử chính:
-
<action>
:- Khai báo hành động mà thành phần có thể thực hiện.
- Intent gửi đến phải có hành động (action) trùng khớp với một trong các hành động được liệt kê trong filter.
- Một filter phải chứa ít nhất một thẻ
<action>
. Nếu không có, filter đó sẽ không chấp nhận Intent nào. - Ví dụ:
android:name="android.intent.action.VIEW"
(Xem),android:name="android.intent.action.SEND"
(Chia sẻ),android:name="android.intent.action.MAIN"
(Điểm vào chính). - Bạn cũng có thể định nghĩa các action tùy chỉnh (custom actions) riêng cho ứng dụng của mình.
- Lưu ý: Tên action phải là chuỗi (String).
-
<category>
:- Khai báo loại (category) của Intent mà thành phần có thể xử lý.
- Cung cấp ngữ cảnh hoặc thông tin bổ sung về hành động.
- Một filter có thể chứa không hoặc nhiều thẻ
<category>
. - Để Intent match với filter, *tất cả* các category trong Intent phải khớp với *ít nhất một* category trong filter.
- Có một ngoại lệ quan trọng: Nếu Intent gửi đi không có category nào, nó vẫn sẽ khớp với filter *miễn là* filter đó có chứa category
android.intent.category.DEFAULT
(hầu hết các Activity được khởi chạy bằngstartActivity()
đều được tự động thêm categoryCATEGORY_DEFAULT
vào Intent). - Ví dụ:
android:name="android.intent.category.LAUNCHER"
(ứng dụng xuất hiện trong launcher),android:name="android.intent.category.BROWSABLE"
(có thể mở từ trình duyệt web),android:name="android.intent.category.DEFAULT"
(category mặc định).
-
<data>
:- Khai báo loại dữ liệu (data type) mà thành phần có thể xử lý.
- Xác định URI (Uniform Resource Identifier) và/hoặc kiểu MIME (MIME type) của dữ liệu.
- Một filter có thể chứa không hoặc nhiều thẻ
<data>
. - Các thuộc tính phổ biến:
android:scheme
: Giao thức (ví dụ:http
,https
,content
,file
, hoặc custom scheme nhưmyapp
).android:host
: Tên host trong URI (ví dụ:www.example.com
).android:port
: Cổng trong URI.android:path
,android:pathPattern
,android:pathPrefix
: Đường dẫn cụ thể hoặc mẫu đường dẫn.android:mimeType
: Kiểu dữ liệu MIME (ví dụ:image/*
,text/plain
,application/pdf
).
- Để Intent match với filter, URI và/hoặc MIME type trong Intent phải khớp với *ít nhất một* đặc tả
<data>
trong filter. Quy tắc match của phần tử<data>
khá phức tạp và phụ thuộc vào việc các thuộc tính nào được chỉ định trong cả Intent và filter.
Cách Hoạt Động Của Cơ Chế Lọc (Intent Resolution)
Khi hệ thống nhận được một Implicit Intent để khởi động một Activity, Service, hoặc gửi Broadcast, nó sẽ thực hiện quá trình được gọi là “Intent Resolution” để tìm ra thành phần phù hợp nhất. Quá trình này bao gồm việc so sánh Intent với các Intent Filter đã đăng ký theo các quy tắc sau:
- Action Test: Intent phải có một action khớp với *một trong* các action được liệt kê trong filter. Nếu Intent không có action, nó chỉ vượt qua bài kiểm tra này nếu filter không liệt kê action nào (điều này hiếm khi xảy ra và filter sẽ không nhận được Intent nào có action).
- Category Test: *Tất cả* các category được chỉ định trong Intent phải khớp với *ít nhất một* category được liệt kê trong filter. Lưu ý đặc biệt về
CATEGORY_DEFAULT
: nếu Intent không có category nào, nó sẽ vượt qua bài kiểm tra này chỉ khi filter cóCATEGORY_DEFAULT
. Ngược lại, nếu Intent có category (ngay cả khi đó chỉ làCATEGORY_DEFAULT
được thêm tự động), nó sẽ vượt qua chỉ khi filter chứa category đó. - Data Test: Bài kiểm tra dữ liệu là phức tạp nhất. Nó so sánh URI và MIME type của Intent với các đặc tả
<data>
trong filter.- Nếu Intent không có dữ liệu (không có URI và không có MIME type), nó chỉ vượt qua bài kiểm tra này nếu filter không chỉ định bất kỳ
<data>
nào. - Nếu Intent có URI nhưng không có MIME type, nó vượt qua bài kiểm tra chỉ khi một trong các
<data>
trong filter khớp với URI và cũng không chỉ định MIME type. - Nếu Intent có MIME type nhưng không có URI, nó vượt qua bài kiểm tra chỉ khi một trong các
<data>
trong filter khớp với MIME type và cũng không chỉ định URI. - Nếu Intent có cả URI và MIME type, nó vượt qua bài kiểm tra chỉ khi một trong các
<data>
trong filter khớp với cả URI *và* MIME type.
Các quy tắc khớp URI (scheme, host, port, path) cũng có những chi tiết phức tạp mà bạn có thể tham khảo thêm trong tài liệu chính thức của Android để hiểu sâu hơn.
- Nếu Intent không có dữ liệu (không có URI và không có MIME type), nó chỉ vượt qua bài kiểm tra này nếu filter không chỉ định bất kỳ
Chỉ khi một Implicit Intent vượt qua *tất cả* ba bài kiểm tra (Action, Category, và Data) của một Intent Filter, nó mới được coi là có khả năng được xử lý bởi thành phần sở hữu filter đó. Nếu có nhiều thành phần đủ điều kiện, hệ thống có thể hỏi người dùng chọn (như khi bạn mở một liên kết web và có nhiều trình duyệt cài đặt).
Cách Sử Dụng Intent Filter Hiệu Quả
Sử dụng Intent Filter hiệu quả giúp ứng dụng của bạn tương tác mượt mà với hệ thống và các ứng dụng khác. Dưới đây là một số cách sử dụng phổ biến:
1. Đặt Activity Khởi Chạy (Launcher Activity)
Mỗi ứng dụng thường có một Activity là điểm vào chính, xuất hiện khi người dùng chạm vào biểu tượng ứng dụng trên màn hình chính. Điều này được cấu hình bằng Intent Filter sau:
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
ACTION_MAIN
: Chỉ ra đây là điểm vào chính.CATEGORY_LAUNCHER
: Chỉ ra rằng Activity này nên xuất hiện trong danh sách các ứng dụng có thể chạy.android:exported="true"
: Thuộc tính này (bắt buộc từ Android 12 trở lên nếu có intent filter cho phép bên ngoài tương tác) chỉ rõ rằng thành phần này có thể được khởi chạy bởi các thành phần bên ngoài ứng dụng của bạn. VớiMAIN
/LAUNCHER
, điều này là cần thiết.
2. Xử Lý Liên Kết Web (Deep Links)
Bạn muốn một trang web cụ thể mở ra một Activity trong ứng dụng của mình? Sử dụng Intent Filter với thẻ <data>
:
<activity android:name=".DetailActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Xử lý liên kết HTTP/HTTPS -->
<data android:scheme="http" android:host="www.mygreatapp.com" android:pathPrefix="/products/" />
<data android:scheme="https" android:host="www.mygreatapp.com" android:pathPrefix="/products/" />
<!-- Xử lý custom scheme cho ứng dụng của bạn -->
<data android:scheme="mygreatapp" android:host="example.com" />
</intent-filter>
</activity>
ACTION_VIEW
: Hành động xem dữ liệu.CATEGORY_DEFAULT
: Quan trọng để Activity có thể nhận Implicit Intent (như Intent được gửi bởistartActivity()
).CATEGORY_BROWSABLE
: Cho phép Activity được mở từ trình duyệt web. Nếu không có category này, người dùng nhấp vào liên kết trên web sẽ không mở ứng dụng của bạn.- Thẻ
<data>
: Khai báo các URI patterns mà Activity có thể xử lý. Ở đây là các liên kết HTTP/HTTPS đến hostwww.mygreatapp.com
với đường dẫn bắt đầu bằng `/products/`, và cả một custom schememygreatapp://example.com
. android:exported="true"
: Cần thiết vì các liên kết này có thể đến từ các ứng dụng bên ngoài (trình duyệt, email, etc.).
Trong DetailActivity
, bạn có thể lấy URI từ Intent:
val intent = intent
val data: Uri? = intent.data
if (data != null) {
val productId = data.pathSegments.lastOrNull()
// Sử dụng productId để tải dữ liệu chi tiết sản phẩm và hiển thị
}
3. Nhận Dữ Liệu Chia Sẻ
Làm cho ứng dụng của bạn có thể nhận dữ liệu được chia sẻ từ các ứng dụng khác (ví dụ: chia sẻ một bức ảnh từ ứng dụng Gallery, hoặc một đoạn văn bản từ trình duyệt):
<activity android:name=".ShareActivity" android:exported="true">
<intent-filter>
<!-- Nhận một mục duy nhất -->
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" /> <!-- Nhận bất kỳ loại ảnh nào -->
<data android:mimeType="text/plain" /> <!-- Nhận văn bản thuần túy -->
</intent-filter>
<intent-filter>
<!-- Nhận nhiều mục -->
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" /> <!-- Nhận nhiều ảnh -->
</intent-filter>
</activity>
ACTION_SEND
vàACTION_SEND_MULTIPLE
: Các action chuẩn cho việc chia sẻ dữ liệu.- Thẻ
<data>
vớiandroid:mimeType
: Chỉ định loại dữ liệu có thể nhận. Bạn có thể liệt kê nhiều loại hoặc sử dụng wildcard (*
) cho loại phụ (subtype). android:exported="true"
: Cần thiết để các ứng dụng khác có thể gửi Intent chia sẻ đến Activity này.
Trong ShareActivity
, bạn có thể lấy dữ liệu được chia sẻ từ Intent:
when (intent.action) {
Intent.ACTION_SEND -> {
if (intent.type?.startsWith("image/") == true) {
(intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM))?.let { imageUri ->
// Xử lý URI ảnh đơn
}
} else if (intent.type == "text/plain") {
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { sharedText ->
// Xử lý văn bản đơn
}
}
}
Intent.ACTION_SEND_MULTIPLE -> {
if (intent.type?.startsWith("image/") == true) {
intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)?.let { imageUris ->
// Xử lý danh sách URI ảnh
}
}
}
}
4. Đăng Ký Nhận Broadcast System
Broadcast Receiver sử dụng Intent Filter để lắng nghe các Broadcast Intent từ hệ thống hoặc các ứng dụng khác:
<receiver android:name=".ConnectivityChangeReceiver" android:exported="false"> <!-- exported="false" thường là an toàn hơn -->
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<!-- Kể từ Android 8.0, các broadcast tĩnh này thường bị hạn chế hoặc cần quyền đặc biệt -->
</intent-filter>
</receiver>
Lưu ý: Từ Android 8.0 (API level 26), hầu hết các broadcast ngầm (implicit broadcast) gửi đến các receiver khai báo tĩnh trong manifest đã bị hạn chế để cải thiện hiệu năng và pin. Bạn thường cần đăng ký Broadcast Receiver động (dynamically) trong mã nguồn hoặc sử dụng các cơ chế khác (như WorkManager) cho các tác vụ nền.
5. Khởi Động Service Bằng Implicit Intent (Cẩn trọng)
Mặc dù có thể khai báo Intent Filter cho Service, việc sử dụng Implicit Intent để khởi động hoặc bind đến Service đã bị hạn chế đáng kể từ Android 5.0 (API level 21) để tăng cường bảo mật và độ tin cậy. Bạn nên luôn sử dụng Explicit Intent để khởi động hoặc bind đến Service trong ứng dụng của mình hoặc sử dụng cơ chế gửi Intent rõ ràng từ các ứng dụng khác (cần chữ ký tương đồng hoặc quyền đặc biệt). Tuy nhiên, bạn vẫn có thể thấy các filter cho Service trong một số trường hợp đặc biệt hoặc các API cũ.
Bảng Tóm Tắt Các Ví Dụ Phổ Biến
Dưới đây là bảng tóm tắt một số trường hợp sử dụng Intent Filter phổ biến:
Mục đích | Intent Action | Intent Category | Data Specification | Ví dụ Khai báo trong Manifest |
---|---|---|---|---|
Activity khởi chạy chính | android.intent.action.MAIN |
android.intent.category.LAUNCHER |
(Không có) | <action android:name="android.intent.action.MAIN"/><br/><category android:name="android.intent.category.LAUNCHER"/> |
Xử lý liên kết web (Deep Link) | android.intent.action.VIEW |
android.intent.category.DEFAULT ,android.intent.category.BROWSABLE |
scheme , host , path /pathPattern /pathPrefix |
<action android:name="android.intent.action.VIEW"/><br/><category android:name="android.intent.category.DEFAULT"/><br/><category android:name="android.intent.category.BROWSABLE"/><br/><data android:scheme="http" android:host="example.com"/><br/><data android:scheme="https" android:host="example.com"/> |
Xử lý dữ liệu (file, content URI) | android.intent.action.VIEW |
android.intent.category.DEFAULT |
mimeType hoặc scheme (ví dụ: content , file ) + mimeType |
<action android:name="android.intent.action.VIEW"/><br/><category android:name="android.intent.category.DEFAULT"/><br/><data android:mimeType="image/*"/> |
Nhận dữ liệu chia sẻ (đơn) | android.intent.action.SEND |
android.intent.category.DEFAULT |
mimeType |
<action android:name="android.intent.action.SEND"/><br/><category android:name="android.intent.category.DEFAULT"/><br/><data android:mimeType="text/plain"/> |
Nhận dữ liệu chia sẻ (nhiều) | android.intent.action.SEND_MULTIPLE |
android.intent.category.DEFAULT |
mimeType |
<action android:name="android.intent.action.SEND_MULTIPLE"/><br/><category android:name="android.intent.category.DEFAULT"/><br/><data android:mimeType="image/*"/> |
Lắng nghe một số System Broadcast (Receiver – Cần cẩn trọng với Android 8+) | Các action của hệ thống (ví dụ: android.intent.action.BOOT_COMPLETED ) |
(Thường không có, hoặc android.intent.category.DEFAULT ) |
(Không có) | <action android:name="android.intent.action.BOOT_COMPLETED"/> |
An Toàn Với Intent Filter (exported)
Như bạn đã thấy trong các ví dụ, thuộc tính android:exported
là rất quan trọng khi làm việc với Intent Filter. Thuộc tính này chỉ định liệu thành phần của bạn có thể được khởi chạy bởi các ứng dụng khác trên thiết bị hay không.
- Nếu một thành phần có bất kỳ Intent Filter nào (ngoại trừ filter chỉ chứa
ACTION_MAIN
vàCATEGORY_LAUNCHER
), thuộc tínhexported
của nó sẽ mặc định làtrue
(đối với các phiên bản Android cũ hơn API 31). - Nếu một thành phần không có Intent Filter nào, thuộc tính
exported
của nó sẽ mặc định làfalse
. - Từ Android 12 (API level 31), bạn *phải* khai báo rõ ràng giá trị của
android:exported
(true hoặc false) cho bất kỳ thành phần nào có khai báo Intent Filter. Nếu không, ứng dụng của bạn sẽ không cài đặt được. Ngoại lệ duy nhất là các Activity chỉ chứa filter choMAIN
vàLAUNCHER
.
Luôn cẩn trọng khi đặt android:exported="true"
hoặc để thuộc tính này mặc định là true (trên các API cũ). Nếu bạn khai báo một Intent Filter cho phép ứng dụng khác gọi đến thành phần của mình, hãy đảm bảo rằng thành phần đó (Activity, Service, Receiver) đã được bảo vệ đầy đủ để xử lý các Intent đến từ bên ngoài một cách an toàn. Đừng xử lý dữ liệu nhạy cảm hoặc thực hiện các hành động nguy hiểm dựa trên dữ liệu từ Intent mà không xác thực nguồn gốc nếu cần thiết.
Kiểm Tra Intent Filter Của Bạn
Để kiểm tra xem Intent Filter của bạn có hoạt động như mong đợi hay không, bạn có thể sử dụng công cụ adb
(Android Debug Bridge) thông qua dòng lệnh:
adb shell am start -W -a android.intent.action.VIEW -d "http://www.mygreatapp.com/products/123" com.your.package.name
Lệnh này sẽ cố gắng khởi động một Activity (am start
) với action ACTION_VIEW
(-a
) và dữ liệu URI http://www.mygreatapp.com/products/123
(-d
) trong ứng dụng có package name com.your.package.name
. Hệ thống sẽ tìm kiếm Intent Filter phù hợp và nếu tìm thấy Activity của bạn, nó sẽ được khởi chạy. Bạn cũng có thể thêm category (-c
), mime type (-t
) và các extras khác vào lệnh am start
để mô phỏng các Intent phức tạp hơn.
Kết Luận
Intent Filter là một phần không thể thiếu trong hệ thống Intent của Android, đặc biệt là đối với Implicit Intent. Chúng là cơ chế mạnh mẽ giúp các thành phần ứng dụng khai báo khả năng của mình và cho phép hệ thống Android tìm thấy thành phần phù hợp nhất để xử lý các yêu cầu (Intent) từ các ứng dụng khác hoặc từ hệ thống.
Việc nắm vững cách cấu hình và sử dụng các phần tử <action>
, <category>
, và <data>
trong Intent Filter sẽ mở ra cánh cửa cho ứng dụng của bạn để tương tác liền mạch với hệ sinh thái Android, từ việc trở thành ứng dụng mặc định cho các loại dữ liệu cụ thể, xử lý deep links, cho đến việc nhận dữ liệu chia sẻ từ người dùng. Đồng thời, luôn ghi nhớ về thuộc tính android:exported
và các khía cạnh bảo mật khi làm cho các thành phần của bạn có thể truy cập được từ bên ngoài.
Đây là một bước tiến quan trọng trên lộ trình trở thành Lập trình viên Android chuyên nghiệp của bạn. Hãy thực hành cấu hình các Intent Filter khác nhau trong file AndroidManifest.xml
của bạn và thử gửi các Implicit Intent để xem chúng hoạt động như thế nào. Đừng ngần ngại tham khảo thêm tài liệu chính thức của Android để hiểu sâu hơn về các quy tắc matching phức tạp, đặc biệt là với phần tử <data>
.
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 của loạt bài “Android Developer Roadmap”!