開發人員關係工程師
使用者會在手機、穿戴式裝置和音訊周邊裝置之間切換,因此提供流暢的跨裝置體驗,是提升參與度的關鍵。不過,如果採用標準網路做法,往往會導致電池耗電、延遲時間激增,以及使用者工作階段不連貫。
本深度探討課程將介紹專為支援多種裝置通訊打造的 Android API,提供記憶體和省電模式,讓裝置間的轉換即時、可靠且直覺。
轉錄稿
大家好,
歡迎參加「實現流暢的跨裝置互動」課程。
我是 Hanrui,今天我的同事 Yacine 和 Nataraj 也會一起主持。
談到連線能力時,我們通常會想到基本的網路連結或藍牙頭戴式耳機。
在 Android 上,連線能力遠不只如此。
重點在於讓裝置搭配運作,相得益彰。
今天,我們將逐步說明常見的跨裝置情境,並介紹適合各情境的 API。
在很長一段時間內,我們都只為單一裝置 (手機) 建構應用程式。
接著,穿戴式裝置開始蓬勃發展。
不久後,智慧手錶與手機配對、在背景同步資料或直接接聽來電,就成了非常普遍的做法。
新增耳機,新增無線耳機。
現在,使用者可以在通話期間切換音訊裝置,從手錶、手機、耳機到車輛都能切換。
最近,許多使用者也會攜帶第二部大螢幕裝置,例如摺疊式裝置或平板電腦。
他們預期應用程式能在這些裝置上順暢運作。
這項功能也延伸到筆電和 Chromebook,使用者可以直接在桌上執行 Android 應用程式並保持連線。
早在 2023 年,一般使用者就擁有三到四部裝置。
使用者對這些裝置的期望不只是獨立功能。
他們希望獲得更優質的搭配使用體驗。
當使用者身處同一個空間 (就像我們今天一樣),並想輕鬆與身邊的人分享相片、連結或應用程式時,這種「合作無間」的體驗就特別明顯。
從數據來看,這個生態系統規模龐大,而且成長迅速。
今年全球約有 220 億部物聯網裝置,年增率為 14%。
在所有端點中,Wi-Fi 占三分之一,藍牙占四分之一。
跨裝置連線能力不再是邊緣案例。
已成為日常生活的一部分。
然而,目前大多數應用程式仍以低階網路和藍牙 API 建構跨裝置功能,這會造成三個問題。
首先是電池和記憶體。
持續執行背景掃描和服務會耗用電池和記憶體。
第二,延遲時間。
無法預測的延遲會干擾即時通話和資料同步。
第三,連線不順暢。
管理所有裝置的探索、配對和重新連線功能相當複雜,容易導致使用者體驗不佳。
為解決這些難題,我們在三大領域打造了專用 API,讓跨裝置功能變得非常簡單。
我會先介紹隨附應用程式,接著由 Nataraj 說明 VoIP 應用程式,最後由 Yacine 介紹 P2P 共用。
首先,我們來看看隨附應用程式。
隨附應用程式是指安裝在手機上的應用程式,可讓智慧手錶或葡萄糖感應器等配對裝置與手機搭配運作。
本節將著重介紹藍牙連線裝置。
配對裝置通常分為四類。
首先是醫療器材,例如血糖感測器和生命徵象監測器。
以及智慧手錶和健身手環等穿戴式裝置。
耳機,具備快速配對和智慧控制功能。
車用套件,用於數位車鑰和免持通話等功能。
每個類別都有自己的主要使用者歷程,但所有類別都仰賴裝置和手機之間的可靠連線。
對醫療器材而言,這項連線甚至可能攸關生命。
以下介紹這幾類客戶的四種常見需求。
首先是裝置管理。
應用程式必須在附近所有裝置中找到正確的裝置 (例如這裡的手錶,而非耳機或喇叭),然後啟動配對流程進行連線。
第二,檢查是否存在。
應用程式需要知道裝置何時在附近,何時不在。
當裝置出現時,應用程式需要一種喚醒方式,以便在使用者未開啟應用程式的情況下執行工作。
第三,維護連線。
連線後,應用程式會希望連線穩定且持續。
如果連線中斷 (例如使用者走出範圍後又返回),應用程式必須自動重新連線,即使當時並未執行也一樣。
第四,資料同步和移轉。
這才是真正的價值所在。
同步感應器讀數、推送韌體更新,或將設定傳送至裝置。
這對醫療器材來說至關重要。
如果錯過讀數,可能會錯過警示。
這些都需要應用程式程序在適當時間保持運作並連線。
那麼,應用程式如何滿足所有這些需求?
傳統上,應用程式高度仰賴前景服務。
以下是常見的實作方式。
如果您曾建構隨附應用程式,可能對此不陌生。
您需要宣告藍牙掃描和連線權限、前景服務權限、連結裝置的前景服務類型,以及通知權限,因為前景服務必須顯示永久通知。
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />接著,您會執行黏性服務,讓應用程式程序保持運作。
class BleForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
…
return START_STICKY
}
}雖然這個方法可行,但由於服務會持續執行,即使裝置不在附近也一樣,因此會消耗電池和記憶體。
因此,我們打造了隨附裝置管理工具 (簡稱 CDM)。
首先,系統可以根據裝置存在狀態喚醒應用程式,只要裝置保持在範圍內,系統就會讓應用程式程序保持運作。
不需要前景服務。
其次,CDM 支援裝置設定檔。
有了裝置設定檔,使用者只要輕觸一下,就能授予應用程式所需的所有權限,不必逐一回應權限對話方塊。
這能大幅改善使用者體驗和轉換率。
手錶設定檔自 Android 12 起提供,眼鏡設定檔自 Android 14 起提供。
Android 17 新增了兩個設定檔,專為醫療器材和運動智慧手環設計。
目前也有一些僅限系統應用程式使用的特殊權限設定檔。
如果生態系統有更廣泛的需求,我們可能會考慮開放更多開發人員使用。
CDM 使用起來非常簡單。
以醫療器材為例。
首先,請宣告必要權限:一項用於醫療器材、一項用於在背景執行,另一項用於觀測裝置是否存在。
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_MEDICAL" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />值得注意的是,與我們剛才看到的前景服務實作權限不同,這裡列出的所有權限都是一般權限,也就是說,系統會在安裝時授予這些權限,不會顯示提示對話方塊。
使用者只需核准關聯。
現在來看看如何建立關聯。
我們在這裡建立關聯要求,並加入裝置篩選器,只顯示正確的裝置。
val paringRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(true)
.setDeviceProfile(AssociationRequest.DEVICE_PROFILE_MEDICAL)
.build()
companionDeviceManager.associate(paringRequest, ContextCompat.getMainExecutor(this),
object : CompanionDeviceManager.Callback() {
override fun onAssociationPending(intentSender: IntentSender) {
intentSenderLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
}
override fun onAssociationCreated(associationInfo: AssociationInfo) { … }
override fun onFailure(error: CharSequence?) { … }
}
)setSingleDevice 設為 true,因為我們只需要一個相符項目,並將設定檔設為醫療。
然後呼叫 associate。
CDM 會處理掃描作業。
在 onAssociationPending 中,我們會取得 IntentSender。
啟動後,系統會顯示關聯對話方塊。
有了醫療設定檔,只要輕觸一下,不僅能核准關聯本身,還能授予設定檔所需的所有權限,甚至略過「零打擾」模式,這對重大警示至關重要。
使用者輕觸「允許」後,我們會透過 AssociationInfo 取得 onAssociationCreated,其中包含關聯 ID。
接著,我們可以使用該關聯 ID 呼叫 startObservingDevicePresence,觀察裝置是否在場。
您也需要實作擴充 CompanionDeviceService 的服務。
class BleCompanionDeviceService : CompanionDeviceService() {
override fun onDevicePresenceEvent(event: DevicePresenceEvent) {
when (event.event) {
DevicePresenceEvent.EVENT_BLE_APPEARED -> onDeviceAppeared(event.associationId)
DevicePresenceEvent.EVENT_BLE_DISAPPEARED -> onDeviceDisappeared(event.associationId)
}
}
private fun onDeviceAppeared(associationId: Int) { … }
private fun onDeviceDisappeared(associationId: Int) { … }
}當裝置進入範圍時,即使應用程式未執行,系統也會繫結至您的服務。
系統會使用 EVENT_BLE_APPEARED 呼叫 onDevicePresenceEvent。
接著,你就可以連結並同步。
裝置離開時,你會收到EVENT_BLE_DISAPPEARED。
接著,您就可以進行清理。
沒有常駐服務,也沒有僅用於維持運作的通知。
不過,CDM 確實有一些限制,開發人員必須留意。
首先是 OS 版本。
大部分 CDM 功能是在 Android 12 和 13 中推出。
不過,Android 16 推出了一些很棒的改善項目,Android 17 則推出新的設定檔。
因此,請在執行階段檢查 OS 版本,並為舊版本保留備用方案。
不過,目標 SDK 每年都會提高,最低 SDK 也會隨之提高。
為解決應用程式目前和未來面臨的記憶體壓力,強烈建議您盡快從前景服務遷移至 CDM。
如果 CDM 目前不支援您的用途,請告訴我們。
其次,CDM 目前支援背景掃描,但不支援背景廣告。
我們很少聽說有這項需求。
不過,如果您的應用程式需要手機做為周邊裝置,並持續在背景廣播,我們很樂意進一步瞭解。
第三,CDM 會使用低功耗掃描模式。
這項功能會稍微犧牲即時回應能力,以節省電池和記憶體用量。
因此,請不要期待能立即喚醒。
最後,不窺探酬載。
您無法註冊特定特徵或廣告資料。
但只要裝置進入範圍,應用程式就會喚醒。
因此,它可視需要連線並檢查酬載。
以上就是隨附應用程式和 CDM 的介紹。
接下來,Nataraj 會介紹 VoIP 應用程式。
如果應用程式擁有專屬周邊裝置,且會持續與其通訊,則 CompanionDeviceManager 是合適的工具。
大家好。
我是 Nataraj K R。
接下來,我會介紹下一個跨裝置情境:即時通話。
現在,人們處理來電的方式正在改變。
超過 40% 的通話是透過 VoIP 網路進行。
Android 在其中扮演重要角色。
事實上,有 71% 的通話是在 Android 裝置上進行,幾乎每兩通電話就有一通。
不只是通話。
使用者擁有的裝置生態系統也大幅成長。
目前有超過 2.5 億輛汽車搭載 Android Auto,我們也向使用者提供超過 10 億部藍牙裝置。
耳機、Wear OS 手錶、Android 手機、搭載 Android Auto 的車輛、藍牙喇叭,都是使用者在手機周圍擁有的裝置,他們希望通話能在這些裝置之間順暢切換。
舉例來說,使用者接到來電時,會看一眼手錶,瞭解來電者資訊。
他們會在手錶上接聽來電。
他們會視需要將麥克風設為靜音或取消靜音,並戴上耳塞式耳機繼續通話。
如果他們想出門,就會進入車內,並希望通話能流暢切換到車內。
因此,這已成為使用者的日常,並非優質體驗。
對使用者來說,這是基本要求,而 Android 開發人員有責任在 VoIP 應用程式中,為使用者提供流暢的體驗。
但如果嘗試從頭開始自行建構,最終會面臨三大問題。
第一,藍牙通訊端的分散問題。
由於系統不知道您的通話正在處理通話,因此您最終會建構自訂藍牙通訊端和自訂媒體工作階段,以便將通話投射到手錶或 Android Auto。
第二個問題是音訊焦點。
因為你必須處理低階 AudioManager API 和藍牙 API,所以會發生音訊外洩,而且這不是系統的優先事項,對吧?
應用程式正在進行 VoIP 通話,因此每當有電信業者來電時,就會中斷通話,且數據機也會擷取音訊,導致應用程式沒有音訊。
更糟的是,即使行動網路通話結束,音訊焦點也不會回到應用程式。
第三個問題是同步處理。
使用者在手錶上輕觸靜音,但手機 UI 仍會顯示通話未靜音。
這會造成很大的混淆。
使用者從車輛方向機柱掛斷電話,但手機仍顯示通話中的通知。
發生這種情況時,使用者會感到非常挫折。
因此,為瞭解決上述所有問題,我們打造了 Jetpack Telecom。
因此,您可以使用 CallsManager 向 Jetpack Telecom 註冊通話一次,Jetpack Telecom 就會做為中央系統通話協調工具。
負責將通話投射到手錶、Android Auto 和使用者擁有的其他遠端介面。
此外,它也會處理音訊焦點。
最重要的是,所有功能都能順暢運作,甚至支援 Android 8。
將通話投射到手錶或車輛螢幕上只是其中一部分,因為使用者顯然也想透過這些裝置控制通話狀態。
他們希望視需要透過手錶將通話設為靜音或取消靜音。
他們想透過車輛方向機柱或輕觸兩下耳機來掛斷通話。
因此,Jetpack Telecom 會監聽這些硬體事件,並在 CallControlScope 集中顯示為回呼。
您只需要監聽這些回呼、處理通話引擎,並據此更新通話狀態。
Jetpack Telecom 則負責在裝置之間同步處理通話狀態。
因此,程式庫在幕後提供兩大好處。
首先,這顯然能解決我們談到的所有問題。
因此,您不必處理低階 AudioManager API,程式庫會提供宣告式呼叫端點、管理音訊焦點,並在裝置之間同步處理。
負責在裝置之間轉接通話。
此外,也能處理行動通訊來電。
不過,這個程式庫還有另一個好處,那就是它是以 Kotlin 為基礎建構而成,因此每當您使用 Jetpack Telecom 新增通話時,系統就會開啟 Kotlin 協同程式範圍。
這樣一來,就能充分利用 Kotlin 協同程式,且 Kotlin 協同程式會持續運作,直到呼叫結束。
因此,通話中斷後,系統會自動清除與通話相關的所有工作。
這樣一來,就不會有任何惡意工作,且程式庫也會處理呼叫生命週期。
現在,您可能會想知道這個程式庫很棒,而且您想要實作,對吧?
只要簡單三個步驟即可。
步驟 1:您必須宣告依附元件。
接著,宣告必要權限 MANAGE_OWN_CALLS,然後例項化 CallsManager 物件。
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />將 CallsManager 物件例項化後,請註冊應用程式,並提供您要透過應用程式提供的功能,例如視訊通話或通話串流。
// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)
callsManager.registerAppWithTelecom(
capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)接著是步驟 2。
您已向 Jetpack Telecom 註冊應用程式,現在可以處理來電和撥出電話了。
為此,您需要建構 CallAttributesCompat 物件,其中包含通話的中繼資料,例如來電者或被來電者的 displayName、地址 (即來電者端點)、方向 (指定通話是來電還是撥出電話,以便 Jetpack Telecom 在這個遠端介面上顯示相應的 UI),以及 callType (指定通話是語音通話還是視訊通話,以便取得所需資源)。
val attributes = CallAttributesCompat(
displayName = "Kukk Nat",
address = Uri.parse("tel:+16505550199"),
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL
)
callsManager.addCall(
attributes, onAnswer, onDisconnect, onSetActive, onSetInactive
) {
// Coroutine CallControlScope active for the duration of the call
}中繼資料物件準備就緒後,請使用 callsManager.addCall 方法新增。
這會開啟通話控制協同程式範圍。
新增來電後,接著是步驟 3。
步驟 3 是監聽使用者在這些遠端介面上執行的動作。
因此,由於您有 CallControlScope,只需收集 Kotlin 流程即可。
callsManager.addCall(attributes, onAnswer, onDisconnect, onSetActive, onSetInactive) {
// 1. React to hardware mute toggles from Watch, Car, or Headset
launch {
isMuted.collect { muted -> audioEngine.setMicMuted(muted) }
}
// 2. Observe available endpoints & request declarative switches
launch {
currentCallEndpoint.collect { endpoint -> updateUi(endpoint) }
}
requestEndpointChange(preferredBluetoothEndpoint)
}您可以收集 isMuted,在使用者透過遠端介面將通話設為靜音時做出反應,並據此更新通話狀態。
收集 currentCallEndpoint,瞭解目前通話的端點,以便據此更新 UI。
你可以在手機 UI 上顯示特定裝置。
每當使用者想切換裝置時,只要使用偏好的端點呼叫 requestEndpointChange 方法即可。
就是這麼簡單。
完成這三個步驟後,應用程式就已整合 Jetpack Telecom,可以開始使用。
但這還不是全部。
Jetpack Telecom 1.1.0 帶來更多功能,例如整合通話記錄,也就是所有 VoIP 通話現在都會原生顯示在系統撥號工具中。
這樣一來,使用者就能直接從系統撥號程式,一鍵回撥到您的應用程式。
因此,您不必在所有應用程式中搜尋特定應用程式。
他們只要從熟悉的系統撥號程式啟動回撥即可。
不過,我們也提供精細的控制選項。
如果不想將特定呼叫記錄到系統中,只要在 CallAttributesCompat 物件中將 isLogExcluded 標記設為 true 即可。
只要將我剛才提到的中繼資料物件設為 true,系統就不會記錄通話,也不會顯示在系統撥號器中。
1.1.0 版的第二項實用功能是 5G 網路切片。
因此,在支援的電信業者網路上,網路會提供優質的網路切片,也就是說,針對特定用途,電信業者會提供低延遲且無其他中斷的網路切片,讓您享有更優質的體驗。
因此,在支援的電信業者網路上,您不需額外設定,就能直接使用這項功能,甚至不需要撰寫任何一行程式碼。
整合程式庫後,通話就會利用這些網路切片,縮短延遲時間並提升效能。
但如果因故不想使用這項功能,也可以使用 CAPABILITY_OPT_OUT_OF_PREMIUM_NETWORK 旗標選擇不採用。
Jetpack Telecom 已由 Google Meet、WhatsApp、KakaoTalk 和 Zoho Arattai 等全球頂尖通訊應用程式導入。
現在,您需要將這個程式庫整合到應用程式中,為使用者提供更流暢、更優質的體驗。
以上就是即時通話功能的介紹。接下來,我要將麥克風交給優秀的同事 Yacine,由他介紹「輕碰分享」功能。
感謝您!
各位,我知道大家有點累了,別擔心,這是最後一個部分。
因此,我會介紹點對點檔案共用,但其他用途也可以透過點對點共用完成。
我們觀察到,在整個行動生態系統中,每天有超過 140 億張相片和影片在社群媒體和訊息應用程式上分享,數量非常龐大。
而且這些檔案大多是透過雲端分享,因此你拍下相片後,要透過你偏好的服務上傳,然後對方也必須透過相同的方式下載。
當你邀請某人或朋友加入社群網路,或邀請某人協作播放清單或文件時,通常必須輸入他們的使用者名稱或電子郵件,這在鍵盤上需要花一點時間。
你可能會犯錯,這其實不是很好的體驗,尤其是當你們兩個都坐在彼此旁邊時,所以就像是,哦,我必須透過雲端傳送,我必須等待。
但效果不佳。
現在,我們都有各種精美的手機、裝置、平板電腦、折疊式裝置,並用這些裝置拍出精彩相片。
但透過部分社群媒體或訊息應用程式分享時,基於效能和儲存空間考量,相片會經過壓縮,因此在精美螢幕上顯示時,會出現明顯像素化或畫質不佳的情況。
這絕對不是你希望在頂級裝置上獲得的體驗。
第二點,現在的檔案其實相當大。
一張相片的大小很容易就達到 10 MB,5 分鐘的影片則約為 500 MB,因此上傳和下載這些內容都需要相當長的時間。
最後,在另一端,您必須等待該延遲。
因此,有時您可能會搭上公車,車上很擁擠,而且公車在移動時,您沒有最佳的延遲時間、最佳的網路覆蓋範圍,有時檔案傳輸會中斷,有時因為延遲時間真的很長,您可能會逾時,檔案傳輸會取消,這對使用者來說真的很令人沮喪。
因此,與其依賴雲端分享所有檔案,並等待所有檔案上傳,不如使用藍牙或 Wi-Fi 等區域網路。
但如果想自行完成,顯然必須處理藍牙管理員、Wi-Fi 管理員,還得自行編寫通訊協定,並非每個人都想花時間做這些事。
但其實 Android 已經有類似功能。
這項功能已內建於裝置中,你可能聽過,那就是「快速分享」。
Android 的快速分享功能已在系統中提供這種抽象化機制,您只要與其他裝置分享檔案,不必管理所有相關作業。
這項功能已在分享工作表中提供,並會在兩部裝置之間切換最適合裝置的通訊協定,而且適用於 Marshmallow 以上版本,這在現今是相當龐大的受眾群體,對吧?
最後,您在側邊看到的熟悉使用者介面和使用者體驗,確實能協助使用者。
因此,他們知道使用者介面是什麼,不需要學習如何使用,因為他們過去已經看過了。
但我們知道開發人員也想擁有自己的體驗。
您比我們更瞭解自己的應用程式和使用者。
因此,何不直接在應用程式中加入「快速分享」功能?
因此,我們開始與開發人員討論,發現「快速分享」已相當普及。
Google Play 支援 Android、Chromebook、Windows,以及最近加入的 Google 圖書,涵蓋超過 30 億部裝置,規模相當龐大。
Pixel 11 還支援輕碰分享功能。
因此,你不需要選擇要分享的裝置,只要輕觸一下,我們就會透過 NFC 直接為你偵測。
因此,你可以選取相片或檔案,然後輕觸兩部裝置,就能直接透過碰觸分享。
我們也知道,有些開發人員希望自行掌控體驗。
我們知道,這是讓使用者能使用特定功能 (在本例中為檔案共用) 的最佳方式。
因此,何不在應用程式中嵌入「快速分享」功能?
因此,我們明年將直接推出「快速分享」SDK。
您可以直接在應用程式中使用這項功能,而且這項功能已內建安全性,可在兩部裝置之間進行端對端加密。
由於傳輸作業是在這些區域網路之間進行,因此傳輸速度會達到最快,這在住家 Wi-Fi 網路中顯而易見,但兩部裝置之間也可以自動建立熱點,你不需要手動設定。
如果裝置支援 Wi-Fi Direct,則已內建這項功能。
使用 Pixel 絕佳相機拍攝的相片和影片,也會保留最佳解析度和細節,在大螢幕上看起來更清晰。
由於這項功能仰賴區域網路,因此即使在倫敦地鐵最深處,你仍可透過網路與好友分享內容。
即使在飛機上,你也能在幾千英尺的高空中分享檔案。
最後,對開發人員來說,這能大幅節省資料成本,因為使用者不必使用數據方案,這對旅客來說非常重要。
此外,您也不必維護自己的伺服器。
去年,我們向開發人員和使用者提到,他們將能透過「快速分享」功能,使用 AirDrop 與 iPhone 裝置分享檔案。
現在,我們也透過「快速分享」為開發人員提供相同功能。
我們將推出「快速分享」iOS SDK,讓 iOS 應用程式開發人員能跨平台分享檔案,不受裝置限制。
以上是精美的簡報,現在我們來快速看看程式碼。
我們盡可能簡化了這項程序。
您需要啟動 QuickShareClient,並附加要分享的檔案。啟動工作階段後,您會看到右側這個精美的互動視窗,底部功能表會顯示要傳送的檔案預覽。如果您認為內容較為敏感,也可以啟用畫面上顯示的選項。
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val attachment = FileAttachment(
fileName = "report.pdf",
size = 1_048_576L,
uri = fileUri,
mimeType = "application/pdf",
// Sensitive content will be masked in preview
isSensitiveContent = false,
)
val content = ShareableContent.withFiles(listOf(attachment))
val request = SharingRequest.withContent(content)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)如果想在應用程式中接收檔案,做法也一樣。當然,在這種情況下,您不必新增附件,而且工作階段開始後,您就不必處理任何相關事宜。
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val request = SharingRequest.withoutContent()
val session = quickShareClient.startSharingSession(request, lifecycleOwner)
// Open the Quick Share overlay in receive mode
session.launchWindow(activity, contentSelectionIntent = null)到目前為止,您看到的程式碼只有 20 行。
我們知道,透過快速分享和輕碰分享功能,只要輕觸裝置就能分享檔案,這也確實能實現新的用途。
您的應用程式可能會在其他裝置上關閉,只要輕觸一下,檔案就會立即傳輸,您的應用程式也會自動開啟,您將收到 URI,以便在裝置上進行處理,或直接在應用程式中向使用者顯示內容。
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val urlAttachment = TextAttachment(
text = groupAddUrl,
type = TextAttachment.Type.URL,
)
val content = ShareableContent.withText(urlAttachment)
val appLink = AppLink("https://myapp.com/group/invite")
val request = SharingRequest.withContent(content, appLink)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)因此,您不必在接收裝置上開啟應用程式。
輕碰分享功能會自動開啟該應用程式。
當然,如果您希望 UI 對檔案傳輸做出反應,只要使用 Kotlin Flow,就能處理所有事件。
// Final API may undergo further updates
scope.launch {
for (event in session.sharingEvents) {
when (event) {
is SharingEvent.SendComplete ->
println("Send finished")
is SharingEvent.ReceiveComplete ->
println("Received, files=${event.content?.files?.size}")
else -> println("other event")
}
}
}今天介紹了許多內容,現在來總結一下。
別擔心,您會有幾分鐘的休息時間。
如 Hanrui 先前所述,CompanionDeviceManager 是處理任何藍牙互動、持續藍牙互動的最佳 API。
如果應用程式有任何網路語音通話功能,不一定要是主要用途,例如應用程式有支援用戶端,使用者可以撥打電話給支援團隊。
在這種情況下,Jetpack Telecom 也是在 Android 上處理網路語音通話的最佳方式。
最後,如要分享點對點檔案或開啟應用程式,只要輕碰分享,快速分享功能就是最佳解決方案。
感謝各位,在大家離開前,我想提醒一下,我們稍後會在外面回答各位的問題。
如果對我們介紹的用途有任何疑問,歡迎在諮詢時間提問。
當然,也別忘了在社群媒體上分享你從演講或其他講座中學到的知識。
我們有 #AndroidDevSummit。
再次強調,我們非常重視參與度,希望大家能真正享受活動。
這讓我們知道,我們確實有一個想再次見到的社群。
別忘了到服務台領取社群吊飾,掛在 Android 機器人鑰匙圈上。
我做得對嗎?
感謝您!
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />class BleForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
…
return START_STICKY
}
}<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_MEDICAL" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />val paringRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(true)
.setDeviceProfile(AssociationRequest.DEVICE_PROFILE_MEDICAL)
.build()
companionDeviceManager.associate(paringRequest, ContextCompat.getMainExecutor(this),
object : CompanionDeviceManager.Callback() {
override fun onAssociationPending(intentSender: IntentSender) {
intentSenderLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
}
override fun onAssociationCreated(associationInfo: AssociationInfo) { … }
override fun onFailure(error: CharSequence?) { … }
}
)class BleCompanionDeviceService : CompanionDeviceService() {
override fun onDevicePresenceEvent(event: DevicePresenceEvent) {
when (event.event) {
DevicePresenceEvent.EVENT_BLE_APPEARED -> onDeviceAppeared(event.associationId)
DevicePresenceEvent.EVENT_BLE_DISAPPEARED -> onDeviceDisappeared(event.associationId)
}
}
private fun onDeviceAppeared(associationId: Int) { … }
private fun onDeviceDisappeared(associationId: Int) { … }
}<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)
callsManager.registerAppWithTelecom(
capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)val attributes = CallAttributesCompat(
displayName = "Kukk Nat",
address = Uri.parse("tel:+16505550199"),
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL
)
callsManager.addCall(
attributes, onAnswer, onDisconnect, onSetActive, onSetInactive
) {
// Coroutine CallControlScope active for the duration of the call
}callsManager.addCall(attributes, onAnswer, onDisconnect, onSetActive, onSetInactive) {
// 1. React to hardware mute toggles from Watch, Car, or Headset
launch {
isMuted.collect { muted -> audioEngine.setMicMuted(muted) }
}
// 2. Observe available endpoints & request declarative switches
launch {
currentCallEndpoint.collect { endpoint -> updateUi(endpoint) }
}
requestEndpointChange(preferredBluetoothEndpoint)
}// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val attachment = FileAttachment(
fileName = "report.pdf",
size = 1_048_576L,
uri = fileUri,
mimeType = "application/pdf",
// Sensitive content will be masked in preview
isSensitiveContent = false,
)
val content = ShareableContent.withFiles(listOf(attachment))
val request = SharingRequest.withContent(content)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val request = SharingRequest.withoutContent()
val session = quickShareClient.startSharingSession(request, lifecycleOwner)
// Open the Quick Share overlay in receive mode
session.launchWindow(activity, contentSelectionIntent = null)// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val urlAttachment = TextAttachment(
text = groupAddUrl,
type = TextAttachment.Type.URL,
)
val content = ShareableContent.withText(urlAttachment)
val appLink = AppLink("https://myapp.com/group/invite")
val request = SharingRequest.withContent(content, appLink)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)// Final API may undergo further updates
scope.launch {
for (event in session.sharingEvents) {
when (event) {
is SharingEvent.SendComplete ->
println("Send finished")
is SharingEvent.ReceiveComplete ->
println("Received, files=${event.content?.files?.size}")
else -> println("other event")
}
}
}