1. 程式人生 > >安卓 藍芽通訊之聊天小程式

安卓 藍芽通訊之聊天小程式

安卓 藍芽聊天小程式

一、簡述

  記--簡單的藍芽聊天小程式。使用的是傳統藍芽開發。(某些手機由於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) 程式不夠"健壯性",不夠"人性化", 程式碼不夠精簡。