国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Android 藍牙通信(通過 BluetoothSocket 傳輸文件/文本)

這篇具有很好參考價值的文章主要介紹了Android 藍牙通信(通過 BluetoothSocket 傳輸文件/文本)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

前言:Android 藍牙通信,通過BluetoothSocket方式建立長連接并傳輸文本或文件。前段時間有個項目的功能需求是:AR眼鏡通過藍牙的方式連接北斗設備,當北斗設備收到文本/語音/圖片消息時轉發(fā)到AR眼鏡上,AR眼鏡也可以發(fā)送文本/語音/圖片數(shù)據到北斗設備上并轉發(fā)到指定的目標地址。剛開始在百度和github找了許多方法都不盡人意而且大多數(shù)據傳輸都僅僅停留在文字方面,不過好在最后臨近項目deadline時想到了一種傻瓜也簡單的方法實現(xiàn)了這個需求。如果你也恰好遇到了這種 "通過藍牙或其他低效率的方式傳輸文件" 類似的情景可以參考這篇文章,希望這篇文章對你的思路有所啟發(fā),如果有錯漏或可優(yōu)化之處也歡迎提醒。

一、上代碼

DEMO:https://github.com/LXTTTTTT/Android-Bluetooth-Chat-And-Transfer
源碼資源:https://download.csdn.net/download/lxt1292352578/88677601
直接復制就能使用

package com.example.sockettransfer;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.util.Log;
import org.greenrobot.eventbus.EventBus;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

// 藍牙 Socket 工具
public class BluetoothSocketUtil {

    private static String TAG = "BluetoothSocketUtil";
    private Context context;
    private BluetoothAdapter bluetoothAdapter;
    private BluetoothSocket bluetoothSocket;
    public BluetoothDevice nowDevice;
    private InputStream inputStream;
    private OutputStream outputStream;
    private Set<BluetoothDevice> pairedDeviceList;
    private List<BluetoothDevice> devices = new ArrayList();
    private ReceiveDataThread receiveDataThread;
    private ListenThread listenThread;

    private final UUID MY_UUID = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");

    private int state = 0;
    private final int STATE_DISCONNECT = 0;
    private final int STATE_CONNECTING = 1;
    private final int STATE_CONNECTED = 2;

    public boolean isConnectedDevice = false;
    public boolean isSendFile = false;
// 單例 ----------------------------------------------------------------
    private static BluetoothSocketUtil bluetoothSocketUtil;
    public static BluetoothSocketUtil getInstance() {
        if (bluetoothSocketUtil == null) {
            bluetoothSocketUtil = new BluetoothSocketUtil();
        }
        return bluetoothSocketUtil;
    }

