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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

يختار المستخدم بعد ذلك أحد الصفوف المعروضة من واجهة المستخدم لمشاهدة البيانات. يحصل التطبيق على الصف المقابل من 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، يمكنك قد ترغب في توفير قيمة افتراضية لها إما في رمز الموفّر أو في قاعدة البيانات Google.

تُرجع هذه الطريقة معرف الموارد المنتظم (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) المقابل. النمط. الخيار الجيد لـ <name> هو اسم شركتك أو جزء من اسم حزمة Android لتطبيقك. اختيار جيد <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) التي تم منح إذن الوصول المؤقت إليها.

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