یک ارائه دهنده محتوا ایجاد کنید

یک ارائه دهنده محتوا دسترسی به یک مخزن مرکزی داده ها را مدیریت می کند. شما یک ارائه دهنده را به عنوان یک یا چند کلاس در یک برنامه اندروید به همراه عناصر موجود در فایل مانیفست پیاده سازی می کنید. یکی از کلاس های شما یک زیر کلاس از ContentProvider را پیاده سازی می کند که رابط بین ارائه دهنده شما و سایر برنامه ها است.

اگرچه ارائه‌دهندگان محتوا قرار است داده‌ها را در اختیار سایر برنامه‌ها قرار دهند، اما می‌توانید فعالیت‌هایی در برنامه خود داشته باشید که به کاربر اجازه می‌دهد داده‌های مدیریت شده توسط ارائه‌دهنده شما را پرس و جو کند و تغییر دهد.

این صفحه شامل فرآیند اساسی برای ساخت یک ارائه دهنده محتوا و لیستی از APIهای مورد استفاده است.

قبل از شروع ساخت

قبل از شروع ساخت یک ارائه دهنده، موارد زیر را در نظر بگیرید:

  • تصمیم بگیرید که آیا به یک ارائه دهنده محتوا نیاز دارید یا خیر. اگر می خواهید یک یا چند مورد از ویژگی های زیر را ارائه دهید، باید یک ارائه دهنده محتوا بسازید:
    • شما می خواهید داده ها یا فایل های پیچیده ای را به برنامه های کاربردی دیگر ارائه دهید.
    • می‌خواهید به کاربران اجازه دهید داده‌های پیچیده را از برنامه شما در برنامه‌های دیگر کپی کنند.
    • می‌خواهید با استفاده از چارچوب جستجو، پیشنهادهای جستجوی سفارشی ارائه دهید.
    • شما می خواهید داده های برنامه خود را در معرض ویجت ها قرار دهید.
    • می‌خواهید کلاس‌های AbstractThreadedSyncAdapter ، CursorAdapter یا CursorLoader را پیاده‌سازی کنید.

    اگر استفاده کاملاً در برنامه شما باشد و به هیچ یک از ویژگی‌های ذکر شده قبلی نیاز ندارید، برای استفاده از پایگاه‌های داده یا سایر انواع ذخیره‌سازی دائمی نیازی به ارائه‌دهنده ندارید . درعوض، می‌توانید از یکی از سیستم‌های ذخیره‌سازی شرح داده شده در نمای کلی ذخیره‌سازی داده و فایل استفاده کنید.

  • اگر قبلاً این کار را انجام نداده‌اید، اصول ارائه‌دهنده محتوا را بخوانید تا درباره ارائه‌دهندگان و نحوه کار آنها بیشتر بدانید.

