1. 程式人生 > >點對點檔案傳輸,Android無網路傳輸檔案的原理

點對點檔案傳輸,Android無網路傳輸檔案的原理


今日科技快訊

昨日,上海交通執法部門在虹橋機場、虹橋火車站等共14個執法點開展了代號為“天網2號”的網約車非法客運專項整治行動。當天行動累計查處利用網約平臺從事非法客運的案件37件,其中“滴滴”29件,“美團”6件,“神州”1件,“嘀嗒”1件。這些車輛及駕駛員均無營運資質,他們通過自己在網約平臺軟體上的註冊賬號接單,將乘客送至目的地,並收取車費。

作者簡介

明天就是清明小長假了,希望大家都能好好享受這幾天的假期時光,我們清明節後見!

本篇來自  葉應是葉 的投稿,分享了Android 實現無網路傳輸檔案,一起來看看!希望大家喜歡。

 葉應是葉  的部落格地址:

https://www.jianshu.com/u/9df45b87cfdf

正文

最近的專案需要實現一個Android手機之間無網路傳輸檔案的功能,就發現了Wifi P2P(Wifi點對點)這麼一個功能,最後也實現了通過Wifi 隔空傳輸檔案的功能,這裡我也來整理下程式碼,分享給大家。

Wifi P2P是在Android 4.0以及更高版本系統中加入的功能,通過Wifi P2P可以在不連線網路的情況下,直接與配對的裝置進行資料交換。相對於藍芽,Wifi P2P的搜尋速度和傳輸速度更快,傳輸距離更遠

實現的效果如下所示:

一般而言,開發步驟分為以下幾點:

  • 在AndroidManifest中宣告相關許可權(網路和檔案讀寫許可權)

  • 獲取WifiP2pManager,註冊相關廣播監聽Wifi直連的狀態變化

  • 指定某一臺裝置為伺服器(用來接收檔案),建立群組並作為群主存在,在指定埠監聽客戶端的連線請求,等待客戶端發起連線請求以及檔案傳輸請求

  • 客戶端(用來發送檔案)主動搜尋附近的裝置,加入到伺服器建立的群組,獲取伺服器的IP地址,向其發起檔案傳輸請求

  • 校驗檔案完整性

宣告許可權

Wifi P2P技術並不訪問網路,但由於會使用到Java套接字,所以需要申請網路許可權。此外,由於是要實現檔案互傳,所以也需要申請SD卡讀寫許可權。

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
   <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"
/>
   <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
   <uses-permission android:name="android.permission.INTERNET" />
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

註冊廣播

與Wifi P2P相關的廣播有以下幾個:

  1. WIFI_P2P_STATE_CHANGED_ACTION(用於指示Wifi P2P是否可用)

  2. WIFI_P2P_PEERS_CHANGED_ACTION(對等節點列表發生了變化)

  3. WIFI_P2P_CONNECTION_CHANGED_ACTION(Wifi P2P的連線狀態發生了改變)

  4. WIFI_P2P_THIS_DEVICE_CHANGED_ACTION(本裝置的裝置資訊發生了變化)

當接收到這幾個廣播時,我們都需要到WifiP2pManager(對等網路管理器)來進行相應的資訊請求,此外還需要用到Channel物件作為請求引數

mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mWifiP2pManager.initialize(this, getMainLooper(), this);

當收到WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION廣播時,可以判斷當前Wifi P2P是否可用

int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
   mDirectActionListener.wifiP2pEnabled(true);
} else {
   mDirectActionListener.wifiP2pEnabled(false);                
}

當收到WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION廣播時,意味裝置周圍的可用裝置列表發生了變化,可以通過requestPeers方法得到可用的裝置列表,之後就可以選擇當中的某一個裝置進行連線操作

mWifiP2pManager.requestPeers(mChannel, new WifiP2pManager.PeerListListener() {
   @Override
   public void onPeersAvailable(WifiP2pDeviceList peers) {
       mDirectActionListener.onPeersAvailable(peers.getDeviceList());
   }
});

當收到WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION廣播時,意味著Wifi P2P的連線狀態發生了變化,可能是連線到了某裝置,或者是與某裝置斷開了連線

NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo.isConnected()) {
   mWifiP2pManager.requestConnectionInfo(mChannel, new WifiP2pManager.ConnectionInfoListener() {
       @Override
       public void onConnectionInfoAvailable(WifiP2pInfo info) {
           mDirectActionListener.onConnectionInfoAvailable(info);
       }
   });
   Log.e(TAG, "已連線p2p裝置");
} else {
   mDirectActionListener.onDisconnection();
   Log.e(TAG, "與p2p裝置已斷開連線");
}

如果是與某裝置連線上了,則可以通過requestConnectionInfo方法獲取到連線資訊。

當收到WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION廣播時,則可以獲取到本裝置變化後的裝置資訊

(WifiP2pDevice) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)

可以看出Wifi P2P的介面高度非同步化,到現在已經用到了三個系統的回撥函式,一個用於WifiP2pManager的初始化,兩個用於在廣播中非同步請求資料,為了簡化操作,此處統一使用一個自定義的回撥函式,方法含義與系統的回撥函式一致

public interface DirectActionListener extends WifiP2pManager.ChannelListener {

   void wifiP2pEnabled(boolean enabled);

   void onConnectionInfoAvailable(WifiP2pInfo wifiP2pInfo);

   void onDisconnection();

   void onSelfDeviceAvailable(WifiP2pDevice wifiP2pDevice);

   void onPeersAvailable(Collection<WifiP2pDevice> wifiP2pDeviceList);

}

所以,整個廣播接收器使用到的所有程式碼是:

/**
* 作者:chenZY
* 時間:2018/2/9 17:53
* 描述:
*/

public class DirectBroadcastReceiver extends BroadcastReceiver {

   private static final String TAG = "DirectBroadcastReceiver";

   private WifiP2pManager mWifiP2pManager;

   private WifiP2pManager.Channel mChannel;

   private DirectActionListener mDirectActionListener;

   public DirectBroadcastReceiver(WifiP2pManager wifiP2pManager, WifiP2pManager.Channel channel, DirectActionListener directActionListener) {
       mWifiP2pManager = wifiP2pManager;
       mChannel = channel;
       mDirectActionListener = directActionListener;
   }

   public static IntentFilter getIntentFilter() {
       IntentFilter intentFilter = new IntentFilter();
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
       return intentFilter;
   }

   @Override
   public void onReceive(Context context, Intent intent) {
       Log.e(TAG, "接收到廣播: " + intent.getAction());
       if (!TextUtils.isEmpty(intent.getAction())) {
           switch (intent.getAction()) {
               // 用於指示 Wifi P2P 是否可用
               case WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION: {
                   int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
                   if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
                       mDirectActionListener.wifiP2pEnabled(true);
                   } else {
                       mDirectActionListener.wifiP2pEnabled(false);
                       List<WifiP2pDevice> wifiP2pDeviceList = new ArrayList<>();
                       mDirectActionListener.onPeersAvailable(wifiP2pDeviceList);
                   }
                   break;
               }
               // 對等節點列表發生了變化
               case WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION: {
                   mWifiP2pManager.requestPeers(mChannel, new WifiP2pManager.PeerListListener() {
                       @Override
                       public void onPeersAvailable(WifiP2pDeviceList peers) {
                           mDirectActionListener.onPeersAvailable(peers.getDeviceList());
                       }
                   });
                   break;
               }
               // Wifi P2P 的連線狀態發生了改變
               case WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION: {
                   NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
                   if (networkInfo.isConnected()) {
                       mWifiP2pManager.requestConnectionInfo(mChannel, new WifiP2pManager.ConnectionInfoListener() {
                           @Override
                           public void onConnectionInfoAvailable(WifiP2pInfo info) {
                               mDirectActionListener.onConnectionInfoAvailable(info);
                           }
                       });
                       Log.e(TAG, "已連線p2p裝置");
                   } else {
                       mDirectActionListener.onDisconnection();
                       Log.e(TAG, "與p2p裝置已斷開連線");
                   }
                   break;
               }
               //本裝置的裝置資訊發生了變化
               case WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION: {
                   mDirectActionListener.onSelfDeviceAvailable((WifiP2pDevice) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));
                   break;
               }
           }
       }
   }

}

伺服器端建立群組

假設當裝置甲搜尋到了裝置B,並與裝置乙連線到了一起,此時系統會自動建立一個群組(集團)並隨機指定一臺裝置為群主(GroupOwner)。此時,對於兩臺裝置來說,群主的IP地址是可知的(系統回撥函式中有提供),但客戶端的IP地址需要再來通過其他方法來主動獲取。例如,可以在裝置連線成功後,客戶端主動發起對伺服器端的插座連線請求,伺服器端在指定埠監聽客戶端的連線請求,當連線成功後,伺服器端就可以獲取到客戶端的IP地址了。此處為了簡化操作,直接指定某臺裝置作為伺服器端(群主),即直接指定某臺裝置用來接收檔案。因此,伺服器端要主動建立群組,並等待客戶端的連線。

wifiP2pManager.createGroup(channel, new WifiP2pManager.ActionListener() {
   @Override
   public void onSuccess() {
       Log.e(TAG, "createGroup onSuccess");
       dismissLoadingDialog();
       showToast("onSuccess");
   }

    @Override
    public void onFailure(int reason) {
       Log.e(TAG, "createGroup onFailure: " + reason);
       dismissLoadingDialog();
       showToast("onFailure");
   }
});

此處,使用IntentService在後臺監聽客戶端的Socket連線請求,並通過輸入輸出流來傳輸檔案。此處的程式碼比較簡單,就是在指定埠一直堵塞監聽客戶端的連線請求,獲取待傳輸的檔案資訊模型FileTransfer,之後就進行實際的資料傳輸

@Override
   protected void onHandleIntent(Intent intent) {
       clean();
       File file = null;
       try {
           serverSocket = new ServerSocket();
           serverSocket.setReuseAddress(true);
           serverSocket.bind(new InetSocketAddress(PORT));
           Socket client = serverSocket.accept();
           Log.e(TAG, "客戶端IP地址 : " + client.getInetAddress().getHostAddress());
           inputStream = client.getInputStream();
           objectInputStream = new ObjectInputStream(inputStream);
           FileTransfer fileTransfer = (FileTransfer) objectInputStream.readObject();
           Log.e(TAG, "待接收的檔案: " + fileTransfer);
           String name = new File(fileTransfer.getFilePath()).getName();
           //將檔案儲存至指定位置
           file = new File(Environment.getExternalStorageDirectory() + "/" + name);
           fileOutputStream = new FileOutputStream(file);
           byte buf[] = new byte[512];
           int len;
           long total = 0;
           int progress;
           while ((len = inputStream.read(buf)) != -1) {
               fileOutputStream.write(buf, 0, len);
               total += len;
               progress = (int) ((total * 100) / fileTransfer.getFileLength());
               Log.e(TAG, "檔案接收進度: " + progress);
               if (progressChangListener != null) {
                   progressChangListener.onProgressChanged(fileTransfer, progress);
               }
           }
           serverSocket.close();
           inputStream.close();
           objectInputStream.close();
           fileOutputStream.close();
           serverSocket = null;
           inputStream = null;
           objectInputStream = null;
           fileOutputStream = null;
           Log.e(TAG, "檔案接收成功,檔案的MD5碼是:" + Md5Util.getMd5(file));
       } catch (Exception e) {
           Log.e(TAG, "檔案接收 Exception: " + e.getMessage());
       } finally {
           clean();
           if (progressChangListener != null) {
               progressChangListener.onTransferFinished(file);
           }
           //再次啟動服務,等待客戶端下次連線
           startService(new Intent(this, WifiServerService.class));
       }
   }

因為客戶端可能會多次發起連線請求,所以當此處理檔案傳輸完成後(不管成功或失敗),都需要重新startService,讓服務再次堵塞等待客戶端的連線請求。

FileTransfer包含三個欄位,MD5碼值用於校驗檔案的完整性,fileLength是為了用於計算檔案的傳輸進度。

public class FileTransfer implements 
            
           

相關推薦

檔案傳輸Android網路傳輸檔案原理

今日科技快訊昨日,上海交通執法部門在虹橋機場、虹橋火車站等共14個執法點開展了代號為“天網2號”

比特幣交易所面臨倒閉、場外交易將盛行

上進 str 訂單 虛擬 交易所 服務 com 交易 比特 9月16日火幣網、OKCoin聲稱要關閉所有虛擬幣業務,關閉交易和關閉交易所是兩個概念。 sosobtc官網也發布通告:於19日晚間23點關閉網站的行情數據、聊天社交等信息服務,APP也將於3日內關閉相關服務。

【51NOD1766】樹上的最遠(線段樹LCARMQ)

long exit div ssi put lac shu 最值 最遠點對 題意:n個點被n-1條邊連接成了一顆樹,給出a~b和c~d兩個區間, 表示點的標號請你求出兩個區間內各選一點之間的最大距離,即你需要求出max{dis(i,j) |a<=i<=b,c&l

【181027】聊天室程式VC++程式原始碼

基於VC++的點對點聊天室程式,程式包括服務端和客戶端,伺服器程式是chatsrvr.exe,客戶端是ChatClient.exe.   伺服器先執行,客戶連線到伺服器執行的計算機的IP即可。在客戶端傳送訊息的時候,可選擇字型顏色、聊天物件、過濾、是否設定為悄悄話等,都是相對基礎點的功能,希望剛

區塊鏈C2C系統搭建區塊鏈交易系統的兩種交易模式你知道嗎?

在平時的交易過程中由於法幣交易受國家監管限制,現在許多交易所都沒有法幣交易版塊,我們平時看到的許多大型的交易所上面寫的是法幣交易,但是卻不是真正的法幣交易,而是點對點交易模式,像比較常見的ZB交易平臺,上面寫著法幣交易,確實C2C點對點交易模式,那麼接下來源中瑞黃顧問(具體加vx:ruiec1688)就給大家

Spring Boot整合websocket實現群聊聊天,圖片傳送音訊傳送

參考:基於https://blog.csdn.net/qq_38455201/article/details/80374712  基礎上進行新增圖片傳送和音訊傳送功能   單點圖片傳送: 單點音訊傳送: 音訊傳送相關js參考:https://github.

Altium軟體怎麼測量的距離怎麼測量邊緣與邊緣之間的距離

Altium軟體怎麼測量點對點的距離,怎麼測量邊緣與邊緣之間的距離 Altium提供了兩種方式進行測量,一種是點對點的測量,一種是邊緣和邊緣的距離測量,其操作命令分別是: 點對點:Reports-M

快速利用第三方平臺SDK實現語音通話

最近公司的一個APP專案需要一個加一個網際網路點對點語音功能,網上找了Linphone 研究了一個月發現這些開源庫很強大也很優秀,支援平臺廣泛 windows, mac,ios,android,linux,但是程式碼量和結構實在龐大,坑爹的專案經理要求一個月把這個功

區塊鏈支付系統開發支付系統開發

支付行業發展最快的領域是移動支付,但是現在最吸引大家眼球的當屬區塊鏈。現下,區塊鏈成為金融科技領域的大熱門,區塊鏈技術在各個行業領域有著廣泛的應用。那麼,“區塊鏈與支付”這個組合能不能友好的合作,是否能達到人們的預期,還要進一步驗證。跨境支付主要有銀行電匯、第三方支付和提現三種主要方式,但是均存在手續費高、流

SpringBoot整合WebSocket【基於STOMP協議】進行[一對一]和廣播[一對多]實時推送內附簡易聊天室demo

最近專案來了新需求,需要做一個實時推送的功能,伺服器主動推送訊息給客戶端,在網上經過一輪搜查之後,確定使用WebSocket來進行開發。以前經常聽說WebSocket的神奇之處,如今終於可以嘗試使用它了。1.淺談WebSocketWebSocket是在HTML5基礎上單個TC

C#完整的通訊程式碼(同步非同步UDPTCP)

C# code namespace UDPServer { class Program { static void Main(string[] args) { int recv; byte[] data = new byte[1024]; //構建TCP 伺服器 //得到本機IP,設定TCP埠號 IPEnd

ActiveMQ的使用與遇到的相關坑(釋出與訂閱,resreq)

1、介紹 ActiveMQ是Apache出品,最流行的,能力強勁的開源訊息匯流排。ActiveMQ 是一個完全支援JMS1.1和J2EE 1.4規範的 JMS Provider實現,儘管JMS規範出臺已經是很久的事情了,但是JMS在當今的J2EE應用中間仍然扮演著特殊的地位

Android websocket長連線+訂閱

專案中使用到了websocket長連線+點對點訂閱,部落格記錄下。 長連線通常使用的是名稱叫做STOMP的協議,具體跟伺服器端的開發人員確認即可。 直接貼乾貨: module build.gradle新增依賴: compile 'com.github.NaikS

使用ActiveMQ+MQTT實現Android訊息通知

使用ActiveMQ+MQTT實現Android點對點訊息通知 [email protected] 2013-12-20 實現點對點訊息通知的關鍵問題 ActiveMQ使用MQTT協議,加上android上的paho包,即可簡單實現訊息通知功能,但是mqtt協議只

JMS消息隊列ActiveMQ(模式)

jms activemq 消息隊列 生產者(producer)->消息隊列(message queue)package com.java1234.activemq; import javax.jms.Connection; import javax.jms.ConnectionFactor

境外“|場外交易OTC新模式數字貨幣開發”強勢來襲

數字貨幣 點對點 場外交易 系統開發 境外“點對點|場外交易OTC新模式數字貨幣開發”強勢來襲 當下面臨著國家監管的政策,行情的變化,很多交易平臺紛紛關閉,傳統的撮合交易系統退出了歷史的舞臺,很多玩家現在出現了問題,自己的幣去哪交易呢?如過交易平臺關了就沒有市場進行交易了呀!針對這個問題出現

activeMQ

ssa oca exceptio pac 開啟事務 ive mes 啟動 cal 摘要: ActiveMQ 點對點消息 Point-to-Point 是一對一 創建消息生產者 /** * 點對點消息生產者 * * @author Edward * */

分析比特幣網絡:一種去中心化、的網絡架構

比特幣 區塊鏈 比特幣采用了基於互聯網的點對點(P2P:peer-to-peer)分布式網絡架構。比特幣網絡可以認為是按照比特幣P2P協議運行的一系列節點的集合。本文來分析下比特幣網絡,了解它跟傳統中心化網絡的區別,以及比特幣網絡是如何發現相鄰節點的。中心化網絡為了更好的理解P2P網絡,我們先來看看傳

HTML5 WebSocket實現聊天的示例代碼

HTML案例分析 HTML5講解 HTML5的websocket與Tomcat實現了多人聊天,那是最簡單也是最基本的,其中註意的就是開發環境,要滿足jdk1.7和tomcat8,當然了tom7的7.063也行,在網站上找到了用google關於websocket的點對點聊天,更好的是可以和大多數系統很好

15、網絡--實驗三(搭建VXLAN網絡)

linux hostman docker network 先來搭建一個最簡單的vxlan網絡,兩個namespace構成一個vxlan網絡,每個vxlan網絡上有一個vnet,vnet通過它們的IP互相通信.1) 創建vnet_pair具體參考”實驗二”章節.http://blog.51cto