เมื่อคุณตั้งค่าแอปให้แชร์ไฟล์โดยใช้ URI เนื้อหาแล้ว คุณจะตอบสนองต่อแอปอื่นๆ ได้ คำขอไฟล์เหล่านี้ วิธีหนึ่งในการตอบสนองต่อคำขอเหล่านี้คือการเลือกไฟล์ จากแอปพลิเคชันเซิร์ฟเวอร์ที่แอปพลิเคชันอื่นสามารถเรียกใช้ได้ วิธีนี้ทำให้ลูกค้า เพื่อให้ผู้ใช้เลือกไฟล์จากแอปเซิร์ฟเวอร์แล้วรับไฟล์ที่เลือก URI เนื้อหา
บทเรียนนี้แสดงวิธีสร้างการเลือกไฟล์ Activity
ในแอปของคุณ
ที่ตอบกลับคำขอไฟล์
รับคำขอไฟล์
ในการรับคำขอไฟล์จากแอปไคลเอ็นต์และตอบกลับด้วย URI เนื้อหา แอปของคุณควร
ระบุการเลือกไฟล์ Activity
แอปไคลเอ็นต์เริ่มต้นสิ่งนี้
Activity
โดยโทรหา startActivityForResult()
พร้อม Intent
ที่ดำเนินการอยู่
ACTION_PICK
เมื่อแอปไคลเอ็นต์เรียกใช้
startActivityForResult()
แอปของคุณสามารถ
ส่งผลลัพธ์ไปยังแอปไคลเอ็นต์ในรูปแบบ URI เนื้อหาสำหรับไฟล์ที่ผู้ใช้เลือก
หากต้องการดูวิธีใช้คำขอไฟล์ในแอปไคลเอ็นต์ ให้ดูบทเรียน การขอไฟล์ที่แชร์
สร้างกิจกรรมการเลือกไฟล์
หากต้องการตั้งค่าการเลือกไฟล์ Activity
ให้เริ่มด้วยการระบุ
Activity
ในไฟล์ Manifest พร้อมด้วยตัวกรอง Intent
ที่ตรงกับการดำเนินการ ACTION_PICK
และ
หมวดหมู่ CATEGORY_DEFAULT
และ
CATEGORY_OPENABLE
เพิ่มตัวกรองประเภท MIME ด้วย
สำหรับไฟล์ที่แอปแสดงไปยังแอปอื่นๆ ข้อมูลโค้ดต่อไปนี้จะแสดงวิธีระบุ
ตัวกรอง Activity
และ Intent ใหม่:
<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>
กำหนดกิจกรรมการเลือกไฟล์ในโค้ด
ถัดไป ให้กำหนดคลาสย่อย Activity
ที่แสดงไฟล์ที่พร้อมใช้งาน
ไดเรกทอรี files/images/
ของแอปของคุณในที่จัดเก็บข้อมูลภายในและอนุญาตให้ผู้ใช้เลือก
ไฟล์ที่ต้องการ ข้อมูลโค้ดต่อไปนี้แสดงวิธีกำหนด
Activity
และตอบกลับการเลือกของผู้ใช้ ดังนี้
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 */ ... } ... }
ตอบสนองต่อการเลือกไฟล์
เมื่อผู้ใช้เลือกไฟล์ที่แชร์ แอปพลิเคชันของคุณจะต้องพิจารณาว่าไฟล์ใดถูกเลือก และ
จากนั้นให้สร้าง URI เนื้อหาสำหรับไฟล์ เนื่องจาก Activity
แสดงเมธอด
รายการไฟล์ที่ใช้ได้ใน ListView
เมื่อผู้ใช้คลิกที่ชื่อไฟล์
ระบบจะเรียกเมธอด onItemClick()
ซึ่งคุณจะได้รับไฟล์ที่เลือก
เมื่อใช้ Intent ในการส่ง URI ของไฟล์จากแอปหนึ่งไปยังอีกแอปหนึ่ง
คุณจะต้องระวังเพื่อให้ได้ URI ที่
ที่สามารถอ่านได้ ดำเนินการกับอุปกรณ์ที่ใช้ Android 6.0 (API ระดับ 23) ขึ้นไป
ต้องมีแบบพิเศษ
เนื่องจากมีการเปลี่ยนแปลงโมเดลสิทธิ์ใน Android เวอร์ชันนั้น โดยเฉพาะอย่างยิ่ง
ของ READ_EXTERNAL_STORAGE
การเป็น
สิทธิ์ที่เป็นอันตราย ซึ่งแอปที่ใช้รับอาจไม่มี
โดยคำนึงถึงข้อควรพิจารณาเหล่านี้ เราขอแนะนำให้คุณหลีกเลี่ยงการใช้
Uri.fromFile()
ซึ่ง
ก่อให้เกิดข้อเสียหลายประการ วิธีการนี้
- ไม่อนุญาตให้แชร์ไฟล์ระหว่างโปรไฟล์
- แอปของคุณต้องมี
WRITE_EXTERNAL_STORAGE
สิทธิ์ในอุปกรณ์ที่ใช้ Android 4.4 (API ระดับ 19) หรือต่ำกว่า - แอปที่รับต้องมี
READ_EXTERNAL_STORAGE
ซึ่ง จะล้มเหลวในเป้าหมายการแชร์ที่สำคัญ เช่น Gmail ซึ่งไม่มีสิทธิ์ดังกล่าว
แทนที่จะใช้ Uri.fromFile()
คุณสามารถใช้สิทธิ์ URI เพื่อให้สิทธิ์แอปอื่นๆ
เข้าถึง URI ที่เฉพาะเจาะจง แม้ว่าสิทธิ์ URI จะไม่ทำงานใน URI file://
สร้างโดย Uri.fromFile()
แอปเหล่านี้
ทำงานกับ URI ที่เชื่อมโยงกับผู้ให้บริการเนื้อหา
FileProvider
API สามารถ
ช่วยสร้าง URI ดังกล่าว วิธีนี้ยังใช้กับไฟล์ที่ไม่ได้
ในพื้นที่เก็บข้อมูลภายนอก แต่ในพื้นที่เก็บข้อมูลในเครื่องของแอปที่ส่ง Intent
ใน onItemClick()
รับ
File
สำหรับชื่อไฟล์ของไฟล์ที่เลือก แล้วส่งเป็นอาร์กิวเมนต์ไปยัง
getUriForFile()
รวมถึง
ที่คุณระบุไว้ใน
องค์ประกอบ <provider>
สำหรับ FileProvider
URI เนื้อหาที่ได้จะมีสิทธิ์ ซึ่งเป็นส่วนของเส้นทางที่สอดคล้องกับไฟล์
(ตามที่ระบุในข้อมูลเมตา XML) และชื่อไฟล์รวมถึง
ส่วนขยาย วิธีจับคู่ไดเรกทอรีกับเส้นทาง FileProvider
ส่วนที่ใช้ข้อมูลเมตา XML อธิบายอยู่ในส่วน
ระบุไดเรกทอรีที่แชร์ได้
ข้อมูลโค้ดต่อไปนี้แสดงวิธีตรวจหาไฟล์ที่เลือกและรับ URI เนื้อหาสำหรับไฟล์ดังกล่าว
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()); } ... } }); ... }
โปรดทราบว่าคุณจะสร้าง URI เนื้อหาได้เฉพาะสำหรับไฟล์ที่อยู่ในไดเรกทอรีเท่านั้น
ที่คุณระบุไว้ในไฟล์ข้อมูลเมตาที่มีองค์ประกอบ <paths>
เป็น
ตามที่อธิบายไว้ในส่วนระบุไดเรกทอรีที่แชร์ได้ หากคุณโทรหา
getUriForFile()
เป็นเวลา
File
ในเส้นทางที่คุณยังไม่ได้ระบุ คุณจะได้รับ
IllegalArgumentException
ให้สิทธิ์สำหรับไฟล์
เมื่อคุณมี URI เนื้อหาสำหรับไฟล์ที่ต้องการแชร์กับแอปอื่นแล้ว คุณต้องทำดังนี้
อนุญาตให้แอปไคลเอ็นต์เข้าถึงไฟล์ หากต้องการให้สิทธิ์เข้าถึง โปรดให้สิทธิ์แอปไคลเอ็นต์โดย
การเพิ่ม URI เนื้อหาไปยัง Intent
แล้วตั้งค่าแฟล็กสิทธิ์เป็นเปิด
Intent
สิทธิ์ที่คุณให้เป็นแบบชั่วคราวและจะหมดอายุ
โดยอัตโนมัติเมื่อสแต็กงานของแอปที่รับเสร็จสิ้น
ข้อมูลโค้ดต่อไปนี้แสดงวิธีตั้งค่าสิทธิ์อ่านไฟล์
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); } ... } ... }); ... }
ข้อควรระวัง: การโทรไปยัง setFlags()
เป็นเพียงวิธีเดียว
วิธีให้สิทธิ์เข้าถึงไฟล์อย่างปลอดภัยโดยใช้สิทธิ์การเข้าถึงชั่วคราว หลีกเลี่ยงการโทร
เมธอด Context.grantUriPermission()
สำหรับ
URI เนื้อหาของไฟล์ เนื่องจากวิธีการนี้จะให้สิทธิ์เข้าถึงซึ่งคุณสามารถเพิกถอนได้โดย
กำลังโทรหา Context.revokeUriPermission()
อย่าใช้ Uri.fromFile()
บังคับให้รับแอป
ที่จะได้รับสิทธิ์ READ_EXTERNAL_STORAGE
จะใช้งานไม่ได้เลยหากคุณพยายามแชร์กับผู้ใช้ทั้งหมดและเป็นเวอร์ชัน
ของ Android ที่ต่ำกว่า 4.4 (API ระดับ 19) จะต้องมี
แอปที่มีWRITE_EXTERNAL_STORAGE
และเป้าหมายการแชร์ที่สำคัญจริงๆ เช่น แอป Gmail ไม่มี
READ_EXTERNAL_STORAGE
ซึ่งทําให้
ไม่สำเร็จ
แต่คุณสามารถใช้สิทธิ์ URI เพื่อให้สิทธิ์เข้าถึง URI ที่เจาะจงแก่แอปอื่นๆ แทนได้
แม้ว่าสิทธิ์ URI จะไม่ทำงานกับ URI แบบ file:// ดังที่
Uri.fromFile()
ทำได้
ทำงานกับ Uris ที่เกี่ยวข้องกับผู้ให้บริการเนื้อหา แทนที่จะใช้หัวข้อของคุณเองเพียงเพื่อเรื่องนี้
คุณมีสิทธิ์และควรใช้ FileProvider
ตามที่อธิบายไว้ในการแชร์ไฟล์
แชร์ไฟล์กับแอปที่ส่งคำขอ
หากต้องการแชร์ไฟล์กับแอปที่ขอไฟล์ ให้ส่ง Intent
ที่มี URI ของเนื้อหาและสิทธิ์ในการ setResult()
เมื่อ Activity
ที่คุณเพิ่งกำหนดเสร็จแล้ว
ระบบจะส่ง Intent
ที่มี URI เนื้อหาไปยังแอปไคลเอ็นต์
ข้อมูลโค้ดต่อไปนี้จะแสดงวิธีดำเนินการ
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); } } });
มอบวิธีให้ผู้ใช้กลับไปยังแอปไคลเอ็นต์ได้ทันทีเมื่อเลือกไฟล์แล้ว
วิธีหนึ่งที่สามารถทำได้คือระบุเครื่องหมายถูกหรือปุ่มเสร็จสิ้น เชื่อมโยงเมธอดกับ
ปุ่มโดยใช้
android:onClick
ในเมธอด ให้เรียก
finish()
เช่น
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(); }
ดูข้อมูลที่เกี่ยวข้องเพิ่มเติมได้ที่