در مرحله بعد، این مراحل را برای ایجاد ارائه دهنده خود دنبال کنید:

  1. فضای ذخیره سازی خام برای داده های خود را طراحی کنید. یک ارائه دهنده محتوا داده ها را به دو صورت ارائه می دهد:
    داده های فایل
    داده‌هایی که معمولاً در فایل‌ها مانند عکس، صدا یا ویدیو می‌روند. فایل ها را در فضای خصوصی برنامه خود ذخیره کنید. در پاسخ به درخواست یک فایل از یک برنامه دیگر، ارائه دهنده شما می تواند یک دسته به فایل ارائه دهد.
    داده های "ساختار یافته".
    داده هایی که معمولاً به یک پایگاه داده، آرایه یا ساختار مشابه می روند. داده ها را در فرمی ذخیره کنید که با جداول سطر و ستون سازگار باشد. یک ردیف نشان دهنده یک موجودیت است، مانند یک شخص یا یک کالا در موجودی. یک ستون برخی از داده ها را برای موجودیت، نام شخص یا قیمت یک کالا نشان می دهد. یک روش رایج برای ذخیره این نوع داده ها در پایگاه داده SQLite است، اما می توانید از هر نوع ذخیره سازی دائمی استفاده کنید. برای کسب اطلاعات بیشتر در مورد انواع ذخیره سازی موجود در سیستم اندروید، به بخش طراحی ذخیره سازی داده ها مراجعه کنید.
  2. یک پیاده سازی مشخص از کلاس ContentProvider و روش های مورد نیاز آن را تعریف کنید. این کلاس رابط بین داده های شما و بقیه سیستم اندروید است. برای اطلاعات بیشتر در مورد این کلاس، بخش Implement the ContentProvider class را ببینید.
  3. رشته مرجع، URI محتوا و نام ستون ارائه دهنده را تعریف کنید. اگر می‌خواهید برنامه ارائه‌دهنده مقاصد را مدیریت کند، اقدامات هدف، داده‌های اضافی و پرچم‌ها را نیز تعریف کنید. همچنین مجوزهایی را که برای برنامه‌هایی که می‌خواهند به داده‌های شما دسترسی داشته باشند، نیاز دارید، تعریف کنید. تعریف همه این مقادیر را به عنوان ثابت در یک کلاس قرارداد جداگانه در نظر بگیرید. بعداً می توانید این کلاس را در معرض دید توسعه دهندگان دیگر قرار دهید. برای کسب اطلاعات بیشتر در مورد URI های محتوا، به بخش طراحی محتوای URI مراجعه کنید. برای اطلاعات بیشتر در مورد مقاصد، به بخش Intent و دسترسی به داده مراجعه کنید.
  4. قطعات اختیاری دیگر، مانند داده‌های نمونه یا اجرای AbstractThreadedSyncAdapter را اضافه کنید که می‌تواند داده‌ها را بین ارائه‌دهنده و داده‌های مبتنی بر ابر همگام‌سازی کند.

طراحی ذخیره سازی داده ها

ارائه‌دهنده محتوا رابطی برای داده‌های ذخیره‌شده در قالب ساختاریافته است. قبل از ایجاد رابط، تصمیم بگیرید که چگونه داده ها را ذخیره کنید. می توانید داده ها را به هر شکلی که دوست دارید ذخیره کنید و سپس رابط را طوری طراحی کنید که در صورت لزوم داده ها را بخواند و بنویسد.

اینها برخی از فناوری های ذخیره سازی داده موجود در اندروید هستند:

  • اگر با داده‌های ساخت‌یافته کار می‌کنید، یک پایگاه داده رابطه‌ای مانند SQLite یا یک ذخیره‌گاه داده غیرمرتبط کلیدی مانند LevelDB را در نظر بگیرید. اگر با داده‌های بدون ساختار مانند رسانه‌های صوتی، تصویری یا ویدیویی کار می‌کنید، در نظر داشته باشید که داده‌ها را به صورت فایل ذخیره کنید. می‌توانید چندین نوع مختلف فضای ذخیره‌سازی را با هم ترکیب کنید و در صورت لزوم آنها را با استفاده از یک ارائه‌دهنده محتوا در معرض دید قرار دهید.
  • سیستم Android می‌تواند با کتابخانه ماندگاری اتاق تعامل داشته باشد، که دسترسی به API پایگاه داده SQLite را فراهم می‌کند که ارائه‌دهندگان خود اندروید برای ذخیره داده‌های جدول‌گرا از آن استفاده می‌کنند. برای ایجاد پایگاه داده با استفاده از این کتابخانه، یک زیر کلاس از RoomDatabase را نمونه سازی کنید، همانطور که در ذخیره داده ها در پایگاه داده محلی با استفاده از Room توضیح داده شده است.

    برای پیاده سازی مخزن خود نیازی به استفاده از پایگاه داده ندارید. یک ارائه دهنده به صورت خارجی به عنوان مجموعه ای از جداول، شبیه به یک پایگاه داده رابطه ای ظاهر می شود، اما این یک الزام برای پیاده سازی داخلی ارائه دهنده نیست.

  • برای ذخیره داده های فایل، اندروید دارای انواع API های فایل گرا است. برای کسب اطلاعات بیشتر در مورد ذخیره سازی فایل، نمای کلی داده و ذخیره فایل را بخوانید. اگر ارائه‌دهنده‌ای طراحی می‌کنید که داده‌های مرتبط با رسانه مانند موسیقی یا ویدیوها را ارائه می‌دهد، می‌توانید ارائه‌دهنده‌ای داشته باشید که داده‌های جدول و فایل‌ها را ترکیب می‌کند.
  • در موارد نادر، ممکن است از پیاده سازی بیش از یک ارائه دهنده محتوا برای یک برنامه منفرد بهره مند شوید. برای مثال، ممکن است بخواهید با استفاده از یک ارائه دهنده محتوا، برخی از داده ها را با ویجت به اشتراک بگذارید و مجموعه متفاوتی از داده ها را برای اشتراک گذاری با برنامه های دیگر در معرض دید قرار دهید.
  • برای کار با داده های مبتنی بر شبکه، از کلاس های java.net و android.net استفاده کنید. همچنین می‌توانید داده‌های مبتنی بر شبکه را با یک ذخیره‌سازی داده‌های محلی مانند پایگاه داده همگام‌سازی کنید و سپس داده‌ها را به صورت جداول یا فایل ارائه کنید.

توجه : اگر تغییری در مخزن خود ایجاد کردید که با نسخه قبلی سازگار نیست، باید مخزن را با شماره نسخه جدید علامت گذاری کنید. همچنین باید شماره نسخه برنامه خود را که ارائه دهنده محتوای جدید را پیاده سازی می کند، افزایش دهید. ایجاد این تغییر باعث می‌شود که هنگام تلاش برای نصب مجدد برنامه‌ای که دارای ارائه‌دهنده محتوای ناسازگار است، کاهش رتبه سیستم باعث از کار افتادن سیستم شود.

ملاحظات طراحی داده ها

در اینجا چند نکته برای طراحی ساختار داده ارائه دهنده شما وجود دارد:

  • داده های جدول باید همیشه دارای یک ستون "کلید اصلی" باشد که ارائه دهنده آن را به عنوان یک مقدار عددی منحصر به فرد برای هر ردیف نگه می دارد. می‌توانید از این مقدار برای پیوند دادن ردیف به ردیف‌های مرتبط در جداول دیگر (با استفاده از آن به عنوان «کلید خارجی» استفاده کنید. اگرچه می‌توانید از هر نامی برای این ستون استفاده کنید، استفاده از BaseColumns._ID بهترین انتخاب است، زیرا پیوند دادن نتایج یک درخواست ارائه‌دهنده به ListView مستلزم آن است که یکی از ستون‌های بازیابی شده نام _ID را داشته باشد.
  • اگر می‌خواهید تصاویر بیت مپ یا سایر قطعات بسیار بزرگ داده‌های فایل‌گرا ارائه کنید، داده‌ها را در یک فایل ذخیره کنید و سپس به‌جای ذخیره مستقیم در جدول، آن‌ها را به‌طور غیرمستقیم ارائه کنید. اگر این کار را انجام دهید، باید به کاربران ارائه دهنده خود بگویید که برای دسترسی به داده ها باید از روش فایل ContentResolver استفاده کنند.
  • از نوع داده شی بزرگ باینری (BLOB) برای ذخیره داده هایی که اندازه آنها متفاوت است یا ساختار متفاوتی دارند استفاده کنید. به عنوان مثال، می توانید از یک ستون BLOB برای ذخیره یک بافر پروتکل یا ساختار JSON استفاده کنید.

    همچنین می توانید از BLOB برای پیاده سازی جدول مستقل از طرح واره استفاده کنید. در این نوع جدول، شما یک ستون کلید اولیه، یک ستون نوع MIME و یک یا چند ستون عمومی را به عنوان BLOB تعریف می کنید. معنی داده ها در ستون های BLOB با مقدار ستون نوع MIME نشان داده می شود. این به شما امکان می دهد انواع ردیف های مختلف را در یک جدول ذخیره کنید. جدول "داده" ارائه دهنده مخاطبین ContactsContract.Data نمونه ای از جدول مستقل از طرح واره است.

طراحی URI محتوا

URI محتوا یک URI است که داده ها را در یک ارائه دهنده شناسایی می کند. URIهای محتوا شامل نام نمادین کل ارائه‌دهنده ( اقتدار آن) و نامی است که به جدول یا فایل (یک مسیر ) اشاره می‌کند. قسمت ID اختیاری به یک ردیف در جدول اشاره می کند. هر روش دسترسی به داده 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 دست آید. com.example.<appname>.provider/table2 . مسیرها به یک بخش محدود نمی شوند و لازم نیست جدولی برای هر سطح از مسیر وجود داشته باشد.

شناسه های URI محتوا را مدیریت کنید

طبق قرارداد، ارائه دهندگان با پذیرش یک URI محتوا با یک مقدار شناسه برای ردیف در انتهای URI، دسترسی به یک ردیف در جدول را ارائه می دهند. همچنین طبق قرارداد، ارائه دهندگان مقدار ID را با ستون _ID جدول مطابقت می دهند و دسترسی درخواستی را در مقابل ردیفی که مطابقت دارد انجام می دهند.

این قرارداد یک الگوی طراحی مشترک را برای برنامه هایی که به یک ارائه دهنده دسترسی دارند تسهیل می کند. برنامه یک پرس و جو علیه ارائه دهنده انجام می دهد و Cursor با استفاده از CursorAdapter در ListView نمایش می دهد. تعریف CursorAdapter مستلزم این است که یکی از ستون های Cursor _ID باشد

سپس کاربر یکی از ردیف های نمایش داده شده را از رابط کاربری انتخاب می کند تا داده ها را ببیند یا تغییر دهد. برنامه ردیف مربوطه را از Cursor که از ListView پشتیبانی می کند دریافت می کند، مقدار _ID را برای این ردیف دریافت می کند، آن را به URI محتوا اضافه می کند و درخواست دسترسی را برای ارائه دهنده ارسال می کند. سپس ارائه‌دهنده می‌تواند پرس و جو یا اصلاح را بر اساس ردیفی که کاربر انتخاب کرده انجام دهد.

الگوهای URI محتوا

برای کمک به شما در انتخاب اقدامی که برای یک URI محتوای ورودی انجام دهید، API ارائه‌دهنده شامل کلاس راحتی UriMatcher است که الگوهای URI محتوا را به مقادیر صحیح نگاشت می‌کند. می‌توانید از مقادیر صحیح در دستور switch استفاده کنید که عملکرد مورد نظر را برای URI محتوا یا URIهایی که با یک الگوی خاص مطابقت دارند انتخاب می‌کند.

یک الگوی URI محتوا با استفاده از نویسه‌های wildcard با 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 را نشان می دهد. این کد با استفاده از content://<authority>/<path> برای جداول و content://<authority>/<path>/<id> URIهای کل جدول را متفاوت از URIهای یک ردیف واحد مدیریت می کند. برای ردیف های تک

متد addURI() یک اعتبار و مسیر را به یک مقدار صحیح ترسیم می کند. متد match() مقدار صحیح را برای یک URI برمی گرداند. یک دستور switch بین پرس و جو از کل جدول و پرس و جو برای یک رکورد واحد انتخاب می کند.

کاتلین

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
    }
}

جاوا

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 محتوا را برگردانید. این روش با جزئیات بیشتر در قسمت Implement content provider MIME types توضیح داده شده است.
onCreate()
ارائه دهنده خود را راه اندازی کنید. سیستم اندروید بلافاصله پس از ایجاد ارائه دهنده شما، این روش را فراخوانی می کند. ارائه‌دهنده شما ایجاد نمی‌شود مگر اینکه یک شی ContentResolver سعی کند به آن دسترسی پیدا کند.

این متدها امضای مشابهی با متدهای ContentResolver دارند.

اجرای شما از این روش ها باید موارد زیر را در نظر بگیرد:

  • همه این متدها به جز onCreate() می‌توان با چندین رشته در یک زمان فراخوانی کرد، بنابراین باید از نظر thread ایمن باشند. برای کسب اطلاعات بیشتر در مورد چندین رشته، به نمای کلی فرآیندها و رشته ها مراجعه کنید.
  • از انجام عملیات طولانی در 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 ، را با استفاده از withAppendedId() به URI محتوای جدول اضافه کنید.

متد delete() را پیاده سازی کنید

متد delete() نیازی به حذف ردیف ها از فضای ذخیره سازی داده شما ندارد. اگر از یک آداپتور همگام سازی با ارائه دهنده خود استفاده می کنید، به جای حذف کامل ردیف، یک ردیف حذف شده را با پرچم "حذف" علامت گذاری کنید. آداپتور همگام‌سازی می‌تواند ردیف‌های حذف‌شده را بررسی کند و قبل از حذف آنها از ارائه‌دهنده، آنها را از سرور حذف کند.

