Sau khi thiết lập ứng dụng để chia sẻ tệp bằng URI nội dung, bạn có thể phản hồi với các ứng dụng khác các yêu cầu cho những tệp đó. Một cách để phản hồi những yêu cầu này là cung cấp lựa chọn về tệp giao diện từ ứng dụng máy chủ mà các ứng dụng khác có thể gọi. Phương pháp này cho phép khách hàng cho phép người dùng chọn một tệp từ ứng dụng máy chủ rồi nhận các tệp đã chọn URI nội dung.
Bài học này sẽ hướng dẫn bạn cách chọn tệp Activity
trong ứng dụng
phản hồi các yêu cầu về tệp.
Nhận yêu cầu tệp
Để nhận yêu cầu về tệp từ ứng dụng khách và phản hồi bằng URI nội dung, ứng dụng của bạn phải
hãy cung cấp lựa chọn tệp Activity
. Ứng dụng khách khởi động quy trình này
Activity
bằng cách gọi startActivityForResult()
bằng Intent
chứa thao tác
ACTION_PICK
. Khi ứng dụng khách gọi
startActivityForResult()
, ứng dụng của bạn có thể
trả về kết qu�� cho ứng dụng khách, dưới dạng URI nội dung cho tệp mà người dùng đã chọn.
Để tìm hiểu cách triển khai yêu cầu cho một tệp trong ứng dụng khách, hãy xem bài học Yêu cầu một tệp được chia sẻ.
Tạo một hoạt động lựa chọn tệp
Để thiết lập lựa chọn tệp Activity
, hãy bắt đầu bằng cách chỉ định
Activity
trong tệp kê khai của bạn, cùng với một bộ lọc ý định
phù hợp với hành động ACTION_PICK
và
danh mục CATEGORY_DEFAULT
và
CATEGORY_OPENABLE
. Đồng thời thêm bộ lọc loại MIME
cho các tệp mà ứng dụng của bạn phân phối đến các ứng dụng khác. Đoạn mã sau đây minh hoạ cách chỉ định
Activity
mới và bộ lọc ý định:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> ... <application> ... <activity android:name=".FileSelectActivity" android:label="@File Selector" > <intent-filter> <action android:name="android.intent.action.PICK"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.OPENABLE"/> <data android:mimeType="text/plain"/> <data android:mimeType="image/*"/> </intent-filter> </activity>
Xác định Hoạt động lựa chọn tệp trong mã
Tiếp theo, hãy xác định một lớp con Activity
hiển thị các tệp có sẵn từ
thư mục files/images/
của ứng dụng vào bộ nhớ trong và cho phép người dùng chọn
tệp mong muốn. Đoạn mã sau đây minh hoạ cách xác định điều này
Activity
và phản hồi lựa chọn của người dùng:
Kotlin
class MainActivity : Activity() { // The path to the root of this app's internal storage private lateinit var privateRootDir: File // The path to the "images" subdirectory private lateinit var imagesDir: File // Array of files in the images subdirectory private lateinit var imageFiles: Array<File> // Array of filenames corresponding to imageFiles private lateinit var imageFilenames: Array<String> // Initialize the Activity override fun onCreate(savedInstanceState: Bundle?) { ... // Set up an Intent to send back to apps that request a file resultIntent = Intent("com.example.myapp.ACTION_RETURN_FILE") // Get the files/ subdirectory of internal storage privateRootDir = filesDir // Get the files/images subdirectory; imagesDir = File(privateRootDir, "images") // Get the files in the images subdirectory imageFiles = imagesDir.listFiles() // Set the Activity's result to null to begin with setResult(Activity.RESULT_CANCELED, null) /* * Display the file names in the ListView fileListView. * Back the ListView with the array imageFilenames, which * you can create by iterating through imageFiles and * calling File.getAbsolutePath() for each File */ ... } ... }
Java
public class MainActivity extends Activity { // The path to the root of this app's internal storage private File privateRootDir; // The path to the "images" subdirectory private File imagesDir; // Array of files in the images subdirectory File[] imageFiles; // Array of filenames corresponding to imageFiles String[] imageFilenames; // Initialize the Activity @Override protected void onCreate(Bundle savedInstanceState) { ... // Set up an Intent to send back to apps that request a file resultIntent = new Intent("com.example.myapp.ACTION_RETURN_FILE"); // Get the files/ subdirectory of internal storage privateRootDir = getFilesDir(); // Get the files/images subdirectory; imagesDir = new File(privateRootDir, "images"); // Get the files in the images subdirectory imageFiles = imagesDir.listFiles(); // Set the Activity's result to null to begin with setResult(Activity.RESULT_CANCELED, null); /* * Display the file names in the ListView fileListView. * Back the ListView with the array imageFilenames, which * you can create by iterating through imageFiles and * calling File.getAbsolutePath() for each File */ ... } ... }
Phản hồi lựa chọn tệp
Sau khi người dùng chọn một tệp được chia sẻ, ứng dụng của bạn phải xác định tệp nào được chọn và
sau đó tạo URI nội dung cho tệp. Vì Activity
hiển thị
danh sách các tệp hiện có trong ListView
, khi người dùng nhấp vào tên tệp
hệ thống sẽ gọi phương thức onItemClick()
, trong đó bạn có thể lấy tệp đã chọn.
Khi dùng một ý định để gửi URI của một tệp từ ứng dụng này sang ứng dụng khác,
bạn phải cẩn thận khi lấy URI mà
các ứng dụng có thể đọc. Làm việc này trên thiết bị chạy Android 6.0 (API cấp 23) trở lên
cần đặc biệt
do những thay đổi đối với mô hình quản lý quyền trong phiên bản Android đó, đặc biệt
Điều khoản dịch vụ và Chính sách quyền riêng tư của READ_EXTERNAL_STORAGE
trở thành
quyền nguy hiểm mà ứng dụng nhận có thể thiếu.
Do những cân nhắc này, chúng tôi khuyên bạn nên tránh sử dụng
Uri.fromFile()
,
cho thấy một số hạn chế. Phương thức này:
- Không cho phép chia sẻ tệp giữa các hồ sơ.
- Yêu cầu ứng dụng của bạn có
WRITE_EXTERNAL_STORAGE
quyền trên thiết bị chạy Android 4.4 (API cấp 19) trở xuống. - Yêu cầu ứng dụng nhận dữ liệu có
Quyền
READ_EXTERNAL_STORAGE
sẽ không thực hiện được các mục tiêu chia sẻ quan trọng, chẳng hạn như Gmail, không có quyền đó.
Thay vì sử dụng Uri.fromFile()
,
bạn có thể dùng quyền URI để cấp cho ứng dụng khác
quyền truy cập vào các URI cụ thể. Mặc dù quyền URI không hoạt động trên các URI file://
được tạo bởi Uri.fromFile()
, chúng
hoạt động trên các URI được liên kết với Trình cung cấp nội dung. Chiến lược phát hành đĩa đơn
API FileProvider
có thể
giúp bạn tạo các URI như vậy. Phương pháp này cũng áp dụng với các tệp không
trong bộ nhớ ngoài, nhưng ở bộ nhớ cục bộ của ứng dụng gửi ý định.
Trong onItemClick()
, hãy nhận
Đối tượng File
cho tên tệp của tệp đã chọn và truyền tệp đó làm đối số cho
getUriForFile()
, cùng với
cơ quan cấp chứng nhận mà bạn đã chỉ định trong
Phần tử <provider>
cho FileProvider
.
URI nội dung thu được chứa đơn vị quản lý, một phân đoạn đường dẫn tương ứng với
thư mục (như được chỉ định trong siêu dữ liệu XML) và tên của tệp, bao gồm cả
tiện ích. Cách FileProvider
ánh xạ các thư mục với đường dẫn
dựa trên siêu dữ liệu XML được mô tả trong phần này
Chỉ định thư mục có thể chia sẻ.
Đoạn mã sau đây cho bạn biết cách phát hiện tệp đã chọn và nhận URI nội dung cho tệp đó:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... // Define a listener that responds to clicks on a file in the ListView fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> /* * Get a File for the selected file name. * Assume that the file names are in the * imageFilename array. */ val requestFile = File(imageFilenames[position]) /* * Most file-related method calls need to be in * try-catch blocks. */ // Use the FileProvider to get a content URI val fileUri: Uri? = try { FileProvider.getUriForFile( this@MainActivity, "com.example.myapp.fileprovider", requestFile) } catch (e: IllegalArgumentException) { Log.e("File Selector", "The selected file can't be shared: $requestFile") null } ... } ... }
Java
protected void onCreate(Bundle savedInstanceState) { ... // Define a listener that responds to clicks on a file in the ListView fileListView.setOnItemClickListener( new AdapterView.OnItemClickListener() { @Override /* * When a filename in the ListView is clicked, get its * content URI and send it to the requesting app */ public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { /* * Get a File for the selected file name. * Assume that the file names are in the * imageFilename array. */ File requestFile = new File(imageFilename[position]); /* * Most file-related method calls need to be in * try-catch blocks. */ // Use the FileProvider to get a content URI try { fileUri = FileProvider.getUriForFile( MainActivity.this, "com.example.myapp.fileprovider", requestFile); } catch (IllegalArgumentException e) { Log.e("File Selector", "The selected file can't be shared: " + requestFile.toString()); } ... } }); ... }
Hãy nhớ rằng bạn chỉ có thể tạo URI nội dung cho các tệp nằm trong một thư mục
mà bạn chỉ định trong tệp siêu dữ liệu chứa phần tử <paths>
, như
được mô tả trong phần Chỉ định thư mục có thể chia sẻ. Nếu bạn gọi
getUriForFile()
cho một
File
trong đường dẫn mà bạn chưa chỉ định, bạn sẽ nhận được
IllegalArgumentException
.
Cấp quyền cho tệp
Giờ đây khi đã có URI nội dung cho tệp mà bạn muốn chia sẻ với ứng dụng khác, bạn cần
cho phép ứng dụng khách truy cập vào tệp. Để cho phép truy cập, hãy cấp quyền cho ứng dụng bằng cách
thêm URI nội dung vào Intent
, sau đó đặt cờ cấp quyền
Intent
. Các quyền bạn cấp là tạm thời và hết hạn
tự động khi ngăn xếp tác vụ của ứng dụng nhận hoàn tất.
Đoạn mã sau đây cho bạn biết cách đặt quyền đọc cho tệp này:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... // Define a listener that responds to clicks on a file in the ListView fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> ... if (fileUri != null) { // Grant temporary read permission to the content URI resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) ... } ... } ... }
Java
protected void onCreate(Bundle savedInstanceState) { ... // Define a listener that responds to clicks in the ListView fileListView.setOnItemClickListener( new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { ... if (fileUri != null) { // Grant temporary read permission to the content URI resultIntent.addFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION); } ... } ... }); ... }
Thận trọng: Chỉ gọi setFlags()
để cấp quyền truy cập vào tệp một cách an toàn thông qua quyền truy cập tạm thời. Tránh gọi điện
Phương thức Context.grantUriPermission()
cho một
URI nội dung của tệp, do phương thức này cấp quyền truy cập mà bạn chỉ có thể thu hồi bằng
đang gọi Context.revokeUriPermission()
.
Đừng sử dụng Uri.fromFile()
. Chế độ này buộc nhận ứng dụng
để có quyền READ_EXTERNAL_STORAGE
,
sẽ không hoạt động chút nào nếu bạn cố gắng chia sẻ giữa nhiều người dùng và trong các phiên bản
Android thấp hơn 4.4 (API cấp 19), sẽ yêu cầu
ứng dụng có WRITE_EXTERNAL_STORAGE
.
Và các mục tiêu chia sẻ thực sự quan trọng, chẳng hạn như ứng dụng Gmail, sẽ không
READ_EXTERNAL_STORAGE
, khiến
lệnh gọi này không thành công.
Thay vào đó, bạn có thể sử dụng quyền URI để cấp cho ứng dụng khác quyền truy cập vào các URI cụ thể.
Mặc dù các quyền URI không hoạt động trên các URI file:// được tạo bởi
Uri.fromFile()
, có thể
hoạt động trên URI được liên kết với Trình cung cấp nội dung. Thay vì triển khai công cụ của riêng bạn chỉ cho việc này,
bạn có thể và nên dùng FileProvider
như được giải thích trong phần Chia sẻ tệp.
Chia sẻ tệp với ứng dụng yêu cầu
Để chia sẻ tệp với ứng dụng đã yêu cầu tệp đó, hãy chuyển Intent
chứa URI nội dung và các quyền đối với setResult()
. Khi Activity
bạn vừa xác định hoàn tất,
hệ thống gửi Intent
chứa URI nội dung đến ứng dụng khách.
Đoạn mã sau đây cho bạn biết cách thực hiện việc này:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... // Define a listener that responds to clicks on a file in the ListView fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> ... if (fileUri != null) { ... // Put the Uri and MIME type in the result Intent resultIntent.setDataAndType(fileUri, contentResolver.getType(fileUri)) // Set the result setResult(Activity.RESULT_OK, resultIntent) } else { resultIntent.setDataAndType(null, "") setResult(RESULT_CANCELED, resultIntent) } } }
Java
protected void onCreate(Bundle savedInstanceState) { ... // Define a listener that responds to clicks on a file in the ListView fileListView.setOnItemClickListener( new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { ... if (fileUri != null) { ... // Put the Uri and MIME type in the result Intent resultIntent.setDataAndType( fileUri, getContentResolver().getType(fileUri)); // Set the result MainActivity.this.setResult(Activity.RESULT_OK, resultIntent); } else { resultIntent.setDataAndType(null, ""); MainActivity.this.setResult(RESULT_CANCELED, resultIntent); } } });
Cung cấp cho người dùng cách quay lại ứng dụng khách ngay lập tức sau khi họ chọn một tệp.
Một cách để thực hiện việc này là cung cấp dấu kiểm hoặc nút Xong. Liên kết một phương thức với
bằng cách sử dụng nút
Thuộc tính android:onClick
. Trong phương thức này, hãy gọi
finish()
. Ví dụ:
Kotlin
fun onDoneClick(v: View) { // Associate a method with the Done button finish() }
Java
public void onDoneClick(View v) { // Associate a method with the Done button finish(); }
Để biết thêm thông tin liên quan, hãy tham khảo: