إنشاء موفّر محتوى

يدير موفر المحتوى إمكانية الوصول إلى مستودع مركزي للبيانات. إذا أضفت موفّرًا كفئة واحدة أو أكثر في تطبيق Android، بالإضافة إلى العناصر في ملف البيان. تنفِّذ إحدى فئاتك فئة فرعية من ContentProvider، وهي الواجهة بين موفّر الخدمة والتطبيقات الأخرى.

ومع أنّ موفّري المحتوى يهدفون إلى إتاحة البيانات للتطبيقات الأخرى، يمكنك ممارسة أنشطة في تطبيقك تتيح للمستخدم الاستعلام عن البيانات التي يديرها موفّر الخدمة وتعديلها.

تتضمن هذه الصفحة العملية الأساسية لإنشاء موفّر محتوى وقائمة بواجهات برمجة التطبيقات التي يمكن استخدامها.

قبل البدء في إنشاء

قبل البدء في إنشاء مقدّم خدمة، يجب مراعاة ما يلي:

  • حدِّد ما إذا كنت تحتاج إلى موفّر محتوى. عليك إنشاء موفّر محتوى إذا كنت تريد توفير ميزة واحدة أو أكثر من الميزات التالية:
    • لنفترض أنك تريد تقديم بيانات أو ملفات معقدة لتطبيقات أخرى.
    • إذا كنت تريد السماح للمستخدمين بنسخ البيانات المعقدة من تطبيقك إلى تطبيقات أخرى.
    • تريد تقديم اقتراحات بحث مخصّصة باستخدام إطار عمل البحث.
    • تريد عرض بيانات تطبيقك للأدوات.
    • وتريد تطبيق الصفوف AbstractThreadedSyncAdapter أو CursorAdapter أو CursorLoader.

    لست بحاجة إلى مزوِّد خدمة لاستخدام قواعد البيانات أو الأنواع الأخرى من مساحة التخزين الدائمة إذا كان يتم استخدامها بالكامل في تطبيقك الخاص ولا تحتاج إلى أيّ من الميزات السابقة المذكورة. ويمكنك بدلاً من ذلك استخدام أحد أنظمة التخزين الموضّحة في مقالة نظرة عامة على تخزين البيانات والملفات.

  • اطّلِع على أساسيات موفّر المحتوى إذا لم يسبق لك الاطّلاع عليها، لمعرفة مزيد من المعلومات حول مقدّمي الخدمات وآلية عملهم.

بعد ذلك، اتّبِع الخطوات التالية لإنشاء مقدّم الخدمة:

  1. تصميم سعة التخزين الأولية لبياناتك. يقدّم موفّر المحتوى البيانات بطريقتَين:
    بيانات الملف
    البيانات التي يتم إدراجها عادةً في الملفات، مثل الصور أو الصوت أو الفيديوهات. خزِّن الملفات في المساحة الخاصة لتطبيقك. استجابةً لطلب الحصول على ملف من تطبيق آخر، يمكن لموفّر الخدمة عرض اسم معرِّف للملف.
    البيانات "المهيكلة"
    يشير ذلك المصطلح إلى البيانات التي يتم نقلها عادةً إلى قاعدة بيانات أو مصفوفة أو بنية مشابهة. تخزين البيانات في نموذج متوافق مع جداول الصفوف والأعمدة. ويمثّل الصف كيانًا، مثل شخص أو سلعة في المستودع. ويمثّل العمود بعض البيانات الخاصة بالكيان، مثل اسم الشخص أو سعره. وهناك طريقة شائعة لتخزين هذا النوع من البيانات في قاعدة بيانات SQLite، ولكن يمكنك استخدام أي نوع من مساحات التخزين الدائمة. لمعرفة المزيد من المعلومات عن أنواع التخزين المتوفرة في نظام Android، راجِع قسم تصميم تخزين بيانات التصميم.
  2. حدِّد التنفيذ الملموس لفئة ContentProvider والطرق المطلوبة فيها. هذه الفئة هي الواجهة بين بياناتك وباقي نظام Android. لمزيد من المعلومات حول هذه الفئة، راجِع القسم تنفيذ فئة ContentProvider.
  3. حدِّد سلسلة المرجع ومعرّفات الموارد المنتظمة (URI) للمحتوى وأسماء الأعمدة. إذا كنت تريد أن يعالج تطبيق مقدّم الخدمة الأهداف، حدِّد أيضًا إجراءات الأهداف والبيانات الإضافية والعلامات. حدِّد أيضًا الأذونات التي تشترطها للتطبيقات التي تريد الوصول إلى بياناتك. ننصحك بتحديد كل هذه القيم كثوابت في فئة عقد منفصلة. ويمكنك لاحقًا عرض هذا الصف للمطوّرين الآخرين. لمزيد من المعلومات حول معرّفات الموارد المنتظِمة للمحتوى، يمكنك الاطّلاع على قسم معرّفات الموارد المنتظمة (URI) لمحتوى التصميم. لمزيد من المعلومات حول الأهداف، راجِع قسم الأهداف والوصول إلى البيانات.
  4. أضِف أجزاء اختيارية أخرى، مثل نماذج البيانات أو عملية تنفيذ AbstractThreadedSyncAdapter التي يمكنها مزامنة البيانات بين مقدّم الخدمة والبيانات المستندة إلى السحابة الإلكترونية.

تصميم تخزين البيانات

موفّر المحتوى هو واجهة البيانات المحفوظة بتنسيق منظَّم. قبل إنشاء الواجهة، حدِّد طريقة تخزين البيانات. يمكنك تخزين البيانات بأي شكل تريده، ثم تصميم الواجهة لقراءة البيانات وكتابتها حسب الضرورة.

