安卓 蓝牙通信之聊天小程序

安卓 蓝牙聊天小程序

一、简述

  记--简单的蓝牙聊天小程序。使用的是传统蓝牙开发。(某些手机由于Android版本原因需要添加新的权限)

       两台设备开启蓝牙,一台设备设置蓝牙可见性,另一台设备进行连接,然后互相收发信息。

      开发环境:win7-32bit, ADT,jdk1.7

      测试手机:Android版本4.4.2

      例子打包:链接: https://pan.baidu.com/s/1WXKD_Wan4tc9O86-1H4EQg 提取码: g1ac 

二、效果

                                                          

                                        

三、工程结构

四、源文件

MainActivity.java文件

package com.liang.bluetooth;
/*
 * 首先打开蓝牙并且打开蓝牙可见性是可以正常通信的
 * 问题1:静默蓝牙可见性问题
 * 问题2:询问式开启蓝牙问题
 * 问题3:二次连接处理
 * 问题4:资源释放、退出处理
 * 问题5:主线程不会等待AlertDialog返回,show出来后继续执行。 解决:阻塞主线程,等待AlertDialog返回
 * */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.UUID;

import com.liang.bluetooth.DeviceListActivity;
import com.liang.bluetooth.R;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
//import android.view.Menu;            //如使用菜单加入此三包
//import android.view.MenuInflater;
//import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

@SuppressLint({ "NewApi", "HandlerLeak" }) public class MainActivity extends Activity {
	
	private final static int REQUEST_CONNECT_DEVICE = 1;    //用来表示 查询设备的请求码
	private final static int REQUEST_BT_ENABLE_CODE = 2;	//用来表示 开启蓝牙的请求码
	private final static String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB";   //SPP服务UUID号
	
	
    private TextView tv_dev = null;      //用来显示本机蓝牙设备名称
    private TextView tv_reDev = null;    //用来显示连接的蓝牙设备名称
    private TextView tv_rmsg = null;     //用来显示接收到的信息
    private ScrollView sv = null;        //消息滚动条句柄,目的滚动到底部,显示最新接收的消息
    private EditText edt_smsg = null;    //发送信息输入文本框
    private Button btn_cnnt = null;		 //"连接"按钮
    private Button btn_send_file = null; //"发送文件"按钮   (此功能还没有添加)
    
    private Toast mToast = null; //提示框
    private BluetoothAdapter mAdapter = null;   //获取本地蓝牙适配器
    BluetoothDevice mRemoteDev = null;      //用来表示其他蓝牙设备
    BluetoothSocket mSocket = null;      //蓝牙通信socket
    private BluetoothServerSocket mServerSocket = null;//服务socket
    
    AcceptThread accept_thread = null;   //服务线程    等待别的蓝牙设备请求连接
    ConnectThread connect_thread = null;//客户端连接线程  主动连接其它蓝牙设备
    PrintWriter writer = null;//发送消息给其它蓝牙设备
	private int cs_flag = 0;  //用来标识本机是作为1服务器的还是2客户端,在发送消息的时候要用到
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);   //设置主界面为 main.xml
        
        //1 初始化控件
        init();
		
        //2 请求打开蓝牙操作
		if( mAdapter.isEnabled() )//已经打开蓝牙
		{
			setDiscoverableTimeout(300, 2);//设置蓝牙可见性
			// 开启服务线程
		    accept_thread = new AcceptThread();
		    accept_thread.start();
		}
		else //还没开启蓝牙则开启
		{
			openBlueTooth(MainActivity.this, 0, 2);//询问开启蓝牙 
			
		}
         
    }

    /*
     * 初始化
     * */
    public void init()
    {
    	mAdapter = BluetoothAdapter.getDefaultAdapter();//获取蓝牙适配器
    	//判断设备是否支持蓝牙
    	if(mAdapter == null)
        {
    		//告诉用户设备不支持蓝牙,然后退出
        	showAlertDialog("退出应用","抱歉!该设备不支持蓝牙。 ", "", "确定", 0);
        }
    	
    	//初始化控件
    	tv_dev = (TextView) findViewById(R.id.tv_dev);      //得到本机蓝牙设备名称显示句柄
        tv_dev.setText("本机蓝牙名称:"+mAdapter.getName()+"("+mAdapter.getAddress()+")");//显示本机蓝牙设备名称
        tv_reDev = (TextView) findViewById(R.id.tv_reDev);//得到其他蓝牙设备名称显示句柄
        tv_rmsg = (TextView) findViewById(R.id.in);      //得到数据显示句柄
        sv = (ScrollView)findViewById(R.id.sv_list);  //得到翻页句柄(有滚动条)
        edt_smsg = (EditText)findViewById(R.id.edt_smsg);   //得到输入框句柄
        btn_cnnt = (Button)findViewById(R.id.btn_cnnt);//得到"连接"按钮
        btn_send_file = (Button)findViewById(R.id.btn_send_file);//得到"发送文件"按钮
        
    }
    
    /*
     * 消息处理队列,对于accept、connect子线程一般不能直接更改UI,
     * 所以子线程通过发送Handler消息,让Handler更改UI,(Handler类似独立线程)
     */
    private Handler mHandler = new Handler(){
    	public void handleMessage(Message msg){
    		super.handleMessage(msg);
    		
    		switch(msg.what)
    		{
	    		case 1://弹出Toast提示框
	    			showToast((String)msg.obj);
	    			break;
	    		case 2://确认连接
	    			showAlertDialog("确认连接", "连接"+(String)msg.obj, "取消", "确定", 1);
                    break;
	    		case 3://更改 所连接的蓝牙设备的信息
	    			tv_reDev.setText((String)msg.obj);
	    			break;
	    		case 4://更改"连接"按钮的显示文本
	    			btn_cnnt.setText((String)msg.obj);
	    			break;
	    		case 5://关闭蓝牙可见性
	    			setDiscoverableTimeout(1, 3);
	    			break;
	    		case 9://显示接收到的消息
	    		default:
	    			tv_rmsg.append((String)msg.obj);   //显示接收到的消息
	        		sv.scrollTo(0,tv_rmsg.getMeasuredHeight()); //滚动到最新消息处
	        		break;
	    		}
    		}
    };
    
    
    /*
     * 发送handler消息
     * @param content 消息内容
     * @param what 消息类型
     */
    private void sendHandlerMsg(String content, int what)
    {	
    	Message msg = mHandler.obtainMessage();
    	msg.what = what;
        msg.obj = content;
        mHandler.sendMessage(msg);
    }

    
    /*
     * "发送"按钮单击事件
     */
    public void onSendButtonClicked(View v)
    {
    	
    	if(!mAdapter.isEnabled())//如果还没有开启蓝牙服务
    	{
    		showToast("蓝牙服务不可用!!");
    		return ;
    	}
    	
    	if(cs_flag == 0)//未连接蓝牙设备!
    	{
    		showToast("未连接蓝牙设备!");
    		return ;
    	}
    	
    	//获取要发送的信息
    	String smsg = edt_smsg.getText().toString();
    	if(smsg.equals(""))//如果消息为空则不发送
    	{
    		return ;
    	}
    	edt_smsg.setText("");//清空信息输入框
    	
    	//发送消息
    	if(cs_flag == 1)//本机作为服务器
    	{
    		accept_thread.write(smsg);
    	}
    	else if(cs_flag == 2)//本机作为客户端
    	{
    		connect_thread.write(smsg);
    	}
    }
    
    /* 
     * 接收活动结果,响应startActivityForResult()
     * @param requestCode:请求码(自定义一个 整数,代表要请求什么操作)
     * @param resultCode:结果码(自定义一个 整数,代表请求的处理结果,表示成功与否。。。)
     * @param data:页面返回的数据
     */
    public void onActivityResult(int requestCode, int resultCode, Intent data) 
    {
    	
    	switch(requestCode)
    	{
    	case REQUEST_BT_ENABLE_CODE://请求开启蓝牙界面返回 (系统自带的请求打开蓝牙界面)
    		if (resultCode == RESULT_OK) 
    		{
                //用户允许打开蓝牙(蓝牙开启需要一定的时间)
				showToast("蓝牙已开启");
				//开启服务线程
				accept_thread = new AcceptThread();
				accept_thread.start();
                
            } else if (resultCode == RESULT_CANCELED) {
                //用户没有允许打开蓝牙,退出应用
            	showAlertDialog("退出应用", "抱歉!应用需要开启蓝牙!", "" ,"确认", 0) ;
            }

    		break;
    	case REQUEST_CONNECT_DEVICE://搜索周边蓝牙设备界面结果返回
    		// 响应返回结果
            if (resultCode == Activity.RESULT_OK) //连接成功
            {   
            	//1  获取要连接设备的名称、MAC地址
            	//String dev_name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
                String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
                
                //2 得到要连接的蓝牙设备句柄      
                mRemoteDev = mAdapter.getRemoteDevice(address);
                if(mRemoteDev == null)
                {
                	tv_rmsg.append("获取蓝牙设备句柄失败\n");
                	return;
                }
               
                //3 开启连接线程
                if(connect_thread != null)//取消之前的
                {
                	connect_thread.close();//结束线程
                	connect_thread = null;
                }
                connect_thread = new ConnectThread(mRemoteDev);
                connect_thread.start();
            }
    		break;
    	default:break;
    	}
    }
    
    /*
     * "连接"按钮响应函数
     */
    public void onConnectButtonClicked(View v)
    { 
    	if(!mAdapter.isEnabled())
    	{  //如果蓝牙服务不可用则提示,可能是还没有开启
    		showToast("蓝牙服务不可用!");
    		return;
    	}
    	
    	if(mSocket == null)//如未连接设备则打开DeviceListActivity进行搜索周边蓝牙设备,并选择连接
    	{
    		Intent serverIntent = new Intent(this, DeviceListActivity.class); //跳转程序设置
    		startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);  //设置返回宏定义
    	}
    	else //已经连接上就断开连接
    	{
    		//弹出确认框
    		showAlertDialog("断开连接", "您确认断开吗?", "取消" ,"确认", 3);
    		
    	}
    	
    }
    
    /*
     * 断开连接
     * */
    public void disconnect()
    {
		try
		{//断开连接
			if(cs_flag == 1 && accept_thread != null)//本机作为服务端
			{
				//发送"__DISCONNECT__",告诉客户端要断开操作
				accept_thread.write("__DISCONNECT__");
				accept_thread.disconnect();//断开当前连接
			}
			else if(cs_flag == 2 && connect_thread != null )//本机作为客户端
			{
				//发送"__DISCONNECT__",告诉服务端要断开操作
				connect_thread.write("__DISCONNECT__");
				connect_thread.close();//关闭连接线程,即断开与服务端的连接
				accept_thread = null;
			}
			
			cs_flag = 0;//标识为未连接蓝牙设备
			btn_cnnt.setText("连接");//将"断开"按钮的文本改为连接
			tv_reDev.setText("未连接蓝牙设备");
		}catch(Exception e){}
		
    }
    /*
     * 关闭socket,结束线程
     */
    public void reSource()
    {
    	try
    	{
    		//关闭服务线程
        	if( accept_thread != null)
        	{
    			accept_thread.close();//关闭线程
        		accept_thread = null;
        	}
        	
        	//关闭connect线程
        	if( connect_thread != null)
        	{
        		connect_thread.close();//关闭线程
        		connect_thread = null;
        	}
        	
        	//关闭socket
        	if(mServerSocket != null)
    		{
    			try {
    				mServerSocket.close();
    				mServerSocket = null;
    			} catch (IOException e) {}
    		}
        	
        	if(mSocket != null)
    		{
    			try {
    				mSocket.close();
    				mSocket = null;
    			} catch (IOException e) {}
    		}
        	
        	cs_flag = 0;//表示为未连接蓝牙设备
        	btn_cnnt.setText("连接");
        	tv_reDev.setText("未连接蓝牙设备");
    	}
    	catch(Exception e){}
    }
    
    /*
     * 关闭程序调用处理部分
     */
    public void onDestroy(){
    	super.onDestroy();
    	reSource();
    }
   
    
    /*
     * "发送文件"按钮响应函数
     */
    public void onSendFileButtonClicked(View v)
    {
    	showAlertDialog("发送文件", "此功能有待完善!", "取消" ,"确认", 1);
    }
    
    //"退出"按钮响应函数
    public void onQuitButtonClicked(View v){
    	//弹出一个对话框,确认是否退出
    	showAlertDialog("退出应用", "您确认退出吗?", "取消" ,"确认", 0);
    }
    
    //弹出确认对话框
    public void showAlertDialog(String title, String content, String negative, String positive, final int action)
    {
    	//创建一个对话框
    	AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
    	dialog.setTitle(title);  //对话框标题
    	dialog.setMessage(content);//设置对话框内容提示
		if(!negative.isEmpty() && negative != "" )
		{
			//添加"取消按钮",并且单击时响应
			dialog.setNegativeButton(negative,new DialogInterface.OnClickListener() {  
	            @Override  
	            public void onClick(DialogInterface dialog, int which) 
	            {  
	            	if(action == 2)
	            	{
	            		//用户没有允许开启蓝牙,就退出应用,因为应用需要开启蓝牙才正常工作
	            		showAlertDialog("退出应用", "抱歉!应用需要开启蓝牙!", "" ,"确认", 0) ;
	            	}
	            }
			});
		}
		
		//添加一个确定按钮,并且单击时响应
		dialog.setPositiveButton(positive, new DialogInterface.OnClickListener() {  
            @Override  
            public void onClick(DialogInterface dialog, int which) 
            {  
            	switch(action)
            	{
            	case 0://退出应用操作
            		reSource();//释放资源
            		finish();//关闭本页面
            		break;
            	case 1://"确认"
            		break;
            	case 2://开启蓝牙
            		mAdapter.enable();//开启蓝牙
            		while(!mAdapter.isEnabled());//等待蓝牙开启完毕,需要一定时间
                    setDiscoverableTimeout(300, 2);//设置蓝牙可见性
            		// 开启服务线程
        		    accept_thread = new AcceptThread();
        		    accept_thread.start(); 
        		    
            		break;
            	case 3://断开连接
            		disconnect();
            		break;
            	default:
            		break;
            	}
            }  
            
        });  
		dialog.show();
    }
    
   
    //弹出Toast提示框
    private void showToast(String text) {
        if( mToast == null) {
            mToast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
        }
        else {
            mToast.setText(text);
        }
        mToast.show();
    }
    
    /**
     * 打开蓝牙
     * @param activity
     * @param requestCode
     * @param mode 开启蓝牙的方式:0询问式开启,1直接打开
     */
    public void openBlueTooth(Activity activity, int requestCode, int mode) {
        if(mode == 0)//弹出系统自带确认框询问式开启
        {
        	Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            activity.startActivityForResult(intent, requestCode);
        }
        else if(mode == 2)//弹出自定义的确认框
        {
        	//弹出一个对话框,确认是否开启蓝牙
        	showAlertDialog("开启蓝牙", "确认开启蓝牙?", "取消" ,"确认", 2) ;
        }
        else //静默开启(不弹出提示框)
        {
            mAdapter.enable();//这种方式是直接尝试打开蓝牙,对用户不友好
            // 开启服务线程  (可能需要等待蓝牙开启完毕,开启蓝牙需要时间) 
		    accept_thread = new AcceptThread();
		    accept_thread.start();
        }
        
        //静默设置蓝牙可见性,时间为300秒
        setDiscoverableTimeout(300, 2);
        
    }

    /*
     * 通过PrintWriter发送消息给其他蓝牙设备
     * @param btOs 蓝牙输出流
     * @param msg 要发送的消息文本
     */
    public void sendMsg(OutputStream btOs, String msg)
    {
    	if (btOs != null) 
    	{
            try {
                if (writer == null) {
                    writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true);
                }
                writer.println(msg);//发送给其它蓝牙设备
                if(msg == "__DISCONNECT__")
                {
                	msg = "断开连接";
                }
                
                tv_rmsg.append("我:"+msg+"\n");
               
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                sendHandlerMsg("writer错误:" + e.getMessage(), 100);
            }
        }
    }
    
    /* 设置蓝牙可见性
     * BluetoothAdapter 里面的setDiscoverableTimeout和setScanMode起到了关键性左右,
     * BluetoothAdapter源码将这2个方法隐藏了。利用反射访问
     * @param timeout 可见时间(秒)最多300秒
     * @param mode 1为询问式设置,2为静默设置 3关闭蓝牙可见性
     */
    public void setDiscoverableTimeout(int timeout, int mode) 
    {
    	if(mode == 1)//弹出确认框询问式设置
    	{
    		Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, timeout);
            startActivity(discoverableIntent);
    	}
    	else //静默式,不弹出确认框。
		{
			try {
				//利用反射使用 setDiscoverableTimeout和setScanMode
				Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
				setDiscoverableTimeout.setAccessible(true);
				Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class);
				setScanMode.setAccessible(true);
				
				if(mode == 2)//静默式 timeout不起作用,会一直保持可见性
				{
					setDiscoverableTimeout.invoke(mAdapter, timeout);
					setScanMode.invoke(mAdapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,timeout);
					//mHandler.sendEmptyMessageDelayed(5, 300000);//300秒后发送5类信号让Handler关闭蓝牙可见性
				}
				else if(mode == 3)//实现关闭蓝牙可见性
				{
					setDiscoverableTimeout.invoke(mAdapter, 1);
					setScanMode.invoke(mAdapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE,1);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

    
  //Accept服务端线程
    class AcceptThread extends Thread 
    {
        private InputStream btIs;  //用来获取其他蓝牙设备发来的消息
        private OutputStream btOs; //用来发送消息给其他蓝牙设备
        private boolean thread_run;//线程运行的标志
        private boolean cnnt_state;//连接状态
        public AcceptThread() {
        	thread_run = true;
        	cnnt_state = true;
        }
 
        @Override
        public void run() 
        {
        	String devInfo = null;
            try
            {
                //1 获取套接字
            	mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord("TEST", UUID.fromString(MY_UUID));
            	if (mServerSocket == null) 
                {
            		sendHandlerMsg("accept获取mServerSocket失败\n", 100);
            		//这里应该添加"获取失败"处理, 否则后续操作可能出现异常
            		return;
                }
                
                while(thread_run)//一般程序开始运行,服务就启动,并一直监听客户端的连接
                {
                	//2 监听连接请求 -- 连接一个设备 (如果需要连接多个设备,就需要一直mServerSocket.accept(),每accept一个客户端就就交给一个新的socket去通信-)
                    if(mSocket != null)//关闭之前的
                	{
                		try {
            				mSocket.close();
            				mSocket = null;
            			} catch (IOException e) {}
                	}
                    try
                    {
                    	mSocket = mServerSocket.accept();//阻塞等待客户端连接
                    }
                    catch(Exception ex)
                    {
                    	continue;
                    }
                    
                    devInfo = mSocket.getRemoteDevice().getName()+"("+mSocket.getRemoteDevice().getAddress()+")";
                    //弹出提示框
                    sendHandlerMsg("连接 "+mSocket.getRemoteDevice().getName()+" 成功!", 1);
                    //展示已经连接的设备名称
                    sendHandlerMsg("已连接:"+devInfo, 3);
                    sendHandlerMsg("-----已连接:"+devInfo+" -----\n", 9);
                    
                    cs_flag = 1;//标识本机作为服务器
                    sendHandlerMsg("断开", 4);//将"连接"按钮文本更改为"断开"
                    
                    try
                    {
	                    //3 获取输入输出流
	                    btIs = mSocket.getInputStream();
	                    btOs = mSocket.getOutputStream();
	                    
	                    //4 通讯-接收消息
                    
	                    BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
	                    String content = null;
	                    cnnt_state = true;
                    	while (cnnt_state) 
                        {
                            content = reader.readLine();
                            if(content.startsWith("__DISCONNECT__") )//客户端发来断开连接的请求
                            {
                            	sendHandlerMsg("收到消息:" + "断开连接" +"\n", 9);//将消息显示到TextView
                            	break;
                            }
                            else if(content != "" && !content.isEmpty())
                        	{
                        		sendHandlerMsg("收到消息:" + content +"\n", 9);//将消息显示到TextView
                        		content = "";
                        	}
                        }
                    }
                    catch(Exception e){}
                    
                    sendHandlerMsg("断开连接", 1);
                    sendHandlerMsg("----已断开连接----" +"\n", 9);//将消息显示到TextView
                    cs_flag = 0;//标识未连接蓝牙设备
                    sendHandlerMsg("未连接蓝牙设备", 3);
                    sendHandlerMsg("连接", 4);
                }
                
            } catch (IOException e) {
                e.printStackTrace();
                sendHandlerMsg("accept错误:" + e.getMessage(), 100);
            }
            finally
            {
            	reSource();//释放资源
            	finish();//退出应用
            }
        }
 
        public void write(String msg) 
        {
        	sendMsg(btOs, msg);
        }
        
        //关闭线程
        public void close()
        {

        	cnnt_state = false;
        	thread_run = false;
        	try {
				mSocket.close();
				mServerSocket.close();
			} catch (IOException e) {}
        }
        
        //断开连接
        public void disconnect()
        {
        	cnnt_state = false;
        	try {
				mSocket.close();
			} catch (IOException e) {}
        }
    }
   
    //Connect客户端线程
    class ConnectThread extends Thread 
    {
        private BluetoothDevice mDevice;//要连接的蓝牙设备
        private InputStream btIs;       //用来获取其他蓝牙设备发来的消息
        private OutputStream btOs;      //用来发送消息给其他蓝牙设备
        private boolean thread_run;     //用来控制循环(如果是true则线程一直在运行)
 
        public ConnectThread(BluetoothDevice device) {
            mDevice = device;//被点击选中的蓝牙设备
            thread_run = true;
        }
 
        @Override
        public void run() {
            if (mDevice != null) {
                try {
                    
                	try
                	{
                		if(mSocket != null)
                        {
                        	mSocket.close();
                        	mSocket = null;
                        }
                	}
                	catch(Exception e)
                	{}
                	
                	//1  获取套接字
                	mSocket = mDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
                	if (mSocket == null) 
                    {
                		sendHandlerMsg("cnnt获取mSocket失败\n", 100);
                		return;
                    }
                	
                	try
                    {
	                    //2  发起连接请求
	                	mSocket.connect();
	                	//弹出提示框
	                    sendHandlerMsg("连接 " + mDevice.getName() + " 成功!", 1);
	                    //在TextView上显示已经连接上的蓝牙设备名称
	                    sendHandlerMsg("已连接:"+mDevice.getName()+"("+mDevice.getAddress()+")", 3);
	                    sendHandlerMsg("-----已连接:"+mDevice.getName()+"("+mDevice.getAddress()+") -----\n", 9);
	                    
	                    cs_flag = 2;//标识本机作为客户端
	                    sendHandlerMsg("断开", 4);//将"连接"按钮文本改为断开
	                    
	                    //3  获取输入输出流
	                    btIs = mSocket.getInputStream();
	                    btOs = mSocket.getOutputStream();
	                    
	                    //4 通讯-接收消息
                    	BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
                        String content = null;
                        thread_run = true;
                        while (thread_run) 
                        {
                            content = reader.readLine();
                            if(content.startsWith("__DISCONNECT__") )//收到服务器端的断开信息
                            {
                            	sendHandlerMsg("收到消息:" + "断开连接" + "\n", 9);
                            	break;
                            }
                            else if(content !="" && !content.isEmpty())
                        	{
                        		sendHandlerMsg("收到消息:" + content + "\n", 9);
                        		content = "";
                        	}
                        }
                    }catch(Exception e){}
                    
                    cs_flag = 0;
                    sendHandlerMsg("断开连接", 1);
                    sendHandlerMsg("未连接蓝牙设备", 3);
                    sendHandlerMsg("----已断开连接----" +"\n", 9);//将消息显示到TextView
                    sendHandlerMsg("连接", 4);//将"断开"按钮文本改为"连接"
 
                } catch (IOException e) {
                    e.printStackTrace();
                    sendHandlerMsg("cnnt错误:" + e.getMessage(), 100);
                } 
                finally
                {
                	if(mSocket != null)
                    {
                    	try {
							mSocket.close();
							mSocket = null;
						} catch (IOException e) {}
                    	
                    }
                }
            }
        }
 
        //发送信息
        public void write(String msg) 
        {
        	sendMsg(btOs, msg);
        }
        
        //结束线程
        public void close()
        {
        	thread_run = false;
        	try {
				mSocket.close();
			} catch (IOException e) {}
        }
    }
}

 DeviceListActivity.java文件


package com.liang.bluetooth;

import com.liang.bluetooth.R;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

//蓝牙设备列表
@SuppressLint("NewApi") 
public class DeviceListActivity extends Activity {
    
    // 返回时数据标签
	public static String EXTRA_DEVICE_NAME = "设备名称";
    public static String EXTRA_DEVICE_ADDRESS = "设备地址";

    // 成员域
    private BluetoothAdapter mBtAdapter;//蓝牙适配器
    private ArrayAdapter<String> mPairedDevicesArrayAdapter;//已配对列表
    private ArrayAdapter<String> mNewDevicesArrayAdapter;//新的设备

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 创建并显示窗口
        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);  //设置窗口显示模式为窗口方式,有个滚动圈
        setContentView(R.layout.device_list);//设置界面 

        // 设定默认返回值为取消
        setResult(Activity.RESULT_CANCELED);

        // 设定扫描按键响应
        Button scanButton = (Button) findViewById(R.id.button_scan);
        scanButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                doDiscovery();
                v.setVisibility(View.GONE);//将"扫描"按钮设置为"消失"
            }
        });

        // 初始化设备存储数组
        mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
        mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);

        // 设置已配队设备列表
        ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
        pairedListView.setAdapter(mPairedDevicesArrayAdapter);
        pairedListView.setOnItemClickListener(mDeviceClickListener);

        // 设置新查找设备列表
        ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
        newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
        newDevicesListView.setOnItemClickListener(mDeviceClickListener);

        // 注册接收查找到设备action接收器
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        this.registerReceiver(mReceiver, filter);

        // 注册查找结束action接收器
        filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        this.registerReceiver(mReceiver, filter);

        // 得到本地蓝牙适配器
        mBtAdapter = BluetoothAdapter.getDefaultAdapter();

    }
    
    //销毁
    @Override
    protected void onDestroy() {
        super.onDestroy();

        // 关闭扫描服务
        if (mBtAdapter != null) {
            mBtAdapter.cancelDiscovery();
        }

        // 注销action接收器
        this.unregisterReceiver(mReceiver);
    }
    
    //"取消"按钮响应函数
    public void OnCancel(View v){
    	finish();//关闭本页面
    }
    
    /**
     * 开始服务和设备查找
     */
    private void doDiscovery() {
        
        // 显示进度滚动圈
        setProgressBarIndeterminateVisibility(true);
        //设置窗口标题
        setTitle("查找设备中...");
        //弹出提示框吗,提示用户
        Toast.makeText(DeviceListActivity.this, "查找设备中...", Toast.LENGTH_SHORT).show();
        // 显示其它设备(未配对设备)列表
        findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);

        // 如果正在查找,关闭再进行的服务查找
        if (mBtAdapter.isDiscovering()) {
            mBtAdapter.cancelDiscovery();
        }
        //并重新开始
        mBtAdapter.startDiscovery();
    }

    // 选择设备响应函数 
    private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
        public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
            // 准备连接设备,关闭服务查找
            mBtAdapter.cancelDiscovery();

            // 得到设备名称与MAC地址
            String info = ((TextView) v).getText().toString();
            String dev_name = info.substring(0, info.length() - 18);//设备名称
            String address = info.substring(info.length() - 17);//MAC地址

            // 设置返回数据
            Intent intent = new Intent();
            intent.putExtra(EXTRA_DEVICE_NAME, dev_name);
            intent.putExtra(EXTRA_DEVICE_ADDRESS, address);

            // 设置返回值并结束程序
            setResult(Activity.RESULT_OK, intent);
            finish();
        }
    };

    // 查找到设备和搜索完成action监听器
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) 
        {
            String action = intent.getAction();

            // 查找到设备action
            if (BluetoothDevice.ACTION_FOUND.equals(action)) 
            {
                // 得到蓝牙设备
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // 如果是已配对的则略过,已得到显示,其余的在添加到列表中进行显示
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) 
                {
                    mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                }
                else //添加到已配对设备列表
                {  
                	mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                }
            
            } 
            else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) // 搜索完成action
            {
                setProgressBarIndeterminateVisibility(false);//滚动圈消失
                setTitle("选择要连接的设备");
                if (mNewDevicesArrayAdapter.getCount() == 0) {
                    String noDevices = "没有找到新设备";
                    mNewDevicesArrayAdapter.add(noDevices);
                }
           }
        }
    };


}

布局文件

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="fill_parent"
    android:layout_height="fill_parent"
    >

    <TextView
        android:id="@+id/tv_dev"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/tv_dev" />

    <TextView
        android:id="@+id/tv_reDev"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/tv_reDev" />

    <ScrollView
        android:id="@+id/sv_list"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1.06"
        android:scrollbars="vertical" >

	<TextView android:id="@+id/in"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
    />
    </ScrollView>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >

		<EditText
		    android:id="@+id/edt_smsg"
		    android:layout_width="0dp"
		    android:layout_height="wrap_content"
		    android:layout_gravity="bottom"
		    android:layout_weight="1"
		    android:inputType="text" >

		</EditText>

		<Button
		    android:id="@+id/btn_send"
		    android:layout_width="wrap_content"
		    android:layout_height="wrap_content"
		    android:onClick="onSendButtonClicked"
		    android:text="@string/btn_send" >

		</Button>
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"       
        >

       	<Button
       	    android:id="@+id/btn_cnnt"
       	    android:layout_width="90dp"
       	    android:layout_height="wrap_content"
       	    android:onClick="onConnectButtonClicked"
       	    android:text="@string/btn_cnnt" >

		</Button>

		<Button
		    android:id="@+id/btn_send_file"
		    android:layout_width="152dp"
		    android:layout_height="wrap_content"
		    android:onClick="onSendFileButtonClicked"
		    android:text="@string/btn_send_file" >

		</Button>

		<Button
		    android:id="@+id/Button06"
		    android:layout_width="match_parent"
		    android:layout_height="wrap_content"
		    android:onClick="onQuitButtonClicked"
		    android:text="@string/btn_exit" >

		</Button>
    </LinearLayout>
</LinearLayout>

device_list.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"
    >
    <ListView android:id="@+id/paired_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:stackFromBottom="true"
        android:layout_weight="1"
    />
    <TextView android:id="@+id/title_new_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/tv_disconnect"
        android:visibility="gone"
        android:background="#666"
        android:textColor="#fff"
        android:paddingLeft="5dp"
    />
    <ListView android:id="@+id/new_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:stackFromBottom="true"
        android:layout_weight="2"
    />

    <Button
        android:id="@+id/button_scan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn_search" />

    <Button
        android:id="@+id/button_cancel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="OnCancel"
        android:text="@string/btn_cancel" />

</LinearLayout>

 device_name.xml文件

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="18sp"
    android:padding="5dp"
/>

               

五、总结

1、程序大致流程图

      设备双方开启蓝牙,设备A设置蓝牙可见性,其它设备可以搜索到,开启监听线程,可以监听并接受其它蓝牙设备的连接请求。设备B搜索附近蓝牙,搜索到设备A的蓝牙,发起连接请求,设备A接收之后就可以互相收发消息。

                                 

2、蓝牙开发相关事项     

    蓝牙开发重要对象:本地蓝牙适配器

    BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();//获取本地蓝牙适配器

蓝牙相关操作
操作 代码 备注

蓝牙权限

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
某些Andriod版本需要其他权限

