一、首先说明:蓝牙通信必须用手机测试,因为avd里没有相关的硬件,会报错!
好了,看看最后的效果图:
二、概述:
1.判断是否支持Bluetooth
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(bluetoothAdapter == null) {
//the device doesn't support bluetooth
} else {
//the device support bluetooth
}
2.如果支持,打开Bluetooth
if(!bluetoothAdapter.isEnable()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent,REQUEST_ENABLE_BT);
}
3.监视Bluetooth打开状态
BroadcastReceiver bluetoothState = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String stateExtra = BluetoothAdapter.EXTRA_STATE;
int state = intent.getIntExtra(stateExtra, -1);
switch(state) {
case BluetoothAdapter.STATE_TURNING_ON:
break;
case BluetoothAdapter.STATE_ON:
break;
case BluetoothAdapter.STATE_TURNING_OFF:
break;
case BluetoothAdapter.STATE_OFF:
break;
}
}
}
registerReceiver(bluetoothState,new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
4.设置本地设备可以被其它设备搜索
Intent discoveryIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(discoveryIntent,REQUEST_DISCOVERY);
BroadcastReceiver discovery = new BroadcastReceiver() {
@Override
public void onRecevie(Content context, Intent intent) {
String scanMode = BluetoothAdapter.EXTRA_SCAN_MODE;
String preScanMode = BluetoothAdapter.EXTRA_PREVIOUS_SCAN_MODE;
int mode = intent.getIntExtra(scanMode);
}
}
registerReceiver(discovery,new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
5.搜索设备
开始搜索 bluetoothAdapter.startDiscovery();
停止搜索 bluetoothAdapter.cancelDiscovery();
当发现一个设备时,系统会发出ACTION_FOUND广播消息,我们可以实现接收这个消息的BroadcastReceiver
BroadcastReceiver deviceFound = new BroadcastReceiver() {
@Override
public void onReceiver(Content content, Intent intent) {
String remoteDeviceName = intent.getStringExtra(BluetoothAdapter.EXTRA_NAME);
BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothAdapter.EXTRA_DEVICE);
}
}
registerReceiver(deviceFound, new IntentFilter(BluetoothAdapter.ACTION_FOUND);
6.连接设备
连接两个蓝牙设备要分别实现服务器端(BluetoothServerSocket)和客户端(BluetoothSocket),这点与J2SE中的
ServerSocket和Socket很类似。
BluetoothServerSocket在服务器端调用方法accept()监听,当有客户端请求到来时,accept()方法返回BluetoothSocket,客户端得到后,两端便可以通信。通过InputStream和OutputStream来实现数据的传输。
accept方法是阻塞的,所以不能放在UI线程中,当用到BluetoothServerSocket和BluetoothSocket时,通常把它们放在各自的新线程中。
三、如何实现
以下是开发中的几个关键步骤:
1)首先开启蓝牙
2)搜索可用设备
3)创建蓝牙socket,获取输入输出流
4)读取和写入数据
5)断开连接关闭蓝牙
1、因为有页面切换,这里我使用了TabHost,但原来的效果不好,没有动画,那只好自己复写了
/**
* 带有动画效果的TabHost
*
* @Project App_Bluetooth
* @Package com.android.bluetooth
* @author chenlin
* @version 1.0
* @Date 2013年6月2日
* @Note TODO
*/
public class AnimationTabHost extends TabHost {
private int mCurrentTabID = 0;//当前的tabId
private final long mDuration = 400;//动画时间
public AnimationTabHost(Context context) {
this(context, null);
}
public AnimationTabHost(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 切换动画
*/
@Override
public void setCurrentTab(int index) {
//向右平移
if (index > mCurrentTabID) {
TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF,
-1.0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f);
translateAnimation.setDuration(mDuration);
getCurrentView().startAnimation(translateAnimation);
//向左平移
} else if (index < mCurrentTabID) {
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF, 0f,
Animation.RELATIVE_TO_SELF, 0f);
translateAnimation.setDuration(mDuration);
getCurrentView().startAnimation(translateAnimation);
}
super.setCurrentTab(index);
//-----方向平移------------------------------
if (index > mCurrentTabID) {
TranslateAnimation translateAnimation = new TranslateAnimation( //
Animation.RELATIVE_TO_PARENT, 1.0f,// RELATIVE_TO_SELF
Animation.RELATIVE_TO_PARENT, 0f, Animation.RELATIVE_TO_PARENT, 0f, Animation.RELATIVE_TO_PARENT, 0f);
translateAnimation.setDuration(mDuration);
getCurrentView().startAnimation(translateAnimation);
} else if (index < mCurrentTabID) {
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0f, Animation.RELATIVE_TO_PARENT, 0f,
Animation.RELATIVE_TO_PARENT, 0f);
translateAnimation.setDuration(mDuration);
getCurrentView().startAnimation(translateAnimation);
}
mCurrentTabID = index;
}
}
2、先搭建好主页,使用复写的TabHost滑动,如何滑动,根据状态,有三种状态
/**
* 主页
*
* @Project App_Bluetooth
* @Package com.android.bluetooth
* @author chenlin
* @version 1.0
* @Date 2013年6月2日
*/
@SuppressWarnings("deprecation")
public class BluetoothActivity extends TabActivity {
static AnimationTabHost mTabHost;//动画tabhost
static String BlueToothAddress;//蓝牙地址
static Type mType = Type.NONE;//类型
static boolean isOpen = false;
//类型:
enum Type {
NONE, SERVICE, CILENT
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initTab();
}
private void initTab() {
//初始化
mTabHost = (AnimationTabHost) getTabHost();
//添加tab
mTabHost.addTab(mTabHost.newTabSpec("Tab1").setIndicator("设备列表", getResources().getDrawable(android.R.drawable.ic_menu_add))
.setContent(new Intent(this, DeviceActivity.class)));
mTabHost.addTab(mTabHost.newTabSpec("Tab2").setIndicator("会话列表", getResources().getDrawable(android.R.drawable.ic_menu_add))
.setContent(new Intent(this, ChatActivity.class)));
//添加监听
mTabHost.setOnTabChangedListener(new OnTabChangeListener() {
public void onTabChanged(String tabId) {
if (tabId.equals("Tab1")) {
//TODO
}
}
});
//默认在第一个tabhost上面
mTabHost.setCurrentTab(0);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Toast.makeText(this, "address:", Toast.LENGTH_SHORT).show();
}
}
3、有了主页,就开始分别实现两个列表页面,一个是寻找设备页面DeviceActivity.java,另一个是会话页面ChatActivity.java
1)设备页面DeviceActivity.java
/**
* 发现的设备列表
* @Project App_Bluetooth
* @Package com.android.bluetooth
* @author chenlin
* @version 1.0
* @Date 2013年6月2日
* @Note TODO
*/
public class DeviceActivity extends Activity {
private ListView mListView;
//数据
private ArrayList<DeviceBean> mDatas;
private Button mBtnSearch, mBtnService;
private ChatListAdapter mAdapter;
//蓝牙适配器
private BluetoothAdapter mBtAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.devices);
initDatas();
initViews();
registerBroadcast();
init();
}
private void initDatas() {
mDatas = new ArrayList<DeviceBean>();
mAdapter = new ChatListAdapter(this, mDatas);
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
}
/**
* 列出所有的蓝牙设备
*/
private void init() {
Log.i("tag", "mBtAdapter=="+ mBtAdapter);
//根据适配器得到所有的设备信息
Set<BluetoothDevice> deviceSet = mBtAdapter.getBondedDevices();
if (deviceSet.size() > 0) {
for (BluetoothDevice device : deviceSet) {
mDatas.add(new DeviceBean(device.getName() + "\n" + device.getAddress(), true));
mAdapter.notifyDataSetChanged();
mListView.setSelection(mDatas.size() - 1);
}
} else {
mDatas.add(new DeviceBean("没有配对的设备", true));
mAdapter.notifyDataSetChanged();
mListView.setSelection(mDatas.size() - 1);
}
}
/**
* 注册广播
*/
private void registerBroadcast() {
//设备被发现广播
IntentFilter discoveryFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, discoveryFilter);
// 设备发现完成
IntentFilter foundFilter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, foundFilter);
}
/**
* 初始化视图
*/
private void initViews() {
mListView = (ListView) findViewById(R.id.list);
mListView.setAdapter(mAdapter);
mListView.setFastScrollEnabled(true);
mListView.setOnItemClickListener(mDeviceClickListener);
mBtnSearch = (Button) findViewById(R.id.start_seach);
mBtnSearch.setOnClickListener(mSearchListener);
mBtnService = (Button) findViewById(R.id.start_service);
mBtnService.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
BluetoothActivity.mType = Type.SERVICE;
BluetoothActivity.mTabHost.setCurrentTab(1);
}
});
}
/**
* 搜索监听
*/
private OnClickListener mSearchListener = new OnClickListener() {
@Override
public void onClick(View arg0) {
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
mBtnSearch.setText("重新搜索");
} else {
mDatas.clear();
mAdapter.notifyDataSetChanged();
init();
/* 开始搜索 */
mBtAdapter.startDiscovery();
mBtnSearch.setText("ֹͣ停止搜索");
}
}
};
/**
* 点击设备监听
*/
private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
DeviceBean bean = mDatas.get(position);
String info = bean.message;
String address = info.substring(info.length() - 17);
BluetoothActivity.BlueToothAddress = address;
AlertDialog.Builder stopDialog = new AlertDialog.Builder(DeviceActivity.this);
stopDialog.setTitle("连接");//标题
stopDialog.setMessage(bean.message);
stopDialog.setPositiveButton("连接", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mBtAdapter.cancelDiscovery();
mBtnSearch.setText("重新搜索");
BluetoothActivity.mType = Type.CILENT;
BluetoothActivity.mTabHost.setCurrentTab(1);
dialog.cancel();
}
});
stopDialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
BluetoothActivity.BlueToothAddress = null;
dialog.cancel();
}
});
stopDialog.show();
}
};
/**
* 发现设备广播
*/
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// 获得设备信息
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 如果绑定的状态不一样
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
mDatas.add(new DeviceBean(device.getName() + "\n" + device.getAddress(), false));
mAdapter.notifyDataSetChanged();
mListView.setSelection(mDatas.size() - 1);
}
// 如果搜索完成了
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
setProgressBarIndeterminateVisibility(false);
if (mListView.getCount() == 0) {
mDatas.add(new DeviceBean("û没有发现蓝牙设备", false));
mAdapter.notifyDataSetChanged();
mListView.setSelection(mDatas.size() - 1);
}
mBtnSearch.setText("重新搜索");
}
}
};
@Override
public void onStart() {
super.onStart();
if (!mBtAdapter.isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, 3);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mBtAdapter != null) {
mBtAdapter.cancelDiscovery();
}
this.unregisterReceiver(mReceiver);
}
}
2)会话页面ChatActivity.java
/**
* 会话界面
*
* @Project App_Bluetooth
* @Package com.android.bluetooth
* @author chenlin
* @version 1.0
* @Date 2013年3月2日
* @Note TODO
*/
public class ChatActivity extends Activity implements OnItemClickListener, OnClickListener {
private static final int STATUS_CONNECT = 0x11;
private ListView mListView;
private ArrayList<DeviceBean> mDatas;
private Button mBtnSend;// 发送按钮
private Button mBtnDisconn;// 断开连接
private EditText mEtMsg;
private DeviceListAdapter mAdapter;
/* 一些常量,代表服务器的名称 */
public static final String PROTOCOL_SCHEME_L2CAP = "btl2cap";
public static final String PROTOCOL_SCHEME_RFCOMM = "btspp";
public static final String PROTOCOL_SCHEME_BT_OBEX = "btgoep";
public static final String PROTOCOL_SCHEME_TCP_OBEX = "tcpobex";
// 蓝牙服务端socket
private BluetoothServerSocket mServerSocket;
// 蓝牙客户端socket
private BluetoothSocket mSocket;
// 设备
private BluetoothDevice mDevice;
private BluetoothAdapter mBluetoothAdapter;
// --线程类-----------------
private ServerThread mServerThread;
private ClientThread mClientThread;
private ReadThread mReadThread;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chat);
initDatas();
initViews();
initEvents();
}
private void initEvents() {
mListView.setOnItemClickListener(this);
// 发送信息
mBtnSend.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
String text = mEtMsg.getText().toString();
if (!TextUtils.isEmpty(text)) {
// 发送信息
sendMessageHandle(text);
mEtMsg.setText("");
mEtMsg.clearFocus();
// 隐藏软键盘
InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
manager.hideSoftInputFromWindow(mEtMsg.getWindowToken(), 0);
} else
Toast.makeText(ChatActivity.this, "发送内容不能为空!", Toast.LENGTH_SHORT).show();
}
});
// 关闭会话
mBtnDisconn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (BluetoothActivity.mType == Type.CILENT) {
shutdownClient();
} else if (BluetoothActivity.mType == Type.SERVICE) {
shutdownServer();
}
BluetoothActivity.isOpen = false;
BluetoothActivity.mType = Type.NONE;
Toast.makeText(ChatActivity.this, "已断开连接!", Toast.LENGTH_SHORT).show();
}
});
}
private void initViews() {
mListView = (ListView) findViewById(R.id.list);
mListView.setAdapter(mAdapter);
mListView.setFastScrollEnabled(true);
mEtMsg = (EditText) findViewById(R.id.MessageText);
mEtMsg.clearFocus();
mBtnSend = (Button) findViewById(R.id.btn_msg_send);
mBtnDisconn = (Button) findViewById(R.id.btn_disconnect);
}
private void initDatas() {
mDatas = new ArrayList<DeviceBean>();
mAdapter = new DeviceListAdapter(this, mDatas);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
/**
* 信息处理
*/
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
String info = (String) msg.obj;
switch (msg.what) {
case STATUS_CONNECT:
Toast.makeText(ChatActivity.this, info, 0).show();
break;
}
if (msg.what == 1) {
mDatas.add(new DeviceBean(info, true));
mAdapter.notifyDataSetChanged();
mListView.setSelection(mDatas.size() - 1);
}else {
mDatas.add(new DeviceBean(info, false));
mAdapter.notifyDataSetChanged();
mListView.setSelection(mDatas.size() - 1);
}
}
};
@Override
public void onResume() {
super.onResume();
if (BluetoothActivity.isOpen) {
Toast.makeText(this, "连接已经打开,可以通信。如果要再建立连接,请先断开", Toast.LENGTH_SHORT).show();
return;
}
if (BluetoothActivity.mType == Type.CILENT) {
String address = BluetoothActivity.BlueToothAddress;
if (!"".equals(address)) {
mDevice = mBluetoothAdapter.getRemoteDevice(address);
mClientThread = new ClientThread();
mClientThread.start();
BluetoothActivity.isOpen = true;
} else {
Toast.makeText(this, "address is null !", Toast.LENGTH_SHORT).show();
}
} else if (BluetoothActivity.mType == Type.SERVICE) {
mServerThread = new ServerThread();
mServerThread.start();
BluetoothActivity.isOpen = true;
}
}
// 客户端线程
private class ClientThread extends Thread {
public void run() {
try {
mSocket = mDevice.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
Message msg = new Message();
msg.obj = "请稍候,正在连接服务器:" + BluetoothActivity.BlueToothAddress;
msg.what = STATUS_CONNECT;
mHandler.sendMessage(msg);
mSocket.connect();
msg = new Message();
msg.obj = "已经连接上服务端!可以发送信息。";
msg.what = STATUS_CONNECT;
mHandler.sendMessage(msg);
// 启动接受数据
mReadThread = new ReadThread();
mReadThread.start();
} catch (IOException e) {
Message msg = new Message();
msg.obj = "连接服务端异常!断开连接重新试一试。";
msg.what = STATUS_CONNECT;
mHandler.sendMessage(msg);
}
}
};
// 开启服务器
private class ServerThread extends Thread {
public void run() {
try {
// 创建一个蓝牙服务器 参数分别:服务器名称、UUID
mServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(PROTOCOL_SCHEME_RFCOMM,
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
Message msg = new Message();
msg.obj = "请稍候,正在等待客户端的连接...";
msg.what = STATUS_CONNECT;
mHandler.sendMessage(msg);
/* 接受客户端的连接请求 */
mSocket = mServerSocket.accept();
msg = new Message();
msg.obj = "客户端已经连接上!可以发送信息。";
msg.what = STATUS_CONNECT;
mHandler.sendMessage(msg);
// 启动接受数据
mReadThread = new ReadThread();
mReadThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
};
/* 停止服务器 */
private void shutdownServer() {
new Thread() {
public void run() {
if (mServerThread != null) {
mServerThread.interrupt();
mServerThread = null;
}
if (mReadThread != null) {
mReadThread.interrupt();
mReadThread = null;
}
try {
if (mSocket != null) {
mSocket.close();
mSocket = null;
}
if (mServerSocket != null) {
mServerSocket.close();
mServerSocket = null;
}
} catch (IOException e) {
Log.e("server", "mserverSocket.close()", e);
}
};
}.start();
}
/* ͣ停止客户端连接 */
private void shutdownClient() {
new Thread() {
public void run() {
if (mClientThread != null) {
mClientThread.interrupt();
mClientThread = null;
}
if (mReadThread != null) {
mReadThread.interrupt();
mReadThread = null;
}
if (mSocket != null) {
try {
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
mSocket = null;
}
};
}.start();
}
// 发送数据
private void sendMessageHandle(String msg) {
if (mSocket == null) {
Toast.makeText(this, "没有连接", Toast.LENGTH_SHORT).show();
return;
}
try {
OutputStream os = mSocket.getOutputStream();
os.write(msg.getBytes());
mDatas.add(new DeviceBean(msg, false));
mAdapter.notifyDataSetChanged();
mListView.setSelection(mDatas.size() - 1);
} catch (IOException e) {
e.printStackTrace();
}
}
// 读取数据
private class ReadThread extends Thread {
public void run() {
byte[] buffer = new byte[1024];
int bytes;
InputStream is = null;
try {
is = mSocket.getInputStream();
while (true) {
if ((bytes = is.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);
Message msg = new Message();
msg.obj = s;
msg.what = 1;
mHandler.sendMessage(msg);
}
}
} catch (IOException e1) {
e1.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
}
@Override
public void onClick(View view) {
}
@Override
protected void onDestroy() {
super.onDestroy();
if (BluetoothActivity.mType == Type.CILENT) {
shutdownClient();
} else if (BluetoothActivity.mType == Type.SERVICE) {
shutdownServer();
}
BluetoothActivity.isOpen = false;
BluetoothActivity.mType = Type.NONE;
}
}
三、相关代码下载
链接:http://pan.baidu.com/s/1eSNRvzG 密码:awgv