Xây dựng ứng dụng nhắn tin cho Android Auto

Stay organized with collections Save and categorize content based on your preferences.

Việc duy trì kết nối qua tin nhắn có ý nghĩa quan trọng với nhiều người lái xe. Các ứng dụng nhắn tin có thể cho người dùng biết có cần đến đón con hay không hoặc địa điểm ăn tối đã thay đổi hay chưa. Khung Android cho phép các ứng dụng nhắn tin mở rộng dịch vụ sang trải nghiệm lái xe bằng một giao diện người dùng tiêu chuẩn giúp người lái xe vẫn tập trung quan sát đường đi.

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 Auto đang chạy. Những thông báo này hiển thị trong Auto 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à ít bị phân tâm. Ngoài ra, khi sử dụng API MessagingStyle, bạn sẽ được hưởng lợi từ những 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.

Bài học này giả định rằng bạn đã tạo 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. Bài học này sẽ hướng dẫn bạn cách mở rộng ứng dụng của mình để chuyển những tin nhắn đó sang một thiết bị Auto để hiển thị và trả lời.

Bắt đầu

Để cho phép ứng dụng của bạn cung cấp dịch vụ nhắn tin cho các thiết bị Auto, ứng dụng đó phải có khả năng thực hiện những việc sau:

  1. Tạo và gửi đối tượng NotificationCompat.MessagingStyle chứa các đối tượng Action trả lời và đánh dấu là đã đọc.
  2. Xử lý thao tác trả lời và đánh dấu cuộc trò chuyện là đã đọc bằng Service.
  3. Định cấu hình tệp kê khai của bạn để cho biết ứng dụng hỗ trợ Android Auto.

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. Cuộc trò chuyện (hoặc MessagingStyle) chứa tiêu đề, tin nhắn và thông tin cho biết liệu cuộc trò chuyện có diễn ra trong một nhóm (tức là cuộc trò chuyện có nhiều người nhận khác) 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ư tin nhắn mới), các ứng dụng sẽ đăng Notification lên hệ thống Android. Thông báo 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 qua màn hình của ô tô.

Các ứng dụng cũng có thể thêm đối tượng Action vào Notification để cho phép người dùng nhanh chóng trả lời tin nhắn hoặc đánh dấu là đã đọc ngay từ ngăn thông báo. Android Auto yêu cầu các đối tượng Action đánh dấu là đã đọc và trả lời để quản lý một cuộc trò chuyện.

Tóm lại, một cuộc trò chuyện được biểu thị bằng một đối tượng Notification. Đối tượng này được tạo kiểu bằng một đối tượng MessagingStyle. Đối tượng MessagingStyle chứa tất cả tin nhắn trong cuộc trò chuyện đó cùng với một hoặc nhiều đối tượng MessagingStyle.Message. Cuối cùng, để hoàn toàn tương thích với Android Auto, bạn phải đính kèm 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.

  1. Ứng dụng của bạn nhận được một tin nhắn.
  2. Sau đó, ứng dụng sẽ tạo thông báo MessagingStyleAction trả lời và đánh dấu là đã đọc.
  3. Android Auto nhận được sự kiện “thông báo mới” từ hệ thống Android và thấy MessagingStyle, Action trả lời cũng như Action đánh dấu là đã đọc.
  4. Android Auto tạo và hiển thị thông báo trên ô tô.
  5. Nếu người dùng nhấn vào thông báo qua 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.
  6. Nếu người dùng trả lời thông báo qua 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 Action trả lời rồi kích hoạt thao tác đó.
    • Ở 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 toàn bộ ứng dụng nhắn tin. Tuy nhiên, mã mẫu dưới đây chứa một số nội dung mà ứng dụng của bạn nên có trước khi bạn 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 tính 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 mà bạn nên tạo bằng đường dẫn sau: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml, trong đó bạn khai báo chức năng Android Auto mà ứng dụng của mình hỗ trợ. Ví dụ: để hỗ trợ thông báo, hãy đưa nội dung sau vào automotive_app_desc.xml của bạn:

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

Nếu ứng dụng của bạn yêu cầu tính năng hỗ trợ xử lý SMS, MMS và RCS, thì bạn cũng phải cung cấp thông tin sau:

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

Nhập thư viện cốt lõi AndroidX

Khi tạo thông báo để sử dụng với thiết bị Auto, bạn phải có thư viện cốt lõi AndroidX. Bạn có thể nhập thư viện này vào dự án của mình như sau:

  1. Trong tệp build.gradle cấp cao nhất, hãy nhớ bao gồm kho lưu trữ Maven của Google, như minh hoạ bên dưới.

Groovy

allprojects {
    repositories {
        google()
    }
}

Kotlin

