เมื่อสร้างการเชื่อมต่อระหว่างอุปกรณ์แล้ว คุณจะแลกเปลี่ยนข้อมูลได้โดย
การส่งและรับออบเจ็กต์ Payload
ต
Payload
สามารถแสดงอาร์เรย์ไบต์อย่างง่าย เช่น ข้อความสั้น ไฟล์ เช่น
รูปภาพหรือวิดีโอ หรือสตรีม เช่น สตรีมเสียงจากอุปกรณ์
ไมโครโฟน
ระบบจะส่งเพย์โหลดโดยใช้เมธอด sendPayload()
และได้รับในการใช้งาน PayloadCallback
ซึ่งส่งต่อไปยัง acceptConnection()
ตามที่อธิบายไว้ในส่วนจัดการการเชื่อมต่อ
ประเภทของเพย์โหลด
ไบต์
เพย์โหลดไบต์เป็นประเภทของเพย์โหลดที่ง่ายที่สุด เหมาะสำหรับการส่ง
ข้อมูลทั่วไป เช่น ข้อความหรือข้อมูลเมตา สูงสุดไม่เกิน Connections.MAX_BYTES_DATA_SIZE
ตัวอย่างการส่งเพย์โหลด BYTES
มีดังนี้
Payload bytesPayload = Payload.fromBytes(new byte[] {0xa, 0xb, 0xc, 0xd}); Nearby.getConnectionsClient(context).sendPayload(toEndpointId, bytesPayload);
รับเพย์โหลด BYTES
โดยใช้เมธอด onPayloadReceived()
ของ PayloadCallback
ที่คุณส่งไปยัง acceptConnection()
static class ReceiveBytesPayloadListener extends PayloadCallback { @Override public void onPayloadReceived(String endpointId, Payload payload) { // This always gets the full data of the payload. Is null if it's not a BYTES payload. if (payload.getType() == Payload.Type.BYTES) { byte[] receivedBytes = payload.asBytes(); } } @Override public void onPayloadTransferUpdate(String endpointId, PayloadTransferUpdate update) { // Bytes payloads are sent as a single chunk, so you'll receive a SUCCESS update immediately // after the call to onPayloadReceived(). } }
ระบบจะส่งเพย์โหลด BYTES
รายการเป็นเพย์โหลดเดียว ซึ่งต่างจากเพย์โหลด FILE
และ STREAM
ทำให้ไม่จำเป็นต้องรอการอัปเดต SUCCESS
(แต่
ยังคงจัดส่งได้ทันทีหลังจากโทรติดต่อ onPayloadReceived()
)
แต่คุณสามารถโทรติดต่อ payload.asBytes()
เพื่อขอข้อมูลทั้งหมดของ
เพย์โหลดทันทีที่มีการเรียก onPayloadReceived()
ไฟล์
เพย์โหลดไฟล์สร้างขึ้นจากไฟล์ที่จัดเก็บไว้ในอุปกรณ์ภายใน เช่น
ไฟล์รูปภาพหรือวิดีโอ ตัวอย่างง่ายๆ ของการส่งเพย์โหลด FILE
มีดังนี้
File fileToSend = new File(context.getFilesDir(), "hello.txt"); try { Payload filePayload = Payload.fromFile(fileToSend); Nearby.getConnectionsClient(context).sendPayload(toEndpointId, filePayload); } catch (FileNotFoundException e) { Log.e("MyApp", "File not found", e); }
การใช้ ParcelFileDescriptor
เพื่อสร้างเพย์โหลด FILES
อาจมีประสิทธิภาพมากขึ้น หากมี เช่น จาก ContentResolver
วิธีนี้จะช่วยลดการคัดลอกไบต์ของไฟล์:
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r"); filePayload = Payload.fromFile(pfd);
เมื่อได้รับไฟล์แล้ว ระบบจะบันทึกไฟล์ไว้ในโฟลเดอร์ดาวน์โหลด (DIRECTORY_DOWNLOADS
) ในไฟล์ของผู้รับ
อุปกรณ์ที่มีชื่อทั่วไปและไม่มีส่วนขยาย เมื่อการโอนเสร็จสมบูรณ์
ระบุโดยการโทรไปยัง onPayloadTransferUpdate()
ด้วย
PayloadTransferUpdate.Status.SUCCESS
คุณสามารถดึงออบเจ็กต์ File
เช่น หากแอปของคุณกำหนดเป้าหมาย < อุปกรณ์ Q:
File payloadFile = filePayload.asFile().asJavaFile(); // Rename the file. payloadFile.renameTo(new File(payloadFile.getParentFile(), filename));
หากแอปของคุณกำหนดเป้าหมายเป็นอุปกรณ์ Q คุณสามารถเพิ่ม android:requestLegacyExternalStorage="true" ในองค์ประกอบแอปพลิเคชันของไฟล์ Manifest เพื่อใช้โค้ดก่อนหน้านี้ต่อไป
มิเช่นนั้น สำหรับ Q+ คุณจะต้องปฏิบัติตามกฎ Scoped Storage
และเข้าถึงไฟล์ที่ได้รับโดยใช้ URI ที่ส่งผ่านจากบริการ
// Because of https://developer.android.com/preview/privacy/scoped-storage, we are not // allowed to access filepaths from another process directly. Instead, we must open the // uri using our ContentResolver. Uri uri = filePayload.asFile().asUri(); try { // Copy the file to a new location. InputStream in = context.getContentResolver().openInputStream(uri); copyStream(in, new FileOutputStream(new File(context.getCacheDir(), filename))); } catch (IOException e) { // Log the error. } finally { // Delete the original file. context.getContentResolver().delete(uri, null, null); }
ในตัวอย่างที่ซับซ้อนมากขึ้นต่อไปนี้ Intent ของ ACTION_OPEN_DOCUMENT
จะแจ้งให้ผู้ใช้เลือกไฟล์ และส่งไฟล์เป็นเพย์โหลดอย่างมีประสิทธิภาพโดยใช้ ParcelFileDescriptor
ระบบจะส่งชื่อไฟล์เป็นเพย์โหลด BYTES
ด้วย
private static final int READ_REQUEST_CODE = 42; private static final String ENDPOINT_ID_EXTRA = "com.foo.myapp.EndpointId"; /** * Fires an intent to spin up the file chooser UI and select an image for sending to endpointId. */ private void showImageChooser(String endpointId) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/*"); intent.putExtra(ENDPOINT_ID_EXTRA, endpointId); startActivityForResult(intent, READ_REQUEST_CODE); } @Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) { super.onActivityResult(requestCode, resultCode, resultData); if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK && resultData != null) { String endpointId = resultData.getStringExtra(ENDPOINT_ID_EXTRA); // The URI of the file selected by the user. Uri uri = resultData.getData(); Payload filePayload; try { // Open the ParcelFileDescriptor for this URI with read access. ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r"); filePayload = Payload.fromFile(pfd); } catch (FileNotFoundException e) { Log.e("MyApp", "File not found", e); return; } // Construct a simple message mapping the ID of the file payload to the desired filename. String filenameMessage = filePayload.getId() + ":" + uri.getLastPathSegment(); // Send the filename message as a bytes payload. Payload filenameBytesPayload = Payload.fromBytes(filenameMessage.getBytes(StandardCharsets.UTF_8)); Nearby.getConnectionsClient(context).sendPayload(endpointId, filenameBytesPayload); // Finally, send the file payload. Nearby.getConnectionsClient(context).sendPayload(endpointId, filePayload); } }
เนื่องจากชื่อไฟล์ถูกส่งเป็นเพย์โหลด ผู้รับของเราจึงสามารถย้ายหรือเปลี่ยนชื่อไฟล์เพื่อให้มีนามสกุลที่เหมาะสมได้
static class ReceiveFilePayloadCallback extends PayloadCallback { private final Context context; private final SimpleArrayMap<Long, Payload> incomingFilePayloads = new SimpleArrayMap<>(); private final SimpleArrayMap<Long, Payload> completedFilePayloads = new SimpleArrayMap<>(); private final SimpleArrayMap<Long, String> filePayloadFilenames = new SimpleArrayMap<>(); public ReceiveFilePayloadCallback(Context context) { this.context = context; } @Override public void onPayloadReceived(String endpointId, Payload payload) { if (payload.getType() == Payload.Type.BYTES) { String payloadFilenameMessage = new String(payload.asBytes(), StandardCharsets.UTF_8); long payloadId = addPayloadFilename(payloadFilenameMessage); processFilePayload(payloadId); } else if (payload.getType() == Payload.Type.FILE) { // Add this to our tracking map, so that we can retrieve the payload later. incomingFilePayloads.put(payload.getId(), payload); } } /** * Extracts the payloadId and filename from the message and stores it in the * filePayloadFilenames map. The format is payloadId:filename. */ private long addPayloadFilename(String payloadFilenameMessage) { String[] parts = payloadFilenameMessage.split(":"); long payloadId = Long.parseLong(parts[0]); String filename = parts[1]; filePayloadFilenames.put(payloadId, filename); return payloadId; } private void processFilePayload(long payloadId) { // BYTES and FILE could be received in any order, so we call when either the BYTES or the FILE // payload is completely received. The file payload is considered complete only when both have // been received. Payload filePayload = completedFilePayloads.get(payloadId); String filename = filePayloadFilenames.get(payloadId); if (filePayload != null && filename != null) { completedFilePayloads.remove(payloadId); filePayloadFilenames.remove(payloadId); // Get the received file (which will be in the Downloads folder) // Because of https://developer.android.com/preview/privacy/scoped-storage, we are not // allowed to access filepaths from another process directly. Instead, we must open the // uri using our ContentResolver. Uri uri = filePayload.asFile().asUri(); try { // Copy the file to a new location. InputStream in = context.getContentResolver().openInputStream(uri); copyStream(in, new FileOutputStream(new File(context.getCacheDir(), filename))); } catch (IOException e) { // Log the error. } finally { // Delete the original file. context.getContentResolver().delete(uri, null, null); } } } // add removed tag back to fix b/183037922 private void processFilePayload2(long payloadId) { // BYTES and FILE could be received in any order, so we call when either the BYTES or the FILE // payload is completely received. The file payload is considered complete only when both have // been received. Payload filePayload = completedFilePayloads.get(payloadId); String filename = filePayloadFilenames.get(payloadId); if (filePayload != null && filename != null) { completedFilePayloads.remove(payloadId); filePayloadFilenames.remove(payloadId); // Get the received file (which will be in the Downloads folder) if (VERSION.SDK_INT >= VERSION_CODES.Q) { // Because of https://developer.android.com/preview/privacy/scoped-storage, we are not // allowed to access filepaths from another process directly. Instead, we must open the // uri using our ContentResolver. Uri uri = filePayload.asFile().asUri(); try { // Copy the file to a new location. InputStream in = context.getContentResolver().openInputStream(uri); copyStream(in, new FileOutputStream(new File(context.getCacheDir(), filename))); } catch (IOException e) { // Log the error. } finally { // Delete the original file. context.getContentResolver().delete(uri, null, null); } } else { File payloadFile = filePayload.asFile().asJavaFile(); // Rename the file. payloadFile.renameTo(new File(payloadFile.getParentFile(), filename)); } } } @Override public void onPayloadTransferUpdate(String endpointId, PayloadTransferUpdate update) { if (update.getStatus() == PayloadTransferUpdate.Status.SUCCESS) { long payloadId = update.getPayloadId(); Payload payload = incomingFilePayloads.remove(payloadId); completedFilePayloads.put(payloadId, payload); if (payload.getType() == Payload.Type.FILE) { processFilePayload(payloadId); } } } /** Copies a stream from one location to another. */ private static void copyStream(InputStream in, OutputStream out) throws IOException { try { byte[] buffer = new byte[1024]; int read; while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } out.flush(); } finally { in.close(); out.close(); } } }
สตรีม
เพย์โหลดของสตรีมเหมาะสำหรับเมื่อคุณต้องการส่งข้อมูลปริมาณมากที่
ที่สร้างขึ้นแบบเรียลไทม์ เช่น สตรีมเสียง สร้างเพย์โหลด STREAM
ตาม
กำลังเรียก Payload.fromStream()
ผ่านใน InputStream
หรือ
ParcelFileDescriptor
เช่น
URL url = new URL("https://developers.google.com/nearby/connections/android/exchange-data"); Payload streamPayload = Payload.fromStream(url.openStream()); Nearby.getConnectionsClient(context).sendPayload(toEndpointId, streamPayload);
จากผู้รับ ให้โทรหา payload.asStream().asInputStream()
หรือ payload.asStream().asParcelFileDescriptor()
ในการติดต่อกลับ onPayloadTransferUpdate
ที่สำเร็จ
static class ReceiveStreamPayloadCallback extends PayloadCallback { private final SimpleArrayMap<Long, Thread> backgroundThreads = new SimpleArrayMap<>(); private static final long READ_STREAM_IN_BG_TIMEOUT = 5000; @Override public void onPayloadTransferUpdate(String endpointId, PayloadTransferUpdate update) { if (backgroundThreads.containsKey(update.getPayloadId()) && update.getStatus() != PayloadTransferUpdate.Status.IN_PROGRESS) { backgroundThreads.get(update.getPayloadId()).interrupt(); } } @Override public void onPayloadReceived(String endpointId, Payload payload) { if (payload.getType() == Payload.Type.STREAM) { // Read the available bytes in a while loop to free the stream pipe in time. Otherwise, the // bytes will block the pipe and slow down the throughput. Thread backgroundThread = new Thread() { @Override public void run() { InputStream inputStream = payload.asStream().asInputStream(); long lastRead = SystemClock.elapsedRealtime(); while (!Thread.interrupted()) { if ((SystemClock.elapsedRealtime() - lastRead) >= READ_STREAM_IN_BG_TIMEOUT) { Log.e("MyApp", "Read data from stream but timed out."); break; } try { int availableBytes = inputStream.available(); if (availableBytes > 0) { byte[] bytes = new byte[availableBytes]; if (inputStream.read(bytes) == availableBytes) { lastRead = SystemClock.elapsedRealtime(); // Do something with is here... } } else { // Sleep or just continue. } } catch (IOException e) { Log.e("MyApp", "Failed to read bytes from InputStream.", e); break; } // try-catch } // while } }; backgroundThread.start(); backgroundThreads.put(payload.getId(), backgroundThread); } } }
การสั่งซื้อด้วยเพย์โหลดหลายรายการ
เรารับประกันได้ว่าเพย์โหลดประเภทเดียวกันจะมาถึงตามลำดับที่ส่ง
แต่ไม่มีการรับประกันที่จะคงลำดับการจัดเรียงในเพย์โหลดของ
ประเภทต่างๆ เช่น หากผู้ส่งส่งเปย์โหลด FILE
ที่ตามด้วย
เพย์โหลด BYTE
ผู้รับอาจได้รับเพย์โหลด BYTE
ก่อน ตามด้วย
เพย์โหลด FILE
การอัปเดตความคืบหน้า
เมธอด onPayloadTransferUpdate()
จะให้ข้อมูลอัปเดตเกี่ยวกับความคืบหน้าของทั้งเพย์โหลดขาเข้าและขาออก ในทั้ง 2 กรณี นี่เป็นโอกาสในการแสดงความคืบหน้าของการโอนไปยังผู้ใช้ เช่น การใช้แถบความคืบหน้า สำหรับเพย์โหลดขาเข้า การอัปเดตยังระบุเวลาที่ได้รับข้อมูลใหม่ด้วย
โค้ดตัวอย่างต่อไปนี้แสดงวิธีหนึ่งในการแสดงความคืบหน้าของ เพย์โหลดขาเข้าและขาออกผ่านการแจ้งเตือน:
class ReceiveWithProgressCallback extends PayloadCallback { private final SimpleArrayMap<Long, NotificationCompat.Builder> incomingPayloads = new SimpleArrayMap<>(); private final SimpleArrayMap<Long, NotificationCompat.Builder> outgoingPayloads = new SimpleArrayMap<>(); NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); private void sendPayload(String endpointId, Payload payload) { if (payload.getType() == Payload.Type.BYTES) { // No need to track progress for bytes. return; } // Build and start showing the notification. NotificationCompat.Builder notification = buildNotification(payload, /*isIncoming=*/ false); notificationManager.notify((int) payload.getId(), notification.build()); // Add it to the tracking list so we can update it. outgoingPayloads.put(payload.getId(), notification); } private NotificationCompat.Builder buildNotification(Payload payload, boolean isIncoming) { NotificationCompat.Builder notification = new NotificationCompat.Builder(context) .setContentTitle(isIncoming ? "Receiving..." : "Sending..."); boolean indeterminate = false; if (payload.getType() == Payload.Type.STREAM) { // We can only show indeterminate progress for stream payloads. indeterminate = true; } notification.setProgress(100, 0, indeterminate); return notification; } @Override public void onPayloadReceived(String endpointId, Payload payload) { if (payload.getType() == Payload.Type.BYTES) { // No need to track progress for bytes. return; } // Build and start showing the notification. NotificationCompat.Builder notification = buildNotification(payload, true /*isIncoming*/); notificationManager.notify((int) payload.getId(), notification.build()); // Add it to the tracking list so we can update it. incomingPayloads.put(payload.getId(), notification); } @Override public void onPayloadTransferUpdate(String endpointId, PayloadTransferUpdate update) { long payloadId = update.getPayloadId(); NotificationCompat.Builder notification = null; if (incomingPayloads.containsKey(payloadId)) { notification = incomingPayloads.get(payloadId); if (update.getStatus() != PayloadTransferUpdate.Status.IN_PROGRESS) { // This is the last update, so we no longer need to keep track of this notification. incomingPayloads.remove(payloadId); } } else if (outgoingPayloads.containsKey(payloadId)) { notification = outgoingPayloads.get(payloadId); if (update.getStatus() != PayloadTransferUpdate.Status.IN_PROGRESS) { // This is the last update, so we no longer need to keep track of this notification. outgoingPayloads.remove(payloadId); } } if (notification == null) { return; } switch (update.getStatus()) { case PayloadTransferUpdate.Status.IN_PROGRESS: long size = update.getTotalBytes(); if (size == -1) { // This is a stream payload, so we don't need to update anything at this point. return; } int percentTransferred = (int) (100.0 * (update.getBytesTransferred() / (double) update.getTotalBytes())); notification.setProgress(100, percentTransferred, /* indeterminate= */ false); break; case PayloadTransferUpdate.Status.SUCCESS: // SUCCESS always means that we transferred 100%. notification .setProgress(100, 100, /* indeterminate= */ false) .setContentText("Transfer complete!"); break; case PayloadTransferUpdate.Status.FAILURE: case PayloadTransferUpdate.Status.CANCELED: notification.setProgress(0, 0, false).setContentText("Transfer failed"); break; default: // Unknown status. } notificationManager.notify((int) payloadId, notification.build()); } }