    public BluetoothSocketUtil() {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    public void init(Context context){
        this.context = context;
        registerBroadcast();
        listen();  // 開啟設備連接監(jiān)聽
    }

    public Set<BluetoothDevice> getPairedDeviceList(){
        if(bluetoothAdapter!=null){
            return bluetoothAdapter.getBondedDevices();
        }else {
            return null;
        }
    }
    public void searchDevice(){
        if(bluetoothAdapter==null){return;}
        if (bluetoothAdapter.isDiscovering()) {
            bluetoothAdapter.cancelDiscovery();
        }
        devices.clear();
        bluetoothAdapter.startDiscovery();
    }
    public void stopSearch() {
        if (bluetoothAdapter != null && bluetoothAdapter.isDiscovering()) {
            bluetoothAdapter.cancelDiscovery();
        }
    }

    public void registerBroadcast() {
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.bluetooth.device.action.FOUND");
        filter.addAction("android.bluetooth.adapter.action.DISCOVERY_FINISHED");
        filter.addAction("android.bluetooth.device.action.ACL_CONNECTED");
        filter.addAction("android.bluetooth.device.action.ACL_DISCONNECTED");
        context.registerReceiver(receiver, filter);
        Log.e(TAG, "廣播注冊成功");
    }

    // 藍牙連接監(jiān)聽廣播
    private final BroadcastReceiver receiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Log.e(TAG, "收到廣播: "+action);
            if ("android.bluetooth.device.action.FOUND".equals(action)) {
                BluetoothDevice device = (BluetoothDevice) intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
                if (device.getName() == null) {return;}
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    if (!devices.contains(device)) {
                        devices.add(device);
                        if(onBluetoothSocketWork!=null){onBluetoothSocketWork.onDiscoverNewDevice(devices);}
                    }
                }
            }
        }
    };

    public void listen(){
        if(state!=STATE_DISCONNECT){return;}
        if(listenThread!=null){
            listenThread.cancel();
            listenThread = null;
        }
        listenThread = new ListenThread();
        listenThread.start();
    }

    private class ListenThread extends Thread{
        private BluetoothServerSocket bluetoothServerSocket;
        private boolean listen = false;
        public ListenThread(){
            try {
                bluetoothServerSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord("name", MY_UUID);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        @Override
        public void run() {
            listen = true;
            Log.e(TAG, "開啟設備連接監(jiān)聽"+listen+"/"+(state==STATE_DISCONNECT) );
            while (listen && state==STATE_DISCONNECT){
                try {
                    bluetoothSocket = bluetoothServerSocket.accept();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (bluetoothSocket != null) {
                    try {
                        Log.e(TAG, "監(jiān)聽到設備連接" );
                        state = STATE_CONNECTING;
                        if(onBluetoothSocketWork!=null){onBluetoothSocketWork.onConnecting();}
                        inputStream = bluetoothSocket.getInputStream();
                        outputStream = bluetoothSocket.getOutputStream();
                        state = STATE_CONNECTED;
                        isConnectedDevice = true;
                        nowDevice = bluetoothSocket.getRemoteDevice();
                        receiveDataThread = new ReceiveDataThread();
                        receiveDataThread.start();  // 開啟讀數(shù)據線程

                        if(onBluetoothSocketWork!=null){onBluetoothSocketWork.onConnected(nowDevice.getName());}
                        EventMsg msg = new EventMsg();
                        msg.msgType = EventMsg.CONNECT_DEVICE;
                        msg.content = nowDevice.getName();
                        EventBus.getDefault().post(msg);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        public void cancel(){
            listen = false;
            try {
                if(bluetoothServerSocket!=null){
                    bluetoothServerSocket.close();
                    bluetoothServerSocket=null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void connect(BluetoothDevice device) {
        Log.e(TAG, "連接設備: " + device.getName()+"/"+state);
        if (state == STATE_CONNECTING || state == STATE_CONNECTED) {return;}
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    bluetoothSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
                    state = STATE_CONNECTING;
                    if(onBluetoothSocketWork!=null){onBluetoothSocketWork.onConnecting();}
                    bluetoothSocket.connect();
                    inputStream = bluetoothSocket.getInputStream();
                    outputStream = bluetoothSocket.getOutputStream();
                    state = STATE_CONNECTED;
                    isConnectedDevice = true;
                    nowDevice = device;
                    receiveDataThread = new ReceiveDataThread();
                    receiveDataThread.start();  // 開啟讀數(shù)據線程

                    if(onBluetoothSocketWork!=null){onBluetoothSocketWork.onConnected(device.getName());}
                    EventMsg msg = new EventMsg();
                    msg.msgType = EventMsg.CONNECT_DEVICE;
                    msg.content = device.getName();
                    EventBus.getDefault().post(msg);
                }catch(Exception e){
                    e.printStackTrace();
                    disconnect();
                }
            }
        }).start();
    }

    private byte[] readBuffer = new byte[1024];
    private class ReceiveDataThread extends Thread{
        private boolean receive = false;
        byte[] buffer = new byte[1024];
        @Override
        public void run() {
            if(inputStream==null){return;}
            receive = true;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while (receive){
                try{
                    int size = inputStream.read(buffer);
                    if(size>0){
                        baos.write(buffer, 0, size);
                        readBuffer = baos.toByteArray();
                        receiveData(readBuffer);
                        baos.reset();
                    }else if(size==-1){
                        Log.e(TAG, "BluetoothSocket: 斷開了");
                        cancel();
                        disconnect();
                        break;
                    }
                }catch (Exception e){
                    e.printStackTrace();
                    // 斷開連接了,通常 inputStream.read 時觸發(fā)這個
                    Log.e(TAG, "BluetoothSocket: 讀取數(shù)據錯誤");
                    cancel();
                    disconnect();

                    EventMsg msg = new EventMsg();
                    msg.msgType = EventMsg.DISCONNECT_DEVICE;
                    EventBus.getDefault().post(msg);
                }
            }
        }

        public void cancel(){
            receive = false;
        }
    }

    /**
     * 自定義一個標識頭來描述發(fā)送的數(shù)據
     * 格式:$*x*$0000xxxx
     * 前五位 "$*x*$" 中的x為可變數(shù)字,表示發(fā)送數(shù)據的類型,我這里用到的是 "1"-文本,"2"-圖片,根據實際需求自定義
     * 后八位 "0000xxxx" 為發(fā)送數(shù)據內容的長度,格式為固定8位的16進制數(shù)據,不足8位則高位補0,最多可以表示 0xFFFFFFFF 個字節(jié),如果發(fā)送的文件超出了這個范圍則需要自行修改
     * 例子:
     * 發(fā)送文本數(shù)據 "測試" 打包標識 "$*1*$" + 將"測試"以GB18030標準轉化為byte[]后的長度(hex) —— "$*1*$00000004",后續(xù)發(fā)送轉化后的byte[]
     * 發(fā)送圖片數(shù)據 打包標識 "$*2*$" + 讀取指定路徑的文件byte[]的長度(hex) —— "$*2*$0033CE27",后續(xù)發(fā)送讀取到的文件byte[]
     **/
    public void send_text(String data_str){
        if(outputStream==null){return;}
        if(isSendFile){return;}
        // 建議使用線程池
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    byte[] data_bytes = data_str.getBytes("GB18030");
                    String head = "$*1*$"+String.format("%08X", data_bytes.length);
                    Log.e(TAG, "發(fā)送文本,打包標識頭: "+head );
                    outputStream.write(head.getBytes(StandardCharsets.UTF_8));
                    outputStream.write(data_bytes);

                    EventMsg msg = new EventMsg();
                    msg.msgType = EventMsg.SEND_TEXT;
                    msg.content = data_str;
                    EventBus.getDefault().post(msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void send_file(String path){
        if(outputStream==null){return;}
        if(isSendFile){return;}
        new Thread(new Runnable() {
            @Override
            public void run() {
                File file = new File(path);
                if (!file.exists() || !file.isFile()) {
                    Log.e(TAG, "文件不存在");
                    return;
                }else {
                    Log.e(TAG, "開始發(fā)送文件");
                    isSendFile = true;
                }

                byte[] file_byte = fileToBytes(path);
                try {
                    Thread.sleep(100);
                    String head;
                    head = "$*2*$"+String.format("%08X", file_byte.length);
                    Log.e(TAG, "發(fā)送文件,打包標識頭: "+head );
                    outputStream.write(head.getBytes(StandardCharsets.UTF_8));
                    outputStream.write(file_byte);
                    isSendFile = false;

                    EventMsg msg = new EventMsg();
                    msg.msgType = EventMsg.SEND_FILE;
                    msg.content = path;
                    EventBus.getDefault().post(msg);
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.e(TAG, "文件發(fā)送失敗", e);
                    isSendFile = false;
                }
            }
        }).start();
    }


    private boolean startReceiveFile = false;  // 是否開始接收文件數(shù)據
    private ByteArrayOutputStream file_bytes_baos = new ByteArrayOutputStream();
    private long file_length = 0;  // 文件數(shù)據長度
    private int message_type = 0;  // 消息類型:0-初始狀態(tài) 1-文本 2-圖片
    private void receiveData(byte[] data_bytes) {
        Log.e(TAG, "處理數(shù)據長度: "+data_bytes.length );
        // 還沒收到標識頭,如果一直沒有收到就舍棄直到收到標識頭為止
        if(!startReceiveFile){
            try{
                // 首先判斷收到的數(shù)據是否包含了標識頭
                String data_str = new String(data_bytes,StandardCharsets.UTF_8);
                int head_index = data_str.indexOf("$*");
//                Pattern pattern = Pattern.compile("\\$\\*\\d\\*\\$");
//                Matcher matcher = pattern.matcher(data_str);
                // 有頭
                if(head_index>=0){
                    startReceiveFile = true;
                    String head = data_str.substring(head_index,head_index+13);  // $*1*$00339433
                    String msg_type = head.substring(0,5);  // $*1*$
                    if(msg_type.contains("1")){message_type = 1;} else {message_type = 2;}
                    String length_hex = head.substring(5);  // 00339433
                    file_length = Long.parseLong(length_hex,16);  // 解析文件數(shù)據長度
                    Log.e(TAG, "解析標識頭 head: "+head+" 文件數(shù)據長度:"+file_length);

                    file_bytes_baos.write(data_bytes,13,data_bytes.length-13);  // 存儲標識以外的文件數(shù)據
                    // 如果文本數(shù)據的話則只有一波,這時要判斷收到的數(shù)據總長度是否文件數(shù)據長度+標識頭數(shù)據長度
                    if(data_bytes.length==file_length+13){
                        parseData();
                    }
                }else {
                    Log.e(TAG, "receiveData: 沒有頭"+data_str );
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        // 后續(xù)的都是文件數(shù)據
        else {
            try {
                file_bytes_baos.write(data_bytes);  // 保存文件數(shù)據
                Log.e(TAG, "總長度: "+file_length+" /已接收長度: "+file_bytes_baos.size());
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, "文件數(shù)據保存失敗");
            }
            // 每次接收完數(shù)據判斷一下存儲的文件數(shù)據達到數(shù)據長度了嗎
            if(file_bytes_baos.size()>=file_length){
                parseData();
            }
        }
    }

    public void parseData(){
        if(message_type==0){return;}
        if(message_type==1){
            String content = "";
            try {
                content = new String(file_bytes_baos.toByteArray(),"GB18030");  // 文本消息直接轉碼
                Log.e(TAG, "數(shù)據接收完畢,文本:"+content);
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 初始化狀態(tài)
            startReceiveFile = false;
            file_bytes_baos.reset();
            file_length = 0;
            message_type = 0;

            EventMsg msg = new EventMsg();
            msg.msgType = EventMsg.RECEIVE_TEXT;
            msg.content = content;
            EventBus.getDefault().post(msg);
        }else if(message_type==2){
            Log.e(TAG, "數(shù)據接收完畢,圖片" );
            // 保存圖片數(shù)據
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 默認保存在系統(tǒng)的 Download 目錄下,自行處理
                        String imgFilePath= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + "/receiveImage.jpg";
                        File imageFile = new File(imgFilePath);
                        try (FileOutputStream fos = new FileOutputStream(imageFile)) {
                            fos.write(file_bytes_baos.toByteArray());
                        }
                        // 初始化狀態(tài)
                        startReceiveFile = false;
                        file_bytes_baos.reset();
                        file_length = 0;
                        message_type = 0;

                        EventMsg msg = new EventMsg();
                        msg.msgType = EventMsg.RECEIVE_FILE;
                        msg.content = imgFilePath;
                        EventBus.getDefault().post(msg);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    // 讀取文件數(shù)據
    public static byte[] fileToBytes(String filePath){
        File file = new File(filePath);
        try (FileInputStream fis = new FileInputStream(file);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024*1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }
            return bos.toByteArray();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    public void disconnect(){
        try {
            if(inputStream!=null){
                inputStream.close();
                inputStream=null;
            }
            if(outputStream!=null){
                outputStream.close();
                outputStream=null;
            }
            if(receiveDataThread!=null){
                receiveDataThread.cancel();
                receiveDataThread = null;
            }
            if(bluetoothSocket!=null){
                bluetoothSocket.close();
                bluetoothSocket = null;
            }
            if(onBluetoothSocketWork!=null){onBluetoothSocketWork.onDisconnect();}
            state = STATE_DISCONNECT;
            isConnectedDevice = false;
            nowDevice = null;
            listen();  // 斷開后重新開啟設備連接監(jiān)聽
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void destroy(){
        disconnect();
        if(listenThread!=null){listenThread.cancel();listenThread=null;}
        if(context!=null){context.unregisterReceiver(receiver);}
    }

// 接口 ---------------------------------------------
    public interface OnBluetoothSocketWork{
        void onConnecting();
        void onConnected(String device_name);
        void onDisconnect();
        void onDiscoverNewDevice(List<BluetoothDevice> devices);
    }
    public OnBluetoothSocketWork onBluetoothSocketWork;
    public void setOnBluetoothSocketWork(OnBluetoothSocketWork onBluetoothSocketWork){
        this.onBluetoothSocketWork = onBluetoothSocketWork;
    }

}

二、連接流程說明

1. 連接說明

在 BluetoothSocket 通信中主動發(fā)起連接的一方作為客戶端,被動監(jiān)聽及接受連接的一方作為服務端。他們在連接時需要保證連接/監(jiān)聽過程中設置的 UUID 一致,并且需要先完成系統(tǒng)的配對操作,這里的配對操作有兩種方式:
一種是先在系統(tǒng)的藍牙頁面手動配對,然后在APP里面獲取已配對藍牙并直接連接
第二種則是在APP里面掃描藍牙設備并在首次連接時完成配對操作,但需要注意的一點是設備的藍牙默認是無法被發(fā)現(xiàn)的因此作為接收方的服務端在連接之前需要開啟藍牙可被觀測

new Intent(android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);

在完成配對并且 UUID 一致的情況下直接連接即可

2. 客戶端連接

在進行連接之前先要獲取到藍牙設備,獲取藍牙設備的方式有兩種,一種是直接獲取系統(tǒng)的已配對設備

BluetoothAdapter.getDefaultAdapter().getBondedDevices();

另一種則是通過開啟藍牙掃描并注冊廣播監(jiān)聽來獲取掃描到的設備,這種方式則需要照應上文中提到的首次連接配對

BluetoothAdapter.getDefaultAdapter().startDiscovery();

獲取目標設備之后設置UUID創(chuàng)建 BluetoothSocket ,連接并取得對應的輸入/輸出流,同時開啟子線程監(jiān)聽輸入流的數(shù)據輸出情況,在這里當這個讀取數(shù)據線程的輸入流斷開時可視作Socket長連接的斷開。創(chuàng)建BluetoothSocket的方法有兩個 createRfcommSocketToServiceRecord 和?createInsecureRfcommSocketToServiceRecord 他們的主要區(qū)別在于連接的安全性,這里僅以安全連接作為例子

BluetoothSocket bluetoothSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
bluetoothSocket.connect();
InputStream inputStream = bluetoothSocket.getInputStream();
OutputStream outputStream = bluetoothSocket.getOutputStream();
new Thread(new Runnable() {
    @Override
    public void run() {
       byte[] readBuffer = new byte[1024];
       byte[] buffer = new byte[1024];
       if(inputStream==null){return;}
       ByteArrayOutputStream baos = new ByteArrayOutputStream();
       state = STATE_CONNECTED;
       while (true){
           try{
              int size = inputStream.read(buffer);
              if(size>0){
                   baos.write(buffer, 0, size);
                   readBuffer = baos.toByteArray();
                   receiveData(readBuffer);  // 解析數(shù)據
                   baos.reset();
              }else if(size==-1){
                   Log.e(TAG, "BluetoothSocket: 斷開了");
                   break;
              }
           }catch (Exception e){
               e.printStackTrace();
               // 斷開連接了,通常 inputStream.read 時觸發(fā)這個
               Log.e(TAG, "BluetoothSocket: 讀取數(shù)據錯誤");
               state = STATE_DISCONNECT;
           }
       }
    }
}).start();

3. 服務端監(jiān)聽

服務端作為接受方不需要主動獲取藍牙設備,只需要開啟監(jiān)聽線程等待連接即可

private BluetoothServerSocket bluetoothServerSocket;
private BluetoothSocket bluetoothSocket;
private InputStream inputStream;
private OutputStream outputStream;
private ListenThread listenThread;

public void listen(){
        if(state!=STATE_DISCONNECT){return;}
        if(listenThread!=null){
            listenThread.cancel();
            listenThread = null;
        }
        listenThread = new ListenThread();
        listenThread.start();
}
private class ListenThread extends Thread{
        private boolean listen = false;
        public ListenThread(){
            try {
                bluetoothServerSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord("name", MY_UUID);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        @Override
        public void run() {
            listen = true;
            Log.e(TAG, "開啟設備連接監(jiān)聽"+listen+"/"+(state==STATE_DISCONNECT) );
            while (listen && state==STATE_DISCONNECT){
                try {
                    if(bluetoothSocket==null){
                        bluetoothSocket = bluetoothServerSocket.accept();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (bluetoothSocket != null) {
                    try {
                        Log.e(TAG, "監(jiān)聽到設備連接" );
                        state = STATE_CONNECTING;
                        inputStream = bluetoothSocket.getInputStream();
                        outputStream = bluetoothSocket.getOutputStream();
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                               byte[] readBuffer = new byte[1024];
                               byte[] buffer = new byte[1024];
                               if(inputStream==null){return;}
                               ByteArrayOutputStream baos = new ByteArrayOutputStream();
                               state = STATE_CONNECTED;
                               while (true){
                                  try{
                                      int size = inputStream.read(buffer);
                                      if(size>0){
                                           baos.write(buffer, 0, size);
                                           readBuffer = baos.toByteArray();
                                           receiveData(readBuffer);
                                           baos.reset();
                                      }else if(size==-1){
                                           Log.e(TAG, "BluetoothSocket: 斷開了");
                                           break;
                                      }
                                  }catch (Exception e){
                                       e.printStackTrace();
                                       // 斷開連接了,通常 inputStream.read 時觸發(fā)這個
                                       Log.e(TAG, "BluetoothSocket: 讀取數(shù)據錯誤");
                                       state = STATE_DISCONNECT;
                                  }
                               }
                            }
                        }).start();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        public void cancel(){
            listen = false;
            try {
                if (bluetoothServerSocket != null) {
                    bluetoothServerSocket.close();
                    bluetoothServerSocket = null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

三、數(shù)據傳輸

1. 基本思路思路

其實整個連接流程非常簡單,相對復雜的地方僅僅在于對流程的把控,這一點可以參考我的DEMO或者按照實際的需求自行調整。
本文的核心點在于數(shù)據傳輸功能,以往在項目中對數(shù)據傳輸?shù)男枨髢H僅停留在文本數(shù)據的傳輸因此實現(xiàn)起來非常簡單,只需要在發(fā)送時添加標識符并按照特定標準編碼在接收時識別到標識符后轉碼并解析內容就好了。而本次的項目需求除了需要傳輸文本外還需要傳輸音頻格式和圖片格式的文件,按照以往的方法無法實現(xiàn)因此就到網上找了許多方法但是卻發(fā)現(xiàn)大多都不盡人意,最后換了一種思路想到了一種比較簡單的方法,經過驗證也恰好可以滿足本次需求。

總體思路很簡單:傳輸?shù)臄?shù)據類型有三種,并且每次接收/處理數(shù)據之前要知道本次有效數(shù)據的長度和傳輸?shù)臄?shù)據類型是什么,再根據對應的類型對后續(xù)的特定長度的數(shù)據進行不同的處理。因此一個簡單標識頭就定義出來了,我只要在每次發(fā)送數(shù)據之前先封裝一個標識頭再發(fā)送后續(xù)的有效數(shù)據,而在接收時則根據這個標識頭來判斷數(shù)據類型和長度作不同處理即可
我定義的標識頭格式很簡單:"$*x*$"(x為數(shù)據類型:1-文本、2-圖片、3-語音)+"固定8位長度的有效數(shù)據字節(jié)長度(16進制),不足8位則高位補0",以UTF-8標準轉碼
例如:
"$*1*$00000008********":具有8個字節(jié)長度的文本數(shù)據,將標識頭后8位數(shù)據"********"以GB18030標準轉化為字符串即可得到傳輸?shù)奈谋緝热?br> "$*2*$00078A00***...":具有494080個字節(jié)長度的圖片數(shù)據,將標識頭后494080位數(shù)據以jpg格式保存即可的得到傳輸內容
注意:這里定義的長度標識固定8位,最多能夠表示0xFFFFFFFF個字節(jié),按實際需求自行修改即可

2. 發(fā)送文本

發(fā)送文本數(shù)據之前先將文本內容以GB18030標準轉為byte[],封裝文本標識"$*1*$",將前面的文本byte[]長度轉化為16進制并在高位補0封裝長度標識,將封裝好的標識頭以UTF-8標準轉碼并發(fā)送然后再發(fā)送實際的文本數(shù)據即可

public void send_text(String data_str){
    if(outputStream==null){return;}
    if(isSendFile){return;}
    // 建議使用線程池
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                byte[] data_bytes = data_str.getBytes("GB18030");
                String head = "$*1*$"+String.format("%08X", data_bytes.length);
                Log.e(TAG, "發(fā)送文本,打包標識頭: "+head );
                outputStream.write(head.getBytes(StandardCharsets.UTF_8));
                outputStream.write(data_bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

3. 發(fā)送文件(以圖片為例)

發(fā)送文件數(shù)據之前先按照文件路徑讀取文件數(shù)據,封裝文件標識"$*2*$",將前面的文件數(shù)據長度轉化為16進制并在高位補0封裝長度標識,將封裝好的標識頭以UTF-8標準轉碼并發(fā)送然后再發(fā)送實際的文件數(shù)據即可

public void send_file(String path){
    if(outputStream==null){return;}
    if(isSendFile){return;}
    new Thread(new Runnable() {
        @Override
        public void run() {
            File file = new File(path);
            if (!file.exists() || !file.isFile()) {
                Log.e(TAG, "文件不存在");
                return;
            }else {
                Log.e(TAG, "開始發(fā)送文件");
                isSendFile = true;
            }

            byte[] file_byte = fileToBytes(path);
            try {
                Thread.sleep(100);
                String head;
                head = "$*2*$"+String.format("%08X", file_byte.length);
                Log.e(TAG, "發(fā)送文件,打包標識頭: "+head );
                outputStream.write(head.getBytes(StandardCharsets.UTF_8));
                outputStream.write(file_byte);
                isSendFile = false;
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "文件發(fā)送失敗", e);
                isSendFile = false;
            }
       }
    }).start();
}

讀取文件數(shù)據方法

public static byte[] fileToBytes(String filePath){
    File file = new File(filePath);
    try (FileInputStream fis = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
        byte[] buffer = new byte[1024*1024];
        int bytesRead;
        while ((bytesRead = fis.read(buffer)) != -1) {
            bos.write(buffer, 0, bytesRead);
        }
        return bos.toByteArray();
     }catch (Exception e){
        e.printStackTrace();
        return null;
     }
}

4. 解析數(shù)據

當數(shù)據長度超出最大單次傳輸數(shù)據長度限制時就會以分包的形式進行傳輸,而文本數(shù)據的長度大多都超出了這個限制,因此在接收文件數(shù)據時往往是按照多個分包數(shù)據的形式來處理。因而我們在每次處理新數(shù)據時的第一件事就是判斷本輪數(shù)據是首條包含了標識頭的數(shù)據還是后續(xù)的文件數(shù)據。首先初始化一個開始接收文件數(shù)據的標識變量,每次接收到新數(shù)據時將本輪數(shù)據以UTF-8標準轉化為字符并判斷是否包含了前面定義的標識頭,如果有則表示開始接收新一輪的文件數(shù)據,將標識修改為true,當標識為true時則表示本輪收到的數(shù)據是文件數(shù)據,直接存儲到字節(jié)數(shù)組緩存中,直到存儲的長度達到了解析出來的有效數(shù)據長度才表示本輪文件數(shù)據接收完畢并修改標識為false等待下一個標識頭的到來

private boolean startReceiveFile = false;  // 是否開始接收文件數(shù)據
private ByteArrayOutputStream file_bytes_baos = new ByteArrayOutputStream();
private long file_length = 0;  // 文件數(shù)據長度
private int message_type = 0;  // 消息類型:0-初始狀態(tài) 1-文本 2-圖片
private void receiveData(byte[] data_bytes) {
    Log.e(TAG, "處理數(shù)據長度: "+data_bytes.length );
    // 還沒收到標識頭,如果一直沒有收到就舍棄直到收到標識頭為止
    if(!startReceiveFile){
        try{
            // 首先判斷收到的數(shù)據是否包含了標識頭
            String data_str = new String(data_bytes,StandardCharsets.UTF_8);
            int head_index = data_str.indexOf("$*");
//            Pattern pattern = Pattern.compile("\\$\\*\\d\\*\\$");
//            Matcher matcher = pattern.matcher(data_str);
            // 有頭
            if(head_index>=0){
                startReceiveFile = true;
                String head = data_str.substring(head_index,head_index+13);  // $*1*$00339433
                String msg_type = head.substring(0,5);  // $*1*$
                if(msg_type.contains("1")){message_type = 1;} else {message_type = 2;}
                String length_hex = head.substring(5);  // 00339433
                file_length = Long.parseLong(length_hex,16);  // 解析文件數(shù)據長度
                Log.e(TAG, "解析標識頭 head: "+head+" 文件數(shù)據長度:"+file_length);

                file_bytes_baos.write(data_bytes,13,data_bytes.length-13);  // 存儲標識以外的文件數(shù)據
                // 如果是文本數(shù)據的話則只有一波,這時要判斷收到的數(shù)據總長度是否文件數(shù)據長度+標識頭數(shù)據長度
                if(data_bytes.length==file_length+13){
                    parseData();  // 處理數(shù)據
                }
            }else {
                Log.e(TAG, "receiveData: 沒有頭"+data_str );
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        }
        // 后續(xù)的都是文件數(shù)據
    else {
        try {
            file_bytes_baos.write(data_bytes);  // 保存文件數(shù)據
            Log.e(TAG, "總長度: "+file_length+" /已接收長度: "+file_bytes_baos.size());
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "文件數(shù)據保存失敗");
        }
        // 每次接收完數(shù)據判斷一下存儲的文件數(shù)據達到數(shù)據長度了嗎
        if(file_bytes_baos.size()>=file_length){
            parseData();  // 處理數(shù)據
        }
    }
}

處理數(shù)據 :既然已經知道數(shù)據的類型并且拿到了他的原始數(shù)據那處理起來就很簡單了,如果是文本的話直接將數(shù)據按照GB18030標準轉碼,如果是文件的話直接將數(shù)據按照特定的格式存儲到指定路徑就行了

public void parseData(){
    if(message_type==0){return;}
    if(message_type==1){
        String content = "";
        try {
            content = new String(file_bytes_baos.toByteArray(),"GB18030");  // 文本消息直接轉碼
            Log.e(TAG, "數(shù)據接收完畢,文本:"+content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 初始化狀態(tài)
        startReceiveFile = false;
        file_bytes_baos.reset();
        file_length = 0;
        message_type = 0;
    }else if(message_type==2){
        Log.e(TAG, "數(shù)據接收完畢,圖片" );
        // 保存圖片數(shù)據
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 默認保存在系統(tǒng)的 Download 目錄下,自行處理
                    String imgFilePath=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + "/receiveImage.jpg";
                    File imageFile = new File(imgFilePath);
                    try (FileOutputStream fos = new FileOutputStream(imageFile)) {
                        fos.write(file_bytes_baos.toByteArray());
                    }
                    // 初始化狀態(tài)
                    startReceiveFile = false;
                    file_bytes_baos.reset();
                    file_length = 0;
                    message_type = 0;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
            }
        }).start();
    }
}

四、使用例子

客戶端開啟藍牙可被發(fā)現(xiàn)

android bluetoothsocket,Android,android,信息與通信,javaandroid bluetoothsocket,Android,android,信息與通信,java

服務端掃描/連接目標設備

android bluetoothsocket,Android,android,信息與通信,javaandroid bluetoothsocket,Android,android,信息與通信,javaandroid bluetoothsocket,Android,android,信息與通信,java

首次連接需要進行配對操作

android bluetoothsocket,Android,android,信息與通信,javaandroid bluetoothsocket,Android,android,信息與通信,java

連接成功發(fā)送文本消息

android bluetoothsocket,Android,android,信息與通信,javaandroid bluetoothsocket,Android,android,信息與通信,java

發(fā)送圖片

android bluetoothsocket,Android,android,信息與通信,javaandroid bluetoothsocket,Android,android,信息與通信,java
android bluetoothsocket,Android,android,信息與通信,javaandroid bluetoothsocket,Android,android,信息與通信,java

五、小結

總體流程很簡單,相對復雜的部分在于文件數(shù)據的傳輸,但只要找對了思路實現(xiàn)起來也并不難,在這里對連接和發(fā)送數(shù)據的流程做一個小結

1. 連接

服務端(被連接方)開啟藍牙可被偵測并不斷監(jiān)聽指定UUID的客戶端連接操作 → 客戶端(連接方)掃描并連接目標設備?→ 如果雙方未配對則進行配對操作 → 連接成功各自獲取輸入/輸出流 → 開啟子線程監(jiān)聽輸入流的數(shù)據輸出/通過輸出流寫入數(shù)據

2. 通信

發(fā)送方:將需要發(fā)送的文本/文件轉化為byte[] → 將消息類型和數(shù)據長度封裝成特定格式的標識頭 → 將標識頭轉化為byte[]并發(fā)送 → 發(fā)送文本/文件數(shù)據
接收方:收到數(shù)據后轉化為文本判斷是否包含標識頭 → 解析標識頭得到數(shù)據類型和有效數(shù)據長度 → 如果當前已接收的數(shù)據長度未達到有效數(shù)據長度則繼續(xù)接收 → 如果長度達到則根據消息類型處理數(shù)據

3. DEMO

github:https://github.com/LXTTTTTT/Android-Bluetooth-Chat-And-Transfer
DEMO資源:https://download.csdn.net/download/lxt1292352578/88677601文章來源地址http://www.zghlxwxcb.cn/news/detail-851080.html

到了這里,關于Android 藍牙通信(通過 BluetoothSocket 傳輸文件/文本)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • stm32藍牙模塊通過手機和電腦雙向通信

    只需將藍牙連到單片機上,使用usart3(PB10、PB11)作為藍牙和單片機的數(shù)據傳輸,而電腦的收發(fā)數(shù)據要是用usart1(PA9、PA10),將數(shù)據存入數(shù)組中,從而在串口助手中打印值 ?1. 下面是usart.c文件,將io口和串口初始化,并且加入中斷(其中電腦發(fā)送時,所用的中斷需要回車換行,

    2023年04月09日
    瀏覽(27)
  • BLE Mesh藍牙m(xù)esh傳輸大數(shù)據包傳輸文件照片等大數(shù)據量通訊

    BLE Mesh藍牙m(xù)esh傳輸大數(shù)據包傳輸文件照片等大數(shù)據量通訊

    1、BLE Mesh數(shù)據傳輸現(xiàn)狀 ? ? ? ? ?BLE Mesh網絡技術是低功耗藍牙的一個進階版,Mesh擴大了藍牙在應用中的規(guī)模和范圍,因為它同時支持超過三萬個網絡節(jié)點,可以跨越大型建筑物,不僅可以使得醫(yī)療健康應用更加方便快捷,還能監(jiān)測像學校這類的大型公共場所隨時監(jiān)測學生的

    2024年02月08日
    瀏覽(21)
  • Android Studio 簡要實現(xiàn)藍牙(Bluetooth)通信(附加作業(yè))

    Android Studio 簡要實現(xiàn)藍牙(Bluetooth)通信(附加作業(yè))

    1.兩臺設備可以通過藍牙進行通信 2.模擬Client 和Server端實現(xiàn)簡單的通信。 如果想讓應用啟動設備發(fā)現(xiàn)或操縱藍牙設置,則除了 BLUETOOTH 權限以外,還必須聲明 BLUETOOTH_ADMIN 權限。大多數(shù)應用只是需利用此權限發(fā)現(xiàn)本地藍牙設備。除非應用是根據用戶請求修改藍牙設置的“超級

    2024年02月03日
    瀏覽(18)
  • 電腦可以通過藍牙發(fā)送文件嗎?電腦藍牙怎么發(fā)送文件

    電腦可以通過藍牙發(fā)送文件嗎?電腦藍牙怎么發(fā)送文件

    藍牙(bluetooth)是一種支持設備短距離通信的無線電技術。能在包括移動電話、PDA、無線耳機、筆記本電腦、相關外設等眾多設備之間進行無線信息交換。藍牙技術讓數(shù)據傳輸變得更加迅速高效,為無線通信拓寬道路。隨著藍牙技術的發(fā)展,其應用領域越來越廣泛,那么,

    2024年02月09日
    瀏覽(23)
  • 藍牙聊天App設計3:Android Studio制作藍牙聊天通訊軟件(完結,藍牙連接聊天,結合生活情景進行藍牙通信的通俗講解,以及代碼功能實現(xiàn),內容詳細,講解通俗易懂)

    藍牙聊天App設計3:Android Studio制作藍牙聊天通訊軟件(完結,藍牙連接聊天,結合生活情景進行藍牙通信的通俗講解,以及代碼功能實現(xiàn),內容詳細,講解通俗易懂)

    前言:藍牙聊天App設計全部有三篇文章(一、UI界面設計,二、藍牙搜索配對連接實現(xiàn),三、藍牙連接聊天),這篇文章是:三、藍牙連接聊天。 課程1:Android Studio小白安裝教程,以及第一個Android項目案例“Hello World”的調試運行 課程2:藍牙聊天App設計1:Android Studio制作藍

    2024年02月12日
    瀏覽(25)
  • Android集成MQTT教程:實現(xiàn)高效通信和實時消息傳輸

    ? ? 隨著物聯(lián)網技術的不斷發(fā)展,Android應用程序對于實時通信和消息傳輸?shù)男枨笤絹碓狡惹?。MQTT(Message Queuing Telemetry Transport)作為一種輕量級的、可擴展的通信協(xié)議,被廣泛應用于物聯(lián)網領域。本文將為您詳細介紹如何在Android應用中集成MQTT,實現(xiàn)高效通信和實時消息傳輸

    2024年02月07日
    瀏覽(21)
  • 遠程服務和web服務和前端,三方通過socket和websocket進行雙向通信傳輸數(shù)據

    1. 什么是socket? 在計算機通信領域,socket 被翻譯為“套接字”,它是計算機之間進行通信的一種約定或一種方式。通過 socket 這種約定,一臺計算機可以接收其他計算機的數(shù)據,也可以向其他計算機發(fā)送數(shù)據。 2. 什么是websocket? WebSocket是一種網絡通信協(xié)議,是HTML5新增的特性,

    2024年02月15日
    瀏覽(21)
  • STM32通過K210進行PID巡線,使用藍牙模塊與電腦通信從而進行P,I,D參數(shù)的調節(jié)

    目錄 一.前言部分(廢話部分) 二.K210色塊識別 1.必要知識 2.色塊識別 3.單片機的接收代碼 三.通過藍牙連接在電腦上實現(xiàn)PID的調參 我使用的是HAL庫,如果你使用的是標準庫的話可以根據對應標準庫的函數(shù)進行更改即可 因為之前使用灰度傳感器進行巡線,即使用上PID,最后的效果也

    2024年02月14日
    瀏覽(39)
  • 通過xshell傳輸文件到服務器

    通過xshell傳輸文件到服務器

    參考鏈接: [已解決]user is not in the sudoers file. This incident will be reported.(簡單不容易出錯的方式)-CSDN博客 簡單解釋下就是: 0、你的root需要設置好密碼 像這樣,我以一個新用戶user1為例: 1、設置好密碼之后,就可以切換到root用戶: 這里root可以省略,默認就是切換root 像這樣

    2024年02月04日
    瀏覽(40)
  • 智能車上位機系統(tǒng),pyqt下的socket通信,python實現(xiàn)服務器+客戶端,文本+視頻不定長字節(jié)傳輸,超詳細,小白都能看懂

    智能車上位機系統(tǒng),pyqt下的socket通信,python實現(xiàn)服務器+客戶端,文本+視頻不定長字節(jié)傳輸,超詳細,小白都能看懂

    目錄 前言: 準備工作: 初級服務器端編寫: 中級服務器端編寫+客戶端收數(shù)據函數(shù)實現(xiàn): 數(shù)據包格式v1.0 客戶端收數(shù)據函數(shù)V1.0 客戶端分析1.0 ??? 本地測試:成功! ???? 兩臺主機測試1.0:失敗,視頻解析失敗,直接花屏了! 問題分析: 問題解決: 數(shù)據包格式V2.0 客戶端接

    2024年04月17日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包