في ما يلي بعض تقنيات تخزين البيانات المتاحة على Android:

  • إذا كنت تعمل على البيانات المنظَّمة، ننصحك باستخدام قاعدة بيانات ارتباطية مثل SQLite أو مخزن بيانات غير مرتبط بالقيمة الأساسية، مثل LevelDB. إذا كنت تستخدم بيانات غير منظَّمة، مثل وسائط الصوت أو الصور أو وسائط الفيديو، ننصحك بتخزينها كملفات. يمكنك مزج عدة أنواع مختلفة من مساحات التخزين ومطابقتها وعرضها باستخدام موفّر محتوى واحد إذا لزم الأمر.
  • يمكن لنظام Android التفاعل مع مكتبة تثبيت الغرف التي توفّر إمكانية الوصول إلى واجهة برمجة تطبيقات قاعدة بيانات SQLite التي يستخدمها موفّرو Android لتخزين البيانات الموجَّهة إلى الجداول. لإنشاء قاعدة بيانات باستخدام هذه المكتبة، يمكنك إنشاء مثيل لفئة فرعية من RoomDatabase، كما هو موضّح في حفظ البيانات في قاعدة بيانات محلية باستخدام الغرفة.

    لا تحتاج إلى استخدام قاعدة بيانات لتنفيذ المستودع. يظهر مقدّم الخدمة خارجيًا كمجموعة من الجداول، على غرار قاعدة البيانات الارتباطية، ولكن هذا ليس شرطًا للتنفيذ الداخلي لموفّر الخدمة.

  • لتخزين بيانات الملفات، يوفّر Android مجموعة متنوعة من واجهات برمجة التطبيقات الموجَّهة للملفات. للحصول على مزيد من المعلومات حول تخزين الملفات، يمكنك الاطّلاع على نظرة عامة على تخزين البيانات والملفات. إذا كنت تصمم موفّرًا يقدم بيانات متعلقة بالوسائط، مثل الموسيقى أو الفيديوهات، يمكنك الاستعانة بموفّر خدمة يجمع بين بيانات الجداول وملفاتها.
  • في حالات نادرة، قد تستفيد من توفير أكثر من موفّر محتوى لتطبيق واحد. على سبيل المثال، قد ترغب في مشاركة بعض البيانات مع تطبيق مصغّر باستخدام أحد موفّري المحتوى، وعرض مجموعة مختلفة من البيانات لمشاركتها مع التطبيقات الأخرى.
  • للتعامل مع البيانات المستندة إلى الشبكة، يمكنك استخدام الفئات في java.net وandroid.net. يمكنك أيضًا مزامنة البيانات المستندة إلى الشبكة مع مخزن بيانات محلي، مثل قاعدة بيانات، ثم تقديم البيانات كجداول أو ملفات.

ملاحظة: إذا أجريت تغييرًا على المستودع لا يتوافق مع الأنظمة القديمة، عليك إضافة رقم إصدار جديد إلى المستودع. عليك أيضًا زيادة رقم إصدار تطبيقك المستخدَم في موفّر المحتوى الجديد. ويؤدي إجراء هذا التغيير إلى منع عمليات الرجوع إلى إصدار سابق من النظام من التسبب في تعطل النظام عند محاولة إعادة تثبيت تطبيق يتضمن موفّر محتوى غير متوافق.

اعتبارات تصميم البيانات

فيما يلي بعض النصائح لتصميم هيكل بيانات المزود:

  • يجب أن تحتوي بيانات الجدول دائمًا على عمود "مفتاح أساسي" يحتفظ به الموفّر كقيمة رقمية فريدة لكل صف. ويمكنك استخدام هذه القيمة لربط الصف بالصفوف ذات الصلة في جداول أخرى (باستخدامها على أنّها "مفتاح خارجي"). يمكنك استخدام أي اسم لهذا العمود، إلا أنّ استخدام BaseColumns._ID هو الخيار الأفضل لأنّ ربط نتائج طلب البحث الخاص بمقدّم الخدمة بالحقل ListView يتطلّب أن يكون لأحد الأعمدة التي تم استردادها الاسم _ID.
  • إذا كنت تريد توفير صور نقطية أو أجزاء أخرى كبيرة جدًا من البيانات الموجّهة إلى الملفات، يمكنك تخزين البيانات في ملف وتقديمها بشكل غير مباشر بدلاً من تخزينها مباشرةً في جدول. وفي حال إجراء ذلك، عليك إبلاغ مستخدمي مقدّم الخدمة بأنّ عليهم استخدام طريقة ملف ContentResolver للوصول إلى البيانات.
  • استخدِم نوع بيانات الكائن الثنائي الكبير (BLOB) لتخزين البيانات التي تختلف في الحجم أو لها بنية مختلفة. على سبيل المثال، يمكنك استخدام عمود كائن تخزين البيانات الثنائية الكبيرة لتخزين المخزن المؤقت للبروتوكول أو بنية JSON.

    يمكنك أيضًا استخدام كائن تخزين البيانات الكبير لتنفيذ جدول مستقل عن المخطط. في هذا النوع من الجداول، يمكنك تحديد عمود مفتاح أساسي وعمود من نوع MIME وعمود واحد أو أكثر من الأعمدة العامة كـ BLOB. يشار إلى معنى البيانات في أعمدة كائن تخزين البيانات الثنائية الكبيرة من خلال القيمة الموجودة في عمود نوع MIME. يتيح لك هذا الإجراء تخزين أنواع صفوف مختلفة في الجدول نفسه. إنّ جدول "البيانات" ContactsContract.Data الخاص بـ "مقدِّم جهات الاتصال" هو مثال على جدول مستقل عن المخطط.

تصميم معرفات الموارد المنتظمة (URI) لمحتوى

معرّف الموارد المنتظم (URI) للمحتوى هو معرّف موارد منتظم (URI) يحدّد البيانات في أحد الموفّرين. وتتضمّن معرّفات الموارد المنتظمة (URI) الخاصة بالمحتوى الاسم الرمزي للموفّر بأكمله (جهة إصداره) والاسم الذي يشير إلى جدول أو ملف (مسار). ويشير الجزء الاختياري لرقم التعريف إلى صف فردي في جدول. وتشتمل كل طريقة للوصول إلى البيانات في ContentProvider على معرّف موارد منتظم (URI) للمحتوى كوسيطة. ويتيح لك هذا الإجراء تحديد الجدول أو الصف أو الملف الذي تريد الوصول إليه.

