API ของเซสชัน

ต่อยอดจาก API การค้นพบอุปกรณ์และการเชื่อมต่อที่ปลอดภัย Sessions API ช่วยลดความซับซ้อนของการทำงานข้ามอุปกรณ์ได้อย่างมีประสิทธิภาพ ได้ง่ายขึ้น เซสชันแสดงถึงประสบการณ์ของผู้ใช้แอปพลิเคชันที่สามารถ โอนและแชร์ระหว่างอุปกรณ์แล้ว

Sessions API ยังสร้างขึ้นจากแนวคิดเรื่องความเป็นส่วนตัวและชุมชน ประสบการณ์ที่แสดงโดยการโอนเซสชันและกรณีการใช้งานการแชร์เซสชัน ตามลำดับ แผนภาพต่อไปนี้แสดงเซสชันในระดับสูง

วันที่
รูปที่ 1 แผนภาพเซสชัน

สร้างและโอนเซสชัน

Sessions API จะแยกความแตกต่างระหว่างอุปกรณ์ต้นทางและอุปกรณ์ที่เป็นผู้รับ อุปกรณ์ที่เริ่มต้นสร้างเซสชันและค้นหา ที่สามารถจัดการเซสชันได้ ผู้ใช้อุปกรณ์ที่ต้นทาง เลือกอุปกรณ์จากรายการที่ให้ไว้โดยกล่องโต้ตอบของระบบ เมื่อผู้ใช้ เลือกอุปกรณ์ที่เป็นผู้รับ ระบบจะโอนและนำเซสชันเริ่มต้นออก จากอุปกรณ์ต้นทาง

หากต้องการโอนเซสชัน คุณต้องสร้างเซสชันโดยใช้สิ่งต่อไปนี้ก่อน ได้แก่

  • แท็กเซสชันของแอปพลิเคชัน — ตัวระบุที่ช่วยให้คุณสามารถแยกความแตกต่าง ระหว่างหลายๆ เซสชันในแอปของคุณ

จากนั้นเริ่มต้นการโอนโดยใช้พารามิเตอร์ต่อไปนี้

  • DeviceFilter เพื่อกรองอุปกรณ์ที่สามารถจัดการเซสชันได้
  • ออบเจ็กต์ Callback กำลังติดตั้งใช้งาน OriginatingSessionStateCallback

สร้างเซสชันในอุปกรณ์ต้นทางตามตัวอย่างด้านล่าง

KotlinJava
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()
   
)
}
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
);
}

ถัดไป ให้กำหนด Callback ของเซสชันในอุปกรณ์ต้นทางดังนี้

KotlinJava
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
 
}
}
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 มีทุกอย่าง ที่จำเป็นต่อการโอนเซสชัน

วิธีโอนให้เสร็จสมบูรณ์ในอุปกรณ์ที่รับข้อมูล

KotlinJava
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
 
}
}
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 อื่นๆ ความแตกต่างจะอยู่ในเมธอด Callback ของสถานะเซสชัน

KotlinJava
// 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
 
}
}
// 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.
 
}
}

ฝั่งผู้รับ:

KotlinJava
// 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.
 
}
}
// 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.
 
}
}