allprojects {
    repositories {
        google()
    }
}
  1. Trong tệp build.gradle của mô-đun ứng dụng, hãy thêm phần phụ thuộc của thư viện cốt lõi AndroidX, như minh hoạ bên dưới:

    Groovy

    dependencies {
       implementation 'androidx.core:core:1.0.0'
    }
    

    Kotlin

    dependencies {
       implementation("androidx.core:core:1.0.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 thực hiện điều này thông qua việc sử dụng IntentService. IntentService mang lại sự linh hoạt khi xử lý các lệnh gọi có thể tốn kém ở chế độ nền và 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 (không nên nhầm với thao tác thông báo) là các chuỗi đơn giả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 Intent.action thay vì xác định nhiều IntentServices sẽ dễ dàng hơn.

Trong ứng dụng nhắn tin mẫu, chúng ta có 2 loại thao tác: trả lời và đánh dấu là đã đọc, như được khai báo 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 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 – và một khoá đầu vào từ xa, như được thảo luận chi tiết hơn ở phía cuối phần này. Mã mẫu sau đây tạo ra một dịch vụ như vậy.

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?) {
        // Fetch our internal data
        val conversationId = intent!!.getIntExtra(EXTRA_CONVERSATION_ID_KEY, -1)

        // And search our database for that conversation
        val conversation = YourAppConversation.getById(conversationId)

        // Then handle the action that was requested in the intent. The TODOs
        // are addressed in a section below.
        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ư minh hoạ dưới đây.

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

Tạo và xử lý ý định

Các ứng dụng khác không có cách nào để lấy Intent kích hoạt MessagingService vì mỗi Intent đều được chuyển vào ứng dụng bên ngoài thông qua PendingIntent. Do giới hạn này, bạn cần tạo đối tượng RemoteInput để cho phép những ứng dụng khác đó gửi lại văn bản "trả lời" cho ứng dụng của mình, như minh hoạ dưới đây.

/**
 * Creates a [RemoteInput] which allows remote apps to 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 via
    // static methods in the RemoteInput class.
}

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

    // This action string lets the MessagingService know this is a "reply" request.
    intent.action = ACTION_REPLY

    // Provide the conversation id so we know what conversation this applies to.
    intent.putExtra(EXTRA_CONVERSATION_ID_KEY, appConversation.id)

    return intent
}

Bây giờ, bạn đã biết nội dung sẽ chuyển vào Intent trả lời, hãy giải quyết TODO cho mệnh đề chuyển đổi ACTION_REPLY trong MessagingService và trích xuất thông tin đó, như minh hoạ dưới đây:

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

    // Note this conversation object came from above in the MessagingService
    conversation.reply(message)
}

Bạn sẽ xử lý Intent đánh dấu là đã đọc theo cách tương tự (nhưng không yêu cầu RemoteInput).

/** Creates an [Intent] which 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ã mẫu bên dưới giải quyết TODO cho mệnh đề chuyển đổi ACTION_MARK_AS_READ trong MessagingService:

// 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 của ứng dụng nhắn tin, chúng ta có thể chuyển sang cách tạo thông báo tương thích với Android Auto.

Tạo thao tác

Action là đối tượng có thể được chuyển vào ứng dụng khác thông qua Notification để kích hoạt các phương thức trong ứng dụng gốc. Đây là cách Android Auto có thể đánh dấu cuộc trò chuyện là đã đọc và trả lời cuộc trò chuyện đó.

Để tạo Action, hãy bắt đầu bằng Intent, như minh hoạ trong Intent "trả lời" ở bên dưới:

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

Sau đó, chúng ta sẽ bọc 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 đã bọc 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; nhưng tuyệt đối không cho phép ứng dụng bên ngoài truy cập vào Intent cơ bản hoặc dữ liệu trong đó.

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

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:

  • Thao tác ngữ nghĩa phải được đặt thành Action.SEMANTIC_ACTION_REPLY.
  • Action phả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.
  • Action phải chứa một RemoteInput duy nhất.

Mã mẫu sau đây thiết lập Action trả lời trong khi giải quyết các yêu cầu nêu trên:

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

        // The action doesn't show any UI (it's a requirement for apps to
        // not show UI for 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
}

Về thao tác đánh dấu là đã đọc, chúng ta cũng làm như vậy ngoại trừ 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.
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)
    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
}

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ạng đối tượng Person.

fun createMessagingStyle(
        context: Context, appConversation: YourAppConversation): MessagingStyle {
    // Method defined by our 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)

    // Set the conversation title. Note that if your app's target version is lower
    // than P, this will automatically mark this conversation as a group (to
    // maintain backwards compatibility). Simply 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 via a Person object
        val senderPerson = Person.Builder()
            .setName(appMessage.sender.name)
            .setIcon(appMessage.sender.icon)
            .setKey(appMessage.sender.id)
            .build()

        // And the message is added. Note that more complex messages (ie. images)
        // may 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 ActionMessagingStyle, bạn có thể tạo và đăng Notification.

fun notify(context: Context, appConversation: YourAppConversation) {
    // Create our Actions and MessagingStyle from the methods defined above.
    val replyAction = createReplyAction(context, appConversation)
    val markAsReadAction = createMarkAsReadAction(context, appConversation)
    val messagingStyle = createMessagingStyle(context, appConversation)

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

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

        // Add MessagingStyle.
        .setStyle(messagingStyle)

        // Add reply action.
        .addAction(replyAction)

        // Let's say we don't want to show our mark-as-read Action in the
        // notification shade. We can do that by adding it as invisible so it
        // won't appear in Android UI, but still satisfy Android Auto's
        // mark-as-read Action requirement. You're free to add both Actions as
        // visible or invisible. This is just a stylistic choice.
        .addInvisibleAction(markAsReadAction)

        .build()

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

Xem thêm

Báo cáo sự cố Nhắn tin trên Android Auto

Nếu gặp sự cố trong khi phát triển ứng dụng 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.

Báo lỗi mới

Trước khi báo lỗi mới, vui lòng 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ỏ phiếu 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 phần Đăng ký theo dõi lỗi.