在 Googlebook 上支援跨裝置持續性

使用者經常在 Android 手機上開始執行工作,例如查看文件、分類訊息或瀏覽各種項目,然後想改用 Googlebook,因為 Googlebook 的螢幕較大、有實體鍵盤,而且提供多視窗模式工作區。

Android 17 (API 級別 37) 以上版本的「繼續操作」功能,可讓 Googlebook 工作列顯示應用程式圖示 (稱為「建議」),方便使用者繼續在附近的手機上執行的活動。使用者只要按一下,即可在 Googlebook 上繼續先前的活動。

導入跨裝置交接功能後,應用程式就能轉移活動內容,讓使用者在適應性 Android 應用程式或電腦瀏覽器中,從手機上中斷的地方繼續工作流程。

選擇交接流程

啟用跨裝置接續功能前,請先決定使用者在 Googlebook 上選取「繼續在其他裝置上操作」建議時,手機應用程式中的各項活動應如何開啟:

交接流程 Googlebook 目標 適用情境 Key API
應用程式間交接 在電腦視窗中執行的原生 Android 應用程式 Google Play 上針對大螢幕和電腦分割視窗功能最佳化的應用程式 HandoffActivityData.Builder
應用程式間交接 (網頁備用) 如果已安裝,則為原生 Android 應用程式;否則為預設電腦版瀏覽器中的網址 應用程式已針對原生電腦視窗體驗進行最佳化,但如果 Googlebook 未安裝應用程式,則需要可靠的瀏覽器備援機制 setFallbackUri
直接交接給網頁 預設電腦瀏覽器中的網址 (或由原生應用程式攔截,前提是已設定 Android 應用程式連結) 網頁應用程式是主要電腦版體驗的工作流程 HandoffActivityData.createWebHandoff

為活動啟用交接功能

「繼續操作」預設為停用,且會針對每個活動進行設定。在手機上進行每項傳送活動時,請按照下列步驟操作:

  1. 活動載入移交所需的狀態後,請呼叫 setHandoffEnabled 並傳遞 true。啟用後,系統可以在活動位於前景或移至背景時,要求移交資料。
  2. 覆寫 onHandoffActivityDataRequested,傳回非空值的 HandoffActivityData 物件。您可以隨時查詢 isHandoffEnabled,確認活動是否已啟用交接功能。
import android.app.Activity
import android.app.HandoffActivityData
import android.app.HandoffActivityDataRequestInfo
import android.os.Bundle
import android.os.PersistableBundle

class DocumentEditorActivity : Activity() {

    private var documentId: String = "doc_42"
    private var scrollOffsetPx: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Enable handoff once the activity is ready to transfer state.
        setHandoffEnabled(true, null)
    }

    override fun onHandoffActivityDataRequested(
        handoffRequestInfo: HandoffActivityDataRequestInfo
    ): HandoffActivityData {
        val extras = PersistableBundle().apply {
            putString(EXTRA_DOCUMENT_ID, documentId)
            putInt(EXTRA_SCROLL_OFFSET, scrollOffsetPx)
        }

        return HandoffActivityData.Builder(getComponentName())
            .setExtras(extras)
            .build()
    }

    companion object {
        const val EXTRA_DOCUMENT_ID = "extra_document_id"
        const val EXTRA_SCROLL_OFFSET = "extra_scroll_offset"
    }
}

交接的套件活動狀態

系統會在以下兩種情況呼叫 onHandoffActivityDataRequested:

  • 前景交接:活動在手機上處於前景,使用者從 Googlebook 工作列啟動交接。
  • 擷取背景狀態:活動會移至手機背景,系統會快取交接酬載,以防使用者稍後在 Googlebook 上選取建議。

檢查交接要求是否有效

使用 isActiveRequest 檢查 HandoffActivityDataRequestInfo,區分使用者主動發起的交接和背景儲存作業:

  • 如果 isActiveRequest 傳回 true,表示使用者在活動顯示期間主動要求在其他裝置上繼續操作。
  • 如果 isActiveRequest 回傳 false,系統會在活動停止時快取狀態。

如果在手機上顯示視覺指標,確認工作正在移至 Googlebook,請以非同步方式排定 UI 更新作業,確保 onHandoffActivityDataRequested 立即傳回。

附加狀態額外資訊和網頁備用內容

HandoffActivityData 酬載包含下列資訊:

  • 目標元件 (應用程式間交接作業的必要條件):傳遞 this.getComponentName() (活動的 ComponentName) 或明確的 ComponentName(this, TargetActivity::class.java) 至 HandoffActivityData.Builder,指定要在 Googlebook 上啟動的活動。
  • 其他 (選用):將 PersistableBundle 傳遞至 setExtras(),其中包含 ID 和 UI 座標 (例如開啟的文件 ID、有效索引標籤鍵或捲動位置)。組合包大小不得超過 50 KB。
  • 後備 URI (選用):將網頁 Uri 傳遞至 setFallbackUri,這樣一來,如果未安裝 Android 應用程式,Googlebook 就能在預設瀏覽器中開啟工作。
import android.app.Activity
import android.app.HandoffActivityData
import android.app.HandoffActivityDataRequestInfo
import android.app.HandoffActivityParams
import android.net.Uri
import android.os.Bundle
import android.os.PersistableBundle

class ArticleReaderActivity : Activity() {

    private var articleId: String = "android-desktop-windowing"
    private var readingProgressPercent: Int = 45

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setHandoffEnabled(
            true,
            HandoffActivityParams.Builder()
                .setAllowHandoffWithoutPackageInstalled(true)
                .build()
        )
    }

    override fun onHandoffActivityDataRequested(
        handoffRequestInfo: HandoffActivityDataRequestInfo
    ): HandoffActivityData {
        if (handoffRequestInfo.isActiveRequest()) {
            window.decorView.post {
                onHandoffStartedToSecondaryDevice()
            }
        }

        val extras = PersistableBundle().apply {
            putString(EXTRA_ARTICLE_ID, articleId)
            putInt(EXTRA_READING_PROGRESS, readingProgressPercent)
        }

        val fallbackWebUri = Uri.Builder()
            .scheme("https")
            .authority("example.com")
            .appendPath("articles")
            .appendPath(articleId)
            .appendQueryParameter("progress", readingProgressPercent.toString())
            .build()

        return HandoffActivityData.Builder(getComponentName())
            .setExtras(extras)
            .setFallbackUri(fallbackWebUri)
            .build()
    }

    private fun onHandoffStartedToSecondaryDevice() {
        // Save local drafts and update phone UI without blocking handoff.
    }

    companion object {
        const val EXTRA_ARTICLE_ID = "extra_article_id"
        const val EXTRA_READING_PROGRESS = "extra_reading_progress"
    }
}

設定直接轉移至網站

如果 Googlebook 的主要電腦體驗是網頁應用程式,請傳遞 HandoffActivityParams (含 setAllowHandoffWithoutPackageInstalled(true) 至 setHandoffEnabled,並呼叫 HandoffActivityData.createWebHandoff,而非使用 HandoffActivityData.Builder:

import android.app.Activity
import android.app.HandoffActivityData
import android.app.HandoffActivityDataRequestInfo
import android.app.HandoffActivityParams
import android.net.Uri
import android.os.Bundle

class WebDashboardActivity : Activity() {

    private var projectId: String = "proj_901"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setHandoffEnabled(
            true,
            HandoffActivityParams.Builder()
                .setAllowHandoffWithoutPackageInstalled(true)
                .build()
        )
    }

    override fun onHandoffActivityDataRequested(
        handoffRequestInfo: HandoffActivityDataRequestInfo
    ): HandoffActivityData {
        val webUri = Uri.Builder()
            .scheme("https")
            .authority("app.example.com")
            .appendPath("projects")
            .appendPath(projectId)
            .build()

        return HandoffActivityData.createWebHandoff(webUri)
    }
}

在 Googlebook 上接收及還原狀態

使用者在 Googlebook 工作列中選取「繼續使用」建議時,系統會在桌面視窗中啟動目標 ComponentName,並將 PersistableBundle 額外內容附加至啟動 Intent。

在 Googlebook 上處理傳入的交接作業時,請考量電腦分割視窗模式和多窗格版面配置:

  1. 擷取交接額外項目:在接收活動的 onCreate 和 onNewIntent 方法中,從傳入的 Intent 擷取額外項目。處理 onNewIntent 可確保活動使用 singleTop 啟動模式或路徑至現有電腦視窗時,視窗會更新為交接項目。
  2. 配合多窗格版面配置調整狀態:在手機上佔用全螢幕詳細資料檢視畫面的狀態,通常會對應到 Googlebook 上清單詳細資料或支援窗格版面配置中的所選項目。填入主要清單選取項目和詳細資料窗格,讓電腦視窗擁有完整脈絡。
  3. 在調整視窗大小時保留還原的狀態:將移交的狀態儲存在 Jetpack Compose 的 ViewModel (SavedStateHandle) 或 rememberSaveable 中,這樣在 Googlebook 上任意調整視窗大小時,就不會重設還原的工作階段。
import android.app.Activity
import android.content.Intent
import android.os.Bundle

class DocumentEditorReceiverActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Restore from the handoff intent only on initial launch so subsequent
        // desktop window configuration changes use saved instance state.
        if (savedInstanceState == null) {
            handleHandoffIntent(intent)
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        handleHandoffIntent(intent)
    }

    private fun handleHandoffIntent(incomingIntent: Intent?) {
        val documentId = incomingIntent?.getStringExtra(EXTRA_DOCUMENT_ID)
            ?: return
        val scrollOffsetPx = incomingIntent.getIntExtra(EXTRA_SCROLL_OFFSET, 0)

        openDocumentInDesktopWorkspace(
            documentId = documentId,
            initialScrollOffsetPx = scrollOffsetPx
        )
    }

    private fun openDocumentInDesktopWorkspace(
        documentId: String,
        initialScrollOffsetPx: Int
    ) {
        // Load the document into the list-detail layout and restore scroll position.
    }

    companion object {
        const val EXTRA_DOCUMENT_ID = "extra_document_id"
        const val EXTRA_SCROLL_OFFSET = "extra_scroll_offset"
    }
}

測試手機和 Googlebook 之間的接續功能

如要進行端對端驗證,請按照下列步驟操作:

  1. 準備兩部裝置
    • 在 Android 手機 (搭載 Android 17 以上版本) 和 Googlebook (或以電腦分割視窗模式執行的測試裝置) 上登入同一個 Google 帳戶。
    • 開啟藍牙,並將兩部裝置連上相同的 Wi-Fi 網路。
    • 在兩部裝置上依序開啟「設定」>「已連結的裝置」>「連線偏好設定」>「跨裝置服務」>「繼續先前的活動」,然後按照設定提示操作,並啟用「工作」。如需完整的環境必要條件,請參閱「設定及測試」。
  2. 安裝應用程式
    • 如要進行應用程式間交接,請在手機和 Googlebook 上安裝建構版本。
    • 如果是網頁備援或直接移交至網頁,請在手機上安裝建構版本,並在 Googlebook 上解除安裝應用程式,以便確認瀏覽器是否開啟預期網址。
  3. 觸發交接
    • 在手機上開啟應用程式,然後前往可啟用交接的活動 (撥打電話 setHandoffEnabled(true, null) 進行應用程式間交接,或在 Googlebook 上測試時,傳遞 HandoffActivityParams 和 setAllowHandoffWithoutPackageInstalled(true))。
    • 在 Googlebook 上,查看工作列右側是否有應用程式圖示, 並選取附有「在其他裝置上繼續」徽章的圖示。
  4. 確認桌面還原作業
    • 確認應用程式在電腦視窗 (或瀏覽器分頁) 中開啟時,會顯示手機上完全相同的項目和捲動位置。
    • 在 Googlebook 上調整應用程式視窗大小,並確認還原的內容和選取項目在視窗大小類別轉換期間會持續存在。

其他資源