Android-蓝牙通信

Android蓝牙通信

本文介绍的是之前写的一个例子,实现对蓝牙的搜索,在弹窗中列表展示搜索到的蓝牙设备,点击并进行配对或连接。该例子包含两个界面,客户端和服务端,分别使用两台设备打开不同的界面进行连接(首先要开启服务端)。

  • 客户端搜索蓝牙界面:

  • 项目目录:

  • 项目介绍

从项目目录中可看出,该项目主要包含三个界面:MainActivity、ClientActivity、ServiceActivity,还有就是列表展示蓝牙设备用的listView和adapter。下面逐一对各个界面代码进行说明。

MainActivity:其中就包含了两个界面的跳转,代码不在罗列。

ClientActivity:客户端界面。

客户端首先调用initBluetooth()方法,其中主要是对设备上蓝牙的一个初始化,创建BluetoothAdapter对象mBluetoothAdapter,如果设备蓝牙没有打开的话,使用Intent开启蓝牙并接收回调数据,或者直接使用mBTAdapter.enable()方法默认开启蓝牙。

initBluetooth()方法内容:

public void initBluetooth() {
    list_match = new ArrayList<Map<String, Object>>();
    list_noMatch = new ArrayList<Map<String, Object>>();
    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        // 设备不支持蓝牙
        Toast.makeText(this, "该设备不支持蓝牙!", Toast.LENGTH_SHORT).show();
    } else {
        if (!mBluetoothAdapter.isEnabled()) {// 没有打开蓝牙的话,打开蓝牙
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
	    startActivityForResult(intent, 0);// 打开蓝牙后回调
	}
    }
}
// TODO 开启蓝牙后回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case 0:
	if (resultCode == RESULT_OK) {
	    startScan();// 开始搜索蓝牙,并展示设备列表
	}
    }
    super.onActivityResult(requestCode, resultCode, data);
}

 startScan()方法中主要调用了,弹窗展示和蓝牙的搜索。

getBoundedPrinters()中,主要是将搜索到的设备添加到集合中,用于列表展示。使用list_match集合展示已配对的蓝牙设备,list_noMatch展示搜索到的蓝牙设备。在该方法中注册蓝牙监听事件,监听搜索、搜索完成、配对。

/**
** 已经配对成功的设备
*/
private void getBoundedPrinters() {
    list_match.clear();
    list_noMatch.clear();
    Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
    // 如果有配对成功的打印机
    if (pairedDevices.size() > 0) {
        // 遍历配对成功的蓝牙设备
	for (BluetoothDevice device : pairedDevices) {
	    // 添加蓝牙设备名称和MAC地址到展示的list集合中
	    Map<String, Object> map = new HashMap<String, Object>();
	    map.put("NAME", device.getName());
	    map.put("MAC", device.getAddress());
	    map.put("Device", device);
	    list_match.add(map);
	}
    }
    // 需要过滤多个动作,则调用IntentFilter对象的addAction添加新动作
    IntentFilter foundFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    foundFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    foundFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    registerReceiver(mReceiver, foundFilter);
    stopScan();// 先停止搜索
    // 没有发现配对成功的蓝牙设备则进行搜索
    mBluetoothAdapter.startDiscovery();
    new Handler().postDelayed(new Runnable() {
    	@Override
	public void run() {
	    if (mBluetoothAdapter.isDiscovering()) {
		tv_state.setText("停止搜索");
		stopScan();// 停止搜索
	    }
	}
    }, 120000);
}

蓝牙广播监听:监听搜索、搜索完成、配对。配对完成之后,对选中的设备建立蓝牙连接。

// TODO 定义广播接收
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
	BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
	if (action.equals(BluetoothDevice.ACTION_FOUND)) {
	    tv_state.setText("正在搜索...");
	    if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
		// 未配对的蓝牙
		String name = device.getName();
		String mac = device.getAddress();
		boolean isHave = false;
		for (Map<String, Object> mapHave : list_noMatch) {
		    String mac_have = (String) mapHave.get("MAC");
		    if (mac_have.equals(mac) && (name == null || !name.equals("null") || !name.equals(""))) {
		        isHave = true;
			break;
		    }
		}
		if (list_noMatch.size() == 0 || !isHave) {
		    Map<String, Object> map = new HashMap<String, Object>();
		    map.put("NAME", name);
		    map.put("MAC", mac);
		    map.put("Device", device);
		    list_noMatch.add(map);
		}
		if (adapterNoMatch != null) {
		    adapterNoMatch.setDatas(list_noMatch);
		    adapterNoMatch.notifyDataSetChanged();
		}
            }
	} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
	    // 搜索完成
	    tv_state.setText("搜索完成");
	} else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
	    if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
		// 正在配对
		Log.e("---------", device.getAddress() + "正在配对");
	    } else if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
	        // 完成配对
	        Log.e("---------", device.getAddress() + "完成配对");
	        ClientThread clientConnectThread = new ClientThread(device);
	        clientConnectThread.start();
	    } else if (device.getBondState() == BluetoothDevice.BOND_NONE) {
	        // 取消配对
	        Log.e("---------", device.getAddress() + "取消配对");
	    }
        }
    }
};

在监听蓝牙搜索时,搜索到蓝牙设备添加到list_noMatch集合中,并刷新adapterNoMatch数据,对弹窗中的列表数据进行刷新。弹窗具体代码不进行描述了,后面会贴出整个Activity代码。点击弹窗中已配对设备进行绑定bondBT()方法,然后将蓝牙设备信息传递至ClientThread进行蓝牙连接,点击未配对列表只能够的设备可进行配对,并建立连接。

bondBT():

/**
 * 绑定蓝牙或进行配对
 */
private void bondBT(Map<String,Object> map) {
    try {
	BluetoothDevice device = (BluetoothDevice) map.get("Device");
	Log.e("---------", device.getAddress() + "");
	if (device == null) {
	    show("客户端:设备信息为空");
	    return;
	}
	show("客户端:配对蓝牙开始");
	// 获取蓝牙设备的连接状态
	int connectState = device.getBondState();
	switch (connectState) {
	// 未配对
	case BluetoothDevice.BOND_NONE:
	    show("客户端:开始配对");
	    Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
	    createBondMethod.invoke(device);
	    break;
	// 已配对
	case BluetoothDevice.BOND_BONDED:
	    show("客户端:开始连接:");
	    ClientThread clientConnectThread = new ClientThread(device);
	    clientConnectThread.start();
	    break;
	}
    } catch (Exception e) {
	e.printStackTrace();
    }
}

ClientThread:将要连接的蓝牙设备信息传递过来,通过设备对象device的createRfcommSocketToServiceRecord(MY_UUID)来获得BluetoothSocket对象,MY_UUID即蓝牙串口通用的UUID。之后开启ReceiveThread接收线程,通过Socket对象获得输入流得到服务端传送来的数据。想要通过蓝牙进行数据的发送,只需要通过Socket对象获取输出流即可。

/**
 * 开启客户端
 */
private class ClientThread extends Thread {
    BluetoothDevice device;
    public ClientThread(BluetoothDevice device) {
	this.device = device;
    }
    public void run() {
	try {
    	    Log.e("----ClientThread-----", device.getAddress() + "建立连接");
	    // 创建一个Socket连接:只需要服务器在注册时的UUID号
	    BTSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
	    // 连接
	    show("客户端:开始连接...");
	    BTSocket.connect();
	    show("客户端:连接成功");
	    // 启动接受数据
	    show("客户端:启动接受数据");
	    ReceiveThread receiveThread = new ReceiveThread();
	    receiveThread.start();
	} catch (IOException e) {
	    show("客户端:连接服务端异常!断开连接重新试一试");
	    e.printStackTrace();
	}
    }
}

ClientActivity整体代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import com.mxang.adapter.BlueToothListAdapter;
import com.mxang.tools.MyListView;

import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class ClientActivity extends Activity implements OnClickListener {

	private Button btn_scan, btn_connect, btn_send, btn_close;
	private EditText et_send;
	private TextView tv_data;
	private BluetoothAdapter mBluetoothAdapter = null;
	private BluetoothSocket BTSocket = null;
	private OutputStream outStream = null;
	private InputStream inStream = null;
	private ReceiveThread rThread = null; // 数据接收线程
	// 这条是蓝牙串口通用的UUID,不要更改
	private final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
	// 接收到的字符串
	private String receiveData = "";
	private List<Map<String, Object>> list_match;// 存放配对过的蓝牙集合
	private List<Map<String, Object>> list_noMatch;// 存放没有配对过的蓝牙集合
	private StringBuffer stringBuffer = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_client);
		init();
		initBluetooth();
	}

	public void init() {
		btn_scan = (Button) this.findViewById(R.id.btn_scan);
		btn_connect = (Button) this.findViewById(R.id.btn_connect);
		btn_send = (Button) this.findViewById(R.id.btn_send);
		btn_close = (Button) this.findViewById(R.id.btn_close);
		et_send = (EditText) this.findViewById(R.id.et_send);
		tv_data = (TextView) this.findViewById(R.id.tv_data);
		// 搜索
		btn_scan.setOnClickListener(this);
		// 连接
		btn_connect.setOnClickListener(this);
		// 断开连接
		btn_close.setOnClickListener(this);
		// 发送
		btn_send.setOnClickListener(this);
		
		stringBuffer = new StringBuffer();
	}

	public void initBluetooth() {
		list_match = new ArrayList<Map<String, Object>>();
		list_noMatch = new ArrayList<Map<String, Object>>();
		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		if (mBluetoothAdapter == null) {
			// 设备不支持蓝牙
			Toast.makeText(this, "该设备不支持蓝牙!", Toast.LENGTH_SHORT).show();
		} else {
			if (!mBluetoothAdapter.isEnabled()) {// 没有打开蓝牙的话,打开蓝牙
				Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
				startActivityForResult(intent, 0);// 打开蓝牙后回调
			}
		}

	}

	/**
	 ** 搜索并弹窗展示列表
	 */
	private void startScan() {
		showDefaultDialog();// 弹窗展示配对过的蓝牙信息,进行连接
		getBoundedPrinters();// 获得已经配对成功的设备
	}

	/**
	 ** 停止搜索
	 */
	private void stopScan() {
		if (mBluetoothAdapter.isDiscovering()) {
			mBluetoothAdapter.cancelDiscovery();
		}
	}

	@Override
	public void onClick(View view) {
		switch (view.getId()) {
		case R.id.btn_scan:// 搜索
			startScan();
			break;
		case R.id.btn_connect:// 连接
			// 创建连接
			break;
		case R.id.btn_send:// 发送
			new SendInfoTask().execute(et_send.getText().toString().trim());
			break;
		case R.id.btn_close:// 断开连接
			if (BTSocket != null) {
				try {
					BTSocket.close();
					BTSocket = null;
					if (rThread != null) {
						rThread.join();
					}
					show("当前连接已断开");
				} catch (IOException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			break;

		default:
			break;
		}
	}

	/**
	 ** 已经配对成功的设备
	 * 
	 */
	private void getBoundedPrinters() {
		list_match.clear();
		list_noMatch.clear();
		Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
		// 如果有配对成功的打印机
		if (pairedDevices.size() > 0) {
			// 遍历配对成功的蓝牙设备
			for (BluetoothDevice device : pairedDevices) {
				// 添加蓝牙设备名称和MAC地址到展示的list集合中
				Map<String, Object> map = new HashMap<String, Object>();
				map.put("NAME", device.getName());
				map.put("MAC", device.getAddress());
				map.put("Device", device);
				list_match.add(map);
			}
		}

		// 需要过滤多个动作,则调用IntentFilter对象的addAction添加新动作
		IntentFilter foundFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
		foundFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
		foundFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
		registerReceiver(mReceiver, foundFilter);

		stopScan();// 先停止搜索
		// 没有发现配对成功的蓝牙设备则进行搜索
		mBluetoothAdapter.startDiscovery();
		new Handler().postDelayed(new Runnable() {
			@Override
			public void run() {
				if (mBluetoothAdapter.isDiscovering()) {
					tv_state.setText("停止搜索");
					stopScan();// 停止搜索
				}
			}
		}, 120000);
	}

	// TODO 定义广播接收
	private BroadcastReceiver mReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
			if (action.equals(BluetoothDevice.ACTION_FOUND)) {
				tv_state.setText("正在搜索...");
				if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
					// 未配对的蓝牙
					String name = device.getName();
					String mac = device.getAddress();
					boolean isHave = false;
					for (Map<String, Object> mapHave : list_noMatch) {
						String mac_have = (String) mapHave.get("MAC");
						if (mac_have.equals(mac)
								&& (name == null || !name.equals("null") || !name.equals(""))) {
							isHave = true;
							break;
						}
					}
					if (list_noMatch.size() == 0 || !isHave) {
						Map<String, Object> map = new HashMap<String, Object>();
						map.put("NAME", name);
						map.put("MAC", mac);
						map.put("Device", device);
						list_noMatch.add(map);
					}
					if (adapterNoMatch != null) {
						adapterNoMatch.setDatas(list_noMatch);
						adapterNoMatch.notifyDataSetChanged();
					}
				}
			} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
				// 搜索完成
				tv_state.setText("搜索完成");
			} else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
				if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
					// 正在配对
					Log.e("---------", device.getAddress() + "正在配对");
				} else if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
					// 完成配对
					Log.e("---------", device.getAddress() + "完成配对");
					ClientThread clientConnectThread = new ClientThread(device);
					clientConnectThread.start();
				} else if (device.getBondState() == BluetoothDevice.BOND_NONE) {
					// 取消配对
					Log.e("---------", device.getAddress() + "取消配对");
				}
			}
		}
	};

	// TODO 开启蓝牙后回调
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		switch (requestCode) {
		case 0:
			if (resultCode == RESULT_OK) {
				startScan();
			}
		}
		super.onActivityResult(requestCode, resultCode, data);
	}

	/**
	 * UI显示信息
	 * 
	 * @param msg
	 */
	private void show(String msg) {
		stringBuffer.append(msg + "\n");
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				tv_data.setText(stringBuffer.toString());
			}
		});
	}

	/**
	 * 绑定蓝牙或进行配对
	 */
	private void bondBT(Map<String,Object> map) {
		try {
			BluetoothDevice device = (BluetoothDevice) map.get("Device");
			Log.e("---------", device.getAddress() + "");
			if (device == null) {
				show("客户端:设备信息为空");
				return;
			}
			show("客户端:配对蓝牙开始");
			// 获取蓝牙设备的连接状态
			int connectState = device.getBondState();
			switch (connectState) {
			// 未配对
			case BluetoothDevice.BOND_NONE:
				show("客户端:开始配对");
				Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
				createBondMethod.invoke(device);
				break;
			// 已配对
			case BluetoothDevice.BOND_BONDED:
				show("客户端:开始连接:");
				ClientThread clientConnectThread = new ClientThread(device);
				clientConnectThread.start();
				break;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 开启客户端
	 */
	private class ClientThread extends Thread {
		BluetoothDevice device;

		public ClientThread(BluetoothDevice device) {
			this.device = device;
		}

		public void run() {
			try {
				Log.e("----ClientThread-----", device.getAddress() + "建立连接");
				// 创建一个Socket连接:只需要服务器在注册时的UUID号
				BTSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
				// 连接
				show("客户端:开始连接...");
				BTSocket.connect();
				show("客户端:连接成功");
				// 启动接受数据
				show("客户端:启动接受数据");
				ReceiveThread receiveThread = new ReceiveThread();
				receiveThread.start();
			} catch (IOException e) {
				show("客户端:连接服务端异常!断开连接重新试一试");
				e.printStackTrace();
			}
		}
	}

	/**
	 * 读取数据
	 */
	private class ReceiveThread extends Thread {
		public void run() {
			byte[] buff = null;
			try {
				inStream = BTSocket.getInputStream();
				show("客户端:获得输入流");
				if (inStream != null && inStream.available() > 0) {
					int backLength = inStream.available();
					buff = new byte[backLength];
					int len = 0;
					// 读取数据存储在buff数组中
					while (len < backLength) {
						len += inStream.read(buff, len, backLength - len);
					}
					processBuffer(buff, len);
				}
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}

		private void processBuffer(byte[] buff, int size) {
			int length = 0;
			for (int i = 0; i < size; i++) {
				if (buff[i] > '\0') {
					length++;
				} else {
					break;
				}
			}
			byte[] newbuff = new byte[length]; // newbuff字节数组,用于存放真正接收到的数据
			for (int j = 0; j < length; j++) {
				newbuff[j] = buff[j];
			}
			receiveData = receiveData + new String(newbuff);
			show(receiveData);
		}
	}

	/**
	 * @author M.xang
	 * @时间 2019年3月19日
	 * @描述 发送数据到蓝牙设备的异步任务
	 */
	class SendInfoTask extends AsyncTask<String, String, String> {

		@Override
		protected void onPostExecute(String result) {
			super.onPostExecute(result);
			tv_data.setText(result);
			// 将发送框清空
			et_send.setText("");
		}

		@Override
		protected String doInBackground(String... arg0) {
			if (BTSocket == null) {
				return "未创建连接";
			}
			try {
				outStream = BTSocket.getOutputStream();
				// 不是空白串
				if (arg0[0].length() > 0) {
					// String target=arg0[0];
					byte[] msgBuffer = arg0[0].getBytes();
					// 将msgBuffer中的数据写到outStream对象中
					outStream.write(msgBuffer);
				} else {
					return "发送数据为空";
				}
			} catch (IOException e) {
				return "发送失败";
			}
			return "发送成功";
		}
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		try {
			if (rThread != null) {
				BTSocket.close();
				BTSocket = null;
				rThread.join();
			}
			// 解除注册
			if (mReceiver != null) {
				unregisterReceiver(mReceiver);
				mReceiver = null;
			}
			finish();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private AlertDialog dlg = null;
	private BlueToothListAdapter adapterMatch, adapterNoMatch;
	private TextView tv_state;

	/**
	 ** 进行打印机选择进行连接
	 */
	public void showDefaultDialog() {
		if (dlg != null && dlg.isShowing()) {
			dlg.dismiss();
			dlg = null;
		}
		dlg = new AlertDialog.Builder(this).create();
		LayoutInflater inflater = LayoutInflater.from(this);
		View v = inflater.inflate(R.layout.dlg_bluetoothlayout, null);
		TextView dlg_title = (TextView) v.findViewById(R.id.dlg_title);
		MyListView dlg_listViewMatch = (MyListView) v.findViewById(R.id.dlg_listMatch);
		MyListView dlg_listViewNoMatch = (MyListView) v.findViewById(R.id.dlg_listNoMatch);
		Button btn_cancel = (Button) v.findViewById(R.id.dlg_btn_cancel);
		tv_state = (TextView) v.findViewById(R.id.tv_state);// 搜索状态
		btn_cancel.setText("取消");
		dlg_title.setText("请选择设备");

		adapterMatch = new BlueToothListAdapter(this, list_match);
		dlg_listViewMatch.setAdapter(adapterMatch);
		adapterNoMatch = new BlueToothListAdapter(this, list_noMatch);
		dlg_listViewNoMatch.setAdapter(adapterNoMatch);

		dlg.show();
		dlg.getWindow().setContentView(v);// 自定义布局应该在这里添加,要在dialog.show()的后面
		dlg.getWindow().setGravity(Gravity.CENTER);// 可以设置显示的位置
		WindowManager m = getWindowManager();
		Display d = m.getDefaultDisplay(); // 为获取屏幕宽、高
		android.view.WindowManager.LayoutParams p = dlg.getWindow().getAttributes(); // 获取对话框当前的参数值
		int width = (int) (d.getWidth() * 0.8);// 宽度设置为屏幕的0.8
		p.width = width;
		dlg.getWindow().setAttributes(p); // 设置生效
		// 已配对设备
		dlg_listViewMatch.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long arg3) {
				// 进行蓝牙连接
				Map<String, Object> map = list_match.get(position);
				bondBT(map);
				if (dlg != null && dlg.isShowing()) {
					dlg.dismiss();
					dlg = null;
				}
			}
		});
		// 可用蓝牙
		dlg_listViewNoMatch.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long arg3) {
//				Toast.makeText(MainActivity.this, name + ">>" + address, Toast.LENGTH_SHORT).show();
				Map<String, Object> map = list_noMatch.get(position);
				bondBT(map);// 进行蓝牙配对
				if (dlg != null && dlg.isShowing()) {
					dlg.dismiss();
					dlg = null;
				}
			}
		});
		// 取消
		btn_cancel.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				if (dlg != null && dlg.isShowing()) {
					dlg.dismiss();
					dlg = null;
				}
			}
		});

		// 弹窗消失时,取消搜索
		dlg.setOnCancelListener(new OnCancelListener() {
			@Override
			public void onCancel(DialogInterface arg0) {
				stopScan();
			}
		});
	}

}

