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

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

عادةً ما تعمل مع موفّري المحتوى في واحد من حالتين: تطبيق رمز للدخول إلى موفّر محتوى حالي في تطبيق آخر أو إنشاء موفّر محتوى جديد في تطبيقك لمشاركة البيانات مع التطبيقات الأخرى.

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

يوضِّح هذا الموضوع ما يلي:

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

نظرة عامة

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

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

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

الشكل 1. العلاقة بين موفر المحتوى والمكونات الأخرى.

الوصول إلى مقدّم خدمة

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

يتلقى كائن الموفِّر طلبات البيانات من العملاء، وينفّذ الإجراء المطلوب، ويعرض النتائج. يحتوي هذا الكائن على طرق تستدعي الطرق التي لها أسماء متطابقة في كائن الموفِّر، وهو مثال على إحدى الفئات الفرعية الملموسة من ContentProvider. توفّر طرق ContentResolver وظائف "CRUD" الأساسية (الإنشاء والاسترداد والتحديث والحذف) لمساحة التخزين الدائمة.

هناك نمط شائع للوصول إلى ContentProvider من واجهة المستخدم يستخدم CursorLoader لتشغيل طلب بحث غير متزامن في الخلفية. تستدعي Activity أو Fragment في واجهة المستخدم CursorLoader لطلب البحث، ما يؤدي بدوره إلى الحصول على ContentProvider باستخدام ContentResolver.

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

التفاعل بين ContentProvider والفئات الأخرى والتخزين.

الشكل 2. التفاعل بين "ContentProvider" والصفوف الأخرى ومساحة التخزين

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

أحد مزوِّدي الخدمة المُدمَجة في نظام Android الأساسي هو "موفِّر قاموس المستخدم" الذي يخزِّن الكلمات غير العادية التي يريد المستخدم الاحتفاظ بها. يوضّح الجدول 1 الشكل الذي قد تبدو عليه البيانات في جدول مقدّم الخدمة هذا:

الجدول 1: نموذج لجدول قاموس المستخدم

كلمة الرقم التعريفي للتطبيق النشر اللغة _ID
mapreduce المستخدم1 100 en_US 1
precompiler مستخدم 14 200 fr_FR 2
applet المستخدم2 225 fr_CA 3
const المستخدم1 255 pt_BR 4
int مستخدم 5 100 ar_UK 5

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

للحصول على قائمة بالكلمات ولغاتها من "موفِّر قاموس المستخدم"، عليك الاتصال بـ ContentResolver.query(). تستدعي الطريقة query() الطريقة ContentProvider.query() التي حدّدها "موفِّر قاموس المستخدم". تعرض أسطر الرمز التالية طلب ContentResolver.query():

Kotlin

// Queries the UserDictionary and returns results
cursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
        projection,                        // The columns to return for each row
        selectionClause,                   // Selection criteria
        selectionArgs.toTypedArray(),      // Selection criteria
        sortOrder                          // The sort order for the returned rows
)

Java

// Queries the UserDictionary and returns results
cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    projection,                        // The columns to return for each row
    selectionClause,                   // Selection criteria
    selectionArgs,                     // Selection criteria
    sortOrder);                        // The sort order for the returned rows

يوضّح الجدول 2 كيفية مطابقة الوسيطات لـ query(Uri,projection,selection,selectionArgs,sortOrder) مع عبارة SQL SELECT:

الجدول 2: query() مقارنةً بطلب بحث SQL.

الوسيطة query() تحديد كلمة رئيسية/معلمة Notes
Uri FROM table_name يتم ربط Uri بالجدول في المزوّد المسمى table_name.
projection col,col,col,... projection هي مصفوفة من الأعمدة المضمّنة لكل صف تم استرداده.
selection WHERE col = value تحدّد selection معايير اختيار الصفوف.
selectionArgs ما مِن مكافئ دقيق. تحل وسيطات الاختيار محل العناصر النائبة ? في عبارة الاختيار.
sortOrder ORDER BY col,col,... تحدّد sortOrder الترتيب الذي تظهر به الصفوف في Cursor المعروضة.

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

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

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

يستخدم ContentProvider جزء المسار من عنوان URI للمحتوى لاختيار الجدول الذي تريد الوصول إليه. عادةً ما يكون لدى الموفر مسار لكل جدول يعرضه.

في السطور السابقة من الرمز، يكون معرّف الموارد المنتظم (URI) الكامل للجدول Words هو:

content://user_dictionary/words
  • السلسلة content:// هي scheme، وهي متوفّرة دائمًا وتحدّدها على أنّها معرّف موارد منتظم (URI) للمحتوى.
  • السلسلة user_dictionary هي مرجع الجهة الموفّرة.
  • السلسلة words هي مسار الجدول.

يتيح لك العديد من مقدّمي الخدمات الوصول إلى صف واحد في جدول من خلال إلحاق قيمة المعرّف بنهاية معرّف الموارد المنتظم (URI). على سبيل المثال، لاسترداد صف يكون فيه _ID هو 4 من مزود قاموس المستخدم، يمكنك استخدام معرف الموارد المنتظم (URI) للمحتوى هذا:

Kotlin

val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)

Java

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

غالبًا ما تستخدم قيم المعرّف عند استرداد مجموعة من الصفوف ثم تريد تعديل أحدها أو حذفه.

ملاحظة: تحتوي الفئتان Uri وUri.Builder على طرق ملائمة لإنشاء كائنات URI بتنسيق صحيح من السلاسل. تحتوي الفئة ContentUris على طرق سهلة لإلحاق قيم المعرّف بمعرّف موارد منتظم (URI). يستخدم المقتطف السابق withAppendedId() لإلحاق رقم تعريف بمعرّف الموارد المنتظم (URI) للمحتوى الخاص بمزوّد قاموس المستخدم.

استرداد البيانات من مزود الخدمة

يوضّح هذا القسم كيفية استرداد البيانات من مقدّم خدمة، باستخدام "User Dictionary Provider" كمثال.

للتوضيح، نستدعي مقتطفات الرمز في هذا القسم ContentResolver.query() في سلسلة واجهة المستخدم. أمّا في الرموز البرمجية الفعلية، فتُجري طلبات البحث بشكلٍ غير متزامن على سلسلة محادثات منفصلة. يمكنك استخدام الفئة CursorLoader الموضحة بالتفصيل في دليل Loaders. كما أن أسطر الرمز هي مقتطفات فقط. وهي لا تعرض طلبًا كاملاً.

لاسترداد البيانات من مزود خدمة، اتبع الخطوات الأساسية التالية:

  1. اطلب إذن الوصول للقراءة إلى مقدّم الخدمة.
  2. حدِّد الرمز الذي يرسل طلب بحث إلى مقدّم الخدمة.

طلب إذن بالوصول للقراءة

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

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

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

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

يحدّد موفِّر قاموس المستخدم الإذن android.permission.READ_USER_DICTIONARY في ملف البيان الخاص به، لذلك يجب أن يطلب التطبيق الذي يريد القراءة من موفِّر التطبيق هذا الإذن.

إنشاء الاستعلام

الخطوة التالية في استرداد البيانات من موفر هي إنشاء استعلام. ويحدّد المقتطف التالي بعض المتغيّرات للوصول إلى مزود قاموس المستخدم:

Kotlin

// A "projection" defines the columns that are returned for each row
private val mProjection: Array<String> = arrayOf(
        UserDictionary.Words._ID,    // Contract class constant for the _ID column name
        UserDictionary.Words.WORD,   // Contract class constant for the word column name
        UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
)

// Defines a string to contain the selection clause
private var selectionClause: String? = null

// Declares an array to contain selection arguments
private lateinit var selectionArgs: Array<String>

Java

// A "projection" defines the columns that are returned for each row
String[] mProjection =
{
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
};

// Defines a string to contain the selection clause
String selectionClause = null;

// Initializes an array to contain selection arguments
String[] selectionArgs = {""};

ويوضّح المقتطف التالي طريقة استخدام ContentResolver.query()، مع الاستعانة بموفِّر خدمة قاموس المستخدم كمثال. يتشابه طلب بحث العميل لموفّر الخدمة مع طلب بحث SQL، ويحتوي على مجموعة من الأعمدة المطلوب عرضها، بالإضافة إلى مجموعة من معايير الاختيار وترتيب الترتيب.

تُسمى مجموعة الأعمدة التي يعرضها طلب البحث projection، والمتغير هو mProjection.

ينقسم التعبير الذي يحدد الصفوف المطلوب استردادها إلى عبارة اختيار ووسيطات اختيار. عبارة الاختيار هي مجموعة من التعبيرات المنطقية والمنطقية وأسماء الأعمدة والقيم. المتغيّر هو mSelectionClause. إذا حدّدت المَعلمة القابلة للاستبدال ? بدلاً من قيمة، تجلب طريقة طلب البحث القيمة من مصفوفة وسيطات الاختيار، وهو المتغيّر mSelectionArgs.

في المقتطف التالي، إذا لم يُدخل المستخدم أي كلمة، سيتم ضبط عبارة الاختيار على null وعرض طلب البحث جميع الكلمات في مقدّم الخدمة. إذا أدخل المستخدم كلمة، يتم ضبط عبارة الاختيار على UserDictionary.Words.WORD + " = ?" ويتم ضبط العنصر الأول في مصفوفة وسيطات الاختيار على الكلمة التي يُدخلها المستخدم.

Kotlin

/*
 * This declares a String array to contain the selection arguments.
 */
private lateinit var selectionArgs: Array<String>

// Gets a word from the UI
searchString = searchWord.text.toString()

// Insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let {
    selectionClause = "${UserDictionary.Words.WORD} = ?"
    arrayOf(it)
} ?: run {
    selectionClause = null
    emptyArray<String>()
}

// Does a query against the table and returns a Cursor object
mCursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI, // The content URI of the words table
        projection,                       // The columns to return for each row
        selectionClause,                  // Either null or the word the user entered
        selectionArgs,                    // Either empty or the string the user entered
        sortOrder                         // The sort order for the returned rows
)

// Some providers return null if an error occurs, others throw an exception
when (mCursor?.count) {
    null -> {
        /*
         * Insert code here to handle the error. Be sure not to use the cursor!
         * You might want to call android.util.Log.e() to log this error.
         */
    }
    0 -> {
        /*
         * Insert code here to notify the user that the search is unsuccessful. This isn't
         * necessarily an error. You might want to offer the user the option to insert a new
         * row, or re-type the search term.
         */
    }
    else -> {
        // Insert code here to do something with the results
    }
}

Java

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] selectionArgs = {""};

// Gets a word from the UI
searchString = searchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(searchString)) {
    // Setting the selection clause to null returns all words
    selectionClause = null;
    selectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered
    selectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments
    selectionArgs[0] = searchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI, // The content URI of the words table
    projection,                       // The columns to return for each row
    selectionClause,                  // Either null or the word the user entered
    selectionArgs,                    // Either empty or the string the user entered
    sortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You can
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search is unsuccessful. This isn't necessarily
     * an error. You can offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}

يشبه هذا الاستعلام عبارة SQL التالية:

SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

في عبارة SQL هذه، يتم استخدام أسماء الأعمدة الفعلية بدلاً من ثوابت فئة العقد.

الحماية من الإدخال الضارّ

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

ضع في اعتبارك عبارة الاختيار التالية:

Kotlin

// Constructs a selection clause by concatenating the user's input to the column name
var selectionClause = "var = $mUserInput"

Java

// Constructs a selection clause by concatenating the user's input to the column name
String selectionClause = "var = " + userInput;

إذا قمت بذلك، فإنك تسمح للمستخدم بسلسلة SQL الضارة في عبارة SQL الخاصة بك. على سبيل المثال، يمكن للمستخدم إدخال "nothing; DROP TABLE *;" في mUserInput، ما يؤدي إلى ظهور عبارة الاختيار var = nothing; DROP TABLE *;.

وبما أنّه يتم التعامل مع عبارة التحديد على أنّها عبارة SQL، قد يمحو موفّر الخدمة جميع الجداول في قاعدة بيانات SQLite الأساسية، إلا إذا تم إعداد موفِّر الخدمة لرصد محاولات إدخال SQL.

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

Kotlin

// Constructs a selection clause with a replaceable parameter
var selectionClause = "var = ?"

