مشاركة ملف

بعد إعداد التطبيق لمشاركة الملفات باستخدام معرّفات الموارد المنتظمة (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();
    }

لمزيد من المعلومات ذات الصلة، راجع: