יצירת ספק תוכן

ספק תוכן מנהל את הגישה למאגר נתונים מרכזי. אתה מיישם ספק כמחלקה אחת או יותר באפליקציה ל-Android, יחד עם רכיבים ב- את קובץ המניפסט. אחת מהכיתות שלך מטמיעים תת-מחלקה של ContentProvider, שהוא הממשק בין הספק שלכם יישומים אחרים.

למרות שספקי תוכן אמורים להנגיש את הנתונים למשתמשים אחרים אפשר ליצור באפליקציה פעילויות שמאפשרות למשתמש שאילתה ולשנות את הנתונים שמנוהלים על ידי הספק.

הדף הזה מכיל את התהליך הבסיסי ליצירת ספק תוכן ורשימה של ממשקי API לשימוש.

לפני שמתחילים לבנות

לפני שמתחילים להקים ספק, כדאי להביא בחשבון את הנקודות הבאות:

  • מחליטים אם אתם צריכים ספק תוכן. אתם צריכים ליצור תוכן אם רוצים לספק אחת או יותר מהתכונות הבאות:
    • אתם רוצים להציע לאפליקציות אחרות נתונים מורכבים או קבצים מורכבים.
    • אתם רוצים לאפשר למשתמשים להעתיק נתונים מורכבים מהאפליקציה שלכם לאפליקציות אחרות.
    • אתם רוצים לספק הצעות לחיפוש בהתאמה אישית באמצעות מסגרת החיפוש.
    • ברצונך לחשוף את נתוני האפליקציה לווידג'טים.
    • אתם רוצים להטמיע את AbstractThreadedSyncAdapter, CursorAdapter, או CursorLoader הסוגים.

    לא צריך ספק כדי להשתמש במסדי נתונים או בסוגים אחרים של אחסון מתמיד, אם השימוש הוא אך ורק באפליקציה שלכם ואתם לא צריכים אף אחת מהתכונות שצוינו קודם. במקום זאת, אפשר משתמשים באחת ממערכות האחסון שמתוארות במאמר סקירה כללית על נתונים ואחסון קבצים

  • אם עדיין לא עשית זאת, עליך לקרוא את מידע בסיסי על ספקי תוכן כדי לקבל מידע נוסף על ספקים ועל אופן הפעולה שלהם.

לאחר מכן, פועלים לפי השלבים הבאים כדי ליצור את הספק:

  1. עיצוב האחסון הגולמי של הנתונים. ספק תוכן מציע נתונים בשתי דרכים:
    נתוני הקובץ
    נתונים שבדרך כלל נכנסים לקבצים, כמו תמונות, אודיו או סרטונים. אחסון הקבצים באזור הפרטי של האפליקציה המרחב המשותף. בתגובה לבקשה לקובץ מאפליקציה אחרת, יכול להציע כינוי לקובץ.
    "מובנה" רוחב פס
    נתונים שבדרך כלל עוברים למסד נתונים, מערך או מבנה דומה. מאחסנים את הנתונים בצורה שתואמת לטבלאות של שורות ועמודות. שורה מייצג ישות, כמו אדם או פריט במלאי. עמודה מייצגת נתונים מסוימים של הישות, כמו שם האדם או מחיר הפריט. דרך מקובלת נתונים מהסוג הזה נמצאים במסד נתונים של SQLite, אבל אפשר להשתמש בכל סוג של אחסון מתמיד. למידע נוסף על סוגי האחסון הזמינים במערכת Android, אפשר לעיין ב עיצוב של אחסון נתונים.
  2. להגדיר יישום קונקרטי של המחלקה ContentProvider ו לשיטות הנדרשות. הסיווג הזה הוא הממשק בין הנתונים שלכם לבין שאר מערכת Android. למידע נוסף על הכיתה הזו, אפשר להיכנס אל הטמיעו את הקטע ContentProvider class.
  3. מגדירים את מחרוזת ההרשאה, מזהי ה-URI של התוכן ושמות העמודות של הספק. אם רוצים האפליקציה של הספק לטיפול בכוונות, גם להגדיר פעולות של כוונה, נתונים נוספים, ודגלים. הגדר גם את ההרשאות הנדרשות לאפליקציות שרוצות כדי לגשת לנתונים. הגדירו את כל הערכים האלה כקבועים סוג חוזה נפרד. לאחר מכן, תהיה לך אפשרות לחשוף את הכיתה הזו למפתחים אחרים. לקבלת מידע נוסף על מזהי URI של תוכן, עיצוב מזהי URI של תוכן. מידע נוסף על כוונות זמין במאמר מנגנוני Intent וגישה לנתונים.
  4. מוסיפים עוד חלקים אופציונליים, כמו נתונים לדוגמה או הטמעה. של AbstractThreadedSyncAdapter שיכול לסנכרן נתונים של הספק ושל נתונים מבוססי-ענן.

