Sessions API

Dựa trên các API Khám phá thiết bịKết nối an toàn, Phiên API cung cấp tính năng trừu tượng hoá mạnh mẽ để xây dựng liền mạch trên nhiều thiết bị của bạn. Phiên hoạt động thể hiện trải nghiệm người dùng trong ứng dụng được truyền và chia sẻ giữa các thiết bị.

API phiên cũng được xây dựng xung quanh khái niệm cá nhân và cộng đồng trải nghiệm được thể hiện bằng các trường hợp sử dụng chuyển phiên và chia sẻ phiên tương ứng. Sơ đồ dưới đây minh hoạ các phiên hoạt động ở cấp độ cao:

Hình 1. Biểu đồ phiên.

Tạo và chuyển một phiên

API phiên phân biệt giữa thiết bị ban đầu và thiết bị nhận. Thiết bị ban đầu tạo phiên và tìm kiếm một thiết bị có thể xử lý phiên hoạt động đó. Người dùng thiết bị ban đầu chọn một thiết bị trong danh sách do hộp thoại hệ thống cung cấp. Sau khi người dùng chọn thiết bị nhận, phiên ban đầu sẽ được truyền và xoá từ thiết bị gốc.

Để chuyển phiên, trước tiên bạn phải tạo phiên bằng cách sử dụng thông số:

  • Thẻ phiên ứng dụng — một giá trị nhận dạng cho phép bạn phân biệt giữa nhiều phiên trong ứng dụng.

Sau đó, hãy bắt đầu quá trình chuyển bằng cách sử dụng các thông số sau:

  • DeviceFilter để lọc những thiết bị có khả năng xử lý phiên hoạt động
  • Đối tượng gọi lại triển khai OriginatingSessionStateCallback

Trên thiết bị ban đầu, hãy tạo một phiên bằng cách sử dụng ví dụ bên dưới:

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

Tiếp theo, hãy xác định lệnh gọi lại phiên trên thiết bị ban đầu:

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
  }
}

Sau khi quá trình chuyển phiên bắt đầu, thiết bị nhận sẽ nhận được lệnh gọi lại trong phương thức onNewIntent(intent: Intent). Dữ liệu ý định chứa mọi thông tin cần thiết để chuyển phiên.

Cách hoàn tất quá trình chuyển trên thiết bị nhận:

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
  }
}

Giờ đây, thiết bị nhận có thể tiếp tục trải nghiệm người dùng.

Chia sẻ phiên

Bằng cách chia sẻ một phiên, bạn có thể mời người khác ở gần mình tham gia vào một nhóm trải nghiệm người dùng, ví dụ:

  • Chia sẻ trực tiếp vị trí trên bản đồ với tư cách là hành khách với xe của bạn bè.
  • Chia sẻ tuyến đường đạp xe vào Chủ Nhật với những người khác mà bạn đang đạp xe cùng.
  • Thu thập các mặt hàng cho một đơn đặt món ăn theo nhóm mà không cần đưa điện thoại lại.
  • Hãy một nhóm bình chọn cho chương trình truyền hình tiếp theo để cùng xem.

Khi người dùng chọn chia sẻ phiên với một thiết bị khác, thiết bị tìm kiếm và trình bày các thiết bị có thể tham gia phiên và người dùng chọn(các) thiết bị nhận. Ứng dụng nhắc người dùng về thiết bị nhận để tham gia phiên hoạt động từ thiết bị ban đầu. Người nhận sau đó thiết bị được cung cấp một phiên thứ hai để tương tác với phiên trên thiết bị gốc. Ứng dụng cũng có thể thêm những người tham gia khác vào ứng dụng phiên được chia sẻ đang diễn ra.

Quá trình chia sẻ phiên tương tự như chuyển phiên, nhưng thay vì gọi transferSession, hãy gọi shareSession. Khác khác biệt nằm ở phương thức gọi lại trạng thái phiên.

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

Ở bên nhận:

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