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_STORAGEquyề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_STORAGEsẽ 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:
