Android无线蓝牙开发总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35317635/article/details/76423011

一.蓝牙基础知识

      蓝牙(Bluetooth)是一种短距离的无线通信技术标准。这个名子来源于10世纪丹麦国王Harald Blatand,英文名子是Harold Bluetooth。

(一)蓝牙的四层协议

      蓝牙协议分为4层,即核心协议层、电缆替代协议层、电话控制协议层和采纳的其它协议层。这4种协议中最重要的是核心协议。蓝牙的核心协议包括基带、链路管理、逻辑链路控制和适应协议四部分。其中链路管理(LMP)负责蓝牙组件间连接的建立。逻辑链路控制与适应协议(L2CAP)位于基带协议层上,属于数据链路层,是一个为高层传输和应用层协议屏蔽基带协议的适配协议。

(二)蓝牙的操作

Android提供蓝牙API来执行这些不同的操作。 
1. 开关蓝牙 
2. 扫描其他蓝牙设备 
3. 获取配对设备列表 
4. 连接到通过服务发现其他设备

(三)蓝牙权限

1. android.permission.BLUETOOTH:

      允许程序连接到已配对的蓝牙设备,请求连接/接收连接/传输数据需要改权限, 主要用于对配对后进行操作;

2. android.permission.BLUETOOTH_ADMIN :

      允许程序发现和配对蓝牙设备, 该权限用来管理蓝牙设备, 有了这个权限, 应用才能使用本机的蓝牙设备, 主要用于对配对前的操作;

(四)BluetoothAdapter

      BluetoothAdapter代表了移动设备的本地的蓝牙适配器, 通过该蓝牙适配器可以对蓝牙进行基本操作 
BluetoothAdapter.getDefaultAdapter()该静态方法可以获取该适配器对象.

(五)蓝牙的BluetoothAdapter .STATE 状态值 , 即开关状态

1.蓝牙关闭 int STATE_OFF //值为10, 蓝牙模块处于关闭状态; 
2.蓝牙打开中 int STATE_TURNING_ON //值为11, 蓝牙模块正在打开; 
3.蓝牙开启 int STATE_ON //值为12, 蓝牙模块处于开启状态; 
4. 蓝牙开启中 int STATE_TURNING_OFF //值为13, 蓝牙模块正在关闭; 
蓝牙开关状态顺序 : STATE_OFF –> STATE_TURNING_ON –> STATE_ON –>STATE_TURNING_OFF –> STATE_OFF;

(六)BluetoothAdapter SCAN_MOD状态值 ,即扫描状态

  1. 无功能状态 : int SCAN_MODE_NONE //值为20, 查询扫描和页面扫描都失效, 
    该状态下蓝牙模块既不能扫描其它设备, 也不可见;
  2. 扫描状态 : int SCAN_MODE_CONNECTABLE //值为21, 查询扫描失效, 页面扫描有效, 
    该状态下蓝牙模块可以扫描其它设备, 从可见性来说只对已配对的蓝牙设备可见, 只有配对的设备才能主动连接本设备;
  3. 可见状态 : int SCAN_MODE_CONNECTABLE_DISCOVERABLE //值为23, 查询扫描和页 
    面扫描都有效;

(七)打开/关闭蓝牙的两种方法:

1.直接调用函数enable()去打开蓝牙设备 ;

2.系统API去打开蓝牙设备,该方式会弹出一个对话框样式的Activity供用户选择是否打开蓝牙设备。

//第一种启动蓝牙的方法,不推荐
//bluetoothAdapter.enable();
//第二种启动蓝牙的方法,推荐
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_ENABLE);
//第二种方法要写数据回调方法
/**
 * 数据回调方法
 */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_ENABLE) {
        if (resultCode == RESULT_OK) {
            Toast.makeText(this, "蓝牙已开启", Toast.LENGTH_SHORT).show();
           // getMyBondedDevices();//获取绑定的蓝牙设备
            adapter.notifyDataSetChanged();//刷新适配器
        } else {
            Toast.makeText(this, "蓝牙未开启", Toast.LENGTH_SHORT).show();
        }
    } 
}


(八)关闭蓝牙,直接调用API 函数即disable()即可。

public boolean disable () 
返回值:该函数会立即返回。 
1.true 表示关闭操作成功 
2. false 表示蓝牙操作失败 , ①、当前蓝牙已经关闭 ; ②、其他一些异常情况

(九)扫描蓝牙设备

  1.public boolean startDiscovery () 
      功能: 扫描蓝牙设备的开启
      注意: 如果蓝牙没有开启,该方法会返回false ,即不会开始扫描过程。

  2.public  boolean cancelDiscovery ()
     功能: 取消扫描过程。
     注意: 如果蓝牙没有开启,该方法会返回false。

 3.public boolean isDiscovering ()
    功能: 是否正在处于扫描过程中。
    注意: 如果蓝牙没有开启,该方法会返回false。


      这里要特别注意,蓝牙扫描的时候,它会发出系统的广播,这是我们就要创建广播接收者来接收数据,数据里面就有蓝牙的设备对象和名称等等,广播也是蓝牙知识的重中之重。 

(十)蓝牙的广播

Action值 说明
ACTION_STATE_CHANGED 蓝牙状态值发生改变
*ACTION_SCAN_MODE_CHANGED 蓝牙扫描状态(SCAN_MODE)发生改变*
ACTION_DISCOVERY_STARTED 蓝牙扫描过程开始
ACTION_DISCOVERY_FINISHED 蓝牙扫描过程结束
ACTION_LOCAL_NAME_CHANGED 蓝牙设备Name发生改变
ACTION_REQUEST_DISCOVERABLE 请求用户选择是否使该蓝牙能被扫描

      PS:如果蓝牙没有开启,用户点击确定后,会首先开启蓝牙,继而设置蓝牙能被扫描。 
Action值: ACTION_REQUEST_ENABLE // 请求用户选择是否打开蓝牙 
示例: 

创建广播接收者:
/**
 * 广播接收者的创建
 */
private BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        //获取设备的发送的广播
        //做数据处理
     }
};
注册广播接收者 :
/**
 * 广播的注册,注意这里Action可以添加多个
 */
@Override
protected void onResume() {
    super.onResume();
//添加蓝牙广播的Action,发现蓝牙设备时的Action
    IntentFilter intentFilter = new 
IntentFilter(BluetoothDevice.ACTION_FOUND);
//添加蓝牙广播的Action,蓝牙设备扫描完毕时的Action 
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    registerReceiver(receiver, intentFilter);//注册广播接收者
}

广播的注销 :
/**
 * 广播的停止
 */
@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(receiver);//取消广播
}
一般监听发现蓝牙和蓝牙扫描完成的广播就可以了。 
      通过广播接收数据后,再对数据进行处理。就可以看到我们手机上显示的蓝牙设设备名称和其他信息。

(十一)获取蓝牙的相关信息的方法

1.public String getName ()

功能:获取蓝牙设备Name

2.public String getAddress ()

功能:获取蓝牙设备的硬件地址(MAC地址),例如:00:11:22:AA:BB:CC

3.public boolean setName (String name)

功能:设置蓝牙设备的Name。

4.public SetgetBondedDevices ()

功能:获取与本机蓝牙所有绑定的远程蓝牙信息,以BluetoothDevice类实例(稍后讲到)返回。 
注意:如果蓝牙未开启,该函数会返回一个空集合 。

5.public static boolean checkBluetoothAddress (String address)

 功能: 验证蓝牙设备MAC地址是否有效。所有设备地址的英文字母必须大写,48位,形如:00:43:A8:23:10:F1 。

返回值: true 设备地址有效,false 设备地址无效

6.public BluetoothDevice getRemoteDevice (String address)

功能:以给定的MAC地址去创建一个 BluetoothDevice 类实例(代表远程蓝牙实例)。即使该蓝牙地址不可见,也会产生 一个BluetoothDevice 类实例。 
返回:BluetoothDevice 类实例 。注意,如果该蓝牙设备MAC地址不能被识别,其蓝牙Name为null。 
异常:如果MAC address无效,抛出IllegalArgumentException。

      使用上面的方法就可以对蓝牙进行扫描显示。但是要使用蓝牙通信就要使用到Socket编程了。

二.蓝牙Socket通信

(一)UUID

      在蓝牙中,每个服务和服务属性都唯一地由 全局唯一标识符 ,Universally Unique Identifier(UUID)来校验。正如它的名字所暗示的,每一个这样的标识符都要在时空上保证唯一。UUID类可表现为短整形(16或32位)和长整形(128 位)UUID。他提供了分别利用String和16位或32位数值来创建类的构造函数,提供了一个可以比较两个UUID(如果两个都是128位)的方法,还有一个可以转换一个UUID为一个字符串的方法。UUID实例是不可改变的(immutable),只有被UUID标示的服务可以被发现。 
      UUID的格式被分成5段,其中中间3段的字符数相同,都是4个,第1段是8个字符,最后一段是12个字符。所以UUID实际上是8个-4个-4个-4个-12个的字符串。 

      UUID相当于Socket的端口,而蓝牙地址相当于Socket的IP。两个蓝牙设备进行连接时需要使用同一个UUID, 这是一个服务的唯一标识,而且这个UUID的值必须是 
00001101-0000-1000-8000-00805F9B34FB 

      上面这个UUID,直接复制使用就可以了。


三.蓝牙Socket通信示例


程序实现的功能是打开蓝牙,关闭蓝牙,打开蓝牙可见性,搜索远程设备,与远程设备进行通信(两台设备连接之后可以发送一条信息从客户端到服务端)

(一)添加权限

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />

(二)布局文件的设计

<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"
    tools:context="com.example.bluetooth2.MainActivity" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="蓝牙设置" />

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/textView1"
        android:onClick="turnOn"
        android:text="打开蓝牙" />

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_below="@+id/button1"
        android:onClick="turnOff"
        android:text="关闭蓝牙" />

    <Button
        android:id="@+id/button3"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/button2"
        android:onClick="turnOnScan"
        android:text="打开蓝牙可见性" />

    <Button
        android:id="@+id/button4"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/button3"
        android:onClick="findDevice"
        android:text="扫描设备" />

    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/button4" >
    </ListView>

</RelativeLayout>

(三)蓝牙服务器端(Socket服务端)的设计

package com.example.bluetooth2;

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

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.Message;

//创建服务端的套路
	public class ServiceThread extends Thread{
		//创建服务端
		private BluetoothServerSocket serverSocket;
		//Socket套接字
		private BluetoothSocket socket;
		private InputStream in;
		private Handler handler;
		//构造方法
		public ServiceThread(BluetoothAdapter bAdapter,Handler handler) {
			try {
					serverSocket = bAdapter.listenUsingRfcommWithServiceRecord("service", MainActivity.my_UUID);
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
		@Override
		public void run() {
			super.run();
			try {
				socket = serverSocket.accept();
				in = socket.getInputStream();
				while(true){
					byte buffer[] = new byte[128];
					int len = in.read(buffer);
					String string = new String (buffer,0,len,"utf-8");
					Message message = new Message();
					message.obj = string;
					handler.sendMessage(message);
				}
			} catch (Exception e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
}

(四)主方法类的设计


package com.example.bluetooth2;


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import android.app.Activity;
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.net.NetworkInfo.State;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends Activity implements OnItemClickListener{

	//控制蓝牙设备的对象
	private BluetoothAdapter bAdapter;
	//布局内的ListView控件
	ListView listView;
	Button turnOn;
	Button turnOff;
	Button turnOnScan;
	Button findDevice;
	//适配器对象的定义
	private ArrayAdapter adapter;
	
	//蓝牙设备的对象的集合
	ArrayList<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); 
	//设备的名称集合
	ArrayList<String> deviceNames = new ArrayList<String>(); 
	//手机蓝牙的UUID固定值
	static final UUID my_UUID = UUID.fromString("db764ac7-4b08-7f25-aafe-59d03c27bae3");
	
	//定义数据请求码
	private final int REQUEST_CODE1 = 1; //蓝牙打开的请求码
	private final int REQUEST_DISCOVERY_CODE2 = 2; //蓝牙可发现性打开的请求码
	//声明客户端对象
	private BluetoothSocket clientSocket; 
	//声明一个远程设备
    private BluetoothDevice device; 
    
    //服务端的信息传输
  	private Handler handler = new Handler(){
  		public void handleMessage(android.os.Message msg) {
  			Toast.makeText(MainActivity.this, String.valueOf(msg.obj), Toast.LENGTH_LONG).show();
  		};
  	};
  	BluetoothServerSocket serverSocket = null;
	BluetoothSocket socket = null;
	
    @Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		//实例化对象
		bAdapter = BluetoothAdapter.getDefaultAdapter();
		listView = (ListView) findViewById(R.id.listView1);
		turnOn = (Button) findViewById(R.id.button1); //打开蓝牙
		turnOff = (Button) findViewById(R.id.button2); //关闭蓝牙
		turnOnScan = (Button) findViewById(R.id.button3); //启动可发现机制
		findDevice = (Button) findViewById(R.id.button4);//发现远程设备
		//创建适配器,使用系统布局
        adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, deviceNames);
        //给ListView设置适配器
        listView.setAdapter(adapter);
        //设置条目的点击事件
        listView.setOnItemClickListener(this);
        //实现服务端
        ServiceThread serviceThread = new ServiceThread(bAdapter,handler);
        serviceThread.start();
	}
    //实现子条目的点击事件
   	@Override
   	public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
   		connectService(adapter,position);
   	}
   	
  
   	
   //打开蓝牙
	public void turnOn(View view){
		if(bAdapter != null){
			if(bAdapter.isEnabled()){
				Toast.makeText(this, "蓝牙已经开启",Toast.LENGTH_SHORT).show();
			}
			Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
			startActivityForResult(intent, REQUEST_CODE1);
		}
	}
	//关闭蓝牙
	public void turnOff(View view){
		if(bAdapter != null){
			if(bAdapter.isEnabled()){
				bAdapter.disable();
				Toast.makeText(this, "蓝牙已经关闭",Toast.LENGTH_LONG ).show();
				//擦除页面数据
				deviceNames.clear();
				devices.clear();
				adapter.notifyDataSetChanged();
			}
			else{
				Toast.makeText(this, "蓝牙尚未打开",Toast.LENGTH_LONG ).show();
			}
		}
	}
   //启动可发现机制
	public void turnOnScan(View view){
		if(bAdapter != null){
			if (bAdapter.isDiscovering()) {//如果蓝牙设置正在扫描
		            Toast.makeText(this, "正在扫描,别急", Toast.LENGTH_SHORT).show();
		        } else {
		        	//这里可以设置显示自己蓝牙设备的时间,默认是300秒,也可以自定义单位是秒
		            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE).putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 600);
		            startActivityForResult(intent, REQUEST_DISCOVERY_CODE2);
		        }
			//startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), REQUEST_CODE2);
		}
	}
	//扫描远程设备
	public void findDevice(View view) {
		if(bAdapter != null){
			if( bAdapter.isEnabled() ){
				if(bAdapter.isDiscovering()){
					bAdapter.cancelDiscovery();
					setProgressBarVisibility(false);
					setTitle("退出扫描...");
				}
				//清空信息
				devices.clear();
				//清空数据,通知设配器更改
				deviceNames.clear();
				adapter.notifyDataSetChanged();
				//设置进度
				setProgressBarVisibility(true);
				setTitle("正在扫描...");
				bAdapter.startDiscovery();
			}
			else{
				Toast.makeText(MainActivity.this, "蓝牙尚未打开", Toast.LENGTH_SHORT);
			}
		}
	}
	
	
	/**
     * 获取已经绑定的蓝牙设置
     */
    private void getMyBondedDevices() {
    	//清除设备名称的集合
        deviceNames.clear();
      //清除蓝牙设备对象的集合
        devices.clear();
        if (bAdapter.getBondedDevices() != null) {//如果蓝牙适配器对象不为空时
            //获取里面的数据的对象
            List<BluetoothDevice> liset = new ArrayList<BluetoothDevice>(bAdapter.getBondedDevices());
            devices.addAll(liset);
            //拿到适配器对象的名称数据
            for (BluetoothDevice device : liset) {
                deviceNames.add("已经绑定的设备-"+device.getName()+":"+device.getAddress());
            }
        }
    }
	
	
	//数据回调
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		// TODO 自动生成的方法存根
		super.onActivityResult(requestCode, resultCode, data);
		//请求打开蓝牙
		if(requestCode == REQUEST_CODE1){
			if(requestCode == RESULT_OK){
				Toast.makeText(this, "蓝牙开启",Toast.LENGTH_LONG ).show();
				//获取绑定的蓝牙设备
				getMyBondedDevices();
				//刷新适配器
	            adapter.notifyDataSetChanged();
			}
		}
			//请求蓝牙的可发现性 RESULT_CANCELED
		if(requestCode == REQUEST_DISCOVERY_CODE2){
			if(resultCode == RESULT_OK){
				Toast.makeText(this, "蓝牙可发现性已经开启",Toast.LENGTH_LONG ).show();
			}
		}
		
	}
	
	
	 /**
     * 广播的注册
     */
    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        //添加蓝牙广播的Action
        intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        registerReceiver(discoverResult, intentFilter);//注册广播接收者
    }

    /**
     * 广播的停止
     */
    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(discoverResult);//取消广播
    }
	/*
	 * 创建一个广播
	 */
	BroadcastReceiver discoverResult  = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			if(BluetoothDevice.ACTION_FOUND.equals(action)){
				//把对象和名称放到对应的集合中
				String remoteDevicesName = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
				BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
				//判断是否绑定,如果已经绑定 则不需要添加
				if(remoteDevice.getBondState() != BluetoothDevice.BOND_BONDED){
					devices.add(remoteDevice);
					deviceNames.add(remoteDevicesName + ":" + remoteDevice.getAddress());
					adapter.notifyDataSetChanged();
					}
			}else if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
				setProgressBarVisibility(false);
				setTitle("已经搜索完毕");
			}
		}
	};
	
	 /**
     * 客户端连接服务器
     */
    public void connectService(ArrayAdapter adapter,int position){
    	//获取连接设备的 地址
		String  string = adapter.getItem(position).toString().trim();
		String address = string.substring(string.indexOf(":") + 1);
		 if(bAdapter.isDiscovering()){
			 bAdapter.cancelDiscovery();
			 setProgressBarVisibility(false);
			 setTitle("退出扫描...");
			// if(device == null){
				 //通过设备地址获取远程设备对象
				 device = bAdapter.getRemoteDevice(address);
			// }
			// if(clientSocket == null){
				 try {
					clientSocket = device.createRfcommSocketToServiceRecord(my_UUID);
					clientSocket.connect();
					OutputStream os =clientSocket.getOutputStream();
					//if(os != null){
						os.write("从客户端发来信息".getBytes("utf-8"));
					//}
				 } catch (IOException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
			// }
		 }
    }
    
	
}

这里对Socket编程简单做几句介绍,其实Socket编程并不是很难理解。Socket编程一般需要一个服务端SocketServie和一个或多个客户端Socket,通过SocketService或Socket对象获取它的输入流实现对数据的读取,获取输出流实现对数据的写入。其中数据如果要一直读取就要写一个一直执行的子线程,让它一直在跑。 
      而Tcp/Udp编程就要用到ip地址和端口号了。Socket编程可以结合Tcp使用网络。


猜你喜欢

转载自blog.csdn.net/qq_35317635/article/details/76423011