Sessions API 基于设备发现和安全连接 API 构建,可提供强大的抽象功能来打造无缝的跨设备体验。会话代表着可在设备间传输和分享的应用用户体验。
Sessions API 还围绕个人体验和共享体验的概念而构建,分别以会话传输和会话共享用例表示。下图简要说明了会话:
创建和转移会话
Sessions API 区分源设备和接收设备。源设备会创建会话,并搜索能够处理该会话的设备。源设备的用户从系统对话框提供的列表中选择设备。用户选择接收设备后,系统会传输源会话,并将其从源设备中移除。
如需转移会话,您必须先使用以下参数创建会话:
- 应用会话标记 - 一种标识符,可让您区分应用中的多个会话。
然后,使用以下参数启动转移:
DeviceFilter
,用于过滤能够处理会话的设备- 用于实现
OriginatingSessionStateCallback
的回调对象
在源设备上,使用以下示例创建会话:
Kotlin
private val HELLO_WORLD_TRANSFER_ACTION = "hello_world_transfer" private lateinit var originatingSession: OriginatingSession private lateinit var sessions: Sessions override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sessions = Sessions.create(context = this) } suspend fun transferSession() { val sessionId = sessions.createSession( ApplicationSessionTag("hello_world_transfer"), ) originatingSession = sessions.transferSession( sessionId, StartComponentRequest.Builder() .setAction(HELLO_WORLD_TRANSFER_ACTION) .setReason("Transfer reason here") .build(), emptyList(), HelloWorldTransferSessionStateCallback() ) }
Java
private static final String HELLO_WORLD_TRANSFER_ACTION = "hello_world_transfer"; private OriginatingSession originatingSession; private Sessions sessions; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); sessions = Sessions.create(/* context= */ this); } public void transferSession() { SessionId sessionId = sessions.createSession(new ApplicationSessionTag("hello_world_transfer")); ListenableFuture<OriginatingSession> originatingSessionFuture = sessions.transferSessionFuture( sessionId, new StartComponentRequest.Builder() .setAction(HELLO_WORLD_TRANSFER_ACTION) .setReason("Transfer reason here") .build(), Collections.emptyList(), new HelloWorldTransferSessionStateCallback()); Futures.addCallback( originatingSessionFuture, new FutureCallback<>() { @Override public void onSuccess(OriginatingSession result) { // Do nothing, handled in HelloWorldTransferSessionStateCallback originatingSession = result; } @Override public void onFailure(Throwable t) { Log.d(TAG, "onFailure called for transferSessionFuture", t); } }, mainExecutor); }
接下来,在源设备上定义会话回调:
Kotlin
private inner class HelloWorldTransferSessionStateCallback : OriginatingSessionStateCallback { override fun onConnected(sessionId: SessionId) { val startupRemoteConnection = originatingSession.getStartupRemoteConnection() lifecycleScope.launchWhenResumed { startupRemoteConnection.send("hello, world".toByteArray(UTF_8)) startupRemoteConnection.registerReceiver( object : SessionConnectionReceiver { override fun onMessageReceived(participant: SessionParticipant, payload: ByteArray) { val ok = payload.contentEquals("ok".toByteArray(UTF_8)) Log.d(TAG, "Session transfer initialized. ok=$ok") } } ) } } override fun onSessionTransferred(sessionId: SessionId) { Log.d(TAG, "Transfer done.") } override fun onTransferFailure(sessionId: SessionId, exception: SessionException) { // Handle error } }
Java
private class HelloWorldTransferSessionStateCallback implements OriginatingSessionStateCallback { @Override public void onConnected(SessionId sessionId) { SessionRemoteConnection startupRemoteConnection = originatingSession.getStartupRemoteConnection(); Futures.addCallback( startupRemoteConnection.sendFuture("hello, world".getBytes()), new FutureCallback<>() { @Override public void onSuccess(Void result) { Log.d(TAG, "Successfully sent initialization message"); } @Override public void onFailure(Throwable t) { Log.d(TAG, "Failed to send initialization message", t); } }, mainExecutor); } @Override public void onSessionTransferred(SessionId sessionId) { Log.d(TAG, "Transfer done."); } @Override public void onTransferFailure(SessionId sessionId, SessionException exception) { // Handle error } }
启动会话传输后,接收设备会在 onNewIntent(intent: Intent)
方法中收到回调。intent 数据包含传输会话所需的所有内容。
如需在接收设备上完成转移,请执行以下操作:
Kotlin
private lateinit var sessions: Sessions override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sessions = Sessions.create(context = this) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) lifecycleScope.launchWhenResumed { val receivingSession = sessions.getReceivingSession(intent, HelloWorldReceivingSessionStateCallback()) // Get info from receiving device and init. val startupRemoteConnection = receivingSession.getStartupRemoteConnection() startupRemoteConnection.registerReceiver( object : SessionConnectionReceiver { override fun onMessageReceived(participant: SessionParticipant, payload: ByteArray) { lifecycleScope.launchWhenResumed { val transferInfo = String(payload) startupRemoteConnection.send("ok".toByteArray(UTF_8)) // Complete transfer. Log.d(TAG, "Transfer info: " + transferInfo) receivingSession.onComplete() } } } ) } } private inner class HelloWorldReceivingSessionStateCallback : ReceivingSessionStateCallback { override fun onTransferFailure(sessionId: SessionId, exception: SessionException) { // Handle error } }
Java
private Sessions sessions; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); sessions = Sessions.create(/* context= */ this); } @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); ListenableFuture<ReceivingSession> receivingSessionFuture = sessions.getReceivingSessionFuture(intent, new HelloWorldReceivingSessionStateCallback()); ListenableFuture<Void> registerReceiverFuture = Futures.transform( receivingSessionFuture, receivingSession -> { SessionRemoteConnection startupRemoteConnection = receivingSession.getStartupRemoteConnection(); SessionConnectionReceiver receiver = (participant, payload) -> { Log.d( TAG, "Successfully received initialization message of size: " + payload.length); applicationInitialization(receivingSession, payload); }; startupRemoteConnection.registerReceiver(receiver); return null; }, mainExecutor); Futures.addCallback( registerReceiverFuture, new FutureCallback<Void>() { @Override public void onSuccess(Void unused) { Log.d(TAG, "Connection receiver registerd successfully"); } @Override public void onFailure(Throwable t) { Log.w(TAG, "Failed to register connection receiver", t); } }, mainExecutor); } private void applicationInitialization(ReceivingSession receivingSession, byte[] initMessage) { ListenableFuture<SessionId> disconnectFuture = Futures.transform( receivingSession.onCompleteFuture(), sessionId -> { Log.d(TAG, "Succeeded to complete receive transfer for: " + sessionId); return sessionId; }, mainExecutor); Futures.addCallback( disconnectFuture, new FutureCallback<SessionId>() { @Override public void onSuccess(SessionId result) { Log.d(TAG, "Succeeded to remove the old session: " + result); } @Override public void onFailure(Throwable t) { Log.d(TAG, "Failed to remove the old session, which is now orphaned", t); } }, mainExecutor); } private static class HelloWorldReceivingSessionStateCallback implements ReceivingSessionStateCallback { @Override public void onTransferFailure(SessionId sessionId, SessionException exception) { // Handle error } }
现在,接收设备可以继续处理用户体验。
分享课程
通过共享会话,您可以邀请附近的其他人加入群组体验,例如:
- 以乘客的身份直接与朋友的汽车分享地图位置。
- 与他人分享您的周日骑行路线。
- 无需向对方展示手机,即可领取团体订购的食品。
- 进行小组投票,为接下来一起观看的电视节目投票。
当用户选择与其他设备共享会话时,源设备会搜索并显示能够加入会话的设备,然后用户选择接收设备。应用在接收设备上提示用户从源设备加入会话。然后,系统会向接收设备提供辅助会话,以与源设备上的会话进行交互。应用还可以将其他参与者添加到他们正在进行的共享会话中。
共享会话的过程与传输会话类似,但前者不是调用 transferSession
,而是调用 shareSession
。其他区别在于会话状态回调方法。
Kotlin
// Originating side. private val HELLO_WORLD_SHARE_ACTION = "hello_world_share" private var activePrimarySession: PrimarySession? = null private lateinit var sessions: Sessions override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sessions = Sessions.create(context = this) } suspend fun shareSession() { val sessionId = sessions.createSession(ApplicationSessionTag("hello_world_share")) activePrimarySession = sessions.shareSession( sessionId, StartComponentRequest.Builder() .setAction(HELLO_WORLD_SHARE_ACTION) .setReason("Share reason here") .build(), emptyList(), HelloWorldShareSessionStateCallback(), ) } private inner class HelloWorldShareSessionStateCallback : PrimarySessionStateCallback { override fun onShareInitiated(sessionId: SessionId, numPotentialParticipants: Int) { // Custom logic here for when n devices can potentially join. // e.g. if there were 0, cancel/error if desired, // if non-0 maybe spin until numPotentialParticipants join etc. } override fun onParticipantJoined(sessionId: SessionId, participant: SessionParticipant) { // Custom join logic here lifecycleScope.launchWhenResumed { // Example logic: send only to the participant who just joined. val connection = checkNotNull(activePrimarySession).getSecondaryRemoteConnectionForParticipant(participant) connection.send("Initing hello, world.".toByteArray(UTF_8)) connection.registerReceiver( object : SessionConnectionReceiver { override fun onMessageReceived(participant: SessionParticipant, payload: ByteArray) { val ok = payload.contentEquals("ok".toByteArray(UTF_8)) Log.d(TAG, "Session share initialized. ok=$ok") // Example logic: broadcast to all participants, including the one // that just joined. lifecycleScope.launchWhenResumed { checkNotNull(activePrimarySession) .broadcastToSecondaries("hello, all.".toByteArray(UTF_8)) } } } ) } } override fun onParticipantDeparted(sessionId: SessionId, participant: SessionParticipant) { // Custom leave logic here. } override fun onPrimarySessionCleanup(sessionId: SessionId) { // Custom cleanup logic here. activePrimarySession = null } override fun onShareFailureWithParticipant( sessionId: SessionId, exception: SessionException, participant: SessionParticipant ) { // Handle error } }
Java
// Originating side private static final String HELLO_WORLD_SHARE_ACTION = "hello_world_share"; @Nullable private PrimarySession activePrimarySession = null; private Sessions sessions; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); sessions = Sessions.create(/* context= */ this); } private void shareSession() { SessionId sessionId = sessions.createSession(new ApplicationSessionTag("hello_world_share")); ListenableFuture<PrimarySession> shareSessionFuture = sessions.shareSessionFuture( sessionId, new StartComponentRequest.Builder() .setAction(HELLO_WORLD_SHARE_ACTION) .setReason("Share reason here") .build(), Collections.emptyList(), new HelloWorldShareSessionStateCallback()); Futures.addCallback( shareSessionFuture, new FutureCallback<PrimarySession>() { @Override public void onSuccess(PrimarySession primarySession) { activePrimarySession = primarySession; } @Override public void onFailure(Throwable t) { Log.d(TAG, "Failed to share session", t); } }, mainExecutor); } private class HelloWorldShareSessionStateCallback implements PrimarySessionStateCallback { @Override public void onShareInitiated(SessionId sessionId, int numPotentialParticipants) { // Custom logic here for when n devices can potentially join. // e.g. if there were 0, cancel/error if desired, // if non-0 maybe spin until numPotentialParticipants join etc. } @Override public void onParticipantJoined(SessionId sessionId, SessionParticipant participant) { PrimarySession joinedSession = activePrimarySession; if (joinedSession == null) { return; } SessionRemoteConnection connection = joinedSession.getSecondaryRemoteConnectionForParticipant(participant); Futures.addCallback( connection.sendFuture("Initiating hello, world.".getBytes()), new FutureCallback<Void>() { @Override public void onSuccess(Void result) { // Send successful. } @Override public void onFailure(Throwable t) { // Failed to send. } }, mainExecutor); connection.registerReceiver( new SessionConnectionReceiver() { @Override public void onMessageReceived(SessionParticipant participant, byte[] payload) { boolean ok = new String(payload, UTF_8).equals("ok"); Log.d(TAG, "Session share initialized. ok=" + ok); // Example logic: broadcast to all participants, including the one // that just joined. Futures.addCallback( joinedSession.broadcastToSecondariesFuture("hello, all.".getBytes()), new FutureCallback<Void>() { @Override public void onSuccess(Void result) { // Broadcast successful. } @Override public void onFailure(Throwable t) { // Failed to broadcast hello world. } }, mainExecutor); } }); } @Override public void onParticipantDeparted(SessionId sessionId, SessionParticipant participant) { // Custom leave logic here. } @Override public void onPrimarySessionCleanup(SessionId sessionId) { // Custom cleanup logic here. activePrimarySession = null; } @Override public void onShareFailureWithParticipant( SessionId sessionId, SessionException exception, SessionParticipant participant) { // Custom error handling logic here. } }
在接收侧执行的操作:
Kotlin
// Receiving side. override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) lifecycleScope.launchWhenResumed { val secondarySession = sessions.getSecondarySession(intent, HelloWorldSecondaryShareSessionStateCallback()) val remoteConnection = secondarySession.getDefaultRemoteConnection() remoteConnection.registerReceiver( object : SessionConnectionReceiver { override fun onMessageReceived(participant: SessionParticipant, payload: ByteArray) { Log.d(TAG, "Payload received: ${String(payload)}") } } ) } } private inner class HelloWorldSecondaryShareSessionStateCallback : SecondarySessionStateCallback { override fun onSecondarySessionCleanup(sessionId: SessionId) { // Custom cleanup logic here. } }
Java
// Receiving side. @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); sessions = Sessions.create(this); ListenableFuture<SecondarySession> secondarySessionFuture = sessions.getSecondarySessionFuture( intent, new HelloWorldSecondaryShareSessionStateCallback()); Futures.addCallback( secondarySessionFuture, new FutureCallback<SecondarySession>() { @Override public void onSuccess(SecondarySession secondarySession) { SessionRemoteConnection remoteConnection = secondarySession.getDefaultRemoteConnection(); remoteConnection.registerReceiver( new SessionConnectionReceiver() { @Override public void onMessageReceived(SessionParticipant participant, byte[] payload) { Log.d(TAG, "Payload received: " + new String(payload, UTF_8)); } }); } @Override public void onFailure(Throwable t) { // Handle error. } }, mainExecutor); } private static class HelloWorldSecondaryShareSessionStateCallback implements SecondarySessionStateCallback { @Override public void onSecondarySessionCleanup(SessionId sessionId) { // Custom cleanup logic here. } }