Một ứng dụng chỉ cần người dùng chọn ảnh đại diện thường không cần xây màn hình duyệt toàn bộ thư viện. Android Photo Picker cho người dùng chọn ảnh cụ thể và trao quyền đọc phần dữ liệu đã chọn cho ứng dụng. Trách nhiệm tiếp theo là xử lý đúng URI được trả về và thời hạn truy cập của nó.
Bài dùng API AndroidX Activity, đối chiếu tài liệu Android ngày 23/10/2026. Tài liệu yêu cầu Activity từ 1.7.0 để dùng các contract này; đó là mốc API, không phải đề xuất cài một bản cũ bất kỳ. Chọn bản dependency phù hợp compile SDK, min SDK và các thư viện của dự án. Ví dụ chưa được build hay chạy trên thiết bị trong lượt biên tập.
Đặt luồng chọn ảnh trong Activity Result
PickVisualMedia dành cho một ảnh hoặc video. Dùng request ImageOnly khi tính năng chỉ nhận ảnh. Nếu picker không có trên thiết bị, AndroidX có cơ chế fallback sang ACTION_OPEN_DOCUMENT theo điều kiện nền tảng mà tài liệu mô tả; không cần tự suy đoán chỉ bằng một phép so sánh phiên bản Android.
Ví dụ Activity dưới đây chỉ xác nhận đã có lựa chọn. Nó không đọc file, upload hay giải mã ảnh trên main thread. Trong dự án thử, khai báo Activity tương ứng trong manifest và giữ cấu hình AndroidX đang dùng; phần package ở đầu file cần khớp ứng dụng của bạn.
package com.example.photodemo
import android.net.Uri
import android.os.Bundle
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
class PhotoActivity : ComponentActivity() {
private var selectedUri: Uri? = null
private lateinit var status: TextView
private val pickPhoto = registerForActivityResult(PickVisualMedia()) { uri ->
if (uri != null) {
selectedUri = uri
status.text = "Da chon anh"
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
status = TextView(this).apply { text = "Chua chon anh" }
val choose = Button(this).apply {
text = "Chon anh"
setOnClickListener {
pickPhoto.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly))
}
}
setContentView(LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(status)
addView(choose)
})
}
}
Launcher được đăng ký nhất quán khi Activity được tạo, thay vì tạo mới bên trong mỗi lần bấm nút. Khi người dùng hủy và callback nhận null, ví dụ giữ nguyên lựa chọn trước đó. Nếu sản phẩm muốn nút “xóa ảnh”, hãy triển khai thao tác riêng để việc hủy picker không vô tình xóa state.
URI là định danh để truy cập, không phải đường dẫn file
Không lấy một đoạn trong chuỗi URI rồi dùng nó như đường dẫn filesystem. Ảnh có thể đến từ provider và có thể cần tải từ nguồn cloud. Dùng ContentResolver hoặc thư viện hiển thị ảnh hỗ trợ URI, xử lý lỗi đọc và đóng stream bằng cơ chế quản lý tài nguyên phù hợp.
Khi bổ sung upload, đọc và xử lý dữ liệu ở luồng làm việc phù hợp, giới hạn kích thước và kiểm tra loại dữ liệu mà backend chấp nhận. Việc picker lọc ảnh không thay cho kiểm tra file tại nơi nhận upload. Tránh ghi URI và metadata ảnh của người dùng vào log chỉ để chứng minh callback có chạy.
Ví dụ lưu selectedUri trong field để giữ bài tập ngắn. Field đó không tồn tại qua process death và không phải cách khôi phục state hoàn chỉnh. Ứng dụng thật cần quyết định dữ liệu nào giữ bằng SavedState hay bộ lưu trữ của tác vụ, rồi khôi phục giao diện và xử lý URI không còn đọc được.
Upload nền cần xem lại thời hạn quyền
Theo tài liệu Photo Picker, quyền đọc mặc định có thể kết thúc khi ứng dụng dừng hoặc thiết bị khởi động lại. Với công việc kéo dài, ví dụ upload nền, tài liệu hướng dẫn giữ quyền bằng lời gọi sau trong ngữ cảnh có context và URI thực sự được cấp:
val flags = android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flags)
Chỉ lưu chuỗi URI vào database không giữ quyền truy cập. Ngược lại, giữ được quyền cũng không có nghĩa ảnh chắc chắn tồn tại mãi. Tác vụ cần xử lý lỗi quyền hoặc nguồn dữ liệu biến mất, cho phép người dùng chọn lại và giải phóng grant khi ứng dụng không còn sử dụng, sau khi xét các tác vụ khác còn tham chiếu cùng URI.
Lời gọi giữ quyền có thể thất bại; xử lý lỗi phù hợp thay vì luôn đánh dấu tác vụ sẵn sàng. Với ảnh chỉ dùng ngay để preview, đừng tự tạo yêu cầu giữ quyền dài hạn nếu sản phẩm không cần.
Ma trận kiểm tra trước khi release
Thử chọn một ảnh, hủy khi chưa có lựa chọn, hủy sau khi đã chọn và chọn ảnh mới thay ảnh cũ. Sau đó kiểm tra xoay màn hình, ứng dụng bị hệ thống tạo lại, ảnh từ cloud và lỗi trong lúc đọc dữ liệu. Nếu hỗ trợ upload nền, thử cả khởi động lại ứng dụng hoặc thiết bị theo vòng đời được thiết kế.
Ghi rõ thiết bị nào dùng picker hệ thống và thiết bị nào dùng fallback. Riêng luồng chọn ảnh này chỉ yêu cầu quyền với nội dung người dùng chọn; các tính năng đọc thư viện khác của cùng ứng dụng phải được đánh giá riêng. Đừng suy ra một lần chọn thành công cho phép ứng dụng đọc mọi ảnh.
Kết luận
Photo Picker giúp thu hẹp phạm vi dữ liệu mà người dùng chia sẻ. Luồng đáng tin cậy còn cần xử lý hủy chọn, đọc qua URI, khôi phục state và giữ quyền đúng khi chuyển công việc sang nền. Đây là những ca nên kiểm tra trực tiếp trên thiết bị trước khi phát hành.





