אפליקציות שתומכות בהעברת הודעות יכולות להרחיב את ההתראות שלהן על הודעות כדי לאפשר ל-Android Auto להשתמש בהן כשהמערכת פועלת. ההתראות האלה מוצגות ב-Auto ומאפשרות למשתמשים לקרוא ולהגיב להודעות בממשק עקבי עם הסחות דעת מועטות. כשמשתמשים ב- MessagingStyle API, מקבלים התראות על הודעות שעברו אופטימיזציה לכל מכשירי Android, כולל Android Auto. האופטימיזציות כוללות ממשק משתמש שמתמחה בהתראות על הודעות, אנימציות משופרות ותמיכה בתמונות בגוף ההודעה.
במדריך הזה מוסבר איך להרחיב אפליקציה שמציגה הודעות למשתמש ומקבלת את התשובות שלו, כמו אפליקציית צ'אט, כדי להעביר את הצגת ההודעות ואת קבלת התשובות למכשיר Auto. באמצעות השילוב הזה, המשתמשים יכולים לראות רק את היסטוריית ההודעות מההתראות שהתקבלו במהלך הסשן הפעיל שלהם ב-Android Auto. כדי להציג הודעות מלפני תחילת הסשן הפעיל של Android Auto, אתם יכולים ליצור חוויית הודעות מבוססת-תבניות.
הנחיות עיצוב קשורות זמינות במאמר אפליקציות להעברת הודעות באתר Design for Driving.
שנתחיל?
כדי לספק שירות הודעות למכשירי 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
ל-Notification
.
תהליך העבודה של העברת הודעות
בקטע הזה מתואר תהליך טיפוסי של העברת הודעות בין האפליקציה שלכם לבין Android Auto.
- האפליקציה מקבלת הודעה.
- האפליקציה יוצרת התראה
MessagingStyle
עם אובייקטים של תגובה וסימון כנקראAction
. - Android Auto מקבל את האירוע 'התראה חדשה' ממערכת Android ומוצא את
MessagingStyle
, את התשובהAction
ואת הסימון כנקראAction
. - מערכת Android Auto יוצרת התראה ומציגה אותה ברכב.
- אם המשתמש מקיש על ההתראה במסך הרכב, Android Auto מפעיל את הפעולה 'סימון כפריט שנקרא'
Action
.- ברקע, האפליקציה צריכה לטפל באירוע הזה של סימון כפריט שנקרא.
- אם המשתמש מגיב להודעה באמצעות קול, 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>
אם אפשר להגדיר את האפליקציה כמטפל ברירת המחדל ב-SMS, צריך לוודא שכוללים את רכיב <uses>
הבא. אם לא תעשו זאת, נעשה שימוש ב-handler שמוגדר כברירת מחדל ומובנה ב-Android Auto כדי לטפל בהודעות SMS/MMS נכנסות כשהאפליקציה שלכם מוגדרת כ-handler ל-SMS שמוגדר כברירת מחדל, מה שעלול להוביל להתרעות כפולות.
<automotiveApp>
...
<uses name="sms" />
</automotiveApp>
ייבוא ספריית הליבה של AndroidX
כדי ליצור התראות לשימוש במכשירי Auto, צריך להשתמש בספריית הליבה של AndroidX. מייבאים את הספרייה לפרויקט באופן הבא:
- בקובץ
build.gradle
ברמה העליונה, כוללים תלות במאגר Maven של Google, כמו בדוגמה הבאה:
גרוב
allprojects { repositories { google() } }
Kotlin
allprojects { repositories { google() } }
- בקובץ
build.gradle
של מודול האפליקציה, כוללים את תלות הספרייה AndroidX Core, כמו בדוגמה הבאה:
גרוב
dependencies { // If your app is written in Java implementation 'androidx.core:core:1.17.0' // If your app is written in Kotlin implementation 'androidx.core:core-ktx:1.17.0' }
Kotlin
dependencies { // If your app is written in Java implementation("androidx.core:core:1.17.0") // If your app is written in Kotlin implementation("androidx.core:core-ktx:1.17.0") }
טיפול בפעולות משתמש
אפליקציית הצ'אט צריכה דרך לעדכן שיחה באמצעות Action
. ב-Android Auto, יש שני סוגים של Action
אובייקטים שהאפליקציה צריכה לטפל בהם: תשובה וסימון כנקרא. מומלץ לטפל בהם באמצעות IntentService
, שמאפשר לטפל בקריאות יקרות פוטנציאליות ברקע, וכך לאפשר לשרשור הראשי של האפליקציה לפעול באופן חופשי.
הגדרת פעולות של כוונות
Intent
actions הן מחרוזות פשוטות שמזהות את המטרה של ה-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
switch בתוך 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
switch בתוך 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
}
כשיוצרים את הכוונות בהמתנה, משתמשים בשתי שיטות: createReplyId()
ו-createMarkAsReadId()
. ה-methods האלה משמשות כקודי בקשה לכל PendingIntent
, שמשמשים את Android כדי לשלוט ב-intents ממתינים קיימים. השיטות create()
צריכות להחזיר מזהים ייחודיים לכל שיחה, אבל קריאות חוזרות לאותה שיחה צריכות להחזיר את המזהה הייחודי שכבר נוצר.
נניח שיש שתי שיחות, א' וב': מזהה התשובה של שיחה א' הוא 100, ומזהה הסימון כשיחה שנקראה הוא 101. מזהה התשובה בשיחה ב' הוא 102, ומזהה הסימון כנקרא הוא 103. אם שיחה א' מתעדכנת, המזהים של התשובה ושל הסימון כנקרא עדיין יהיו 100 ו-101. מידע נוסף זמין במאמר PendingIntent.FLAG_UPDATE_CURRENT
.
יצירת MessagingStyle
MessagingStyle
הוא הספק של פרטי ההודעות, ו-Android Auto משתמש בו כדי להקריא כל הודעה בשיחה.
קודם צריך לציין את המשתמש במכשיר באמצעות אובייקט 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. חשוב למלא את כל הפרטים הנדרשים בתבנית הבעיה.
לפני ששולחים דיווח על בעיה חדשה, כדאי לבדוק אם היא כבר מופיעה ברשימת הבעיות. כדי להירשם לעדכונים על בעיות ולהצביע על בעיות, לוחצים על הכוכב שלצד הבעיה בכלי המעקב. מידע נוסף זמין במאמר בנושא הרשמה לבעיה.