Sessions API

با تکیه بر میاناهای برنامه‌سازی کاربردی کاوش دستگاه و اتصال ایمن، میانای برنامه‌سازی کاربردی «جلسه‌ها» انتزاعی قدرتمند برای ساختن تجربه‌های یکپارچه بین‌دستگاهی ارائه می‌دهد. جلسه نشان‌دهنده تجربه کاربری برنامه است که می‌تواند بین دستگاه‌ها منتقل و هم‌رسانی شود.

‫Sessions API همچنین براساس مفهوم تجربه‌های شخصی و جمعی ساخته شده است که به‌ترتیب با موارد استفاده انتقال جلسه و هم‌رسانی جلسه نشان داده می‌شوند. نمودار زیر جلسه‌ها را در سطح بالا نشان می‌دهد:

شکل ۱. نمودار جلسه‌ها.

ایجاد و انتقال جلسه

Sessions API بین دستگاه مبدأ و دستگاه گیرنده تمایز قائل می‌شود. دستگاه مبدأ جلسه را ایجاد می‌کند و به‌دنبال دستگاهی می‌گردد که بتواند جلسه را مدیریت کند. کاربر دستگاه مبدأ دستگاهی را از فهرست ارائه‌شده توسط کادر گفتگوی سیستم انتخاب می‌کند. وقتی کاربر دستگاه گیرنده را انتخاب می‌کند، جلسه مبدأ منتقل می‌شود و از دستگاه مبدأ برداشته می‌شود.

برای انتقال جلسه، ابتدا باید آن را بااستفاده از پارامترهای زیر ایجاد کنید:

  • برچسب جلسه برنامه — شناسه‌ای که به شما امکان می‌دهد بین چندین جلسه در برنامه‌تان تمایز قائل شوید.

سپس انتقال را بااستفاده از پارامترهای زیر شروع کنید:

  • DeviceFilter برای فیلتر کردن دستگاه‌هایی که می‌توانند جلسه را مدیریت کنند
  • شیء فراخوانی که OriginatingSessionStateCallback را پیاده‌سازی می‌کند

در دستگاه مبدأ، بااستفاده از مثال زیر جلسه‌ای ایجاد کنید:

کاتلین

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

سپس، فراخوانی برگشتی جلسه را در دستگاه مبدأ تعریف کنید:

کاتلین

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) دریافت می‌کند. داده‌های قصد شامل همه چیزهای لازم برای انتقال جلسه است.

برای تکمیل انتقال در دستگاه گیرنده:

کاتلین

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 را فراخوانی کنید. تفاوت‌های دیگر در روش‌های فراخوانی وضعیت جلسه است.

کاتلین

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

در طرف گیرنده:

کاتلین

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