للحصول على معلومات حول معرّفات الموارد المنتظمة (URI)، يُرجى الاطّلاع على أساسيات موفّر المحتوى.

تصميم مرجع

عادةً ما يكون للمزود جهة إصدار واحدة تُستخدَم كاسم داخلي في Android. لتجنُّب حدوث أي تعارضات مع مقدّمي الخدمة الآخرين، استخدِم ملكية نطاق الإنترنت (بالعكس) كأساس لسلطة الموفّر. وبما أنّ هذا الاقتراح ينطبق أيضًا على أسماء حزم Android، يمكنك تحديد مرجع موفّر الخدمة كامتداد لاسم الحزمة التي تحتوي على موفّر الخدمة.

على سبيل المثال، إذا كان اسم حزمة Android هو com.example.<appname>، امنح موفّر الخدمة المرجع com.example.<appname>.provider.

تصميم هيكل المسار

ينشئ المطوّرون عادةً معرّفات الموارد المنتظمة (URI) للمحتوى من الجهة التي أصدرتها، وذلك من خلال إلحاق مسارات تشير إلى جداول فردية. على سبيل المثال، إذا كان لديك جدولان، table1 وtable2، يمكنك دمجهما مع المرجع من المثال السابق للحصول على معرّفي الموارد المنتظمين (URI) للمحتوى com.example.<appname>.provider/table1 وcom.example.<appname>.provider/table2. لا تقتصر المسارات على شريحة واحدة، ولا يجب أن يكون هناك جدول لكل مستوى من المسار.

التعامل مع معرّفات الموارد المنتظمة (URI) للمحتوى

حسب الاصطلاح، يتيح الموفّرون الوصول إلى صف واحد في جدول من خلال قبول معرّف موارد منتظم (URI) للمحتوى مع قيمة رقم تعريف للصف في نهاية URI. ووفقًا للاصطلاح أيضًا، يطابق الموفّرون قيمة المعرّف مع عمود _ID في الجدول وينفّذون الوصول المطلوب في مقابل الصف المطابق.

يعمل هذا الاصطلاح على تسهيل نمط تصميم شائع للتطبيقات التي يمكنها الوصول إلى موفّر خدمة. يُجري التطبيق طلب بحث مقابل موفّر الخدمة ويعرض النتيجة Cursor في ListView باستخدام CursorAdapter. يتطلّب تعريف CursorAdapter قيمة _ID في أحد الأعمدة في Cursor.

يختار المستخدم بعد ذلك أحد الصفوف المعروضة من واجهة المستخدم للاطّلاع على البيانات أو تعديلها. يحصل التطبيق على الصف المقابل من Cursor مع إظهار ListView، ويحصل على قيمة _ID لهذا الصف، ويضيفه إلى معرّف الموارد المنتظم (URI) الخاص بالمحتوى، ويرسل طلب الوصول إلى موفّر الخدمة. ويمكن لموفّر الخدمة بعد ذلك إجراء طلب البحث أو التعديل في الصف المحدّد الذي اختاره المستخدم.

أنماط معرف الموارد المنتظم (URI) للمحتوى

لمساعدتك في اختيار الإجراء الذي يجب اتخاذه بشأن معرّف الموارد المنتظم (URI) الخاص بالمحتوى الوارد، تتضمّن واجهة برمجة تطبيقات الموفّر الفئة UriMatcher المناسبة التي تربط أنماط معرّف الموارد المنتظم (URI) للمحتوى بقيم أعداد صحيحة. يمكنك استخدام قيم الأعداد الصحيحة في عبارة switch التي تختار الإجراء المطلوب لمعرّف الموارد المنتظم (URI) للمحتوى أو معرّفات الموارد المنتظمة (URI) التي تتطابق مع نمط معيّن.

يتطابق نمط معرف الموارد المنتظم (URI) للمحتوى مع معرّفات الموارد المنتظمة (URI) للمحتوى باستخدام أحرف البدل:

  • تتطابق * مع سلسلة من أي أحرف صالحة بأي طول.
  • تتطابق # مع سلسلة من الأحرف الرقمية بأي طول.

كمثال على تصميم عملية معالجة معرّف الموارد المنتظم (URI) للمحتوى وترميزها، ننصحك بأن يستند موفّر المرجع com.example.app.provider إلى معرّفات الموارد المنتظمة (URI) الخاصة بالمحتوى والتي تشير إلى الجداول:

  • content://com.example.app.provider/table1: جدول باسم table1.
  • content://com.example.app.provider/table2/dataset1: جدول باسم dataset1.
  • content://com.example.app.provider/table2/dataset2: جدول باسم dataset2.
  • content://com.example.app.provider/table3: جدول باسم table3.

يتعرّف الموفّر أيضًا على معرّفات الموارد المنتظمة (URI) هذه للمحتوى إذا تم إلحاق رقم تعريف صف بها، مثل content://com.example.app.provider/table3/1 للصف الذي يحدّده 1 في table3.

في ما يلي أنماط معرف الموارد المنتظم (URI) للمحتوى:

content://com.example.app.provider/*
يتطابق مع أي معرِّف موارد منتظم (URI) للمحتوى في الموفِّر.
content://com.example.app.provider/table2/*
تتطابق مع معرّف الموارد المنتظم (URI) للمحتوى للجدولين dataset1 وdataset2، ولكنها لا تتطابق مع معرّفات الموارد المنتظمة (URI) للمحتوى في table1 أو table3.
content://com.example.app.provider/table3/#
تطابق معرّف الموارد المنتظم (URI) للمحتوى للصفوف الفردية في table3، مثل content://com.example.app.provider/table3/6 للصف الذي يحدّده 6.

يعرض مقتطف الرمز التالي طريقة عمل الطرق في UriMatcher. يتعامل هذا الرمز مع معرّفات الموارد المنتظمة (URI) للجدول بأكمله بطريقة تختلف عن معرّفات الموارد المنتظمة (URI) لصف واحد، وذلك عند استخدام نمط معرّف الموارد المنتظم (URI) للمحتوى content://<authority>/<path> للجداول وcontent://<authority>/<path>/<id> للصفوف الفردية.

