بعد إعداد التطبيق لمشاركة الملفات باستخدام معرّفات الموارد المنتظمة (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 */ ... } ... }
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)
بإمكان التطبيقات قراءتها. إجراء ذلك على الأجهزة التي تعمل بالإصدار 6.0 من نظام التشغيل Android (المستوى 23 لواجهة برمجة التطبيقات) والإصدارات الأحدث
تتطلب معلومات خاصة
نظرًا للتغييرات التي تطرأ على نموذج الأذونات في إصدار Android، لاسيما
READ_EXTERNAL_STORAGE
التحول إلى
خطير، الذي قد يفتقر إليه التطبيق المستلِم.
مع وضع هذه الاعتبارات في الاعتبار، ننصحك بتجنب استخدام
Uri.fromFile()
، الذي
بعض العيوب. هذه الطريقة:
- لا يسمح بمشاركة الملفات بين الملفات الشخصية.
- يتطلب أن يتوفّر في التطبيق
WRITE_EXTERNAL_STORAGE
على الأجهزة التي تعمل بنظام التشغيل Android 4.4 (المستوى 19 لواجهة برمجة التطبيقات) أو الإصدارات الأقدم. - يتطلب أن تحتوي التطبيقات المستلِمة على
إذن
READ_EXTERNAL_STORAGE
، الذي ستفشل في أهداف المشاركة المهمة، مثل Gmail، التي لا تمتلك هذا الإذن.
بدلاً من استخدام السمة Uri.fromFile()
يمكنك استخدام أذونات معرّف الموارد المنتظم (URI) لمنح تطبيقات أخرى
الوصول إلى معرفات موارد منتظمة (URI) محددة. لا تعمل أذونات معرّف الموارد المنتظم (URI) على معرّفات الموارد المنتظِمة file://
.
تم إنشاؤها بواسطة Uri.fromFile()
،
تعمل على معرفات الموارد المنتظمة (URI) المرتبطة بمزودي المحتوى. تشير رسالة الأشكال البيانية
يمكن لواجهة برمجة تطبيقات FileProvider
في إنشاء معرفات الموارد المنتظمة (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 (المستوى 19 لواجهة برمجة التطبيقات)، حيث يتطلب ذلك
هو WRITE_EXTERNAL_STORAGE
.
كما أن أهداف المشاركة المهمة حقًا، مثل تطبيق Gmail، لا
READ_EXTERNAL_STORAGE
، مما يؤدي إلى
فشل هذا الاستدعاء.
وبدلاً من ذلك، يمكنك استخدام أذونات معرف الموارد المنتظم (URI) لمنح التطبيقات الأخرى إمكانية الدخول إلى معرفات موارد منتظمة (URI) محددة.
لا تعمل أذونات معرف الموارد المنتظم (URI) على معرفات الموارد المنتظمة (URI) لـ file:// كما يتم إنشاؤها بواسطة
Uri.fromFile()
، لقد فعلت
على نظام Uris المرتبط بمزودي المحتوى. بدلاً من تنفيذ نموذجك الخاص لهذا الغرض فقط،
يمكنك استخدام FileProvider
كما هو موضّح في مقالة مشاركة الملفات.
مشاركة الملف مع التطبيق الذي يقدّم الطلب
لمشاركة الملف مع التطبيق الذي طلبه، عليك اجتياز Intent
.
يحتوي على معرّف الموارد المنتظم (URI) للمحتوى والأذونات الخاصة بـ setResult()
. عند انتهاء Activity
الذي حدّدته للتو،
يرسل النظام 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) { ... // 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(); }
لمزيد من المعلومات ذات الصلة، راجع:
- تصميم معرفات الموارد المنتظمة (URI) للمحتوى
- تطبيق أذونات موفّر المحتوى
- الأذونات
- فلاتر الأهداف والغايات