安卓蓝牙通信

蓝牙通信

基本步骤:

1.strings.xml添加程序状态描述文本及配色

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">蓝牙Demo</string> 
    <string name="send">发送</string>
    <string name="not_connected">你没有链接一个设备</string>
    <string name="bt_not_enabled_leaving">蓝牙不可用,离开聊天室</string>
    <string name="title_connecting">链接中...</string>
    <string name="title_connected_to">连接到:</string>
    <string name="title_not_connected">无链接</string> 
    <string name="scanning">蓝牙设备搜索中...</string>
    <string name="select_device">选择一个好友链接</string>
    <string name="none_paired">没有配对好友</string>
    <string name="none_found">附近没有发现好友</string>
    <string name="title_paired_devices">已配对好友</string>
    <string name="title_other_devices">其它可连接好友</string>
    <string name="button_scan">搜索好友</string>
    <string name="connect">我的好友</string>
    <string name="discoverable">设置在线</string>
    <string name="back">退出</string>
    <string name="startVideo">开始聊天</string>
    <string name="stopVideo">结束聊天</string>  
</resources>

2.activity_main.xml整体布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg01">

    <!--新版Android支持的Toolbar,对标题栏布局-->
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/title_left_text"
                style="?android:attr/windowTitleStyle"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_alignParentLeft="true"
                android:layout_weight="1"
                android:gravity="left"
                android:ellipsize="end"
                android:singleLine="true" />
            <TextView
                android:id="@+id/title_right_text"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_alignParentRight="true"
                android:layout_weight="1"
                android:ellipsize="end"
                android:gravity="right"
                android:singleLine="true"
                android:textColor="#fff" />
        </LinearLayout>
    </android.support.v7.widget.Toolbar>

    <ListView android:id="@+id/in"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:stackFromBottom="true"
        android:transcriptMode="alwaysScroll"
        android:layout_weight="1" />
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
        <EditText android:id="@+id/edit_text_out"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="bottom" />
        <Button android:id="@+id/button_send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/send"/>
    </LinearLayout>
</LinearLayout>

3.添加蓝牙会话的服务组件ChatService

public class ChatService {
//本应用的主Activity组件名称
private static final String NAME = “BluetoothChat”;
// UUID:通用唯一识别码,是一个128位长的数字,一般用十六进制表示
//算法的核心思想是结合机器的网卡、当地时间、一个随机数来生成
//在创建蓝牙连接
private static final UUID MY_UUID = UUID.fromString(“fa87c0d0-afac-11de-8a39-0800200c9a66”);
private final BluetoothAdapter mAdapter;
private final Handler mHandler;
private AcceptThread mAcceptThread;
private ConnectThread mConnectThread;
private ConnectedThread mConnectedThread;
private int mState;
public static final int STATE_NONE = 0;
public static final int STATE_LISTEN = 1;
public static final int STATE_CONNECTING = 2;
public static final int STATE_CONNECTED = 3;

//构造方法,接收UI主线程传递的对象
public ChatService(Context context, Handler handler) {
    //构造方法完成蓝牙对象的创建
    mAdapter = BluetoothAdapter.getDefaultAdapter();
    mState = STATE_NONE;
    mHandler = handler;
}

private synchronized void setState(int state) {
    mState = state;
    mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
}

public synchronized int getState() {
    return mState;
}

public synchronized void start() {
    if (mConnectThread != null) {
        mConnectThread.cancel();
        mConnectThread = null;
    }
    if (mConnectedThread != null) {
        mConnectedThread.cancel();
        mConnectedThread = null;
    }
    if (mAcceptThread == null) {
        mAcceptThread = new AcceptThread();
        mAcceptThread.start();
    }
    setState(STATE_LISTEN);
}

//取消 CONNECTING 和 CONNECTED 状态下的相关线程,然后运行新的 mConnectThread 线程
public synchronized void connect(BluetoothDevice device) {
    if (mState == STATE_CONNECTING) {
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }
    }
    if (mConnectedThread != null) {
        mConnectedThread.cancel();
        mConnectedThread = null;
    }
    mConnectThread = new ConnectThread(device);
    mConnectThread.start();
    setState(STATE_CONNECTING);
}

/*
    开启一个 ConnectedThread 来管理对应的当前连接。之前先取消任意现存的 mConnectThread 、
    mConnectedThread 、 mAcceptThread 线程,然后开启新 mConnectedThread ,传入当前刚刚接受的
    socket 连接。最后通过 Handler来通知UI连接
 */
public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
    if (mConnectThread != null) {
        mConnectThread.cancel();
        mConnectThread = null;
    }
    if (mConnectedThread != null) {
        mConnectedThread.cancel();
        mConnectedThread = null;
    }
    if (mAcceptThread != null) {
        mAcceptThread.cancel();
        mAcceptThread = null;
    }
    mConnectedThread = new ConnectedThread(socket);
    mConnectedThread.start();
    Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);
    Bundle bundle = new Bundle();
    bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());
    msg.setData(bundle);
    mHandler.sendMessage(msg);
    setState(STATE_CONNECTED);
}

// 停止所有相关线程,设当前状态为 NONE
public synchronized void stop() {
    if (mConnectThread != null) {
        mConnectThread.cancel();
        mConnectThread = null;
    }
    if (mConnectedThread != null) {
        mConnectedThread.cancel();
        mConnectedThread = null;
    }
    if (mAcceptThread != null) {
        mAcceptThread.cancel();
        mAcceptThread = null;
    }
    setState(STATE_NONE);
}

// 在 STATE_CONNECTED 状态下,调用 mConnectedThread 里的 write 方法,写入 byte
public void write(byte[] out) {
    ConnectedThread r;
    synchronized (this) {
        if (mState != STATE_CONNECTED)
            return;
        r = mConnectedThread;
    }
    r.write(out);
}

// 连接失败的时候处理,通知 ui ,并设为 STATE_LISTEN 状态
private void connectionFailed() {
    setState(STATE_LISTEN);
    Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
    Bundle bundle = new Bundle();
    bundle.putString(BluetoothChat.TOAST, "链接不到设备");
    msg.setData(bundle);
    mHandler.sendMessage(msg);
}

// 当连接失去的时候,设为 STATE_LISTEN 状态并通知 ui
private void connectionLost() {
    setState(STATE_LISTEN);
    Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
    Bundle bundle = new Bundle();
    bundle.putString(BluetoothChat.TOAST, "设备链接中断");
    msg.setData(bundle);
    mHandler.sendMessage(msg);
}

// 创建监听线程,准备接受新连接。使用阻塞方式,调用 BluetoothServerSocket.accept()
private class AcceptThread extends Thread {
    private final BluetoothServerSocket mmServerSocket;

    public AcceptThread() {
        BluetoothServerSocket tmp = null;
        try {
            //使用射频端口(RF comm)监听
            tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
        } catch (IOException e) {
        }
        mmServerSocket = tmp;
    }

    @Override
    public void run() {
        setName("AcceptThread");
        BluetoothSocket socket = null;
        while (mState != STATE_CONNECTED) {
            try {
                socket = mmServerSocket.accept();
            } catch (IOException e) {
                break;
            }
            if (socket != null) {
                synchronized (ChatService.this) {
                    switch (mState) {
                        case STATE_LISTEN:
                        case STATE_CONNECTING:
                            connected(socket, socket.getRemoteDevice());
                            break;
                        case STATE_NONE:
                        case STATE_CONNECTED:
                            try {
                                socket.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            break;
                    }
                }
            }
        }
    }

    public void cancel() {
        try {
            mmServerSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/*
    连接线程,专门用来对外发出连接对方蓝牙的请求和处理流程。
    构造函数里通过 BluetoothDevice.createRfcommSocketToServiceRecord() ,
    从待连接的 device 产生 BluetoothSocket. 然后在 run 方法中 connect ,
    成功后调用 BluetoothChatSevice 的 connected() 方法。定义 cancel() 在关闭线程时能够关闭相关socket 。
 */
private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;

    public ConnectThread(BluetoothDevice device) {
        mmDevice = device;
        BluetoothSocket tmp = null;
        try {
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mmSocket = tmp;
    }

    @Override
    public void run() {
        setName("ConnectThread");
        mAdapter.cancelDiscovery();
        try {
            mmSocket.connect();
        } catch (IOException e) {
            connectionFailed();
            try {
                mmSocket.close();
            } catch (IOException e2) {
                e.printStackTrace();
            }
            ChatService.this.start();
            return;
        }
        synchronized (ChatService.this) {
            mConnectThread = null;
        }
        connected(mmSocket, mmDevice);
    }

    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/*
    双方蓝牙连接后一直运行的线程;构造函数中设置输入输出流。
    run()方法中使用阻塞模式的 InputStream.read()循环读取输入流,然后发送到 UI 线程中更新聊天消息。
    本线程也提供了 write() 将聊天消息写入输出流传输至对方,传输成功后回写入 UI 线程。最后使用cancel()关闭连接的 socket
 */
private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    @Override
    public void run() {
        byte[] buffer = new byte[1024];
        int bytes;
        while (true) {
            try {
                bytes = mmInStream.read(buffer);
                mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
            } catch (IOException e) {
                connectionLost();
                break;
            }
        }
    }

    public void write(byte[] buffer) {
        try {
            mmOutStream.write(buffer);
            mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

}

4.菜单布局

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/scan"
          android:icon="@android:drawable/ic_menu_myplaces"
          android:title="@string/connect" />
    <item android:id="@+id/discoverable"
          android:icon="@android:drawable/ic_menu_view"
          android:title="@string/discoverable" />
    <item android:id="@+id/back"
          android:icon="@android:drawable/ic_menu_close_clear_cancel"
          android:title="@string/back" />
</menu>

5.选择好友界面布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView android:id="@+id/title_paired_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/title_paired_devices"
        android:visibility="gone"
        android:background="#666"
        android:textColor="#fff"
        android:paddingLeft="5dp" />
    <ListView android:id="@+id/paired_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1" />
    <TextView android:id="@+id/title_new_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/title_other_devices"
        android:visibility="gone"
        android:background="#666"
        android:textColor="#fff"
        android:paddingLeft="5dp" />
    <!--android:visibility="gone"表示不占空间的隐藏,invisible是占空间-->
    <ListView android:id="@+id/new_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="2" />
    <Button android:id="@+id/button_scan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/button_scan" />
</LinearLayout>

源码地址:https://github.com/Yao-yaoling/andriod

你可能感兴趣的:(安卓)