בניית אפליקציות להעברת הודעות ל-Android Auto

הקטגוריה 'תקשורת' תהיה זמינה בקרוב
אנחנו מרחיבים את הקטגוריה 'העברת הודעות' ותכלול תמיכה ביכולות חדשות, כולל היסטוריית ההודעות וחוויות השיחה

לנהגים רבים חשוב לשמור על קשר בהודעות. אפליקציות צ'אט יכולות לאפשר למשתמשים אם צריך לאסוף את הילד או הילדה או אם מיקום ארוחת הערב השתנה. ה-framework של Android מאפשר לאפליקציות הודעות להאריך את התוקף שלהן לחוויית הנהיגה באמצעות ממשק משתמש סטנדרטי שמאפשר לנהגים לשמור על עיניים על הכביש.

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

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

שנתחיל?

כדי לספק שירות העברת הודעות למכשירים אוטומטית, האפליקציה שלך צריכה להצהיר על תמיכה ב-Android Auto במניפסט, ויכולת לבצע את הפעולות הבאות:

  • יצירה ושליחה NotificationCompat.MessagingStyle אובייקטים שמכילים אובייקטים מסוג תשובה וסימון כפריט שנקרא Action.
  • לטפל במענה ובסימון של שיחה ש'כבר קראתי' באמצעות Service.

מושגים ואובייקטים

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

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

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

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

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

בסיכום, שיחה אחת מיוצגת על ידי Notification של אובייקט עם סגנון של MessagingStyle. MessagingStyle מכיל את כל ההודעות בשיחה אחת או יותר. MessagingStyle.Message אובייקטים. וכדי לעמוד בדרישות של Android Auto, אפליקציה צריך לצרף אובייקטים מסוג תשובה וסימון כ'נקראו' Action ל-Notification.

תהליך העברת הודעות

בקטע הזה מתואר תהליך אופייני להעברת הודעות בין האפליקציה שלכם לבין Android Auto.

  1. מתקבלת הודעה באפליקציה.
  2. האפליקציה יוצרת התראה מסוג MessagingStyle עם תשובה סימון כאובייקטים Action כ'נקראו'.
  3. Android Auto מקבל את האירוע 'התראה חדשה' ממערכת Android ומוצא את MessagingStyle, את התשובה Action ומסמן ש'כבר קראתי' את Action.
  4. מערכת Android Auto יוצרת ומציגה התראה ברכב.
  5. אם המשתמש יקיש על ההתראה במסך הרכב, Android Auto מפעיל את התכונה Action כ'סימון כנקרא'.
    • ברקע, האפליקציה צריכה לטפל באירוע הסימון כ'נקרא'.
  6. אם המשתמש משיב להתראה באמצעות הקול, Android Auto מעביר תמלול של תגובת המשתמש בתשובה Action, ואז שמפעילה אותו.
    • ברקע, האפליקציה צריכה לטפל באירוע התשובה הזה.

הנחות ראשוניות

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

data class YourAppConversation(
        val id: Int,
        val title: String,
        val recipients: MutableList<YourAppUser>,
        val icon: Bitmap) {
    companion object {
        /** Fetches [YourAppConversation] by its [id]. */
        fun getById(id: Int): YourAppConversation = // ...
    }

    /** Replies to this conversation with the given [message]. */
    fun reply(message: String) {}

    /** Marks this conversation as read. */
    fun markAsRead() {}

    /** Retrieves all unread messages from this conversation. */
    fun getUnreadMessages(): List<YourAppMessage> { return /* ... */ }
}
data class YourAppUser(val id: Int, val name: String, val icon: Uri)
data class YourAppMessage(
    val id: Int,
    val sender: YourAppUser,
    val body: String,
    val timeReceived: Long)

הצהרה על תמיכה ב-Android Auto

כש-Android Auto מקבל התראה מאפליקציית הודעות, הוא בודק האפליקציה הצהירה על תמיכה ב-Android Auto. כדי להפעיל את התמיכה הזו, צריך לכלול הרשומה הבאה במניפסט של האפליקציה:

<application>
    ...
    <meta-data
        android:name="com.google.android.gms.car.application"
        android:resource="@xml/automotive_app_desc"/>
    ...
</application>

רשומת המניפסט הזו מתייחסת לקובץ XML אחר שעליך ליצור עם בנתיב הבא: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml. בautomotive_app_desc.xml, צריך להצהיר על היכולות של Android Auto באפליקציה נתמך. לדוגמה, כדי להצהיר על תמיכה בהתראות, צריך לכלול את הבאים:

<automotiveApp>
    <uses name="notification" />
</automotiveApp>

אם אפשר להגדיר את האפליקציה כרכיב ה-handler שמוגדר כברירת מחדל ב-SMS, חשוב לכלול את הרכיב <uses> הבא. אם לא, ברירת מחדל ה-handler שמובנה ב-Android Auto ישמש לטיפול בהודעות SMS/MMS נכנסות כשהאפליקציה שלך מוגדרת כ-handler ברירת המחדל ל-SMS, דבר שעלול להוביל לכפילות התראות.

<automotiveApp>
    ...
    <uses name="sms" />
</automotiveApp>

ייבוא ספריית הליבה של AndroidX

כדי ליצור התראות לשימוש במכשירים אוטומטיים נדרשת ספריית הליבה של AndroidX. לייבא את הספרייה פרויקט באופן הבא:

  1. בקובץ build.gradle ברמה העליונה, יש לכלול תלות ב-Maven של Google כמו בדוגמה הבאה:

מגניב

allprojects {
    repositories {
        google()
    }
}

Kotlin

allprojects {
    repositories {
        google()
    }
}
  1. בקובץ build.gradle של מודול האפליקציה, יש לכלול את AndroidX Core של הספרייה, כפי שמוצג בדוגמה הבאה:

מגניב

dependencies {
    // If your app is written in Java
    implementation 'androidx.core:core:1.13.1'

    // If your app is written in Kotlin
    implementation 'androidx.core:core-ktx:1.13.1'
}

Kotlin

dependencies {
    // If your app is written in Java
    implementation("androidx.core:core:1.13.1")

    // If your app is written in Kotlin
    implementation("androidx.core:core-ktx:1.13.1")
}

ביצוע פעולות של משתמשים

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

הגדרה של פעולות Intent

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

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

private const val ACTION_REPLY = "com.example.REPLY"
private const val ACTION_MARK_AS_READ = "com.example.MARK_AS_READ"

יצירת השירות

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

private const val EXTRA_CONVERSATION_ID_KEY = "conversation_id"
private const val REMOTE_INPUT_RESULT_KEY = "reply_input"

/**
 * An [IntentService] that handles reply and mark-as-read actions for
 * [YourAppConversation]s.
 */
class MessagingService : IntentService("MessagingService") {
    override fun onHandleIntent(intent: Intent?) {
        // Fetches internal data.
        val conversationId = intent!!.getIntExtra(EXTRA_CONVERSATION_ID_KEY, -1)

        // Searches the database for that conversation.
        val conversation = YourAppConversation.getById(conversationId)

        // Handles the action that was requested in the intent. The TODOs
        // are addressed in a later section.
        when (intent.action) {
            ACTION_REPLY -> TODO()
            ACTION_MARK_AS_READ -> TODO()
        }
    }
}

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

<application>
    <service android:name="com.example.MessagingService" />
    ...
</application>

יצירת כוונות וטיפול בהן

אין לאפליקציות אחרות, כולל Android Auto, אפשרות לקבל את Intent שמפעיל את MessagingService, כי ערכי Intent מועברים לאפליקציות אחרות דרך PendingIntent. בגלל את המגבלה הזו, צריך ליצור RemoteInput אובייקט כדי לאפשר לאפליקציות אחרות לשלוח את טקסט התשובה בחזרה לאפליקציה, כפי שמוצג בדוגמה הבאה:

/**
 * Creates a [RemoteInput] that lets remote apps provide a response string
 * to the underlying [Intent] within a [PendingIntent].
 */
fun createReplyRemoteInput(context: Context): RemoteInput {
    // RemoteInput.Builder accepts a single parameter: the key to use to store
    // the response in.
    return RemoteInput.Builder(REMOTE_INPUT_RESULT_KEY).build()
    // Note that the RemoteInput has no knowledge of the conversation. This is
    // because the data for the RemoteInput is bound to the reply Intent using
    // static methods in the RemoteInput class.
}

/** Creates an [Intent] that handles replying to the given [appConversation]. */
fun createReplyIntent(
        context: Context, appConversation: YourAppConversation): Intent {
    // Creates the intent backed by the MessagingService.
    val intent = Intent(context, MessagingService::class.java)

    // Lets the MessagingService know this is a reply request.
    intent.action = ACTION_REPLY

    // Provides the ID of the conversation that the reply applies to.
    intent.putExtra(EXTRA_CONVERSATION_ID_KEY, appConversation.id)

    return intent
}

בסעיף המתג ACTION_REPLY בתוך MessagingService, לחלץ את המידע שנכלל בתשובה Intent, כפי שמוצג בדוגמה הבאה:

ACTION_REPLY -> {
    // Extracts reply response from the intent using the same key that the
    // RemoteInput uses.
    val results: Bundle = RemoteInput.getResultsFromIntent(intent)
    val message = results.getString(REMOTE_INPUT_RESULT_KEY)

    // This conversation object comes from the MessagingService.
    conversation.reply(message)
}

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

/** Creates an [Intent] that handles marking the [appConversation] as read. */
fun createMarkAsReadIntent(
        context: Context, appConversation: YourAppConversation): Intent {
    val intent = Intent(context, MessagingService::class.java)
    intent.action = ACTION_MARK_AS_READ
    intent.putExtra(EXTRA_CONVERSATION_ID_KEY, appConversation.id)
    return intent
}

סעיף המתג של ACTION_MARK_AS_READ בMessagingService לא נדרשת כל לוגיקה נוספת, כפי שמוצג בדוגמה הבאה:

// Marking as read has no other logic.
ACTION_MARK_AS_READ -> conversation.markAsRead()

שליחת הודעה למשתמשים על הודעות

לאחר השלמת הטיפול בפעולות בשיחה, השלב הבא הוא ליצור התראות שתואמות ל-Android Auto.

יצירת פעולות

ניתן להעביר Action אובייקטים לאפליקציות אחרות באמצעות Notification כדי שיטות הפעלה באפליקציה המקורית. כך Android Auto יכול לסמן שיחה שנקראה או מענה עליה.

כדי ליצור Action, צריך להתחיל ב-Intent. הדוגמה הבאה מראה איך יוצרים 'תשובה' Intent:

fun createReplyAction(
        context: Context, appConversation: YourAppConversation): Action {
    val replyIntent: Intent = createReplyIntent(context, appConversation)
    // ...

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

    // ...
    val replyPendingIntent = PendingIntent.getService(
        context,
        createReplyId(appConversation), // Method explained later.
        replyIntent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
    // ...

לפני שמגדירים את התשובה Action, חשוב לדעת שב-Android Auto יש דרישות לתשובה Action:

  • צריך להגדיר את הפעולה הסמנטית ל-Action.SEMANTIC_ACTION_REPLY.
  • חובה לציין Action שלא יוצג ממשק משתמש כלשהו כשהוא מופעל.
  • השדה Action חייב להכיל RemoteInput יחיד.

דוגמת הקוד הבאה מגדירה תשובה Action שעונה על הדרישות המפורטות למעלה:

    // ...
    val replyAction = Action.Builder(R.drawable.reply, "Reply", replyPendingIntent)
        // Provides context to what firing the Action does.
        .setSemanticAction(Action.SEMANTIC_ACTION_REPLY)

        // The action doesn't show any UI, as required by Android Auto.
        .setShowsUserInterface(false)

        // Don't forget the reply RemoteInput. Android Auto will use this to
        // make a system call that will add the response string into
        // the reply intent so it can be extracted by the messaging app.
        .addRemoteInput(createReplyRemoteInput(context))
        .build()

    return replyAction
}

הטיפול בפעולת הסימון כ'נקרא' דומה, אבל אין 'RemoteInput'. לכן, ב-Android Auto יש שתי דרישות לגבי המאפיין 'סימון כנקרא' (Action):

  • הפעולה הסמנטית מוגדרת לAction.SEMANTIC_ACTION_MARK_AS_READ.
  • הפעולה מציינת שלא יוצג אף ממשק משתמש כשהוא מופעל.

דוגמת הקוד הבאה מגדירה Action כסימן קריאה שעונה על דרישות:

fun createMarkAsReadAction(
        context: Context, appConversation: YourAppConversation): Action {
    val markAsReadIntent = createMarkAsReadIntent(context, appConversation)
    val markAsReadPendingIntent = PendingIntent.getService(
            context,
            createMarkAsReadId(appConversation), // Method explained below.
            markAsReadIntent,
            PendingIntent.FLAG_UPDATE_CURRENT  or PendingIntent.FLAG_IMMUTABLE)
    val markAsReadAction = Action.Builder(
            R.drawable.mark_as_read, "Mark as Read", markAsReadPendingIntent)
        .setSemanticAction(Action.SEMANTIC_ACTION_MARK_AS_READ)
        .setShowsUserInterface(false)
        .build()
    return markAsReadAction
}

כשיוצרים את ה-Intents הממתינים, צריך להשתמש בשתי שיטות: createReplyId() וגם createMarkAsReadId() השיטות האלה קודי בקשה לכל PendingIntent, שמשמשים את Android כדי לשלוט אובייקטים מסוג Intent קיימים בהמתנה. ה-methods create() חייבות החזרת מזהים ייחודיים לכל שיחה, אבל קריאות חוזרות לאותה שיחה השיחה חייבת להחזיר את המזהה הייחודי שכבר נוצר.

נבחן דוגמה עם שתי שיחות, א' וב': מזהה התשובה של שיחה א' הוא 100, ומזהה הסימון כ'נקרא' שלו הוא 101. מזהה התשובה של שיחה ב' הוא 102, ומזהה הסימון כ'נקרא' שלו הוא 103. אם שיחה א' מתעדכנת, מזהי תשובה ו'סימון כפריט שנקרא' הם עדיין 100 ו-101. מידע נוסף זמין במאמר הבא: PendingIntent.FLAG_UPDATE_CURRENT

יצירת סגנון העברת הודעות

MessagingStyle הוא הספק של פרטי העברת ההודעות, והוא Android המערכת משתמשת אוטומטית כדי להקריא בקול כל הודעה בשיחה.

קודם כל, יש לציין את משתמש המכשיר בצורת Person, כפי שמוצג בדוגמה הבאה:

fun createMessagingStyle(
        context: Context, appConversation: YourAppConversation): MessagingStyle {
    // Method defined by the messaging app.
    val appDeviceUser: YourAppUser = getAppDeviceUser()

    val devicePerson = Person.Builder()
        // The display name (also the name that's read aloud in Android auto).
        .setName(appDeviceUser.name)

        // The icon to show in the notification shade in the system UI (outside
        // of Android Auto).
        .setIcon(appDeviceUser.icon)

        // A unique key in case there are multiple people in this conversation with
        // the same name.
        .setKey(appDeviceUser.id)
        .build()
    // ...

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

    // ...
    val messagingStyle = MessagingStyle(devicePerson)

    // Sets the conversation title. If the app's target version is lower
    // than P, this will automatically mark the conversation as a group (to
    // maintain backward compatibility). Use `setGroupConversation` after
    // setting the conversation title to explicitly override this behavior. See
    // the documentation for more information.
    messagingStyle.setConversationTitle(appConversation.title)

    // Group conversation means there is more than 1 recipient, so set it as such.
    messagingStyle.setGroupConversation(appConversation.recipients.size > 1)
    // ...

לסיום, מוסיפים את ההודעות שלא נקראו.

    // ...
    for (appMessage in appConversation.getUnreadMessages()) {
        // The sender is also represented using a Person object.
        val senderPerson = Person.Builder()
            .setName(appMessage.sender.name)
            .setIcon(appMessage.sender.icon)
            .setKey(appMessage.sender.id)
            .build()

        // Adds the message. More complex messages, like images,
        // can be created and added by instantiating the MessagingStyle.Message
        // class directly. See documentation for details.
        messagingStyle.addMessage(
                appMessage.body, appMessage.timeReceived, senderPerson)
    }

    return messagingStyle
}

אורזים ודוחפים את ההתראה

אחרי שיוצרים את האובייקטים Action ו-MessagingStyle, אפשר ליצור ולפרסם את Notification.

fun notify(context: Context, appConversation: YourAppConversation) {
    // Creates the actions and MessagingStyle.
    val replyAction = createReplyAction(context, appConversation)
    val markAsReadAction = createMarkAsReadAction(context, appConversation)
    val messagingStyle = createMessagingStyle(context, appConversation)

    // Creates the notification.
    val notification = NotificationCompat.Builder(context, channel)
        // A required field for the Android UI.
        .setSmallIcon(R.drawable.notification_icon)

        // Shows in Android Auto as the conversation image.
        .setLargeIcon(appConversation.icon)

        // Adds MessagingStyle.
        .setStyle(messagingStyle)

        // Adds reply action.
        .addAction(replyAction)

        // Makes the mark-as-read action invisible, so it doesn't appear
        // in the Android UI but the app satisfies Android Auto's
        // mark-as-read Action requirement. Both required actions can be made
        // visible or invisible; it is a stylistic choice.
        .addInvisibleAction(markAsReadAction)

        .build()

    // Posts the notification for the user to see.
    val notificationManagerCompat = NotificationManagerCompat.from(context)
    notificationManagerCompat.notify(appConversation.id, notification)
}

מקורות מידע נוספים

דיווח על בעיה בהעברת הודעות ב-Android Auto

אם נתקלתם בבעיה במהלך הפיתוח של אפליקציית הודעות ל-Android Auto, אפשר לדווח עליה באמצעות הכלי של Google למעקב אחר בעיות. חשוב להקפיד למלא את כל המידע הנדרש בתבנית הבעיה.

דיווח על בעיה חדשה

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