עיצוב מאגר נתונים

ספק תוכן הוא הממשק לנתונים שנשמרו בפורמט מובנה. לפני שיוצרים את הממשק, להחליט איך לאחסן את הנתונים. אפשר לאחסן את הנתונים בכל צורה שרוצים ולאחר מכן לעצב את הממשק כך שיקרא ותכתוב את הנתונים לפי הצורך.

אלו חלק מהטכנולוגיות של אחסון נתונים שזמינות ב-Android:

  • אם אתם עובדים עם נתונים מובְנים, כדאי לשקול מסד נתונים רלציוני כמו כמו SQLite או מאגר נתונים עם מפתח/ערך לא יחסי, LevelDB. אם את/ה עובד/ת עם נתונים לא מובנים כמו מדיה אודיו, תמונה או וידאו, אז כדאי לשמור את נתונים כקבצים. אפשר לשלב ולהתאים כמה סוגי אחסון שונים ולחשוף אותם באמצעות ספק תוכן אחד, במידת הצורך.
  • מערכת Android יכולה לקיים אינטראקציה עם ספריית הקבועים של החדרים, מספקת גישה לממשק ה-API של מסד הנתונים SQLite שהספקים של Android שבהם משתמשים כדי לאחסן נתונים ממוקדי טבלה. ליצור מסד נתונים באמצעות יצירת מחלקה משנית של RoomDatabase, כפי שמתואר ב שמירת נתונים במסד נתונים מקומי באמצעות Room

    לא צריך להשתמש במסד נתונים כדי להטמיע את המאגר. ספק מופיעה כלפי חוץ כקבוצה של טבלאות, בדומה למסד נתונים רלציוני, לא דרישה להטמעה הפנימית של הספק.

  • ל-Android יש מגוון ממשקי API מוכווני קבצים לאחסון נתוני קבצים. למידע נוסף על אחסון קבצים, אפשר לקרוא את סקירה כללית על נתונים ואחסון קבצים אם אתם אם אתם מעצבים ספק שמציע נתונים שקשורים למדיה כמו מוזיקה או סרטונים, תוכלו יש ספק שמשלב נתוני טבלה וקבצים.
  • במקרים נדירים, ייתכן שכדאי לך להטמיע יותר מספק תוכן אחד באפליקציה אחת. לדוגמה, ייתכן שתרצו לשתף נתונים מסוימים עם ווידג'ט באמצעות ספק תוכן אחד, ולחשוף סט נתונים שונה לשיתוף עם אחרים תרגום מכונה.
  • כדי לעבוד עם נתונים מבוססי רשת, צריך להשתמש בכיתות ב-java.net וב- android.net. אפשר גם לסנכרן נתונים מבוססי רשת עם נתונים מקומיים יאחסן את הנתונים, כמו מסד נתונים, ויציע את הנתונים כטבלאות או קבצים.

הערה: אם אתם מבצעים שינוי במאגר שאינו תואם לאחור, עליך לסמן את המאגר בגרסה חדשה מספר. צריך גם להגדיל את מספר הגרסה של האפליקציה מטמיעה את ספק התוכן החדש. ביצוע שינוי זה מונע מהמערכת יורדת לאחור כך שלא תגרום למערכת לקרוס כשהיא מנסה להתקין מחדש אפליקציה שספק התוכן שלה לא תואם.

