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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

فيما يلي بعض النصائح لتصميم بنية بيانات مزوّد الخدمة:

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

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

تصميم معرفات الموارد المنتظمة (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 أن يكون أحد الأعمدة في Cursor هو _ID.

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

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

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

يتعرّف موفِّر المحتوى أيضًا على معرّفات الموارد المنتظمة الخاصة بالمحتوى في حال إلحاق رقم تعريف صف بها، مثل 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.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

هناك طريقتان لعرض أنواع MIME للفئة ContentProvider:

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

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

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

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

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

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

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

على سبيل المثال، إذا كانت جهة إصدار مقدّم الخدمة هي 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). ونظرًا لأنها فئة، يمكن أن تحتوي على مستندات JavaScript. يمكن لبيئات التطوير المتكاملة، مثل Android Studio، إكمال الأسماء الثابتة تلقائيًا من فئة العقد وعرض مستند Javadoc للثوابت.

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

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

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

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

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

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

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

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

ويمكنك تحديد الأذونات لموفّر الخدمة باستخدام عنصر <permission> واحد أو أكثر في ملف البيان. لجعل الإذن فريدًا لموفّر الخدمة، استخدِم تحديد النطاق بأسلوب JavaScript للسمة 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) التي يتم منح إذن الوصول المؤقت إليها.

لتفويض الوصول المؤقت إلى تطبيق، يجب أن يتضمن الغرض العلامة 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>.

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

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

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

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

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

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

للحصول على مزيد من المعلومات ذات الصلة، يمكنك الاطّلاع على نظرة عامة على موفِّر التقويم.