متد update() را پیاده سازی کنید

متد update() همان آرگومان ContentValues ​​استفاده شده توسط insert() و همان آرگومان های selection و selectionArgs را می گیرد که توسط delete() و ContentProvider.query() استفاده می شود. این ممکن است به شما امکان استفاده مجدد از کد بین این روش ها را بدهد.

متد onCreate() را پیاده سازی کنید

سیستم اندروید زمانی که ارائه دهنده را راه اندازی می کند، onCreate() فراخوانی می کند. در این روش فقط وظایف اولیه سازی سریع را انجام دهید و ایجاد پایگاه داده و بارگذاری داده را تا زمانی که ارائه دهنده واقعاً درخواستی برای داده دریافت کند به تعویق بیاندازید. اگر کارهای طولانی را در onCreate() انجام دهید، سرعت راه اندازی ارائه دهنده خود را کاهش می دهید. به نوبه خود، پاسخ ارائه دهنده به سایر برنامه ها را کند می کند.

دو قطعه زیر تعامل بین ContentProvider.onCreate() و Room.databaseBuilder() را نشان می دهد. اولین قطعه پیاده‌سازی ContentProvider.onCreate() را نشان می‌دهد که در آن شی پایگاه داده ساخته می‌شود و دسته‌هایی برای اشیاء دسترسی به داده ایجاد می‌شوند:

کاتلین

// 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.
    }
}

جاوا

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 را برای آن داده برمی‌گرداند. لیست کامل این انواع استاندارد در وب سایت IANA MIME Media Types موجود است.

برای URIهای محتوا که به یک سطر یا ردیف از داده‌های جدول اشاره می‌کنند، getType() یک نوع MIME را در قالب MIME مخصوص فروشنده اندروید برمی‌گرداند:

  • نوع قطعه: vnd
  • بخش فرعی:
    • اگر الگوی URI برای یک ردیف است: android.cursor. item /
    • اگر الگوی URI برای بیش از یک ردیف است: android.cursor. dir /
  • بخش خاص ارائه دهنده: vnd.<name> . <type>

    شما <name> و <type> را ارائه می دهید. مقدار <name> در سطح جهانی منحصر به فرد است و مقدار <type> برای الگوی URI مربوطه منحصر به فرد است. یک انتخاب خوب برای <name> نام شرکت شما یا بخشی از نام بسته اندروید برنامه شما است. یک انتخاب خوب برای <type> رشته ای است که جدول مرتبط با URI را مشخص می کند.

برای مثال، اگر مرجع ارائه‌دهنده com.example.app.provider باشد و جدولی به نام table1 را نمایش دهد، نوع MIME برای چندین ردیف در table1 است:

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