Java

// Constructs a selection clause with a replaceable parameter
String selectionClause =  "var = ?";

يمكنك إعداد مصفوفة من وسيطات الاختيار كما يلي:

Kotlin

// Defines a mutable list to contain the selection arguments
var selectionArgs: MutableList<String> = mutableListOf()

Java

// Defines an array to contain the selection arguments
String[] selectionArgs = {""};

ضع قيمة في مصفوفة وسيطات التحديد على النحو التالي:

Kotlin

// Adds the user's input to the selection argument
selectionArgs += userInput

Java

// Sets the selection argument to the user's input
selectionArgs[0] = userInput;

وتُعدّ عبارة الاختيار التي تستخدم ? كمَعلمة قابلة للاستبدال ومجموعة من مصفوفة وسيطات الاختيار الطريقة المفضّلة لتحديد الاختيار، حتى إذا لم يكن مقدّم الخدمة يعتمد على قاعدة بيانات SQL.

عرض نتائج طلب البحث

تعرض طريقة العميل ContentResolver.query() دائمًا خطأ Cursor الذي يحتوي على الأعمدة المحدّدة في توقّعات طلب البحث للصفوف التي تتطابق مع معايير اختيار طلب البحث. يوفّر العنصر Cursor إذن وصول عشوائيًا للقراءة إلى الصفوف والأعمدة التي يحتوي عليها.

باستخدام طرق Cursor، يمكنك التكرار على الصفوف في النتائج، وتحديد نوع البيانات لكل عمود، واستخراج البيانات من عمود، وفحص الخصائص الأخرى للنتائج.

تعدّل بعض عمليات تنفيذ Cursor العنصر تلقائيًا عند تغيير بيانات مقدّم الخدمة أو عند تشغيل طرق في كائن مراقب عند تغيير علامة Cursor، أو عند تطبيق الإجراءين معًا.

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

إذا لم تتطابق أي صفوف مع معايير الاختيار، يعرض الموفِّر عنصر Cursor بقيمة Cursor.getCount() 0، أي مؤشر فارغ.

في حالة حدوث خطأ داخلي، تعتمد نتائج الاستعلام على الموفر المحدد. وقد يعرض الخطأ null أو قد يؤدي إلى عرض علامة Exception.

بما أنّ Cursor هي قائمة صفوف، ومن الطرق الجيدة لعرض محتوى Cursor ربطها بـ ListView باستخدام SimpleCursorAdapter.

يؤدي المقتطف التالي إلى متابعة الرمز من المقتطف السابق. تنشئ الأداة عنصر SimpleCursorAdapter يحتوي على Cursor الذي تم استرداده من خلال طلب البحث، وتضبط هذا العنصر ليكون محوِّل ListView.

Kotlin

// Defines a list of columns to retrieve from the Cursor and load into an output row
val wordListColumns : Array<String> = arrayOf(
        UserDictionary.Words.WORD,      // Contract class constant containing the word column name
        UserDictionary.Words.LOCALE     // Contract class constant containing the locale column name
)

// Defines a list of View IDs that receive the Cursor columns for each row
val wordListItems = intArrayOf(R.id.dictWord, R.id.locale)

// Creates a new SimpleCursorAdapter
cursorAdapter = SimpleCursorAdapter(
        applicationContext,             // The application's Context object
        R.layout.wordlistrow,           // A layout in XML for one row in the ListView
        mCursor,                        // The result from the query
        wordListColumns,                // A string array of column names in the cursor
        wordListItems,                  // An integer array of view IDs in the row layout
        0                               // Flags (usually none are needed)
)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter)

Java

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] wordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that receive the Cursor columns for each row
int[] wordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
cursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    wordListColumns,                       // A string array of column names in the cursor
    wordListItems,                         // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter);

ملاحظة: لإظهار ListView مع Cursor، يجب أن يحتوي المؤشر على عمود باسم _ID. لهذا السبب، يسترد طلب البحث المعروض في السابق عمود "_ID" للجدول Words، على الرغم من أنّ ListView لا يعرضه. يوضّح هذا الشرط أيضًا سبب توفّر عمود _ID لكل جدول من جداولهم لدى معظم مقدّمي الخدمات.