שיקולים בנוגע לעיצוב נתונים

הנה כמה טיפים לעיצוב מבנה הנתונים של הספק:

  • לנתוני הטבלה תמיד חייב להיות 'מפתח ראשי' ששמורה בספק, כערך מספרי ייחודי בכל שורה. אפשר להשתמש בערך הזה כדי לקשר את השורה לחשבונות קשורים שורות בטבלאות אחרות (באמצעותו כ'מפתח זר'). למרות שאפשר להשתמש בכל שם לעמודה הזו, השימוש ב-BaseColumns._ID הוא כי קישור התוצאות של שאילתת ספק בעמודה ListView נדרשת השם של אחת מהעמודות שאוחזרו _ID.
  • אם רוצים לספק תמונות של מפת סיביות (bitmap) או חלקים גדולים מאוד של נתונים מבוססי-קבצים, את הנתונים בקובץ ולאחר מכן מספקים אותם באופן עקיף במקום לאחסן אותם ישירות טבלה. אם עושים זאת, צריך ליידע את המשתמשים של הספק שהם צריכים להשתמש שיטה של ContentResolver לגישה לנתונים.
  • שימוש בסוג הנתונים בינארי של אובייקט גדול (BLOB) כדי לאחסן נתונים שגודלם משתנה או שיש להם מבנה שונה. לדוגמה, אפשר להשתמש בעמודת BLOB כדי לאחסן מאגר אחסון לפרוטוקולים או מבנה JSON.

    אפשר גם להשתמש ב-BLOB כדי להטמיע טבלה בלתי תלויה בסכימה. לחשבון לסוג הטבלה הזה, מגדירים עמודה של מפתח ראשי, עמודה של סוג MIME ועמודה אחת או עמודות כלליות יותר כמו BLOB. משמעות הנתונים בעמודות 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 של תוכן נכנס, ה-API של הספק כולל סיווג הנוחות 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()

ה-method ContentProvider.query() חייבת להחזיר אובייקט Cursor, או אם היא נכשל, זורקים Exception. אם משתמשים במסד נתונים של SQLite בתור הנתונים באחסון, אפשר להחזיר את Cursor שהוחזר על ידי אחד query() methods של המחלקה SQLiteDatabase.

אם השאילתה לא תואמת לאף שורה, מוחזר Cursor מכונה שהשיטה getCount() שלה מחזירה 0. הפונקציה מחזירה את הערך null רק אם אירעה שגיאה פנימית במהלך ביצוע השאילתה.

אם אתם לא משתמשים במסד נתונים של SQLite כאחסון הנתונים שלכם, השתמשו באחת מתת-מחלקות הבטון. מתוך Cursor. לדוגמה, הכיתה MatrixCursor מטמיעה סמן שבו כל שורה היא מערך של מופעי Object. בכיתה הזו, יש להשתמש ב-addRow() כדי להוסיף שורה חדשה.

מערכת Android צריכה לתקשר עם Exception את גבולות התהליכים. מערכת Android יכולה לעשות זאת במקרים החריגים השימושיים הבאים בטיפול בשגיאות שאילתה:

הטמעת השיטה insert()

השיטה insert() מוסיפה שורה חדשה לטבלה המתאימה, תוך שימוש בערכים שבשדה ContentValues ארגומנט. אם שם עמודה לא נמצא בארגומנט ContentValues, אולי תרצו לספק ערך ברירת מחדל עבורו בקוד הספק או במסד הנתונים שלכם של Google.

השיטה הזו מחזירה את ה-URI של התוכן לשורה החדשה. כדי ליצור את זה, מצרפים את הפקודה המפתח הראשי של השורה, בדרך כלל הערך _ID, ל-URI של התוכן של הטבלה, באמצעות withAppendedId().

הטמעת השיטה delete()

השיטה 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 לטבלאות