是否支持蓝牙

BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();//获取蓝牙适配器
        
如果mAdapter 为null就说明设备不支持蓝牙
蓝牙是否已经开启 mAdapter.isEnabled(); 返回true说明已经打开

打开蓝牙

方式1:弹出系统自带的请求对话框。

Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            activity.startActivityForResult(intent, requestCode);
方式2:直接后台打开

mAdapter.enable();//这种方式是直接尝试打开蓝牙,对用户不友好

蓝牙打开需要一定的时间
关闭蓝牙 mAdapter.disable();  
搜索蓝牙 mBtAdapter.startDiscovery();

搜索周边的蓝牙设备(搜索会持续一段时间,手动结束搜索mBtAdapter.cancelDiscovery();)

搜索到一个蓝牙设备系统会发出广播BluetoothDevice.ACTION_FOUND

可以自定义广播接受者来接收广播。

监听连接 BluetoothServerSocket mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord("TEST", UUID.fromString(MY_UUID));
mSocket = mServerSocket.accept();//阻塞等待客户端连接

(监听线程、等待别的蓝牙设备来链接)

MY_UUID = "00001101-0000-1000-8000-00805F9B34FB";   //SPP服务UUID号

发起连接

BluetoothSocket mSocket = mDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));

//发起连接请求
 mSocket.connect();//阻塞等待接受
                    

