לנהגים רבים חשוב לשמור על קשר בהודעות. אפליקציות צ'אט יכולות לאפשר למשתמשים אם צריך לאסוף את הילד או הילדה או אם מיקום ארוחת הערב השתנה. ה-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.
- מתקבלת הודעה באפליקציה.
- האפליקציה יוצרת התראה מסוג
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>
אם אפשר להגדיר את האפליקציה כרכיב ה-handler שמוגדר כברירת מחדל ב-SMS,
חשוב לכלול את הרכיב <uses>
הבא. אם לא, ברירת מחדל
ה-handler שמובנה ב-Android Auto ישמש לטיפול בהודעות SMS/MMS נכנסות
כשהאפליקציה שלך מוגדרת כ-handler ברירת המחדל ל-SMS, דבר שעלול להוביל לכפילות
התראות.
<automotiveApp>
...
<uses name="sms" />
</automotiveApp>
ייבוא ספריית הליבה של AndroidX
כדי ליצור התראות לשימוש במכשירים אוטומטיים נדרשת ספריית הליבה של 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.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 למעקב אחר בעיות. חשוב להקפיד למלא את כל המידע הנדרש בתבנית הבעיה.
לפני ששולחים בעיה חדשה, כדאי לבדוק אם היא כבר דווחה במסגרת הבעיות חדשה. ניתן להירשם ולהצביע על בעיות על ידי לחיצה על הכוכב של בעיה ב- מכשיר המעקב. מידע נוסף זמין במאמר הבא: הרשמה לבעיה.