تربط الطريقة addURI() المرجع والمسار بقيمة عدد صحيح. تعرض الطريقة match() القيمة العددية لمعرّف الموارد المنتظم (URI). تختار عبارة switch بين الاستعلام عن الجدول بأكمله والاستعلام لسجلّ واحد.

Kotlin

private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    /*
     * The calls to addURI() go here for all the content URI patterns that the provider
     * recognizes. For this snippet, only the calls for table 3 are shown.
     */

    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path.
     */
    addURI("com.example.app.provider", "table3", 1)

    /*
     * Sets the code for a single row to 2. In this case, the # wildcard is
     * used. content://com.example.app.provider/table3/3 matches, but
     * content://com.example.app.provider/table3 doesn't.
     */
    addURI("com.example.app.provider", "table3/#", 2)
}
...
class ExampleProvider : ContentProvider() {
    ...
    // Implements ContentProvider.query()
    override fun query(
            uri: Uri?,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        var localSortOrder: String = sortOrder ?: ""
        var localSelection: String = selection ?: ""
        when (sUriMatcher.match(uri)) {
            1 -> { // If the incoming URI was for all of table3
                if (localSortOrder.isEmpty()) {
                    localSortOrder = "_ID ASC"
                }
            }
            2 -> {  // If the incoming URI was for a single row
                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                localSelection += "_ID ${uri?.lastPathSegment}"
            }
            else -> { // If the URI isn't recognized,
                // do some error handling here
            }
        }

        // Call the code to actually do the query
    }
}

Java

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         * The calls to addURI() go here for all the content URI patterns that the provider
         * recognizes. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to one. No wildcard is used
         * in the path.
         */
        uriMatcher.addURI("com.example.app.provider", "table3", 1);

        /*
         * Sets the code for a single row to 2. In this case, the # wildcard is
         * used. content://com.example.app.provider/table3/3 matches, but
         * content://com.example.app.provider/table3 doesn't.
         */
        uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (uriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                selection = selection + "_ID = " + uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI isn't recognized, do some error handling here
        }
        // Call the code to actually do the query
    }

توفّر فئة أخرى، وهي ContentUris، طرقًا ملائمة للعمل باستخدام الجزء id من معرّفات الموارد المنتظمة (URI) للمحتوى. تتضمن الفئتان Uri وUri.Builder طرقًا ملائمة لتحليل كائنات Uri الحالية وإنشاء كائنات جديدة.

تنفيذ فئة ContentProvider

يدير المثيل ContentProvider إمكانية الوصول إلى مجموعة منظَّمة من البيانات من خلال معالجة الطلبات الواردة من التطبيقات الأخرى. ويُطلق على جميع أشكال الوصول في النهاية اسم ContentResolver، والذي يطلب بعد ذلك طريقة ملموسة ContentProvider للوصول إلى هذه الأداة.

الطرق المطلوبة

تحدّد الفئة التجريدية ContentProvider ست طُرق مجردة تنفذها كجزء من الفئة الفرعية الملموسة. يستدعي أحد التطبيقات العميلة جميع هذه الطرق، باستثناء onCreate()، ويحاول الوصول إلى موفّر المحتوى الخاص بك.

query()
يمكنك استرداد البيانات من مقدّم الخدمة. واستخدِم الوسيطات لاختيار الجدول الذي تريد طلب البحث عنه والصفوف والأعمدة التي تريد عرضها وترتيب النتائج. عرض البيانات كعنصر Cursor
insert()
أدرِج صفًا جديدًا في مقدّم الخدمة. استخدِم الوسيطات لاختيار جدول الوجهة والحصول على قيم الأعمدة المطلوب استخدامها. اعرض معرّف موارد منتظم (URI) للمحتوى للصف الذي تم إدراجه حديثًا.
update()
تعديل الصفوف الحالية في مقدّم الخدمة استخدِم الوسيطات لاختيار الجدول والصفوف لتعديلها والحصول على قيم الأعمدة المعدَّلة. عرض عدد الصفوف التي تم تحديثها.
delete()
احذف صفوفًا من مقدّم الخدمة. استخدِم الوسيطات لاختيار الجدول والصفوف المراد حذفها. إرجاع عدد الصفوف المحذوفة.
getType()
اعرض نوع MIME المتوافق مع معرّف الموارد المنتظم (URI) للمحتوى. يمكن الاطّلاع على هذه الطريقة بمزيد من التفاصيل في قسم تنفيذ أنواع MIME لموفّر المحتوى.
onCreate()
يُرجى إعداد موفّر الخدمة. يطلب نظام Android هذه الطريقة مباشرةً بعد إنشاء موفِّر الخدمة. لا يتم إنشاء مقدِّم الخدمة إلا بعد محاولة كائن ContentResolver الوصول إليه.

تحتوي هاتان الطريقتان على التوقيع نفسه المرتبط بطُرق ContentResolver التي تحمل أسماء متطابقة.

يجب أن يراعي تنفيذ هذه الطرق ما يلي:

  • يمكن استدعاء جميع هذه الطرق باستثناء onCreate() باستخدام سلاسل محادثات متعددة في وقت واحد، لذلك يجب أن تكون آمنة في سلاسل المحادثات. لمزيد من المعلومات حول سلاسل المحادثات المتعددة، يمكنك الاطّلاع على نظرة عامة على العمليات وسلاسل المحادثات.
  • تجنَّب إجراء عمليات طويلة في onCreate(). أجِّل مهام الإعداد إلى أن تكون هناك حاجة إليها. يناقش القسم الخاص بتنفيذ طريقة onCreate() هذا الموضوع بمزيد من التفصيل.
  • عليك تنفيذ هذه الطرق، إلا أنّه لا حاجة إلى اتخاذ أي إجراء بشأن الرمز باستثناء عرض نوع البيانات المتوقّع. على سبيل المثال، يمكنك منع التطبيقات الأخرى من إدراج بيانات في بعض الجداول من خلال تجاهل طلب insert() وعرض القيمة 0.