用来请求连接某个蓝牙设备

mDevice:是要连接的蓝牙设备对象。

设置蓝牙可见性

方式1:弹出确认框  (timeout:可见时间最大300秒)

Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, timeout);
            startActivity(discoverableIntent);

方式2:不弹出提示框


                //利用反射使用 setDiscoverableTimeout和setScanMode
                Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
                setDiscoverableTimeout.setAccessible(true);
                Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class);
                setScanMode.setAccessible(true);

setDiscoverableTimeout.invoke(mAdapter, timeout);
                    setScanMode.invoke(mAdapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,timeout);
                    

一般默认不可见,设置为可见,其它蓝牙设备才能搜索到。
收发消息 //3  获取输入输出流
InputStream btIs = mSocket.getInputStream();//用来接收消息
OutputStream btOs = mSocket.getOutputStream();//用来发送消息
 

3、待完善

    a) 例子只简单演示了一对一通信,其实可以一对多。

    b) 蓝牙文件传输(文件检索,文件编码, 续传)。

    c) 例子中如果断开链接又进行链接的情况处理不当,可能是对socket等资源的释放存在问题。

    d) 程序没有使用"广播"方式来处理蓝牙的链接与断开(推荐)。

    e) 程序不够"健壮性",不够"人性化", 代码不够精简。

猜你喜欢

转载自blog.csdn.net/nanfeibuyi/article/details/83278096