เมื่อสร้างการเชื่อมต่อระหว่างอุปกรณ์แล้ว คุณจะแลกเปลี่ยนข้อมูลโดยการส่งและรับออบเจ็กต์ 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
จะต่างจากเพย์โหลด BYTES
และ STREAM
เพย์โหลด ดังนั้นจึงไม่จําเป็นต้องรอการอัปเดต SUCCESS
(แต่จะยังคงนําส่งอยู่ทันทีหลังจากที่เรียกใช้ 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 device:
File payloadFile = filePayload.asFile().asJavaFile(); // Rename the file. payloadFile.renameTo(new File(payloadFile.getParentFile(), filename));
หากแอปกําหนดเป้าหมายอุปกรณ์ Q คุณเพิ่ม android:requestLegacyExternalStorage="true" ในองค์ประกอบแอปพลิเคชันของไฟล์ Manifest เพื่อใช้โค้ดก่อนหน้าต่อได้
มิฉะนั้นสําหรับกฎ Q+ คุณจะต้องปฏิบัติตามกฎ Scoped Storage
และเข้าถึงไฟล์ที่ได้รับโดยใช้ URL ที่ส่งจากบริการ
// 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); }
ในตัวอย่างที่ซับซ้อนยิ่งขึ้นต่อไปนี้ ความตั้งใจของ 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()); } }