קבלת תוכן עשיר

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

משתמשים אוהבים תמונות, סרטונים ותוכן אחר שמאפשר להם להביע את עצמם, אבל לא תמיד קל להוסיף את התוכן הזה לאפליקציות ולהעביר אותו. כדי לפשט את התהליך של קבלת תוכן עשיר באפליקציות, ב-Android 12 (רמת API‏ 31) הושק API מאוחד שמאפשר לאפליקציה לקבל תוכן מכל מקור: לוח, מקלדת או גרירה.

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

כדי לשמור על תאימות לאחור עם גרסאות קודמות של Android, ממשק ה-API הזה זמין גם ב-AndroidX, החל מCore 1.7 ומ-Appcompat 1.4. מומלץ להשתמש בו כשמטמיעים את הפונקציונליות הזו.

סקירה כללית

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

תמונה שבה מוצגות הפעולות השונות וה-API הרלוונטי להטמעה
איור 2. בעבר, באפליקציות הוטמע API שונה לכל מנגנון של ממשק משתמש להוספת תוכן.

ממשק ה-API‏ OnReceiveContentListener מאחד את נתיבי הקוד השונים האלה על ידי יצירת API יחיד להטמעה, כך שתוכלו להתמקד בלוגי הספציפי לאפליקציה שלכם ולתת לפלטפורמה לטפל בשאר:

תמונה שבה רואים את ה-API המאוחד הפשוט
איור 3. ה-API המאוחד מאפשר לכם להטמיע API יחיד שתומך בכל מנגנוני ממשק המשתמש.

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

הטמעה

ה-API הוא ממשק listener עם method אחת,‏ OnReceiveContentListener. כדי לתמוך בגרסאות ישנות יותר של פלטפורמת Android, מומלץ להשתמש בממשק התואם OnReceiveContentListener בספריית AndroidX Core.

כדי להשתמש ב-API, צריך להטמיע את ה-listener על ידי ציון סוגי התוכן שהאפליקציה יכולה לטפל בהם:

Kotlin

object MyReceiver : OnReceiveContentListener {
    val MIME_TYPES = arrayOf("image/*", "video/*")
    
    // ...
    
    override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat? {
        TODO("Not yet implemented")
    }
}

Java

public class MyReceiver implements OnReceiveContentListener {
     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};
     // ...
}

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

Kotlin

class MyReceiver : OnReceiveContentListener {
    override fun onReceiveContent(view: View, contentInfo: ContentInfoCompat): ContentInfoCompat {
        val split = contentInfo.partition { item: ClipData.Item -> item.uri != null }
        val uriContent = split.first
        val remaining = split.second
        if (uriContent != null) {
            // App-specific logic to handle the URI(s) in uriContent.
        }
        // Return anything that your app didn't handle. This preserves the
        // default platform behavior for text and anything else that you aren't
        // implementing custom handling for.
        return remaining
    }

    companion object {
        val MIME_TYPES = arrayOf("image/*", "video/*")
    }
}

Java

 public class MyReceiver implements OnReceiveContentListener {
     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};

     @Override
     public ContentInfoCompat onReceiveContent(View view, ContentInfoCompat contentInfo) {
         Pair<ContentInfoCompat, ContentInfoCompat> split = contentInfo.partition(
                 item -> item.getUri() != null);
         ContentInfo uriContent = split.first;
         ContentInfo remaining = split.second;
         if (uriContent != null) {
             // App-specific logic to handle the URI(s) in uriContent.
         }
         // Return anything that your app didn't handle. This preserves the
         // default platform behavior for text and anything else that you aren't
         // implementing custom handling for.
         return remaining;
     }
 }

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

אחרי שמטמיעים את ה-listener, מגדירים אותו ברכיבי ממשק המשתמש המתאימים באפליקציה:

Kotlin

class MyActivity : Activity() {
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        val myInput = findViewById(R.id.my_input)
        ViewCompat.setOnReceiveContentListener(myInput, MyReceiver.MIME_TYPES, MyReceiver())
    }
}

Java

public class MyActivity extends Activity {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         // ...

         AppCompatEditText myInput = findViewById(R.id.my_input);
         ViewCompat.setOnReceiveContentListener(myInput, MyReceiver.MIME_TYPES, new MyReceiver());
     }
}

הרשאות URI

הרשאות קריאה מוענקות ומשוחררות באופן אוטומטי על ידי הפלטפורמה לכל מזהי ה-URI של התוכן במטען הייעודי (payload) שמועבר אל OnReceiveContentListener.

בדרך כלל, האפליקציה מעבדת מזהי URI של תוכן בשירות או בפעילות. לעיבוד ממושך, צריך להשתמש ב-WorkManager. כשמטמיעים את ההגדרה הזו, מעבירים את התוכן באמצעות Intent.setClipData ומגדירים את הדגל FLAG_GRANT_READ_URI_PERMISSION כדי להרחיב את ההרשאות לשירות או לפעילות היעד.

לחלופין, אפשר להשתמש בשרשור ברקע בהקשר הנוכחי כדי לעבד את התוכן. במקרה כזה, כדי לוודא שהפלטפורמה לא תבטל את ההרשאות לפני הזמן, צריך לשמור הפניה לאובייקט payload שמתקבל על ידי רכיב ה-listener.

תצוגות מותאמות אישית

אם האפליקציה משתמשת במחלקת משנה מותאמת אישית של View, חשוב לוודא שלא נעקף.OnReceiveContentListener

אם המחלקה View מחליפה את השיטה onCreateInputConnection, צריך להשתמש ב-Jetpack API‏ InputConnectionCompat.createWrapper כדי להגדיר את InputConnection.

אם המחלקה View מבטלת את השיטה onTextContextMenuItem, צריך להעביר את הפריט בתפריט אל super כשהוא R.id.paste או R.id.pasteAsPlainText.

השוואה ל-API של תמונת המקלדת

אפשר לחשוב על OnReceiveContentListener API כגרסה הבאה של keyboard image API הקיים. ממשק ה-API המאוחד הזה תומך בפונקציונליות של ממשק ה-API של תמונת המקלדת, וגם בכמה תכונות נוספות. התאימות של המכשיר והתכונה משתנה בהתאם לשימוש בספריית Jetpack או בממשקי ה-API המקוריים מ-Android SDK.

טבלה 1. תכונות נתמכות ורמות API עבור Jetpack.
פעולה או תכונה נתמך על ידי API של תמונת מקלדת נתמך על ידי Unified API
הוספה מהמקלדת כן (רמת API 13 ומעלה) כן (רמת API 13 ומעלה)
הוספה באמצעות הדבקה מהתפריט של לחיצה ארוכה לא כן
הוספה באמצעות גרירה ושחרור לא כן (רמת API 24 ומעלה)
טבלה 2. תכונות נתמכות ורמות API עבור ממשקי API מקוריים.
פעולה או תכונה נתמך על ידי API של תמונת מקלדת נתמך על ידי Unified API
הוספה מהמקלדת כן (רמת API‏ 25 ומעלה) כן (Android מגרסה 12 ואילך)
הוספה באמצעות הדבקה מהתפריט של לחיצה ארוכה לא
הוספה באמצעות גרירה ושחרור לא