ה-method getType() מחזירה String בפורמט MIME שמתאר את סוג הנתונים שהוחזרו על-ידי התוכן ארגומנט URI. הארגומנט Uri יכול להיות דפוס ולא URI ספציפי. במקרה זה, מוחזר סוג הנתונים המשויכים למזהי URI של התוכן שתואמים ל- דפוס.

בסוגים נפוצים של נתונים כמו טקסט, HTML או JPEG, הפונקציה getType() מחזירה את הערך הרגיל סוג ה-MIME לנתונים האלה. רשימה מלאה של הסוגים הרגילים זמינה סוגי מדיה של IANA MIME האתר.

במזהי 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 Studio יכולה לבצע השלמה אוטומטית של שמות קבועים מכיתת החוזה ולהציג Javadoc בשביל את הקבועים.

למפתחים אין אפשרות לגשת לקובץ הכיתה של כיתת החוזה מהאפליקציה שלך, אבל הם יכולים להדר אותו באופן סטטי לאפליקציה שלהם מקובץ JAR שאתם מספקים.

המחלקה ContactsContract והמחלקות המקוננות שלה הם דוגמאות ל סוגי חוזה.

הטמעת הרשאות של ספקי תוכן

ההרשאות והגישה לכל ההיבטים של מערכת Android מתוארות בפירוט טיפים בנושא אבטחה. אפשר גם לקרוא את הסקירה הכללית על נתונים ואחסון קבצים מתארת את האבטחה וההרשאות שחלות על סוגי האחסון השונים. בקצרה, הנקודות החשובות הן:

  • כברירת מחדל, קובצי נתונים השמורים באחסון הפנימי של המכשיר הם פרטיים ונגישים לך האפליקציה והספק.
  • SQLiteDatabase מסדי נתונים שאתה יוצר הם פרטיים לחשבון שלך האפליקציה והספק.
  • כברירת מחדל, קובצי נתונים שאתם שומרים באחסון חיצוני הם ציבוריים ו קריאים בכל העולם. לא ניתן להשתמש בספק תוכן כדי להגביל את הגישה לקבצים ב: האחסון החיצוני, כי אפליקציות אחרות יכולות להשתמש בקריאות אחרות ל-API כדי לקרוא ולכתוב אותן.
  • השיטה קוראת לפתיחה או ליצירה של קבצים או מסדי נתונים 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 של התוכן או מזהי URI שעבורם ניתנה גישה זמנית.

כדי להעניק גישה זמנית לאפליקציה, Intent חייב להכיל הדגל FLAG_GRANT_READ_URI_PERMISSION, הדגל FLAG_GRANT_WRITE_URI_PERMISSION, או שניהם. האלה מוגדרות באמצעות ה-method setFlags().

אם המאפיין android:grantUriPermissions לא נמצא, ההנחה היא "false".

הספק <provider> רכיב

כמו רכיבים של Activity ו-Service, תת-מחלקה של ContentProvider מוגדר בקובץ המניפסט של האפליקציה, באמצעות התו רכיב <provider>. מערכת Android מקבלת את המידע הבא מ- הרכיב:

רשות (android:authorities)
שמות סימבוליים שמזהים את כל הספק במערכת. הזה מתואר בפירוט עיצוב מזהי URI של תוכן.
שם רמת הספק (android:name)
המחלקה שבה ContentProvider מוטמע. הכיתה הזו שמתוארים בפירוט הטמיעו את הקטע 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, חשוב לתעד זאת באופן יסודי. הסבר למה עדיף להשתמש בגישת Intent באמצעות ממשק המשתמש של האפליקציה מאשר לנסות לשנות את הנתונים באמצעות הקוד שלהם.

אין הבדל בין טיפול ב-Intent נכנס שמעוניין לשנות את הנתונים של הספק. טיפול בכוונות אחרות. כדי לקבל מידע נוסף על השימוש בכוונות, אפשר לקרוא מסננים של Intent ו-Intent.

מידע נוסף בנושא זמין סקירה כללית של ספק היומן