Các ứng dụng hỗ trợ tính năng nhắn tin có thể mở rộng thông báo nhắn tin để cho phép Android Auto sử dụng thông báo đó khi đang chạy. Android Auto hiển thị những thông báo này và cho phép người dùng đọc rồi trả lời tin nhắn trong một giao diện nhất quán và hạn chế sự mất tập trung. Đồng thời, khi sử dụng API MessagingStyle, bạn sẽ nhận được thông báo tin nhắn được tối ưu hoá dành cho mọi thiết bị Android, bao gồm cả Android Auto. Những điểm tối ưu hoá nói trên bao gồm giao diện người dùng dành riêng cho thông báo tin nhắn, ảnh động cải tiến và tính năng hỗ trợ hình ảnh trên cùng dòng.
Hướng dẫn này cho bạn biết cách mở rộng một ứng dụng hiển thị tin nhắn cho người dùng và nhận tin nhắn trả lời của người dùng (chẳng hạn như ứng dụng nhắn tin) để chuyển màn hình tin nhắn và thông báo xác nhận tin nhắn sang Android Auto. Thông qua chế độ tích hợp này, người dùng chỉ có thể xem nhật ký tin nhắn từ các thông báo nhận được trong phiên hoạt động Android Auto. Để hiển thị tin nhắn từ trước khi phiên hoạt động Android Auto bắt đầu, bạn có thể tạo trải nghiệm nhắn tin dựa trên mẫu.
Để biết hướng dẫn có liên quan về thiết kế, hãy xem phần Ứng dụng giao tiếp trong trung tâm Design for Cars.
Bắt đầu
Để cung cấp dịch vụ nhắn tin cho Android Auto, ứng dụng của bạn phải khai báo tính năng hỗ trợ Android Auto trong tệp kê khai và có thể thực hiện những tác vụ sau:
- Khai báo tính năng hỗ trợ Android Auto
- Tạo và gửi các đối tượng
NotificationCompat.MessagingStylechứa các đối tượngActiontrả lời và đánh dấu là đã đọc. - Xử lý thao tác trả lời và đánh dấu cuộc trò chuyện là đã đọc bằng
Service.
Khái niệm và đối tượng
Trước khi bắt đầu thiết kế ứng dụng, bạn cần nắm được cách Android Auto xử lý thao tác nhắn tin.
Một đoạn thông tin trao đổi riêng lẻ được gọi là tin nhắn và được biểu thị bằng lớp MessagingStyle.Message. Tin nhắn chứa người gửi, nội dung tin nhắn và thời gian gửi tin nhắn.
Hoạt động giao tiếp giữa những người dùng được gọi là cuộc trò chuyện và được biểu thị bằng đối tượng MessagingStyle. Một cuộc trò chuyện (MessagingStyle) chứa tiêu đề, tin nhắn và thông tin cho biết liệu đó có phải là cuộc trò chuyện giữa một nhóm người dùng hay không.
Để thông báo cho người dùng về nội dung cập nhật của cuộc trò chuyện (chẳng hạn như một tin nhắn mới), các ứng dụng sẽ đăng một Notification lên hệ thống Android. Notification này sử dụng đối tượng MessagingStyle để hiển thị giao diện người dùng dành riêng cho tính năng nhắn tin trong ngăn thông báo. Nền tảng Android cũng chuyển Notification này cho Android Auto, còn MessagingStyle được trích xuất và dùng để đăng thông báo trên màn hình của ô tô.
Android Auto cũng yêu cầu ứng dụng thêm các đối tượng Action vào Notification để cho phép người dùng trả lời tin nhắn hoặc đánh dấu tin nhắn là đã đọc ngay trên màn hình của ô tô.
Tóm lại, một cuộc trò chuyện được biểu thị bằng đối tượng Notification. Đối tượng này được tạo kiểu bằng một đối tượng MessagingStyle. MessagingStyle chứa tất cả tin nhắn trong cuộc trò chuyện đó, trong một hoặc nhiều đối tượng MessagingStyle.Message. Ngoài ra, để tương thích với Android Auto, ứng dụng phải đính kèm đối tượng Action trả lời và đánh dấu là đã đọc vào Notification.
Quy trình nhắn tin
Phần này mô tả quy trình nhắn tin thông thường giữa ứng dụng của bạn và Android Auto.
- Ứng dụng của bạn nhận được một tin nhắn.
- Ứng dụng tạo một thông báo
MessagingStylecó các đối tượngActiontrả lời và đánh dấu là đã đọc. - Android Auto nhận được sự kiện "thông báo mới" từ hệ thống Android và tìm thấy
MessagingStyle,Actiontrả lời cũng nhưActionđánh dấu là đã đọc. - Android Auto tạo và hiển thị thông báo trên ô tô.
- Nếu người dùng nhấn vào thông báo trên màn hình của ô tô, thì Android Auto sẽ kích hoạt
Actionđánh dấu là đã đọc.- Ở chế độ nền, ứng dụng của bạn phải xử lý sự kiện đánh dấu là đã đọc này.
- Nếu người dùng trả lời thông báo bằng giọng nói, thì Android Auto sẽ thêm bản chép lời phản hồi của người dùng vào
Actiontrả lời rồi kích hoạt đối tượng đó.- Ở chế độ nền, ứng dụng của bạn phải xử lý sự kiện trả lời này.
Giả định sơ bộ
Trang này không hướng dẫn bạn cách tạo một ứng dụng nhắn tin hoàn chỉnh. Mã mẫu sau đây cho biết một số việc bạn cần thực hiện đối với ứng dụng của mình để có thể bắt đầu hỗ trợ tính năng nhắn tin qua 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)
Khai báo chức năng hỗ trợ Android Auto
Khi nhận được thông báo từ một ứng dụng nhắn tin, Android Auto sẽ kiểm tra để đảm bảo ứng dụng đó đã khai báo tính năng hỗ trợ Android Auto. Để bật tính năng hỗ trợ này, hãy thêm mục sau vào tệp kê khai của ứng dụng:
<application>
...
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/>
...
</application>
Mục kê khai này là một tệp XML khác (automotive_app_desc.xml) mà bạn cần tạo trong thư mục res/xml của mô-đun ứng dụng. Trong automotive_app_desc.xml, hãy khai báo các tính năng của Android Auto mà ứng dụng của bạn hỗ trợ. Để khai báo tính năng hỗ trợ cho thông báo, hãy thêm nội dung sau:
<automotiveApp>
<uses name="notification" />
</automotiveApp>
Nếu có thể đặt ứng dụng làm trình xử lý tin nhắn SMS mặc định, hãy nhớ thêm phần tử <uses> sau. Nếu không, Android Auto sẽ dùng trình xử lý mặc định tích hợp để xử lý tin nhắn SMS/MMS đến khi ứng dụng của bạn được đặt làm trình xử lý tin nhắn SMS mặc định. Điều này có thể dẫn đến thông báo trùng lặp.
<automotiveApp>
...
<uses name="sms" />
</automotiveApp>
Nhập thư viện AndroidX Core
Khi tạo thông báo để sử dụng với Android Auto, bạn phải có thư viện cốt lõi AndroidX. Nhập thư viện này vào dự án của bạn theo cách dưới đây:
- Trong tệp
build.gradlecấp cao nhất, hãy đưa phần phụ thuộc vào kho lưu trữ Maven của Google, như trong ví dụ sau:
Groovy
allprojects { repositories { google() } }
Kotlin
allprojects { repositories { google() } }
- Trong tệp
build.gradlecủa mô-đun ứng dụng, hãy thêm phần phụ thuộc thư viện AndroidX Core, như trong ví dụ sau:
Groovy
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") }
Xử lý thao tác của người dùng
Ứng dụng nhắn tin của bạn cần có cách thức xử lý thao tác cập nhật cuộc trò chuyện thông qua Action. Đối với Android Auto, ứng dụng của bạn cần xử lý 2 loại đối tượng Action: trả lời và đánh dấu là đã đọc. Bạn nên xử lý các đối tượng này bằng IntentService, cho phép bạn xử lý linh hoạt các lệnh gọi có khả năng tốn nhiều tài nguyên ở chế độ nền, giúp giải phóng luồng chính của ứng dụng.
Xác định các thao tác theo ý định
Thao tác Intent là các chuỗi cơ bản do bạn chọn để xác định mục đích của Intent. Vì một dịch vụ có thể xử lý nhiều loại ý định nên việc xác định nhiều chuỗi thao tác sẽ dễ dàng hơn so với xác định nhiều thành phần IntentService.
Ứng dụng nhắn tin mẫu trong hướng dẫn này có 2 loại thao tác bắt buộc: trả lời và đánh dấu là đã đọc, như minh hoạ trong mã mẫu sau:
private const val ACTION_REPLY = "com.example.REPLY"
private const val ACTION_MARK_AS_READ = "com.example.MARK_AS_READ"
Tạo dịch vụ
Để tạo một dịch vụ xử lý những đối tượng Action này, bạn cần có mã nhận dạng cuộc trò chuyện – một cấu trúc dữ liệu tuỳ ý do ứng dụng của bạn xác định để nhận dạng cuộc trò chuyện. Bạn cũng cần có một khoá nhập từ xa. Điều này sẽ được thảo luận chi tiết sau trong phần này. Mã mẫu sau đây sẽ tạo một dịch vụ để xử lý các hành động bắt buộc:
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()
}
}
}
Để liên kết dịch vụ này với ứng dụng của bạn, bạn cũng cần đăng ký dịch vụ trong tệp kê khai của ứng dụng, như trong ví dụ sau:
<application>
<service android:name="com.example.MessagingService" />
...
</application>
Tạo và xử lý ý định
Các ứng dụng khác, bao gồm cả Android Auto, không thể có được Intent kích hoạt MessagingService vì các ý định được truyền đến các ứng dụng khác thông qua PendingIntent. Do giới hạn này, hãy tạo một đối tượng RemoteInput để cho phép các ứng dụng khác cung cấp văn bản trả lời cho ứng dụng của bạn, như minh hoạ trong ví dụ sau:
/**
* 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
}
Trong mệnh đề chuyển đổi ACTION_REPLY trong MessagingService, hãy trích xuất thông tin chuyển đến Intent trả lời, như trong ví dụ sau:
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)
}
Bạn sẽ xử lý Intent đánh dấu là đã đọc theo cách tương tự. Tuy nhiên, đối tượng này không yêu cầu RemoteInput, như trong ví dụ sau:
/** 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
}
Mệnh đề chuyển đổi ACTION_MARK_AS_READ trong MessagingService không cần thêm logic, như trong ví dụ sau:
// Marking as read has no other logic.
ACTION_MARK_AS_READ -> conversation.markAsRead()
Thông báo cho người dùng về tin nhắn
Sau khi xử lý xong thao tác trò chuyện, bước tiếp theo là tạo thông báo tương thích với Android Auto.
Tạo thao tác
Bạn có thể chuyển đối tượng Action đến các ứng dụng khác bằng Notification để kích hoạt các phương thức trong ứng dụng ban đầu. Đây là cách Android Auto có thể đánh dấu cuộc trò chuyện là đã đọc hoặc trả lời cuộc trò chuyện đó.
Để tạo Action, hãy bắt đầu bằng Intent. Ví dụ sau đây cho biết cách tạo một Intent "trả lời" bằng phương thức createReplyIntent() trong phần trước:
fun createReplyAction(
context: Context, appConversation: YourAppConversation): Action {
val replyIntent: Intent = createReplyIntent(context, appConversation)
// ...
Sau đó, hãy gói Intent này trong một PendingIntent để chuẩn bị cho việc sử dụng ứng dụng bên ngoài. PendingIntent sẽ chặn mọi quyền truy cập vào Intent đã gói bằng cách chỉ hiển thị một nhóm phương thức chọn lọc cho phép ứng dụng nhận kích hoạt Intent hoặc lấy tên gói của ứng dụng gốc. Ứng dụng bên ngoài không truy cập được vào Intent cơ bản hoặc dữ liệu có trong đó.
// ...
val replyPendingIntent = PendingIntent.getService(
context,
createReplyId(appConversation), // Method explained later.
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
// ...
Trước khi bạn thiết lập Action trả lời, hãy lưu ý rằng Android Auto có 3 yêu cầu đối với Action trả lời:
- Hành động semantic phải được đặt thành
Action.SEMANTIC_ACTION_REPLY. Actionphải cho biết rằng khi được kích hoạt, thao tác này sẽ không hiển thị giao diện người dùng nào.Actionphải chứa mộtRemoteInputduy nhất.
Mã mẫu sau đây thiết lập một Action trả lời đáp ứng các yêu cầu nêu trên:
// ...
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
}
Việc xử lý hành động đánh dấu là đã đọc cũng tương tự như vậy, ngoại trừ việc không có RemoteInput.
Do đó, Android Auto có 2 yêu cầu đối với Action đánh dấu là đã đọc:
- Thao tác ngữ nghĩa được đặt thành
Action.SEMANTIC_ACTION_MARK_AS_READ. - Thao tác này cho biết rằng khi được kích hoạt, thao tác sẽ không hiển thị giao diện người dùng nào.
Mã mẫu sau đây thiết lập một Action đánh dấu là đã đọc, đáp ứng các yêu cầu sau:
fun createMarkAsReadAction(
context: Context, appConversation: YourAppConversation): Action {
val markAsReadIntent = createMarkAsReadIntent(context, appConversation)
val markAsReadPendingIntent = PendingIntent.getService(
context,
createMarkAsReadId(appConversation), // Method explained later.
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
}
Khi tạo ý định đang chờ xử lý, bạn có thể dùng 2 phương thức sau: createReplyId() và createMarkAsReadId(). Các phương thức này đóng vai trò là mã yêu cầu cho từng PendingIntent (được Android dùng để kiểm soát các ý định đang chờ xử lý hiện có).
Các phương thức create() phải trả về mã nhận dạng duy nhất cho mỗi cuộc trò chuyện, nhưng các lệnh gọi lặp lại cho cùng một cuộc trò chuyện phải trả về mã nhận dạng duy nhất đã tạo.
Hãy xem xét ví dụ với 2 cuộc trò chuyện, A và B: Mã trả lời của cuộc trò chuyện A là 100 và mã đánh dấu là đã đọc là 101. Mã trả lời của cuộc trò chuyện B là 102 và mã đánh dấu là đã đọc là 103. Nếu cuộc trò chuyện A được cập nhật, thì mã trả lời và mã đánh dấu là đã đọc vẫn là 100 và 101. Để biết thêm thông tin, hãy xem PendingIntent.FLAG_UPDATE_CURRENT.
Tạo MessagingStyle
MessagingStyle là phần tử mang thông tin nhắn tin. Android Auto sử dụng phần tử này để đọc to từng tin nhắn trong một cuộc trò chuyện.
Trước tiên, bạn phải chỉ định người dùng thiết bị dưới dạng đối tượng Person, như trong ví dụ sau:
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()
// ...
Sau đó, bạn có thể tạo đối tượng MessagingStyle và cung cấp một số thông tin chi tiết về cuộc trò chuyện.
// ...
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)
// ...
Cuối cùng, hãy thêm các tin nhắn chưa đọc.
// ...
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
}
Đóng gói và gửi thông báo
Sau khi tạo các đối tượng Action và MessagingStyle, bạn có thể tạo và đăng 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)
}
Tài nguyên khác
- Google Design for Driving: Messaging apps (Google Design cho hoạt động lái xe: Ứng dụng nhắn tin)
- Tổng quan về thông báo
Báo cáo vấn đề về thông báo nhắn tin trên Android Auto
Nếu gặp sự cố trong khi phát triển thông báo nhắn tin cho Android Auto, bạn có thể báo cáo sự cố đó bằng Công cụ theo dõi lỗi của Google. Hãy nhớ điền tất cả thông tin được yêu cầu vào mẫu báo cáo lỗi.
Trước khi báo lỗi mới, hãy kiểm tra xem lỗi đó đã được báo cáo trong danh sách lỗi hay chưa. Bạn có thể đăng ký theo dõi và bình chọn cho các lỗi bằng cách nhấp vào dấu sao cho một lỗi trong công cụ theo dõi. Để biết thêm thông tin, hãy xem bài viết Đăng ký theo dõi lỗi.