برای یک ردیف از table1 ، نوع MIME این است:

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

انواع MIME برای فایل ها

اگر ارائه دهنده شما فایل هایی را ارائه می دهد، getStreamTypes() را پیاده سازی کنید. این روش یک آرایه String از انواع MIME را برای فایل‌هایی که ارائه‌دهنده شما می‌تواند برای یک URI محتوای معین برگرداند، برمی‌گرداند. انواع MIME را که ارائه می کنید با آرگومان فیلتر نوع MIME فیلتر کنید، به طوری که فقط انواع MIME را که کلاینت می خواهد مدیریت کند، برگردانید.

به عنوان مثال، ارائه دهنده ای را در نظر بگیرید که تصاویر عکس را به صورت فایل با فرمت JPG، PNG و GIF ارائه می دهد. اگر برنامه ای ContentResolver.getStreamTypes() را با رشته فیلتر image/* ، برای چیزی که یک "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 استفاده می‌کنند. از آنجایی که یک کلاس است، می تواند حاوی اسناد جاوادوک باشد. محیط‌های توسعه یکپارچه مانند Android Studio می‌توانند نام‌های ثابت را از کلاس قرارداد تکمیل کنند و Javadoc را برای ثابت‌ها نمایش دهند.

توسعه‌دهندگان نمی‌توانند از برنامه شما به فایل کلاس قرارداد دسترسی داشته باشند، اما می‌توانند آن را به صورت ایستا از فایل JAR که شما ارائه می‌دهید در برنامه خود کامپایل کنند.

کلاس ContactsContract و کلاس های تودرتوی آن نمونه هایی از کلاس های قرارداد هستند.

مجوزهای ارائه دهنده محتوا را پیاده سازی کنید

مجوزها و دسترسی به تمام جنبه های سیستم Android در نکات امنیتی به تفصیل شرح داده شده است. نمای کلی ذخیره‌سازی داده و فایل همچنین امنیت و مجوزهای موجود برای انواع مختلف ذخیره‌سازی را توضیح می‌دهد. به طور خلاصه نکات مهم به شرح زیر است:

  • به طور پیش فرض، فایل های داده ذخیره شده در حافظه داخلی دستگاه برای برنامه و ارائه دهنده شما خصوصی هستند.
  • پایگاه داده های SQLiteDatabase که ایجاد می کنید برای برنامه و ارائه دهنده شما خصوصی هستند.
  • به‌طور پیش‌فرض، فایل‌های داده‌ای که در حافظه خارجی ذخیره می‌کنید عمومی و قابل خواندن در جهان هستند. نمی‌توانید از یک ارائه‌دهنده محتوا برای محدود کردن دسترسی به فایل‌ها در حافظه خارجی استفاده کنید، زیرا سایر برنامه‌ها می‌توانند از تماس‌های API دیگر برای خواندن و نوشتن آنها استفاده کنند.
  • این روش برای باز کردن یا ایجاد فایل‌ها یا پایگاه‌های داده SQLite در حافظه داخلی دستگاه شما می‌تواند به طور بالقوه امکان دسترسی خواندن و نوشتن را به همه برنامه‌های دیگر بدهد. اگر از یک فایل یا پایگاه داده داخلی به عنوان مخزن ارائه دهنده خود استفاده می کنید و به آن دسترسی «قابل خواندن در جهان» یا «قابل نوشتن در جهان» می دهید، مجوزهایی که برای ارائه دهنده خود در مانیفست آن تنظیم کرده اید از داده های شما محافظت نمی کند. دسترسی پیش فرض برای فایل ها و پایگاه های داده در حافظه داخلی "خصوصی" است. این را برای مخزن ارائه دهنده خود تغییر ندهید.

اگر می‌خواهید از مجوزهای ارائه‌دهنده محتوا برای کنترل دسترسی به داده‌های خود استفاده کنید، سپس داده‌های خود را در فایل‌های داخلی، پایگاه‌های داده SQLite یا فضای ابری مانند سرور راه دور ذخیره کنید و فایل‌ها و پایگاه‌های داده را برای برنامه خود خصوصی نگه دارید.

پیاده سازی مجوزها

به طور پیش‌فرض، همه برنامه‌ها می‌توانند از ارائه‌دهنده شما بخوانند یا برای آن بنویسند، حتی اگر داده‌های اصلی خصوصی باشد، زیرا به‌طور پیش‌فرض ارائه‌دهنده شما مجوزها را تنظیم نکرده است. برای تغییر این، مجوزها را برای ارائه دهنده خود در فایل مانیفست خود با استفاده از ویژگی ها یا عناصر فرزند عنصر <provider> تنظیم کنید. می توانید مجوزهایی را تنظیم کنید که برای کل ارائه دهنده، جداول خاص، رکوردهای خاص یا هر سه مورد اعمال می شود.

شما مجوزهایی را برای ارائه دهنده خود با یک یا چند عنصر <permission> در فایل مانیفست خود تعریف می کنید. برای منحصربه‌فرد کردن مجوز برای ارائه‌دهنده خود، از محدوده به سبک جاوا برای ویژگی 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> خود اضافه کنید. هر زمان که پشتیبانی از یک URI محتوای مرتبط با مجوز موقت ارائه‌دهنده خود را حذف می‌کنید، با Context.revokeUriPermission()

مقدار ویژگی تعیین می‌کند که چه مقدار از ارائه‌دهنده شما قابل دسترسی است. اگر مشخصه روی "true" تنظیم شود، سیستم به کل ارائه‌دهنده شما مجوز موقت می‌دهد و هر مجوز دیگری را که توسط مجوزهای سطح ارائه‌دهنده یا سطح مسیر مورد نیاز است لغو می‌کند.

اگر این پرچم روی "false" تنظیم شده است، عناصر فرزند <grant-uri-permission> را به عنصر <provider> خود اضافه کنید. هر عنصر فرزند، URI محتوا یا URIهایی را که دسترسی موقت به آنها داده شده است، مشخص می کند.

برای واگذاری دسترسی موقت به یک برنامه، یک هدف باید حاوی پرچم FLAG_GRANT_READ_URI_PERMISSION ، پرچم FLAG_GRANT_WRITE_URI_PERMISSION یا هر دو باشد. اینها با متد setFlags() تنظیم می شوند.

اگر ویژگی android:grantUriPermissions وجود نداشته باشد، فرض می شود که "false" است.

عنصر <provider>

مانند مؤلفه‌های Activity و Service ، یک زیر کلاس از ContentProvider در فایل manifest برای برنامه آن با استفاده از عنصر <provider> تعریف می‌شود. سیستم اندروید اطلاعات زیر را از عنصر دریافت می کند:

Authority ( android:authorities )
نام‌های نمادینی که کل ارائه‌دهنده را در سیستم شناسایی می‌کنند. این ویژگی با جزئیات بیشتر در بخش طراحی محتوای URI توضیح داده شده است.
نام کلاس ارائه دهنده ( android:name )
کلاسی که ContentProvider را پیاده سازی می کند. این کلاس با جزئیات بیشتر در قسمت Implement the ContentProvider class توضیح داده شده است.
مجوزها
ویژگی هایی که مجوزهایی را که سایر برنامه ها برای دسترسی به داده های ارائه دهنده باید داشته باشند را مشخص می کنند:

مجوزها و ویژگی‌های مربوط به آن‌ها با جزئیات بیشتر در بخش مجوزهای ارائه‌دهنده محتوا پیاده‌سازی شده‌اند.

ویژگی های راه اندازی و کنترل
این ویژگی‌ها تعیین می‌کنند که سیستم Android چگونه و چه زمانی ارائه‌دهنده را راه‌اندازی می‌کند، ویژگی‌های فرآیند ارائه‌دهنده، و سایر تنظیمات زمان اجرا:
  • android:enabled : پرچمی که به سیستم اجازه می دهد ارائه دهنده را راه اندازی کند
  • android:exported : پرچم گذاری کنید تا برنامه های دیگر از این ارائه دهنده استفاده کنند
  • android:initOrder : ترتیبی که این ارائه دهنده راه اندازی می شود، نسبت به سایر ارائه دهندگان در همان فرآیند
  • android:multiProcess : پرچمی که به سیستم اجازه می دهد ارائه دهنده را در همان فرآیندی که مشتری تماس می گیرد راه اندازی کند
  • android:process : نام فرآیندی که ارائه دهنده در آن اجرا می شود
  • android:syncable : پرچمی که نشان می‌دهد داده‌های ارائه‌دهنده باید با داده‌های روی سرور همگام‌سازی شوند.

این ویژگی ها به طور کامل در راهنمای عنصر <provider> مستند شده اند.

ویژگی های اطلاعاتی
یک نماد و برچسب اختیاری برای ارائه دهنده:
  • android:icon : یک منبع قابل ترسیم حاوی یک نماد برای ارائه دهنده. نماد در کنار برچسب ارائه دهنده در لیست برنامه ها در تنظیمات > برنامه ها > همه ظاهر می شود.
  • android:label : یک برچسب اطلاعاتی که ارائه دهنده، داده های آن یا هر دو را توصیف می کند. این برچسب در لیست برنامه‌ها در تنظیمات > برنامه‌ها > همه ظاهر می‌شود.

این ویژگی ها به طور کامل در راهنمای عنصر <provider> مستند شده اند.

توجه: اگر Android 11 یا بالاتر را هدف قرار می‌دهید، برای نیازهای بیشتر پیکربندی ، اسناد نمایان بودن بسته را بررسی کنید.

مقاصد و دسترسی به داده ها

برنامه ها می توانند به طور غیرمستقیم با یک Intent به یک ارائه دهنده محتوا دسترسی داشته باشند. برنامه هیچ یک از روش های ContentResolver یا ContentProvider را فراخوانی نمی کند. در عوض، یک intent ارسال می کند که یک فعالیت را شروع می کند، که اغلب بخشی از برنامه خود ارائه دهنده است. فعالیت مقصد وظیفه بازیابی و نمایش داده ها در رابط کاربری خود را بر عهده دارد.

بسته به عمل در intent، فعالیت مقصد همچنین می تواند از کاربر بخواهد که تغییراتی در داده های ارائه دهنده انجام دهد. یک هدف همچنین ممکن است حاوی داده‌های «اضافی» باشد که فعالیت مقصد در رابط کاربری نمایش می‌دهد. سپس کاربر این امکان را دارد که این داده ها را قبل از استفاده از آن برای تغییر داده ها در ارائه دهنده تغییر دهد.

می توانید از دسترسی قصد برای کمک به یکپارچگی داده ها استفاده کنید. ارائه‌دهنده شما ممکن است به درج، به‌روزرسانی و حذف داده‌ها بر اساس منطق تجاری کاملاً تعریف شده وابسته باشد. در این صورت، اجازه دادن به برنامه های کاربردی دیگر برای تغییر مستقیم داده های شما می تواند منجر به داده های نامعتبر شود.

اگر می خواهید توسعه دهندگان از دسترسی قصد استفاده کنند، حتما آن را به طور کامل مستند کنید. توضیح دهید که چرا دسترسی هدف با استفاده از رابط کاربری برنامه شما بهتر از تلاش برای تغییر داده ها با کد آنها است.

مدیریت یک هدف ورودی که می‌خواهد داده‌های ارائه‌دهنده شما را تغییر دهد با مدیریت سایر مقاصد تفاوتی ندارد. با خواندن Intent و Intent Filters می‌توانید درباره استفاده از intent اطلاعات بیشتری کسب کنید.

برای اطلاعات بیشتر مرتبط، به نمای کلی ارائه دهنده تقویم مراجعه کنید.