activity_client布局:

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btn_scan"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="查找设备" />

        <Button
            android:id="@+id/btn_connect"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="连接" />

        <Button
            android:id="@+id/btn_close"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="断开连接" />
    </LinearLayout>

    <EditText
        android:id="@+id/et_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp" />

    <Button
        android:id="@+id/btn_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:padding="10dp"
        android:text="发送" />

    <TextView
        android:id="@+id/tv_data"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp" />

</LinearLayout>

弹窗的布局dlg_bluetoothlayout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#00FFFFFF" >

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@drawable/dlg_bg" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/dlg_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginTop="30dp"
                android:text="标题"
                android:textSize="18sp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="已配对设备:"
                android:layout_marginLeft="5dp"
                android:textColor="@color/blue"
                android:textSize="16sp" />

            <View
                android:layout_width="match_parent"
                android:layout_height="2dp"
                android:background="@android:color/darker_gray" />

            <com.mxang.tools.MyListView
                android:id="@+id/dlg_listMatch"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="10dp"
                android:layout_marginLeft="30dp"
                android:layout_marginRight="30dp"
                android:layout_marginTop="10dp"
                android:textSize="16sp" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:orientation="horizontal" >

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="可用设备:"
                    android:textColor="@color/blue"
                    android:layout_marginLeft="5dp"
                    android:textSize="16sp" />

                <TextView
                    android:id="@+id/tv_state"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/black_light"
                    android:layout_gravity="center"
                    android:textSize="17sp" />
            </LinearLayout>

            <View
                android:layout_width="match_parent"
                android:layout_height="2dp"
                android:background="@android:color/darker_gray" />

            <com.mxang.tools.MyListView
                android:id="@+id/dlg_listNoMatch"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="10dp"
                android:layout_marginLeft="30dp"
                android:layout_marginRight="30dp"
                android:layout_marginTop="10dp"
                android:textSize="16sp" />

            <Button
                android:id="@+id/dlg_btn_cancel"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@null"
                android:padding="5dp"
                android:text="取消"
                android:textSize="16sp" />
        </LinearLayout>
    </ScrollView>

</RelativeLayout>

弹窗样式布局@drawable/dlg_bg:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

    <!-- android:radius 弧形的半径 -->
    <corners android:radius="30dp" />

    <!-- 填充的颜色 -->
    <solid android:color="@android:color/white" />

</shape>

MyListView:

package com.mxang.tools;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;

/**
 * @author ${M.xang} 创建时间:2018-8-2 上午10:08:34 类描述 : 自定义listView
 */
public class MyListView extends ListView {

	public MyListView(Context context) {
		super(context);
	}

	public MyListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
		super.onMeasure(widthMeasureSpec, expandSpec);
	}

}

BlueToothListAdapter:

package com.mxang.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.example.mybuletooth.R;

/**
 * @author M.xang
 * @时间 2019年1月14日
 * @描述 蓝牙展示列表
 */
public class BlueToothListAdapter extends BaseAdapter {

	private Context mContext;
	private List<Map<String, Object>> mDatas = new ArrayList<Map<String,Object>>();

	public BlueToothListAdapter(Context context) {
		mContext = context;
	}

	public BlueToothListAdapter(Context context, List<Map<String, Object>> datas) {
		mContext = context;
		mDatas = datas;
	}

	public void setDatas(List<Map<String, Object>> datas) {
		mDatas = datas;
		notifyDataSetChanged();
	}

	@Override
	public int getCount() {
		return mDatas.size();
	}

	@Override
	public Object getItem(int position) {
		return mDatas.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(final int position, View convertView, ViewGroup parent) {

		final ViewHolder mViewHolder;
		if (convertView == null) {
			convertView = LayoutInflater.from(mContext).inflate(R.layout.item_bluetoothitem, parent, false);
			mViewHolder = new ViewHolder(convertView);
			convertView.setTag(mViewHolder);
		} else {
			mViewHolder = (ViewHolder) convertView.getTag();
		}
		
		Map<String, Object> map = mDatas.get(position);
		String name = (String) map.get("NAME");
		String mac = (String) map.get("MAC");
		// 有名字显示名字,没有名字显示mac
		if (name == null || name.equals("null") || name.equals("")) {
			mViewHolder.tv_name.setText(mac);// 蓝牙MAC地址
		} else {
			mViewHolder.tv_name.setText(name);// 蓝牙名称
		}

		return convertView;
	}

	public static class ViewHolder {
		public ImageView iv_img;
		public TextView tv_name;

		ViewHolder(View view) {
			iv_img = (ImageView) view.findViewById(R.id.iv_img);
			tv_name = (TextView) view.findViewById(R.id.tv_name);
		}
	}
}

列表布局item_bluetoothitem:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_layout"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:padding="2dp" >

    <ImageView
        android:id="@+id/iv_img"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:layout_gravity="center_vertical"
        android:layout_margin="5dp"
        android:src="@drawable/bluetooth" />

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="5dp"
        android:layout_toRightOf="@+id/iv_img"
        android:gravity="center"
        android:text="1" />

</RelativeLayout>

ServiceActivity:服务端,主要是通过蓝牙对象建立Socket连接。这里使用的UUID与客户端保持一致。

服务端界面包含两个按钮:启动服务、断开服务。首先要启动服务之后,客户端那边才能进行连接。

 

package com.example.mybuletooth;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class ServiceActivity extends Activity implements OnClickListener {

	private Button btn_start, btn_close;
	private TextView tv_msg;
	private BluetoothSocket BTSocket;
	private BluetoothAdapter mBluetoothAdapter;
	private StringBuffer stringBuilder;
	// 这条是蓝牙串口通用的UUID,不要更改
	private final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_service);
		init();
		initBluetooth();
	}

	public void init() {
		btn_start = (Button) this.findViewById(R.id.btn_start);
		btn_close = (Button) this.findViewById(R.id.btn_close);
		tv_msg = (TextView) this.findViewById(R.id.tv_msg);
		// 开启服务器
		btn_start.setOnClickListener(this);
		// 断开服务器
		btn_close.setOnClickListener(this);
	}

	/**
	 * UI文本输出
	 *
	 * @param msg
	 */
	public void show(String msg) {
		stringBuilder.append(msg + "\n");
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				tv_msg.setText(stringBuilder.toString());
			}
		});
	}

	public void initBluetooth() {
		stringBuilder = new StringBuffer();
		show("服务端:检查BT");
		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		if (mBluetoothAdapter == null) {
			show("该设备不支持蓝牙!");
			// 设备不支持蓝牙
			Toast.makeText(this, "该设备不支持蓝牙!", Toast.LENGTH_SHORT).show();
		} else {
			if (!mBluetoothAdapter.isEnabled()) {// 没有打开蓝牙的话,打开蓝牙
//				Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//				startActivityForResult(intent, 0);// 打开蓝牙后回调
				Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
				// 设置蓝牙可见性,最多300秒
				intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
				startActivity(intent);
			}
		}
	}

	@Override
	public void onClick(View view) {
		switch (view.getId()) {
		case R.id.btn_start: // 开启服务器
			ServerThread startServerThread = new ServerThread();
			startServerThread.start();
			break;

		case R.id.btn_close: // 断开服务器
			tv_msg.setText("");
			stringBuilder.setLength(0);// 清空stringBuilder
			try {
				if (BTSocket != null) {
					BTSocket.close();
					BTSocket = null;
				}
				if (mserverSocket != null) {
					mserverSocket.close();
					mserverSocket = null;
				}
				if (mreadThread != null) {
					mreadThread.join();
					mreadThread = null;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			break;
		default:
			break;
		}
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		try {
			if (BTSocket != null) {
				BTSocket.close();
				BTSocket = null;
			}
			if (mserverSocket != null) {
				mserverSocket.close();
				mserverSocket = null;
			}
			if (mreadThread != null) {
				mreadThread.join();
				mreadThread = null;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private BluetoothServerSocket mserverSocket = null;
	private ReadThread mreadThread = null;

	/**
	 * 开启服务器
	 */
	private class ServerThread extends Thread {
		public void run() {
			try {
				mserverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("btspp", MY_UUID);
				show("服务端:等待连接");

				BTSocket = mserverSocket.accept();
				show("服务端:连接成功");

				mreadThread = new ReadThread();
				mreadThread.start();
				show("服务端:启动接受数据");

			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 读取数据
	 */
	private class ReadThread extends Thread {
		public void run() {
			byte[] buffer = new byte[1024];
			int bytes;
			InputStream mmInStream = null;
			try {
				mmInStream = BTSocket.getInputStream();
				show("服务端:获得输入流");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			while (true) {
				try {
					if ((bytes = mmInStream.read(buffer)) > 0) {
						byte[] buf_data = new byte[bytes];
						for (int i = 0; i < bytes; i++) {
							buf_data[i] = buffer[i];
						}
						String s = new String(buf_data);
						show("服务端:收到数据>>" + s);
						new SendInfoTask().execute(buf_data);// 返回数据到客户端
					}
				} catch (IOException e) {
					try {
						mmInStream.close();
					} catch (IOException e1) {
						e1.printStackTrace();
					}
					break;
				}
			}
		}
	}

	/**
	 * @author M.xang
	 * @时间 2019年3月19日
	 * @描述 发送数据到蓝牙设备的异步任务
	 */
	class SendInfoTask extends AsyncTask<byte[], String, String> {

		@Override
		protected void onPostExecute(String result) {
			super.onPostExecute(result);
			show(result);
		}

		@Override
		protected String doInBackground(byte[]... arg0) {
			if (BTSocket == null) {
				return "未创建连接";
			}
			try {
				OutputStream outStream = BTSocket.getOutputStream();
				// 不是空白串
				if (arg0.length > 0) {
					byte[] msgBuffer = arg0[0];
					// 将msgBuffer中的数据写到outStream对象中
					outStream.write(msgBuffer);
				} else {
					return "发送数据为空";
				}
			} catch (IOException e) {
				return "发送失败";
			}
			return "发送成功";
		}
	}
}

activity_service布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <Button 
        android:id="@+id/btn_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="启动服务"/>

    <Button 
        android:id="@+id/btn_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="断开服务"/>
    
    <TextView 
        android:id="@+id/tv_msg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

res资源:

color:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <color name="gray">#e1e6f6</color>
    <color name="gray_mid">#dbdbdb</color>
    <color name="gray_dark">#969696</color>
    <color name="zong">#a2a200</color>
    <color name="zong_dark">#808000</color>
    <color name="black">#000000</color>
    <color name="black_light">#4b4b4b</color>
    <color name="blue_dark">#0a73d1</color>
    <color name="blue">#198af4</color>
    <color name="green_dark">#00ea15</color>
    <color name="green">#5bff68</color>
    <color name="pink_dark">#fa00ff</color>
    <color name="pink">#fc84ff</color>
    <color name="yellow_dark">#e9cb00</color>
    <color name="yellow">#ffed6f</color>
    
    <color name="chooseColor">#23d5ba</color> <!-- 绿色 -->
    
</resources>

蓝牙图标:

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值