Content Provider هو مكون Android يوفر واجهة موحدة للوصول إلى البيانات بين التطبيقات. يجرد التخزين الفعلي (SQLite، ملفات، مصادر شبكة) ويتيح التبادل الآمن للمعلومات عبر ContentResolver. وفقاً لـ Android Developer Guide, 2026، Content Provider هو أحد المكونات الرئيسية الأربعة لتطبيق Android إلى جانب Activity وService وBroadcastReceiver. مهمته هي جعل البيانات متاحة للتطبيقات الأخرى مع التحكم في أذونات القراءة والكتابة.
الملامح الرئيسية
Content Provider هو مكون Android يدير الوصول إلى مخزن بيانات مركزي ويوفره للتطبيقات الأخرى عبر واجهة تعاقدية موحدة. يخفي تفاصيل تنفيذ التخزين: يمكن تخزين البيانات في SQLite، على نظام الملفات، في السحابة، أو أن تكون نتيجة طلب شبكة.
يتضمن Android Content Providers مدمجة للبيانات النظامية — ContactsContract، MediaStore، CalendarContract، CallLog. يمكن لتطبيقات الطرف الثالث أيضاً إنشاء مزودات خاصة بها لتبادل البيانات بشكل آمن. يتم تسجيل كل مزود في AndroidManifest.xml مع authority — سلسلة فريدة تشكل الجزء الأول من URI.
يعمل Content Provider وفق نموذج خادم-عميل. يعمل المزود كخادم ينفذ ست طرق إلزامية: query، insert، update، delete، getType وonCreate. العميل (تطبيق آخر) يصل إلى المزود عبر ContentResolver، الذي يترجم الاستدعاءات إلى طرق المزود المقابلة عبر آلية IPC في Android.
يتم تحديد كل Content Provider بواسطة URI من مخطط content://. على سبيل المثال، content://com.example.app.provider/items. الجزء الأول authority (com.example.app.provider) مرتبط بفئة المزود في المانيفست. المسار /items يشير إلى جدول، بينما /items/5 يشير إلى سجل محدد برقم ID=5.
عندما يستدعي تطبيق ContentResolver.query(URI)، يتحقق Android من أذونات الحزمة المستدعية، ويجد المزود بواسطة authority ويبدأ تشغيل عمليته إذا لم تكن قيد التشغيل بالفعل. ينفذ المزود الاستعلام ويعيد Cursor — كائن يحتوي على النتيجة ويسمح للعميل بالتكرار عبر السجلات.
تتطلب فئة ContentProvider تنفيذ ست طرق مجردة. تقبل كل طريقة URI وتعيد نتيجة تتوافق مع نوع العملية. يستدعي النظام هذه الطرق من أي عملية، لذا يجب أن تكون آمنة للخيوط وألا تحجب التنفيذ لفترة طويلة.
تقبل طريقة query URI، مصفوفة أعمدة projection، سلسلة selection مع وسائط وترتيب فرز. تعيد Cursor مع البيانات. في التنفيذ، تحتاج إلى تحليل URI باستخدام UriMatcher وتنفيذ استعلام SQL المقابل في قاعدة البيانات.
تعدل هذه الطرق البيانات في المخزن. تستقبل insert ContentValues — أزواج مفتاح-قيمة — وتعيد URI للسجل الجديد. تقبل update وdelete selection لتصفية السجلات وتعيد عدد الصفوف المتأثرة. بعد تغيير البيانات، يجب على المزود الإخطار بذلك عبر ContentResolver.notifyChange.
| الطريقة | الغرض | الإرجاع |
|---|---|---|
| query | الحصول على البيانات بواسطة URI | Cursor أو null |
| insert | إضافة سجل جديد | URI للسجل الجديد |
| update | تحديث السجلات الموجودة | int (عدد الصفوف) |
| delete | حذف السجلات | int (عدد الصفوف) |
| getType | نوع MIME لـ URI | String |
| onCreate | تهيئة المزود | boolean |
ContentResolver هو نقطة وصول واحدة للعمل مع جميع Content Providers في النظام. لا يستدعي تطبيق العميل طرق المزود مباشرة أبداً — فقط عبر ContentResolver، الذي يحصل عليه Android من السياق. عمليات CRUD في ContentResolver لها نفس الأسماء كما في المزود ولكنها تقبل URIs بدلاً من المراجع المباشرة.
لتحليل URIs الواردة داخل المزود، يُستخدم UriMatcher. يتيح مطابقة URI مع رمز عددي — على سبيل المثال، URI content://authority/items يعطي الرمز 1، وcontent://authority/items/# يعطي الرمز 2. هذا يلغي الحاجة إلى التحليل اليدوي لسلسلة URI في كل طريقة.
// استخدام ContentResolver للوصول إلى جهات الاتصال
val uri = ContactsContract.Contacts.CONTENT_URI
val cursor = contentResolver.query(
uri,
arrayOf(ContactsContract.Contacts.DISPLAY_NAME),
null, null, null
)
cursor?.use {
while (it.moveToNext()) {
val name = it.getString(it.getColumnIndexOrThrow(
ContactsContract.Contacts.DISPLAY_NAME
))
Log.d("جهات الاتصال", "الاسم: $name")
}
}
يجب إغلاق Cursor دائماً بعد الاستخدام — في المثال أعلاه، تقوم دالة use (امتداد Kotlin) بذلك. إذا لم يتم إغلاق Cursor، يحدث تسرب للذاكرة لأنه يحتفظ بمرجع للبيانات في مجموعة Binder. لسيناريوهات واجهة المستخدم، استخدم CursorLoader أو Room مع LiveData/Flow.
يبدأ إنشاء Content Provider الخاص بك بتمديد فئة ContentProvider. يعمل المزود مع قاعدة بيانات SQLite عبر SQLiteOpenHelper ويستخدم UriMatcher لتحديد نوع الاستعلام. فكر في تنفيذ بسيط لإدارة قائمة الملاحظات.
يُسجل المزود في AndroidManifest.xml داخل وسم application. تحدد السمة authorities معرفاً فريداً، وتحدد exported ما إذا كانت التطبيقات الأخرى يمكنها الوصول إلى المزود. بدون exported=true، يكون المزود متاحاً فقط داخل تطبيقك.
// مثال Content Provider للملاحظات
class NotesProvider : ContentProvider() {
companion object {
const val AUTHORITY = "com.example.app.notes"
const val NOTES_PATH = "notes"
const val NOTES_URI = "content://$AUTHORITY/$NOTES_PATH"
const val NOTES_ID = "content://$AUTHORITY/$NOTES_PATH/#"
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, NOTES_PATH, 1)
addURI(AUTHORITY, "$NOTES_PATH/#", 2)
}
}
override fun query(uri: Uri, projection: Array<String>?,
selection: String?, args: Array<String>?, sort: String?): Cursor? {
return when (uriMatcher.match(uri)) {
1 -> dbHelper.readableDatabase.query(TABLE_NOTES,
projection, selection, args, null, null, sort)
2 -> dbHelper.readableDatabase.query(TABLE_NOTES,
projection, "_id=?", arrayOf(uri.lastPathSegment), null, null, null)
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val id = dbHelper.writableDatabase.insert(TABLE_NOTES, null, values)
context?.contentResolver?.notifyChange(uri, null)
return ContentUris.withAppendedId(uri, id)
}
override fun delete(uri: Uri, selection: String?, args: Array<String>?): Int {
val count = dbHelper.writableDatabase.delete(TABLE_NOTES, selection, args)
context?.contentResolver?.notifyChange(uri, null)
return count
}
// getType، update، onCreate تم حذفها للاختصار
}
بعد إنشاء فئة المزود، يجب تسجيله في المانيفست مع السمات android:authorities وandroid:exported (true إذا كان المزود عاماً). ينشئ النظام مثيلاً للمزود عند أول وصول إليه — يحدث هذا في سلسلة واجهة المستخدم، لذا يجب أن ينفذ onCreate بسرعة.
يتيح Content Provider إدارة الوصول إلى البيانات على مستويين: أذونات القراءة وأذونات الكتابة. تُحدد في المانيفست بواسطة السمتين android:readPermission وandroid:writePermission. إذا لم يكن لدى تطبيق العميل الإذن المقابل، يرفض النظام الاستدعاء بـ SecurityException.
يدعم Android الأذونات المؤقتة عبر العلامتين FLAG_GRANT_READ_URI_PERMISSION وFLAG_GRANT_WRITE_URI_PERMISSION. هذا مفيد عندما يمرر تطبيق URI ملف إلى تطبيق آخر عبر Intent — يحصل المستلم على الوصول فقط إلى ذلك URI المحدد لفترة زمنية محدودة. يلغي النظام الإذن المؤقت بعد انتهاء التطبيق المستلم.
بالنسبة للمزودات النظامية، يتطلب Android تحديد أذونات محددة في مانيفست التطبيق. على سبيل المثال، الوصول إلى جهات الاتصال يتطلب READ_CONTACTS، والوصول إلى التقويم يتطلب READ_CALENDAR. بدءاً من Android 6، تُطلب هذه الأذونات أثناء التشغيل وليس أثناء التثبيت.
الأسئلة الشائعة
Content Provider هو مكون Android يوفر واجهة قياسية لتبادل البيانات بين التطبيقات عبر ContentResolver. يجرد طريقة التخزين (SQLite، ملفات، شبكة) ويضمن الوصول الآمن إلى البيانات مع التحكم في أذونات القراءة والكتابة.
Authority هي سلسلة معرفة فريدة للمزود تُحدد في AndroidManifest.xml. تشكل الجزء الأول من URI content://authority/path وتستخدمها النظام لتوجيه استدعاءات ContentResolver إلى المزود الصحيح. يجب أن يكون authority فريداً بين جميع التطبيقات على الجهاز.
UriMatcher يطابق URIs مع رموز رقمية. تضيف أنماطاً عبر addURI، ثم تستدعي match للحصول على الرمز لـ URI وارد. يتيح ذلك لطرق query وinsert وupdate وdelete تحديد أي جدول أو سجل تم طلبه وتنفيذ العملية المقابلة في قاعدة البيانات.
نعم، يجب إغلاق Cursor دائماً بعد الاستخدام. إذا لم يتم إغلاق Cursor، يحدث تسرب للذاكرة لأنه يحتفظ بمرجع Binder لبيانات المزود. في Kotlin، استخدم دالة use للإغلاق التلقائي، وفي Java — try-with-resources أو cursor.close() في كتلة finally.
Content Provider هو مكون للوصول إلى البيانات بين التطبيقات، بينما SQLiteDatabase هو مخزن داخلي لتطبيق واحد. يوفر Content Provider واجهة URI والتحكم في الأذونات، بينما يعمل SQLiteDatabase مباشرة مع قاعدة البيانات دون آليات أمان على مستوى نظام التشغيل.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا