实现Android手机之间在局域网下传输任意文件
資源下載地址:https://download.csdn.net/download/sheziqiong/86763815
資源下載地址:https://download.csdn.net/download/sheziqiong/86763815
相比于通過 Wiif Direct 進行文件傳輸,通過 Wifi 熱點進行設備配對更加方便,邏輯也更為直接,傳輸一個1G左右的壓縮包用了5分鐘左右的時間,平均傳輸速率有 3.5 M/S 左右。此外,相對于上個版本,新版本除了提供傳輸進度外,還提供了傳輸速率、預估完成時間、文件傳輸前后的MD5碼等數據。
實現的效果如下所示:
開啟Ap熱點接收文件
連接Wiif熱點發送文件
文件傳輸完成后校驗文件完整性
開發步驟分為以下幾點:
一、聲明權限
本應用并不會消耗移動數據,但由于要使用到 Wifi 以及 Java Socket,所以需要申請網絡相關的權限。此外,由于是要實現文件互傳,所以也需要申請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" />二、文件接收端
文件接收端作為服務器存在,需要主動開啟Ap熱點供文件發送端連接,由于通過反射來開啟熱點的方法在高版本系統上無法實現,所以需要用戶主動去開啟熱點,并設置固定的熱點名稱
此處需要先定義一個文件信息模型 FileTransfer ,FileTransfer 包含三個字段,MD5碼值用于校驗文件的完整性,fileLength 是為了用于計算文件的傳輸進度和傳輸速率
public class FileTransfer implements Serializable {//文件路徑private String filePath;//文件大小private long fileLength;//MD5碼private String md5;···}Ap熱點開啟成功后,就可以啟動一個服務在后臺等待文件發送端來主動連接了,這里使用 IntentService 在后臺監聽客戶端的Socket連接請求,并通過輸入輸出流來傳輸文件。此處的代碼比較簡單,就只是在指定端口一直堵塞監聽客戶端的連接請求,獲取待傳輸的文件信息模型 FileTransfer ,之后就進行實際的數據傳輸
文件傳輸速率是每一秒計算一次,根據這段時間內接收的字節數與消耗的時間做除法,從而得到傳輸速率,再通過將剩余的未傳輸字節數與傳輸速率做除法,從而得到預估的剩余傳輸時間
@Overrideprotected void onHandleIntent(Intent intent) {if (intent != null && ACTION_START_RECEIVE.equals(intent.getAction())) {clean();File file = null;Exception exception = null;try {serverSocket = new ServerSocket();serverSocket.setReuseAddress(true);serverSocket.bind(new InetSocketAddress(Constants.PORT));Socket client = serverSocket.accept();Log.e(TAG, "客戶端IP地址 : " + client.getInetAddress().getHostAddress());inputStream = client.getInputStream();objectInputStream = new ObjectInputStream(inputStream);fileTransfer = (FileTransfer) objectInputStream.readObject();Log.e(TAG, "待接收的文件: " + fileTransfer);if (fileTransfer == null) {exception = new Exception("從文件發送端發來的文件模型為null");return;} else if (TextUtils.isEmpty(fileTransfer.getMd5())) {exception = new Exception("從文件發送端發來的文件模型不包含MD5碼");return;}String name = new File(fileTransfer.getFilePath()).getName();//將文件存儲至指定位置file = new File(Environment.getExternalStorageDirectory() + "/" + name);fileOutputStream = new FileOutputStream(file);startCallback();byte[] buf = new byte[512];int len;while ((len = inputStream.read(buf)) != -1) {fileOutputStream.write(buf, 0, len);total += len;}Log.e(TAG, "文件接收成功");stopCallback();if (progressChangListener != null) {//因為上面在計算文件傳輸進度時因為小數點問題可能不會顯示到100%,所以此處手動將之設為100%progressChangListener.onProgressChanged(fileTransfer, 0, 100, 0, 0, 0, 0);//開始計算傳輸到本地的文件的MD5碼progressChangListener.onStartComputeMD5();}} catch (Exception e) {Log.e(TAG, "文件接收 Exception: " + e.getMessage());exception = e;} finally {FileTransfer transfer = new FileTransfer();if (file != null && file.exists()) {transfer.setFilePath(file.getPath());transfer.setFileSize(file.length());transfer.setMd5(Md5Util.getMd5(file));Log.e(TAG, "計算出的文件的MD5碼是:" + transfer.getMd5());}if (exception != null) {if (progressChangListener != null) {progressChangListener.onTransferFailed(transfer, exception);}} else {if (progressChangListener != null && fileTransfer != null) {if (fileTransfer.getMd5().equals(transfer.getMd5())) {progressChangListener.onTransferSucceed(transfer);} else {//如果本地計算出的MD5碼和文件發送端傳來的值不一致,則認為傳輸失敗progressChangListener.onTransferFailed(transfer, new Exception("MD5碼不一致"));}}}clean();//再次啟動服務,等待客戶端下次連接startActionTransfer(this);}}}因為客戶端可能會多次發起連接請求,所以當此處文件傳輸完成后(不管成功或失敗),都需要重新 startService ,讓服務再次堵塞等待客戶端的連接請求
為了讓界面能夠實時獲取到文件的傳輸狀態,所以此處除了需要啟動Service外,界面還需要綁定Service,所以需要用到一個更新文件傳輸狀態的接口
public interface OnProgressChangListener {/* 當傳輸進度發生變化時回調** @param fileTransfer 文件發送方傳來的文件模型* @param progress 文件傳輸進度* @param speed 文件傳輸速率* @param remainingTime 預估的剩余完成時間*/void onProgressChanged(FileTransfer fileTransfer, int progress, double speed, long remainingTime);//當傳輸結束時void onTransferFinished(FileTransfer fileTransfer);}在界面層刷新UI
private FileReceiverService.OnReceiveProgressChangListener progressChangListener = new FileReceiverService.OnReceiveProgressChangListener() {private FileTransfer originFileTransfer;@Overridepublic void onProgressChanged(final FileTransfer fileTransfer, final long totalTime, final int progress, final double instantSpeed, final long instantRemainingTime, final double averageSpeed, final long averageRemainingTime) {this.originFileTransfer = fileTransfer;runOnUiThread(new Runnable() {@Overridepublic void run() {if (isCreated()) {progressDialog.setTitle("正在接收的文件: " + originFileTransfer.getFileName());if (progress != 100) {progressDialog.setMessage("原始文件的MD5碼是:" + originFileTransfer.getMd5()+ "\n\n" + "總的傳輸時間:" + totalTime + " 秒"+ "\n\n" + "瞬時-傳輸速率:" + (int) instantSpeed + " Kb/s"+ "\n" + "瞬時-預估的剩余完成時間:" + instantRemainingTime + " 秒"+ "\n\n" + "平均-傳輸速率:" + (int) averageSpeed + " Kb/s"+ "\n" + "平均-預估的剩余完成時間:" + averageRemainingTime + " 秒");}progressDialog.setProgress(progress);progressDialog.setCancelable(true);progressDialog.show();}}});}@Overridepublic void onStartComputeMD5() {runOnUiThread(new Runnable() {@Overridepublic void run() {if (isCreated()) {progressDialog.setTitle("傳輸結束,正在計算本地文件的MD5碼以校驗文件完整性");progressDialog.setMessage("原始文件的MD5碼是:" + originFileTransfer.getMd5());progressDialog.setCancelable(false);progressDialog.show();}}});}@Overridepublic void onTransferSucceed(final FileTransfer fileTransfer) {runOnUiThread(new Runnable() {@Overridepublic void run() {if (isCreated()) {progressDialog.setTitle("傳輸成功");progressDialog.setMessage("原始文件的MD5碼是:" + originFileTransfer.getMd5()+ "\n" + "本地文件的MD5碼是:" + fileTransfer.getMd5()+ "\n" + "文件位置:" + fileTransfer.getFilePath());progressDialog.setCancelable(true);progressDialog.show();Glide.with(FileReceiverActivity.this).load(fileTransfer.getFilePath()).into(iv_image);}}});}@Overridepublic void onTransferFailed(final FileTransfer fileTransfer, final Exception e) {runOnUiThread(new Runnable() {@Overridepublic void run() {if (isCreated()) {progressDialog.setTitle("傳輸失敗");progressDialog.setMessage("原始文件的MD5碼是:" + originFileTransfer.getMd5()+ "\n" + "本地文件的MD5碼是:" + fileTransfer.getMd5()+ "\n" + "文件位置:" + fileTransfer.getFilePath()+ "\n" + "異常信息:" + e.getMessage());progressDialog.setCancelable(true);progressDialog.show();}}});}};三、文件發送端
文件發送端作為客戶端存在,需要主動連接文件接收端開啟的Wifi熱點
/* 連接指定Wifi** @param context 上下文* @param ssid SSID* @param password 密碼* @return 是否連接成功*/public static boolean connectWifi(Context context, String ssid, String password) {String connectedSsid = getConnectedSSID(context);if (!TextUtils.isEmpty(connectedSsid) && connectedSsid.equals(ssid)) {return true;}openWifi(context);WifiConfiguration wifiConfiguration = isWifiExist(context, ssid);if (wifiConfiguration == null) {wifiConfiguration = createWifiConfiguration(ssid, password);}WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);if (wifiManager == null) {return false;}int networkId = wifiManager.addNetwork(wifiConfiguration);return wifiManager.enableNetwork(networkId, true);}/* 開啟Wifi** @param context 上下文* @return 是否成功*/public static boolean openWifi(Context context) {WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);return wifiManager != null && (wifiManager.isWifiEnabled() || wifiManager.setWifiEnabled(true));}/* 獲取當前連接的Wifi的SSID** @param context 上下文* @return SSID*/public static String getConnectedSSID(Context context) {WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);WifiInfo wifiInfo = wifiManager == null ? null : wifiManager.getConnectionInfo();return wifiInfo != null ? wifiInfo.getSSID().replaceAll("\"", "") : "";}連接到指定Wifi后,在選擇了要發送的文件后,就啟動一個后臺線程去主動請求連接服務器端,然后就是進行實際的文件傳輸操作了
demo 提供的例子是只用來傳輸圖片,但理論上是可以傳輸任意格式的文件的
private void navToChose() {Matisse.from(this).choose(MimeType.ofImage()).countable(true).showSingleMediaType(true).maxSelectable(1).capture(false).captureStrategy(new CaptureStrategy(true, BuildConfig.APPLICATION_ID + ".fileprovider")).restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED).thumbnailScale(0.70f).imageEngine(new Glide4Engine()).forResult(CODE_CHOOSE_FILE);}獲取選取的文件的實際路徑,并啟動 FileSenderService 去進行文件傳輸操作
@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == CODE_CHOOSE_FILE && resultCode == RESULT_OK) {List<String> strings = Matisse.obtainPathResult(data);if (strings != null && !strings.isEmpty()) {String path = strings.get(0);File file = new File(path);if (file.exists()) {FileTransfer fileTransfer = new FileTransfer(file);Log.e(TAG, "待發送的文件:" + fileTransfer);FileSenderService.startActionTransfer(this, fileTransfer, WifiLManager.getHotspotIpAddress(this));}}}}資源下載地址:https://download.csdn.net/download/sheziqiong/86763815
資源下載地址:https://download.csdn.net/download/sheziqiong/86763815
總結
以上是生活随笔為你收集整理的实现Android手机之间在局域网下传输任意文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AI面试需要注意哪些事项?
- 下一篇: c 自动打印的服务器,C-Lodop云打