تنفيذ طريقة query()

ويجب أن تعرض الطريقة ContentProvider.query() كائن Cursor أو في حال تعذّر تنفيذ الإجراء، يتم عرض Exception. إذا كنت تستخدم قاعدة بيانات SQLite كمساحة تخزين للبيانات، يمكنك عرض Cursor التي تعرضها إحدى طرق query() من الفئة SQLiteDatabase.

إذا لم يتطابق طلب البحث مع أي صفوف، اعرض مثيل Cursor تعرض طريقة getCount() له القيمة 0. يمكنك عرض null فقط في حال حدوث خطأ داخلي أثناء عملية طلب البحث.

إذا كنت لا تستخدم قاعدة بيانات SQLite كمساحة تخزين للبيانات، استخدِم إحدى الفئات الفرعية الملموسة في Cursor. على سبيل المثال، تستخدم الفئة MatrixCursor مؤشرًا يكون فيه كل صف مصفوفة من مثيلات Object. مع هذا الصف، استخدِم addRow() لإضافة صف جديد.

يجب أن يتمكّن نظام Android من توصيل Exception عبر حدود العملية. ويمكن لنظام التشغيل Android إجراء ذلك للاستثناءات التالية التي تكون مفيدة في التعامل مع أخطاء طلبات البحث:

تنفيذ طريقة Insert()

تضيف الطريقة insert() صفًا جديدًا إلى الجدول المناسب، باستخدام القيم في الوسيطة ContentValues. إذا لم يكن اسم العمود متوفرًا في الوسيطة ContentValues، ننصحك بتوفير قيمة تلقائية له إما في رمز الموفّر أو في مخطط قاعدة البيانات.

تُرجع هذه الطريقة معرف الموارد المنتظم (URI) للمحتوى للصف الجديد. لإنشاء هذه السمة، أضِف المفتاح الأساسي للصف الجديد، وهو عادةً قيمة _ID، إلى معرّف الموارد المنتظم (URI) الخاص بمحتوى الجدول، وذلك باستخدام withAppendedId().

تنفيذ طريقة delete()

ولا يتعيّن على الطريقة delete() حذف الصفوف من مساحة تخزين بياناتك. إذا كنت تستخدم محوّل مزامنة مع موفّر الخدمة، يمكنك وضع علامة "حذف" على صف محذوف بدلاً من إزالة الصف بأكمله. يمكن لمحوّل المزامنة البحث عن الصفوف المحذوفة وإزالتها من الخادم قبل حذفها من موفِّر الخدمة.

تنفيذ طريقة update()

تستخدم الطريقة update() وسيطة ContentValues نفسها المستخدمة في insert() والوسيطتين selection وselectionArgs نفسيهما اللتين يستخدمهما delete() وContentProvider.query(). وقد يتيح لك ذلك إعادة استخدام الرمز بين هاتَين الطريقتَين.

تنفيذ طريقة onCreate()

يطلب نظام Android من onCreate() عند بدء تشغيل موفّر الخدمة. يجب تنفيذ مهام الإعداد السريعة فقط بهذه الطريقة وتأجيل إنشاء قاعدة البيانات وتحميلها إلى أن يتلقّى الموفّر بالفعل طلبًا للحصول على البيانات. إذا كنت تؤدّي مهامًا طويلة في onCreate()، سيؤدي ذلك إلى إبطاء الشركة الناشئة التابعة لموفّر الخدمة. ويؤدي ذلك بدوره إلى إبطاء سرعة استجابة مقدّم الخدمة للتطبيقات الأخرى.

يوضّح المقتطفان التاليان التفاعل بين ContentProvider.onCreate() و Room.databaseBuilder(). يعرض المقتطف الأول تنفيذ ContentProvider.onCreate() حيث يتم إنشاء كائن قاعدة البيانات والأسماء المعرِّفة لعناصر الوصول إلى البيانات:

Kotlin

// Defines the database name
private const val DBNAME = "mydb"
...
class ExampleProvider : ContentProvider() {

    // Defines a handle to the Room database
    private lateinit var appDatabase: AppDatabase

    // Defines a Data Access Object to perform the database operations
    private var userDao: UserDao? = null

    override fun onCreate(): Boolean {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.userDao

        return true
    }
    ...
    // Implements the provider's insert method
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Java

public class ExampleProvider extends ContentProvider

    // Defines a handle to the Room database
    private AppDatabase appDatabase;

    // Defines a Data Access Object to perform the database operations
    private UserDao userDao;

    // Defines the database name
    private static final String DBNAME = "mydb";

    public boolean onCreate() {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.getUserDao();

        return true;
    }
    ...
    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

تنفيذ أنواع MIME في ContentProvider

تتضمّن الفئة ContentProvider طريقتَين لعرض أنواع MIME:

getType()
إحدى الطرق المطلوبة التي تستخدمها لأي مقدّم خدمة.
getStreamTypes()
طريقة يُتوقّع منك تطبيقها إذا كان موفّر الخدمة يقدّم الملفات.

أنواع MIME للجداول

تعرض الطريقة getType() String بتنسيق MIME الذي يصف نوع البيانات التي تعرضها وسيطة معرّف الموارد المنتظم (URI) للمحتوى. يمكن أن تكون الوسيطة Uri نمطًا وليس معرّف موارد منتظم (URI) محدّد. في هذه الحالة، يمكنك عرض نوع البيانات المرتبطة بمعرّفات الموارد المنتظمة (URI) للمحتوى التي تتطابق مع النمط.

بالنسبة إلى الأنواع الشائعة من البيانات، مثل النص أو HTML أو JPEG، تعرض getType() نوع MIME العادي لتلك البيانات. تتوفّر قائمة كاملة بهذه الأنواع العادية على الموقع الإلكتروني لأنواع وسائط MIME في IANA.

في معرّفات الموارد المنتظمة (URI) للمحتوى التي تشير إلى صف أو صفوف من بيانات الجدول، يعرض getType() نوع MIME بتنسيق MIME الخاص بالمورّد في Android:

