بعد إعداد تطبيقك لمشاركة الملفات باستخدام معرّفات الموارد المنتظمة (URI) للمحتوى، يمكنك الردّ على طلبات التطبيقات الأخرى لهذه الملفات. وتتمثل إحدى طرق الاستجابة لهذه الطلبات في توفير واجهة لاختيار الملفات من تطبيق الخادم يمكن للتطبيقات الأخرى استدعاءها. ويسمح هذا الأسلوب لتطبيق العميل بأن يسمح للمستخدمين باختيار ملف من تطبيق الخادم ثم استلام معرّف الموارد المنتظم (URI) لمحتوى الملف المحدّد.
يوضّح لك هذا الدرس كيفية إنشاء مجموعة ملفات Activity
في تطبيقك للاستجابة لطلبات الملفات.
تلقّي طلبات الملفات
لتلقّي طلبات الملفات من تطبيقات العملاء والرد باستخدام معرّف موارد منتظم (URI) للمحتوى، يجب أن يوفّر تطبيقك
اختيار ملف Activity
. تبدأ تطبيقات العملاء عملية Activity
هذه من خلال استدعاء startActivityForResult()
مع Intent
الذي يتضمّن الإجراء ACTION_PICK
. عندما يتصل تطبيق العميل بـ
startActivityForResult()
، يمكن لتطبيقك
عرض نتيجة لتطبيق العميل، على شكل معرّف موارد منتظم (URI) للمحتوى في الملف الذي اختاره المستخدم.
لمعرفة كيفية تنفيذ طلب لملف في تطبيق عميل، راجِع الدرس طلب ملف مشترك.
نشاط إنشاء اختيار ملف
لإعداد اختيار الملف Activity
، ابدأ بتحديد Activity
في البيان، بالإضافة إلى فلتر أهداف يطابق الإجراء ACTION_PICK
والفئات CATEGORY_DEFAULT
وCATEGORY_OPENABLE
. أضِف أيضًا فلاتر من نوع MIME للملفات التي يعرضها تطبيقك للتطبيقات الأخرى. يوضّح لك المقتطف التالي كيفية تحديد
فلتر Activity
الجديد وفلتر الأهداف:
<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 */ ... } ... }
جافا
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()
التي يمكنك من خلالها الحصول على الملف المحدَّد.
عند استخدام الغرض من إرسال عنوان URI لملف من تطبيق إلى آخر،
يجب توخي الحذر للحصول على عنوان URI يمكن للتطبيقات الأخرى قراءته. ويتطلّب ذلك
على الأجهزة التي تعمل بالإصدار 6.0 من نظام التشغيل Android (المستوى 23 من واجهة برمجة التطبيقات) والإصدارات الأحدث
عناية خاصة بسبب التغييرات في نموذج الأذونات في ذلك الإصدار من Android، لا سيّما
منح READ_EXTERNAL_STORAGE
إذنًا خطيرًا، والذي قد لا يملكه التطبيق المستقبِل.
بأخذ هذه الاعتبارات في الاعتبار، ننصحك بتجنّب استخدام السمة Uri.fromFile()
التي تتضمّن العديد من السلبيات. هذه الطريقة:
- لا يسمح بمشاركة الملفات بين الملفات الشخصية.
- يتطلّب أن يكون تطبيقك حاصلاً على
إذن
WRITE_EXTERNAL_STORAGE
على الأجهزة التي تعمل بالإصدار 4.4 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 19) أو الإصدارات الأقدم. - يتطلّب استلام التطبيقات الحصول على
إذن "
READ_EXTERNAL_STORAGE
"، والذي لن يفشل مع أهداف المشاركة المهمة، مثل Gmail، التي لا تمتلك هذا الإذن.
بدلاً من استخدام Uri.fromFile()
،
يمكنك استخدام أذونات معرّف الموارد المنتظم (URI) لمنح التطبيقات الأخرى
إمكانية الوصول إلى معرّفات موارد منتظمة (URI) محددة. على الرغم من أنّ أذونات معرّف الموارد المنتظم (URI) لا تعمل على معرّفات الموارد المنتظمة (URI) file://
التي أنشأها Uri.fromFile()
، فإنّها تعمل على معرّفات الموارد المنتظمة (URI) المرتبطة بمزوّدي المحتوى. ويمكن أن تساعدك واجهة برمجة تطبيقات FileProvider
في إنشاء معرّفات الموارد المنتظمة (URI) هذه. تعمل هذه الطريقة أيضًا مع الملفات غير الموجودة في
وحدة تخزين خارجية، ولكن في مساحة التخزين المحلية للتطبيق الذي يرسل الغرض.
في 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 } ... } ... }
جافا
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) ... } ... } ... }
جافا
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 (مستوى واجهة برمجة التطبيقات 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) } } }
جافا
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() }
جافا
public void onDoneClick(View v) { // Associate a method with the Done button finish(); }
للحصول على معلومات إضافية ذات صلة، راجع:
- تصميم معرّفات الموارد المنتظمة (URI) للمحتوى
- تطبيق أذونات موفِّر المحتوى
- الأذونات
- فلاتر الأهداف والغايات