לרבים מהנהגים חשוב לשמור על קשר באמצעות הודעות. אפליקציות צ'אט יכולות לאפשר למשתמשים לדעת אם צריך לאסוף ילד או אם מקום הארוחה השתנה. מסגרת Android מאפשרת לאפליקציות שליחת הודעות להרחיב את השירותים שלהן לחוויית הנהיגה באמצעות ממשק משתמש רגיל שמאפשר לנהגים להמשיך להביט בכביש.
אפליקציות שתומכות בהעברת הודעות יכולות להרחיב את ההתראות שלהן כדי לאפשר ל-Android Auto להשתמש בהן כשהאפליקציה פועלת. ההתראות האלה מוצגות במצב אוטומטי ומאפשרות למשתמשים לקרוא הודעות ולהגיב להן בממשק עקבי עם מינימום הסחות דעת. בנוסף, כשמשתמשים ב- MessagingStyle API, מקבלים התראות אופטימיות על הודעות בכל מכשירי Android, כולל Android Auto. האופטימיזציות כוללות ממשק משתמש שמותאם במיוחד להתראות על הודעות, אנימציות משופרות ותמיכה בתמונות בגוף ההודעה.
במדריך הזה נסביר איך להרחיב אפליקציה שמציגה הודעות למשתמש ומקבלת את התשובות שלו, כמו אפליקציית צ'אט, כדי להעביר את הצגת ההודעות ואת קבלת התשובות למכשיר 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, האפליקציה צריכה לצרף לאובייקט Notification
אובייקטים מסוג Action
של מענה וסימון כנקרא.
תהליך שליחת ההודעות
בקטע הזה מתוארת תהליך העברת הודעות טיפוסי בין האפליקציה לבין 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>
הבא. אם לא תעשו זאת, מערכת Android Auto תשתמש ב-handler שמובנה בה כברירת מחדל כדי לטפל בהודעות SMS/MMS נכנסות כשהאפליקציה מוגדרת כ-handler ל-SMS כברירת מחדל. כתוצאה מכך, יכול להיות שתקבלו התראות כפולות.
<automotiveApp>
...
<uses name="sms" />
</automotiveApp>
ייבוא ספריית הליבה של AndroidX
כדי ליצור התראות לשימוש במכשירי Auto, צריך את ספריית הליבה AndroidX. מייבאים את הספרייה לפרויקט באופן הבא:
- בקובץ
build.gradle
ברמה העליונה, כוללים יחסי תלות למאגר Maven של Google, כמו בדוגמה הבאה:
Groovy
allprojects { repositories { google() } }
Kotlin
allprojects { repositories { google() } }
- בקובץ
build.gradle
של מודול האפליקציה, כוללים את יחסי התלות בספרייה AndroidX Core, כפי שמתואר בדוגמה הבאה:
Groovy
dependencies { // If your app is written in Java implementation 'androidx.core:core:1.15.0' // If your app is written in Kotlin implementation 'androidx.core:core-ktx:1.15.0' }
Kotlin
dependencies { // If your app is written in Java implementation("androidx.core:core:1.15.0") // If your app is written in Kotlin implementation("androidx.core:core-ktx:1.15.0") }
טיפול בפעולות של משתמשים
באפליקציית ההודעות צריכה להיות דרך לעדכן שיחה באמצעות 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
}
כשמייצרים את הכוונות בהמתנה, נעשה שימוש בשתי שיטות: createReplyId()
ו-createMarkAsReadId()
. השיטות האלה משמשות כקודי הבקשה של כל PendingIntent
, שבהם Android משתמשת כדי לשלוט בכוונות קיימות בהמתנה. השיטות של 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 Messaging
אם נתקלת בבעיה במהלך הפיתוח של אפליקציית ההודעות ל-Android Auto, אפשר לדווח עליה באמצעות Google Issue Tracker. חשוב למלא את כל המידע הנדרש בתבנית הדיווח על הבעיה.
לפני שמדווחים על בעיה חדשה, כדאי לבדוק אם היא כבר דווחה ברשימת הבעיות. כדי להירשם לבעיות ולהצביע עליהן, לוחצים על הכוכב של הבעיה במעקב. מידע נוסף זמין במאמר הרשמה לבעיה.