الحصول على البيانات من نتائج طلب البحث

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

Kotlin

/*
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
* an internal error occurs. Other providers might throw an Exception instead of returning null.
*/
mCursor?.apply {
    // Determine the column index of the column named "word"
    val index: Int = getColumnIndex(UserDictionary.Words.WORD)

    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you get an
     * exception.
     */
    while (moveToNext()) {
        // Gets the value from the column
        newWord = getString(index)

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
}

Java

// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers might throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception
}

تحتوي عمليات تنفيذ Cursor على العديد من طرق "get" لاسترداد أنواع مختلفة من البيانات من الكائن. على سبيل المثال، يستخدم المقتطف السابق السمة getString(). وتحتوي أيضًا على طريقة getType() التي تعرض قيمة تشير إلى نوع بيانات العمود.

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

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

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

يطلب موفِّر قاموس المستخدم الحصول على الإذن android.permission.READ_USER_DICTIONARY لاسترداد البيانات منه. لدى مقدّم الخدمة إذن android.permission.WRITE_USER_DICTIONARY منفصل لإدراج البيانات أو تعديلها أو حذفها.

للحصول على الأذونات اللازمة للوصول إلى مقدّم خدمة، يطلب تطبيق معيّن عنصر <uses-permission> في ملف البيان الخاص به. عند تثبيت تطبيق إدارة حزم Android، يجب أن يوافق المستخدم على جميع الأذونات التي يطلبها التطبيق. وفي حال وافق المستخدم عليها، يواصل مدير الحزمة عملية التثبيت. وفي حال عدم موافقة المستخدم عليها، يوقف مدير الحزم عملية التثبيت.

يطلب نموذج عنصر <uses-permission> التالي إذنًا بالقراءة في "موفِّر قاموس المستخدم":

<uses-permission android:name="android.permission.READ_USER_DICTIONARY">

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

إدراج البيانات وتحديثها وحذفها

بالطريقة نفسها التي تسترد بها البيانات من مقدّم خدمة، يمكنك أيضًا استخدام التفاعل بين برنامج موفّر الخدمة وContentProvider للموفّر لتعديل البيانات. أنت تستدعي طريقة ContentResolver مع وسيطات يتم تمريرها إلى الطريقة المقابلة لـ ContentProvider. يتعامل كلّ من مقدّم الخدمة وبرنامج مقدّم الخدمة تلقائيًا مع الأمان والاتصال بين العمليات.

إدخال البيانات

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

Kotlin

// Defines a new Uri object that receives the result of the insertion
lateinit var newUri: Uri
...
// Defines an object to contain the new values to insert
val newValues = ContentValues().apply {
    /*
     * Sets the values of each column and inserts the word. The arguments to the "put"
     * method are "column name" and "value".
     */
    put(UserDictionary.Words.APP_ID, "example.user")
    put(UserDictionary.Words.LOCALE, "en_US")
    put(UserDictionary.Words.WORD, "insert")
    put(UserDictionary.Words.FREQUENCY, "100")

}

newUri = contentResolver.insert(
        UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
        newValues                           // The values to insert
)

Java

// Defines a new Uri object that receives the result of the insertion
Uri newUri;
...
// Defines an object to contain the new values to insert
ContentValues newValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value".
 */
newValues.put(UserDictionary.Words.APP_ID, "example.user");
newValues.put(UserDictionary.Words.LOCALE, "en_US");
newValues.put(UserDictionary.Words.WORD, "insert");
newValues.put(UserDictionary.Words.FREQUENCY, "100");

newUri = getContentResolver().insert(
    UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
    newValues                           // The values to insert
);

ويتم نقل بيانات الصف الجديد إلى عنصر ContentValues واحد، وهو ما يشبه شكل المؤشر أحادي الصف. وليس من الضروري أن يكون للأعمدة في هذا العنصر نوع البيانات نفسه، وإذا كنت لا تريد تحديد قيمة على الإطلاق، يمكنك ضبط عمود على null باستخدام ContentValues.putNull().

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

يحدّد معرّف الموارد المنتظم للمحتوى الذي يتم عرضه في newUri الصف الذي تمت إضافته مؤخرًا بالتنسيق التالي:

content://user_dictionary/words/<id_value>

<id_value> هو محتوى _ID للصف الجديد. يمكن لمعظم مقدّمي الخدمات اكتشاف هذا النوع من معرّف الموارد المنتظم للمحتوى تلقائيًا، ثم تنفيذ العملية المطلوبة على ذلك الصف تحديدًا.

للحصول على قيمة _ID من قيمة Uri التي تم إرجاعها، يمكنك استدعاء ContentUris.parseId().

تعديل البيانات

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

يغيّر المقتطف التالي جميع الصفوف التي تستخدم لغتها "en" إلى لغة null. والقيمة المعروضة هي عدد الصفوف التي تم تحديثها.

Kotlin

// Defines an object to contain the updated values
val updateValues = ContentValues().apply {
    /*
     * Sets the updated value and updates the selected words.
     */
    putNull(UserDictionary.Words.LOCALE)
}

// Defines selection criteria for the rows you want to update
val selectionClause: String = UserDictionary.Words.LOCALE + "LIKE ?"
val selectionArgs: Array<String> = arrayOf("en_%")

// Defines a variable to contain the number of updated rows
var rowsUpdated: Int = 0
...
rowsUpdated = contentResolver.update(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        updateValues,                      // The columns to update
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

Java

// Defines an object to contain the updated values
ContentValues updateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String selectionClause = UserDictionary.Words.LOCALE +  " LIKE ?";
String[] selectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int rowsUpdated = 0;
...
/*
 * Sets the updated value and updates the selected words.
 */
updateValues.putNull(UserDictionary.Words.LOCALE);

rowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    updateValues,                      // The columns to update
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

مسح البيانات التي أدخلها المستخدم عند طلب الرقم ContentResolver.update() لمعرفة المزيد من المعلومات حول هذا الأمر، يُرجى الاطّلاع على قسم الحماية من الإدخالات الضارة.

حذف البيانات

يشبه حذف الصفوف استرداد بيانات الصفوف. عليك تحديد معايير الاختيار للصفوف التي تريد حذفها، وتُرجع طريقة العميل عدد الصفوف المحذوفة. يحذف المقتطف التالي الصفوف التي يتطابق رقم تعريف تطبيقها مع "user". تعرِض الطريقة عدد الصفوف المحذوفة.

Kotlin

// Defines selection criteria for the rows you want to delete
val selectionClause = "${UserDictionary.Words.APP_ID} LIKE ?"
val selectionArgs: Array<String> = arrayOf("user")

// Defines a variable to contain the number of rows deleted
var rowsDeleted: Int = 0
...
// Deletes the words that match the selection criteria
rowsDeleted = contentResolver.delete(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

Java

// Defines selection criteria for the rows you want to delete
String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] selectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int rowsDeleted = 0;
...
// Deletes the words that match the selection criteria
rowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

مسح البيانات التي أدخلها المستخدم عند طلب الرقم ContentResolver.delete() لمعرفة المزيد من المعلومات حول هذا الأمر، يُرجى الاطّلاع على قسم الحماية من الإدخالات الضارة.

أنواع بيانات مقدِّم الخدمة

يمكن لموفري المحتوى تقديم أنواع بيانات مختلفة ومتعددة. يوفّر "موفِّر قاموس المستخدم" النص فقط، ولكن يمكن أيضًا لموفّري المحتوى توفير التنسيقات التالية:

  • عدد صحيح
  • عدد صحيح طويل (طويل)
  • نقطة عائمة
  • نقطة عائمة طويلة (مزدوجة)

من أنواع البيانات الأخرى التي يستخدمها عادةً موفِّرو البيانات كائن ثنائي كبير (BLOB) يتم تنفيذه على هيئة صفيف بحجم 64 كيلوبايت بايت. يمكنك الاطّلاع على أنواع البيانات المتاحة من خلال الاطّلاع على طرق "get" لفئة Cursor.

عادةً ما يتم إدراج نوع البيانات لكل عمود في الموفر في وثائقه. يتم إدراج أنواع البيانات لموفّر قاموس المستخدم في المستندات المرجعية لفئة العقد UserDictionary.Words. يمكنك الاطّلاع على فئات العقود في القسم فئات العقود. يمكنك أيضًا تحديد نوع البيانات من خلال طلب الرقم Cursor.getType().

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

على سبيل المثال، يستخدم الجدول ContactsContract.Data في "مقدِّم جهات الاتصال" أنواع MIME لتصنيف نوع بيانات جهة الاتصال المخزّنة في كل صف. للحصول على نوع MIME المقابل لمعرّف الموارد المنتظم للمحتوى، اطلب ContentResolver.getType().

يصف القسم مرجع نوع MIME بنية كل من أنواع MIME العادية والمخصّصة.

الأشكال البديلة من إمكانية وصول مقدّم الخدمة

هناك ثلاثة أشكال بديلة لوصول موفّري الخدمات مهمة في تطوير التطبيقات:

  • الوصول المجمَّع: يمكنك إنشاء مجموعة من استدعاءات الوصول باستخدام طُرق في الفئة ContentProviderOperation، ثم تطبيقها باستخدام السمة ContentResolver.applyBatch().
  • طلبات البحث غير المتزامنة: تنفيذ طلبات البحث في سلسلة محادثات منفصلة يمكنك استخدام كائن CursorLoader. تشرح الأمثلة الواردة في دليل العربات طريقة إجراء ذلك.
  • الوصول إلى البيانات باستخدام الأغراض: على الرغم من أنّه لا يمكنك إرسال هدف مباشرةً إلى مقدّم خدمة، يمكنك إرسال هدف إلى تطبيق الموفّر الذي يكون عادةً مستعدًا بشكل أفضل لتعديل بيانات مقدّم الخدمة.

يتم توضيح الوصول المجمَّع والتعديل باستخدام الأغراض في الأقسام التالية.

الوصول المجمّع

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

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

ويسمح هذا الإجراء لكل عنصر ContentProviderOperation في المصفوفة بالعمل مقابل جدول مختلف. تؤدي الاستدعاءات إلى ContentResolver.applyBatch() إلى عرض صفيف من النتائج.

يتضمن وصف فئة العقد ContactsContract.RawContacts مقتطف رمز يوضّح الإدراج المجمَّع.

الوصول إلى البيانات باستخدام الأغراض

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

الحصول على إذن بالوصول باستخدام أذونات مؤقتة

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

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

عند إرسال معرّفات الموارد المنتظمة (URI) للمحتوى إلى تطبيق آخر، عليك تضمين علامة واحدة على الأقل من هذه العلامات. توفّر العلامات الإمكانات التالية لأي تطبيق يتلقّى نية ويستهدف الإصدار 11 من نظام التشغيل Android (المستوى 30 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث:

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

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

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

  1. في تطبيقك، أرسِل عنصرًا يتضمّن الإجراء ACTION_PICK وCONTENT_ITEM_TYPE من النوع MIME لـ "جهات الاتصال"، وذلك باستخدام الطريقة startActivityForResult().
  2. بما أنّ هذا الغرض يتطابق مع فلتر الأهداف لنشاط "اختيار" تطبيق "الأشخاص"، يظهر النشاط في المقدّمة.
  3. في نشاط الاختيار، يختار المستخدم جهة اتصال لتعديلها. وعند حدوث ذلك، يستدعي نشاط الاختيار setResult(resultcode, intent) لإعداد هدف للردّ على تطبيقك. يتضمن الغرض معرّف الموارد المنتظم (URI) للمحتوى الخاص بجهة الاتصال التي اختارها المستخدم، بالإضافة إلى علامات "الإضافات" FLAG_GRANT_READ_URI_PERMISSION. وتمنح هذه العلامات إذن معرّف الموارد المنتظم (URI) لتطبيقك لقراءة بيانات جهة الاتصال المُشار إليها من خلال معرّف الموارد المنتظم للمحتوى. يستدعي نشاط الاختيار finish() لإعادة عنصر التحكّم إلى تطبيقك.
  4. يعود نشاطك إلى المقدّمة ويطلب النظام استخدام طريقة onActivityResult() الخاصة بنشاطك. تتلقّى هذه الطريقة هدف النتيجة الذي تم إنشاؤه من خلال نشاط الاختيار في تطبيق "الأشخاص".
  5. باستخدام معرّف الموارد المنتظم للمحتوى من الغرض من النتيجة، يمكنك قراءة بيانات جهة الاتصال من مقدّم خدمة جهات الاتصال، على الرغم من عدم طلب إذن الوصول الدائم للقراءة إلى مقدّم الخدمة في البيان. ويمكنك بعد ذلك الحصول على معلومات تاريخ ميلاد المستخدم المدرَج في جهات الاتصال أو عنوان بريده الإلكتروني، ثم إرسال رسالة الترحيب الإلكترونية.

استخدام تطبيق آخر

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

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

عرض البيانات باستخدام تطبيق مساعد

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

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

فئات العقود

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

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

Kotlin

val projection : Array<String> = arrayOf(
        UserDictionary.Words._ID,
        UserDictionary.Words.WORD,
        UserDictionary.Words.LOCALE
)

Java

String[] projection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

فئة العقد الأخرى هي ContactsContract لمقدّم خدمة جهات الاتصال. تتضمن الوثائق المرجعية لهذه الفئة أمثلة على مقتطفات التعليمات البرمجية. إحدى الفئات الفرعية الخاصة بها، وهي ContactsContract.Intents.Insert، عبارة عن فئة عقود تحتوي على ثوابت الخاصة بالأغراض وبيانات النية.

مرجع نوع MIME

ويمكن لموفري المحتوى عرض أنواع وسائط MIME القياسية أو سلاسل من نوع MIME مخصصة أو كليهما.

وتكون أنواع MIME بالصيغة التالية:

type/subtype

على سبيل المثال، يحتوي نوع MIME المعروف text/html على النوع text والنوع الفرعي html. وإذا عرض موفّر الخدمة هذا النوع لعنوان URL، هذا يعني أنّ طلب البحث الذي يستخدم معرّف الموارد المنتظم (URI) هذا يعرض نصًا يتضمّن علامات HTML.

إنّ سلاسل أنواع MIME المخصّصة، التي تُعرف أيضًا باسم أنواع MIME الخاصة بالمورّدين، تحتوي على قيم type وsubtype أكثر تعقيدًا. بالنسبة إلى الصفوف المتعددة، تكون قيمة النوع دائمًا على النحو التالي:

vnd.android.cursor.dir

بالنسبة لصف واحد، تكون قيمة النوع دائمًا على النحو التالي:

vnd.android.cursor.item

ويكون subtype خاصًا بمقدّم الخدمة. عادةً ما يكون لمزوِّدي نظام التشغيل Android المضمَّن نوعًا فرعيًا بسيطًا. على سبيل المثال، عندما ينشئ تطبيق "جهات الاتصال" صفًا لرقم هاتف، يتم ضبط نوع MIME التالي في الصف:

vnd.android.cursor.item/phone_v2

قيمة النوع الفرعي هي phone_v2.

يمكن لمطوّري برامج الخدمات الآخرين إنشاء نمط خاص بهم من الأنواع الفرعية استنادًا إلى اسم الموفّر وأسماء الجداول. على سبيل المثال، يمكنك اختيار مقدّم خدمة يحتوي على جداول زمنية للقطارات. مصدر المرجع هو com.example.trains، ويتضمّن الجداول Line1 وLine2 وLine3. استجابةً لعنوان URI للمحتوى التالي للجدول Line1:

content://com.example.trains/Line1

يعرض الموفر نوع MIME التالي:

vnd.android.cursor.dir/vnd.example.line1

استجابة لمعرف الموارد المنتظم (URI) للمحتوى التالي للصف 5 في السطر 2 في الجدول:

content://com.example.trains/Line2/5

يعرض الموفر نوع MIME التالي:

vnd.android.cursor.item/vnd.example.line2

يحدّد معظم موفّري المحتوى ثوابت فئة العقد لأنواع MIME التي يستخدمونها. على سبيل المثال، تحدِّد فئة عقد مقدِّم خدمة جهات الاتصال ContactsContract.RawContacts العنصر CONTENT_ITEM_TYPE الثابت لنوع MIME لصف واحد من جهات الاتصال الأولية.

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