  • كتابة الجزء: vnd
  • النوع الفرعي:
    • إذا كان نمط معرّف الموارد المنتظم (URI) لصف واحد: android.cursor.item/
    • إذا كان نمط URI لأكثر من صف واحد: android.cursor.dir/
  • الجزء الخاص بمقدِّم الخدمة: vnd.<name><type>

    يجب توفير <name> و<type>. تكون قيمة <name> فريدة عالميًا، وقيمة <type> فريدة لنمط معرّف الموارد المنتظم (URI) المقابل. يُعدّ اسم شركتك أو جزءًا من اسم حزمة Android الخاصة بتطبيقك خيارًا مناسبًا لـ <name>. يُعدّ الاختيار الجيد للسمة <type> سلسلة تحدّد الجدول المرتبط بمعرّف الموارد المنتظم (URI).

على سبيل المثال، إذا كانت جهة الإرشاد هي com.example.app.provider، وتم عرض جدول باسم table1، يكون نوع MIME لصفوف متعددة في table1 هو:

vnd.android.cursor.dir/vnd.com.example.provider.table1

بالنسبة إلى صف واحد من table1، يكون نوع MIME هو:

vnd.android.cursor.item/vnd.com.example.provider.table1

أنواع MIME للملفات

إذا كان موفّر الخدمة يقدّم ملفات، نفِّذ السمة getStreamTypes(). تعرض الطريقة مصفوفة String من أنواع MIME للملفات التي يمكن لموفّر الخدمة عرضها لمعرّف الموارد المنتظم (URI) الخاص بالمحتوى. يمكنك فلترة أنواع MIME التي تقدّمها حسب وسيطة فلتر نوع MIME بحيث تعرض فقط أنواع MIME التي يريد العميل معالجتها.

على سبيل المثال، يمكنك الاستعانة بمقدّم خدمة يقدّم صور بتنسيقات JPG وPNG وGIF. في حال استدعاء تطبيق ContentResolver.getStreamTypes() مع سلسلة الفلتر image/*، بالنسبة إلى عنصر "صورة"، تعرض الطريقة ContentProvider.getStreamTypes() الصفيف:

{ "image/jpeg", "image/png", "image/gif"}

إذا كان التطبيق مهتمًا بملفات JPG فقط، يمكنه استدعاء ContentResolver.getStreamTypes() مع سلسلة الفلتر *\/jpeg، ويعرض getStreamTypes():

{"image/jpeg"}

إذا لم يقدّم موفّر الخدمة أيًّا من أنواع MIME المطلوبة في سلسلة الفلتر، تعرض getStreamTypes() السمة null.

تنفيذ فئة من العقد

فئة العقد هي فئة public final تحتوي على تعريفات ثابتة لمعرّفات الموارد المنتظمة (URI) وأسماء الأعمدة وأنواع MIME وغيرها من البيانات الوصفية المتعلقة بموفِّر الخدمة. وتحدّد الفئة عقدًا بين مقدّم الخدمة والتطبيقات الأخرى من خلال ضمان إمكانية الوصول إلى موفِّر الخدمة بشكل صحيح حتى في حال تغيير القيم الفعلية لمعرّفات الموارد المنتظمة (URI) وأسماء الأعمدة وما إلى ذلك.

تساعد فئة العقد أيضًا المطوّرين لأنّها تتضمّن عادةً أسماء ذِكرية للثوابت، وبالتالي من غير المرجّح أن يستخدم المطوّرون قيمًا غير صحيحة لأسماء الأعمدة أو معرّفات الموارد المنتظمة (URI). ولأنّ هذه الفئة، يمكن أن تحتوي على مستندات Javadoc. يمكن لبيئات التطوير المتكاملة، مثل "استوديو Android"، إكمال الأسماء الثابتة تلقائيًا من فئة العقد وعرض Javadoc للثوابت.

لا يمكن للمطوّرين الوصول إلى ملف فئة العقد من تطبيقك، ولكن يمكنهم تجميعه بشكل ثابت في تطبيقاتهم من ملف JAR الذي تقدّمه.

الفئة ContactsContract وفئاتها المدمجة هي أمثلة على فئات العقود.

تنفيذ أذونات موفّر المحتوى

يمكن الاطّلاع على تفاصيل الأذونات وأذونات الوصول في جميع جوانب نظام Android بالتفصيل في نصائح الأمان. توضّح نظرة عامة على تخزين البيانات والملفات أيضًا الأمان والأذونات السارية في أنواع مساحة التخزين المختلفة. باختصار، في ما يلي النقاط المهمة:

  • تكون ملفات البيانات المخزَّنة على وحدة التخزين الداخلية للجهاز خاصة تلقائيًا لدى التطبيق ومقدّم الخدمة.
  • تكون قواعد بيانات SQLiteDatabase التي تنشئها خاصة بالتطبيق ومقدّم الخدمة الذي تتعامل معه.
  • بشكل تلقائي، تكون ملفات البيانات التي تحفظها في وحدة التخزين الخارجية متاحة للجميع وقابلة للقراءة على مستوى العالم. لا يمكنك استخدام موفّر محتوى لحظر الوصول إلى الملفات في وحدة التخزين الخارجية، لأنّ التطبيقات الأخرى يمكنها استخدام طلبات البيانات من واجهة برمجة التطبيقات الأخرى لقراءتها وكتابتها.
  • أمّا الطريقة المطلوبة لفتح الملفات أو إنشاء قواعد بيانات SQLite على مساحة التخزين الداخلية على جهازك، فيمكنها منح جميع التطبيقات الأخرى إذنًا بالقراءة والكتابة. إذا كنت تستخدم ملفًا داخليًا أو قاعدة بيانات كمستودع للموفّر ومنحته إمكانية الوصول "قابل للقراءة عالميًا" أو "قابلاً للكتابة عالميًا"، لن تؤدي الأذونات التي تحدّدها لموفّر الخدمة في ملف البيان الخاص به إلى حماية بياناتك. يكون الوصول التلقائي إلى الملفات وقواعد البيانات في وحدة التخزين الداخلية هو "خاص"، لذا لا تغيّر ذلك في مستودع مقدّم الخدمة.

إذا أردت استخدام أذونات موفّر المحتوى للتحكم في الوصول إلى بياناتك، عليك تخزين بياناتك في ملفات داخلية أو قواعد بيانات SQLite أو السحابة الإلكترونية، على سبيل المثال، على خادم بعيد، والاحتفاظ بخصوصية الملفات وقواعد البيانات في تطبيقك.

تنفيذ الأذونات

وفقًا للإعدادات التلقائية، يمكن لجميع التطبيقات القراءة من مقدّم الخدمة أو الكتابة إليه، حتى إذا كانت البيانات الأساسية خاصة، لأنّ موفِّر الخدمة لا يملك أذونات مضبوطة تلقائيًا. لتغيير ذلك، يجب ضبط الأذونات الخاصة بالموفّر في ملف البيان باستخدام السمات أو العناصر الثانوية للعنصر <provider>. ويمكنك ضبط الأذونات التي تنطبق على موفِّر الخدمة بأكمله أو على جداول معيّنة أو على سجلّات معيّنة أو على الثلاثة جداول كلها.

تحدّد الأذونات الممنوحة للموفِّر المعني باستخدام عنصر <permission> واحد أو أكثر في ملف البيان. لجعل الإذن فريدًا لموفّر الخدمة، استخدِم تحديد النطاق بأسلوب Java للسمة android:name. على سبيل المثال، يمكنك تسمية إذن القراءة com.example.app.provider.permission.READ_PROVIDER.

تصف القائمة التالية نطاق أذونات موفّر الخدمة، بدءًا من الأذونات التي تنطبق على موفِّر الخدمة بأكمله وصولاً إلى التفاصيل الأكثر دقة. وتحظى الأذونات الأكثر دقة بالأولوية على الأذونات ذات النطاق الأكبر.

إذن واحد على مستوى الموفِّر للقراءة والكتابة
إذن واحد يتحكّم في إذن الوصول للقراءة والكتابة إلى الموفّر بأكمله، ويتم تحديده باستخدام السمة android:permission للعنصر <provider>.
أذونات منفصلة للقراءة والكتابة على مستوى مقدِّم الخدمة
إذن بالقراءة وإذن الكتابة لموفّر الخدمة بأكمله ويمكنك تحديدها باستخدام السمتَين android:readPermission و android:writePermission للعنصر <provider>. وتكون لها الأولوية على الإذن الذي يطلبه android:permission.
الإذن على مستوى المسار
إذن بقراءة معرّف الموارد المنتظم (URI) للمحتوى أو كتابته أو القراءة/الكتابة في الموفّر ويمكنك تحديد كل معرّف موارد منتظم (URI) تريد التحكّم فيه باستخدام عنصر <path-permission> ثانوي للعنصر <provider>. بالنسبة إلى كل معرّف موارد منتظم (URI) للمحتوى تحدّده، يمكنك تحديد إذن بالقراءة أو الكتابة أو إذن بالقراءة أو إذن كتابة أو الثلاثة. وتكون الأولوية لأذونات القراءة والكتابة على إذن القراءة/الكتابة. وللإذن على مستوى المسار أيضًا، يحظى الإذن على مستوى المسار بالأولوية على الأذونات على مستوى الموفّر.
إذن مؤقت
مستوى إذن يمنح إمكانية الوصول المؤقت إلى أحد التطبيقات، حتى إذا لم يكن يمتلك الأذونات المطلوبة عادةً. وتعمل ميزة الوصول المؤقت على تقليل عدد الأذونات التي يجب أن يطلبها التطبيق في ملف البيان الخاص به. عند تفعيل الأذونات المؤقتة، فإنّ التطبيقات الوحيدة التي تحتاج إلى أذونات دائمة لمقدّم الخدمة هي التطبيقات التي تصل باستمرار إلى جميع بياناتك.

على سبيل المثال، ضع في اعتبارك الأذونات التي تحتاج إليها إذا كنت تستخدم مزوِّد خدمة بريد إلكتروني وتطبيقًا وكنت تريد السماح لتطبيق خارجي لعرض مرفقات الصور بعرض مرفقات الصور من موفّر الخدمة. لمنح عارض الصور إمكانية الوصول اللازمة بدون الحاجة إلى أذونات، يمكنك إعداد أذونات مؤقتة لمعرّفات الموارد المنتظمة (URI) الخاصة بالمحتوى في الصور.

يمكنك تصميم تطبيق البريد الإلكتروني بحيث عندما يريد المستخدم عرض صورة، يرسل التطبيق هدفًا يحتوي على معرّف الموارد المنتظم (URI) لمحتوى الصورة وعلامات الأذونات إلى عارض الصور. يمكن لعارض الصور بعد ذلك إرسال طلب إلى مزوِّد خدمة البريد الإلكتروني لاسترداد الصورة، حتى لو لم يكن لدى المُشاهد إذن القراءة العادي لموفّر الخدمة.

لتفعيل الأذونات المؤقتة، يمكنك ضبط السمة android:grantUriPermissions للعنصر <provider> أو إضافة عنصر <grant-uri-permission> فرعي واحد أو أكثر إلى العنصر <provider>. يمكنك طلب الرقم Context.revokeUriPermission() عند إزالة إمكانية استخدام معرِّف الموارد المنتظم (URI) للمحتوى المرتبط بإذن مؤقت من موفّر الخدمة.

تحدّد قيمة السمة عدد العروض التي يمكن الوصول إليها من قِبل مقدّم الخدمة. إذا تم ضبط السمة على "true"، يمنح النظام إذنًا مؤقتًا لموفّر الخدمة بأكمله، ويلغي أي أذونات أخرى مطلوبة للأذونات على مستوى موفِّر الخدمة أو على مستوى المسار.

وإذا تم ضبط هذه العلامة على "false"، أضِف العناصر الثانوية <grant-uri-permission> إلى العنصر <provider>. يحدد كل عنصر فرعي معرف الموارد المنتظم (URI) للمحتوى أو معرّفات الموارد المنتظمة (URI) التي يتم منح إمكانية الوصول المؤقت إليها.

لتفويض الوصول المؤقت إلى تطبيق، يجب أن يحتوي الغرض على علامة FLAG_GRANT_READ_URI_PERMISSION أو علامة FLAG_GRANT_WRITE_URI_PERMISSION أو كليهما. ويتم ضبطها باستخدام الطريقة setFlags().

إذا كانت السمة android:grantUriPermissions غير متوفّرة، نفترض أنّها "false".

العنصر <provider>

وكما هي الحال في المكوّنَين Activity وService، يتم تحديد فئة فرعية من ContentProvider في ملف البيان الخاص بتطبيقها، وذلك باستخدام العنصر <provider>. ويحصل نظام Android على المعلومات التالية من العنصر:

المرجع (android:authorities)
أسماء رمزية تعرّف الموفِّر الكامل داخل النظام. يتم وصف هذه السمة بمزيد من التفصيل في قسم معرّفات الموارد المنتظمة (URI) الخاصة بمحتوى التصميم.
اسم فئة مقدّم الخدمة (android:name)
الفئة التي تنفّذ ContentProvider. ويتم وصف هذه الفئة بمزيد من التفصيل في القسم تنفيذ الفئة ContentProvider.
الأذونات
السمات التي تحدد الأذونات التي يجب أن تمتلكها التطبيقات الأخرى للوصول إلى بيانات موفّر الخدمة:

يمكن الاطّلاع على مزيد من التفاصيل حول الأذونات والسمات المرتبطة بها في القسم تطبيق أذونات موفّر المحتوى.

سمات بدء التشغيل والتحكم
تحدّد هذه السمات طريقة بدء نظام Android لموفّر الخدمة ووقت بدء تشغيله، وخصائص عملية عمل الموفّر، وإعدادات بيئة التشغيل الأخرى:
  • android:enabled: علامة تتيح للنظام بدء تشغيل الموفِّر
  • android:exported: وضع علامة على السماح للتطبيقات الأخرى باستخدام هذا المزود
  • android:initOrder: ترتيب بدء تشغيل مقدّم الخدمة هذا، مقارنةً بمقدّمي الخدمات الآخرين في العملية نفسها
  • android:multiProcess: علامة تتيح للنظام بدء موفّر الخدمة بالعملية نفسها التي يتّبعها برنامج الاتصال
  • android:process: اسم العملية التي يُشغِّل بها الموفِّر
  • android:syncable: علامة تشير إلى أنّ بيانات مقدّم الخدمة ستتم مزامنتها مع البيانات على خادم

وقد تم توثيق هذه السمات بالكامل في الدليل المتعلّق بالعنصر <provider>.

السمات الإعلامية
رمز وتصنيف اختياريَين للموفّر:
  • android:icon: مرجع قابل للرسم يحتوي على رمز لموفّر الخدمة يظهر الرمز بجانب تصنيف مقدّم الخدمة في قائمة التطبيقات في الإعدادات > التطبيقات > الكل.
  • android:label: تصنيف معلوماتي يصف مقدّم الخدمة أو بياناته أو كليهما. يظهر التصنيف في قائمة التطبيقات في الإعدادات > التطبيقات > الكل.

وقد تم توثيق هذه السمات بالكامل في الدليل المتعلّق بالعنصر <provider>.

ملاحظة: إذا كنت تستهدف الإصدار 11 من نظام التشغيل Android أو إصدارًا أحدث، يُرجى الاطّلاع على المواد المتعلقة بإذن الوصول إلى الحِزم لمعرفة المزيد من متطلبات الإعداد.

الأهداف والوصول إلى البيانات

يمكن للتطبيقات الوصول إلى موفّر المحتوى بشكل غير مباشر باستخدام Intent. لا يطلب التطبيق أي طريقة من طرق ContentResolver أو ContentProvider. بدلاً من ذلك، فإنّه يرسل هدفًا يؤدي إلى بدء نشاط، ويكون غالبًا جزءًا من التطبيق الخاص بمقدّم الخدمة. نشاط الوجهة هو المسؤول عن استرداد البيانات وعرضها في واجهة المستخدم الخاصة بها.

اعتمادًا على الإجراء في الهدف، يمكن أيضًا أن يطلب نشاط الوجهة من المستخدم إجراء تعديلات على بيانات موفّر الخدمة. قد يحتوي الغرض أيضًا على بيانات "إضافية" يعرضها نشاط الوجهة في واجهة المستخدم. بإمكان المستخدم بعد ذلك تغيير هذه البيانات قبل استخدامها لتعديل البيانات في مقدّم الخدمة.

يمكنك استخدام ميزة الوصول حسب النية بالشراء للمساعدة في الحفاظ على سلامة البيانات. وقد يعتمد موفّر الخدمة على إدراج البيانات وتعديلها وحذفها وفقًا لمنطق أعمال محدّد بدقة. في هذه الحالة، قد يؤدي السماح للتطبيقات الأخرى بتعديل بياناتك مباشرةً إلى إنشاء بيانات غير صالحة.

إذا أردت أن يستخدم المطوّرون الإذن بالوصول حسب النية بالشراء، احرص على توثيقه بدقة. اشرح السبب في أنّ ميزة "الوصول حسب النية بالشراء" باستخدام واجهة المستخدم الخاصة بتطبيقك أفضل من محاولة تعديل البيانات باستخدام الرموز البرمجية.

لا تختلف معالجة النية الواردة التي تريد تعديل بيانات مقدّم الخدمة عن معالجة الأهداف الأخرى. يمكنك معرفة المزيد عن استخدام الأهداف من خلال قراءة فلاتر الأهداف وفلاتر الأهداف.

لمعرفة المعلومات الإضافية ذات الصلة، يُرجى الاطّلاع على نظرة عامة على موفّر "تقويم Google".