Ubuntu編譯核心
當你給朋友傳送手機資料時,過了很久進度條卻動也不動;當你想傳送大檔案給同事時,僅一個檔案就用光了你所有流量;當你跟朋友乘坐飛機時想一起玩遊戲時,卻因沒有網路無奈放棄。
們生活中似乎經常能遇到這種尷尬的場景,近距離資料傳輸功能是使用者的一個痛點。現在,只需要接入華為近距離通訊服務,通過Nearby Connection便可以輕鬆實現裝置間的資料傳輸,傳輸型別支援短文字、流資料和檔案資料等型別,可幫助app實現本地多人遊戲、實時協作、多屏遊戲和離線檔案傳輸等功能。下圖是功能演示:
如果你對實現方式感興趣,可以在Github上下載原始碼:
首先需要了解Nearby Connection 開發流程
1. 業務流程
整體流程可以劃分為4個階段。
廣播掃描階段:廣播端啟動廣播,發現端啟動掃描以發現廣播端。
- 廣播端呼叫startBroadcasting()啟動廣播。
- 發現端呼叫startScan()啟動掃描以發現附近的裝置。
- 由onFound()方法通知掃描結果。
建立連線階段:發現端發起連線並啟動對稱的身份驗證流程,雙端獨立接受或拒絕連線請求。
- 發現端呼叫requestConnect()向廣播端發起連線請求。
- 兩端由onEstablish()通知連線啟動後,均可以呼叫acceptConnect()接受連線或呼叫rejectConnect()拒絕連線。
- 兩端由onResult()通知連線結果。僅當兩端都接受連線時,連線才能建立。
傳輸資料階段:建立連線後,雙端進行資料交換。
- 連線建立後,雙端均可以呼叫sendData()傳送資料給對端。
- 接收資料的一端由onReceived()通知接收到資料;兩端由onTransferUpdate()通知當前的傳輸狀態。
斷開連線階段:雙端任意一端發起斷開連線,通知對端連線斷開。
- 主動斷開連線的一端呼叫disconnect()斷開連線,對端由onDisconnected()通知連線斷開。
2. 開發步驟
2.1 開發準備
如果你以前沒有整合華為移動服務的經驗,那麼需要先配置AppGallery Connect,開通近距離通訊服務並整合HMS SDK。相關步驟請參考官方文件。
2.2 宣告系統許可權
Nearby Connection開發場景需要使用Nearby Discovery API和Nearby Transfer API,你的應用必須根據所使用的策略宣告適當的許可權。例如:使用POLICY_STAR策略開發檔案傳輸的應用,需要新增特定的許可權到AndroidManifest.xml:
<!-- Required for Nearby Discovery and Nearby Transfer --> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- Required for FILE payloads --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
由於ACCESS_FINE_LOCATION,WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE 是危險的系統許可權,因此,必須動態的申請這些許可權。如果許可權不足,近距離通訊服務(Nearby Service)將會拒絕應用開啟廣播或者開啟發現。
2.3 選擇策略
Nearby Discovery支援3種不同的連線策略:POLICY_MESH,POLICY_STAR和POLICY_P2P。可以根據應用場景優選策略。
策略選擇並建立BroadcastOption物件的示例程式碼如下:
Policy policy = Policy.POLICY_STAR; BroadcastOption broadcastOption = new BroadcastOption.Builder().setPolicy (policy).build();
2.4 廣播和掃描
一旦授予應用所需的許可權,併為應用選擇一個策略,就可以開始廣播和掃描以發現附近的裝置。
2.4.1 啟動廣播
廣播端以選定的policy和serviceId為引數,呼叫startBroadcasting()啟動廣播。其中serviceId應該唯一標識的應用。建議使用應用的包名作為serviceId(例如:com.huawei.example.myapp)。示例程式碼如下:
private void doStartBroadcasting() { Policy policy = Policy.POLICY_STAR; BroadcastOption broadcastOption = new BroadcastOption.Builder().setPolicy(policy).build(); Nearby.getDiscoveryEngine(getApplicationContext()) .startBroadcasting(name,serviceId,connectCallback,broadcastOption) .addOnSuccessListener( new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { /* We are broadcasting. */ } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception e) { /* Fail to start broadcasting. */ } }); }
引數connectCallback是一個連線監聽回撥類例項,用於通知連線狀態資訊。有關ConnectCallback類的詳細資訊及示例程式碼,參見確認連線章節。
2.4.2 啟動掃描
發現端以選定的policy和serviceId為引數,呼叫startScan()啟動掃描以發現附近的裝置。示例程式碼如下:
private void doStartScan() { Policy policy = Policy.POLICY_STAR; ScanOption scanOption = new ScanOption.Builder().setPolicy(policy).build(); Nearby.getDiscoveryEngine(getApplicationContext()) .startScan(serviceId,scanEndpointCallback,scanOption) .addOnSuccessListener( new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { /* Start scan success. */ } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception e) { /* Fail to start scan. */ } }); }
引數scanEndpointCallback是一個掃描監聽回撥類例項,通知發現端掃描結果,發現裝置或者已發現裝置丟失。
private ScanEndpointCallback scanEndpointCallback = new ScanEndpointCallback() { @Override public void onFound(String endpointId,ScanEndpointInfo discoveryEndpointInfo) { mEndpointId = endpointId; mDiscoveryEngine.requestConnect(myNameStr,mEndpointId,mConnCb); } @Override public void onLost(String endpointId) { Log.d(TAG,"Nearby Connection Demo app: Lost endpoint: " + endpointId); } };
2.4.3 停止廣播
當需要停止廣播時,呼叫stopBroadcasting()。停止廣播後,廣播端不可以接收來自發現端的連線請求。
2.4.4 停止掃描
當需要停止掃描時,呼叫stopScan()。停止掃描後,發現端仍可以向已發現的裝置請求連線。一種常見的做法是:一旦發現需要連線的裝置,就呼叫stopScan()停止掃描。
2.5 建立連線
2.5.1 請求連線
當附近的裝置被發現,發現端可以呼叫requestConnect()發起連線。示例程式碼如下:
private void doStartConnect(String name,String endpointId) throws RemoteException { Nearby.getDiscoveryEngine(getApplicationContext()) .requestConnect(name,endpointId,connectCallback) .addOnSuccessListener( new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { /* Request success. */ } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception e) { /* Fail to request connect. */ } }); }
當然,根據需要,可以向用戶展示發現的裝置列表,並允許他們選擇連線哪些裝置。
2.5.2 確認連線
發現端發起連線後,通過回撥connectCallback的onEstablish()方法將連線建立事件通知給雙方。雙方必須通過呼叫acceptConnect()接受連線或者通過呼叫rejectConnect()拒絕連線。僅當雙方都接受連線時,連線才會建立成功。如果一方或雙方都選擇拒絕,則連線失敗。無論哪種方式,連線結果都會通過onResult()方法通知。示例程式碼如下:
private final ConnectCallback connectCallback = new ConnectCallback() { @Override public void onEstablish(String endpointId,ConnectInfo connectInfo) { /* Accept the connection request without notifying user. */ Nearby.getDiscoveryEngine(getApplicationContext()) .acceptConnect(endpointId,dataCallback); } @Override public void onResult(String endpointId,ConnectResult result) { switch (result.getStatus().getStatusCode()) { case StatusCode.STATUS_SUCCESS: /* The connection was established successfully,we can exchange data. */ break; case StatusCode.STATUS_CONNECT_REJECTED: /* The Connection was rejected. */ break; default: /* other unknown status code. */ } } @Override public void onDisconnected(String endpointId) { /* The connection was disconneted. */ } };
此示例顯示了一種雙方自動接受連線的確認連線方式。根據需要,可以使用其他的確認連線方式。
2.5.3 驗證連線
應用程式可以提供一種讓使用者確認連線到指定裝置的方法,例如:通過驗證token(token可以是一個短隨機字串或者數字)。通常這涉及在兩個裝置上顯示token並要求使用者手動輸入或者確認,類似於藍芽配對對話方塊。
下面演示一種通過彈窗確認配對碼的方式驗證連線。示例程式碼如下:
@Override public void onEstablish(String endpointId,ConnectInfo connectInfo) { AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext()); builder.setTitle(connectInfo.getEndpointName() + " request connection") .setMessage("Please confirm the match code is: " + connectInfo.getAuthCode()) .setPositiveButton( "Accept",(DialogInterface dialog,int which) -> /* Accept the connection. */ Nearby.getDiscoveryEngine(getApplicationContext()) .acceptConnect(endpointId,dataCallback)) .setNegativeButton( "Reject",int which) -> /* Reject the connection. */ Nearby.getDiscoveryEngine(getApplicationContext()) .rejectConnect(endpointId)) .setIcon(android.R.drawable.ic_dialog_alert); AlertDialog alert = builder.create(); alert.show(); }
2.6 傳輸資料
裝置間建立連線後,可以使用該連線傳輸Data物件。Data物件的型別包括位元組序列、檔案和流。通過呼叫sendData()方法傳送資料,通過DataCallback類例項的onReceived()方法接收資料。
2.6.1 資料型別
1.BYTES
通過呼叫Data.fromBytes()建立Data.Type.BYTES型別的Data物件。
傳送BYTES型別的資料,示例程式碼如下:
Data bytesData = Data.fromBytes(new byte[] {0xA,0xA,0xA}); Nearby.getTransferEngine(getApplicationContext()).sendData(toEndpointId,bytesData);
注意:BYTES型別資料的長度大小不能超過32KB。
接收BYTES型別的資料,示例程式碼如下:
static class BytesDataReceiver extends DataCallback { @Override public void onReceived(String endpointId,Data data) { /* BYTES data is sent as a single block,so we can get complete data. */ if (data.getType() == Data.Type.BYTES) { byte[] receivedBytes = data.asBytes(); } } @Override public void onTransferUpdate(String endpointId,TransferStateUpdate update) { /* We will receive TRANSFER_STATE_SUCCESS update after onReceived() called. */ } }
注意:BYTES與FILE和STREAM型別不同,BYTES是以單個數據塊傳送的,因此接收端不用等待BYTES型別的狀態更新為TRANSFER_STATE_SUCCESS,當onReceived()被呼叫時候,你就可以呼叫data.asBytes()以獲取全部資料。
2.FILE
通過呼叫Data.fromFile()建立Data.Type.FILE型別的Data物件。
傳送FILE型別資料的示例程式碼如下:
File fileToSend = new File(getApplicationContext().getFilesDir(),"fileSample.txt"); try { Data fileData = Data.fromFile(fileToSend); Nearby.getTransferEngine(getApplicationContext()) .sendData(endpointList,fileData); } catch (FileNotFoundException e) { /* Exception handle. */ }
一種更高效方法是使用ParcelFileDescriptor建立FILE型別,可以最大程度地減少檔案的複製。示例程式碼如下:
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri,"r"); Data fileData = Data.fromFile(pfd);
接收裝置收到檔案後,檔案儲存在Download目錄,並且將以fileData.getId()轉化後的字串命名。傳輸完成後,可以獲取FILE物件。示例程式碼如下:
/* We can get the received file in the Download folder. */ File payloadFile = fileData.asFile().asJavaFile(); )
3.STREAM
通過呼叫Data.fromStream()建立Data.Type.STREAM型別的Data物件。傳送流的示例程式碼如下:
URL url = new URL("https://developers.huawei.com"); Data streamData = Data.fromStream(url.openStream()); Nearby.getTransferEngine(getApplicationContext()).sendData(toEndpointId,streamData);
接收端,當onTransferUpdate()回撥成功時,可以呼叫streamData.asStream().asInputStream()或者 streamData.asStream().asParcelFileDescriptor()獲取流物件。示例程式碼如下:
static class StreamDataReceiver extends DataCallback { private final HashMap<Long,Data> incomingData = new HashMap<>(); @Override public void onTransferUpdate(String endpointId,TransferStateUpdate update) { if (update.getStatus() == TransferStateUpdate.Status.TRANSFER_STATE_SUCCESS) { Data data = incomingData.get(update.getDataId()); InputStream inputStream = data.asStream().asInputStream(); /* Further processing... */ } } @Override public void onReceived(String endpointId,Data data) { incomingData.put(data.getId(),data); } }
2.6.2 進度更新
DataCallBack回撥類onTransferUpdate()方法提供資料傳送或接收的進度更新,基於此可以向用戶顯示傳輸進度,例如:進度條。
2.6.3 取消傳輸
如果需要在接收或傳送過程中取消傳輸,呼叫TransferEngine類例項方法cancelDataTransfer()。
2.7 斷開連線
如果需要斷開與對端的連線,呼叫DiscoveryEngine類例項方法disconnect()。一旦呼叫此介面,將不能從此endpoint收發資料。
結後語
基於Nearby Connection,可以幫助你的APP實現如下相關功能:
本地多人遊戲:自組網,提供低延時、穩定可靠的傳輸體驗。離線檔案傳輸:無需流量,可達80MB/S的傳輸速度。
更詳細的開發指南參考華為開發者聯盟官網:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/introduction-0000001050040566
到此這篇關於如何用HMS Nearby Service給自己的App新增近距離資料傳輸功能的文章就介紹到這了,更多相關HMS Nearby Service App資料傳輸內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!