安卓藍牙聊天室(一)

單點聊天(兩臺設備聊天)html

打算作一個真正意義上的聊天室,如今網上找的藍牙博客基本都是點對點的。java

關於藍牙的基本知識:http://developer.android.com/guide/topics/connectivity/bluetooth.htmlandroid

相關知識本身能夠去網上找,相關操做本文結尾處也有幾篇博客能夠看一下app

基本流程socket

目錄結構ide

 

 

 

相關代碼函數

 1.獲取權限佈局

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.rain.bluetoothchat">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/android:Theme.Light">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- 用於顯示藍牙設備列表的Activity -->
        <activity android:name=".DeviceListActivity"
            android:label="@string/select_device"
            android:theme="@android:style/Theme.Holo.Dialog"
            android:configChanges="orientation|keyboardHidden" />
    </application>

</manifest>
View Code

main函數ui

2.custom_title.xml文件,該佈局將title設置爲一個相對佈局RelativeLayout,其中包含了兩個TextView,一個在左邊一個在右邊,分別用於顯示應用程序的標題title和當前的藍牙配對連接名稱this

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical"
    >
    <TextView android:id="@+id/title_left_text"
        android:layout_alignParentLeft="true"
        android:ellipsize="end"
        android:maxLines="1"
        style="?android:attr/windowTitleStyle"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="1"
        />
    <TextView android:id="@+id/title_right_text"
        android:layout_alignParentRight="true"
        android:ellipsize="end"
        android:maxLines="1"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:textColor="#fff"
        android:layout_weight="1"
        />
</RelativeLayout>
View Code

3.activity_main.xml主佈局,整個界面的佈局將是一個線性佈局LinearLayout,其中包含了另外一個ListView(用於顯示聊天的對話信息)和另一個線性佈局來實現一個發送信息的窗口,發送消息發送框有一個輸入框和一個發送按鈕構成。

<?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/in"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:stackFromBottom="true"
        android:transcriptMode="alwaysScroll"
        android:layout_weight="1"
        />
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <!-- 顯示發送消息的編輯框 -->
        <EditText android:id="@+id/edit_text_out"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="bottom"
            />
        <!-- 發送按鈕 -->
        <Button android:id="@+id/button_send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="send"
            />
    </LinearLayout>
</LinearLayout>
View Code

4.message.xml,很簡單,就包含了一個TextView用來顯示對話內容便可,這裏設置了文字標籤的尺寸大小textSize和padding屬性

<?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"
    />
View Code

5.option_menu.xml菜單文件

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

    <item
        android:id="@+id/scan"
        android:showAsAction="ifRoom"
        android:title="scan" />

    <item
        android:id="@+id/discoverable"
        android:showAsAction="ifRoom"
        android:title="discoverable" />


</menu>
View Code

6.MainActivity.java,一些解釋代碼裏面已經有了,

package com.example.rain.bluetoothchat;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

    // Debugging
    private static final String TAG = "BluetoothChat";
    private static final boolean D = true;

    //從BluetoothChatService Handler發送的消息類型
    public static final int MESSAGE_STATE_CHANGE = 1;
    public static final int MESSAGE_READ = 2;
    public static final int MESSAGE_WRITE = 3;
    public static final int MESSAGE_DEVICE_NAME = 4;
    public static final int MESSAGE_TOAST = 5;

    // 從BluetoothChatService Handler接收消息時使用的鍵名(鍵-值模型)
    public static final String DEVICE_NAME = "device_name";
    public static final String TOAST = "toast";

    // Intent請求代碼(請求連接,請求可見)
    private static final int REQUEST_CONNECT_DEVICE = 1;
    private static final int REQUEST_ENABLE_BT = 2;

    // Layout Views
    private TextView mTitle;
    private ListView mConversationView;
    private EditText mOutEditText;
    private Button mSendButton;

    // 連接的設備的名稱
    private String mConnectedDeviceName = null;

    // Array adapter for the conversation thread
    private ArrayAdapter mConversationArrayAdapter;

    // 將要發送出去的字符串
    private StringBuffer mOutStringBuffer;

    // 本地藍牙適配器
    private BluetoothAdapter mBluetoothAdapter = null;

    // 聊天服務的對象
    private BluetoothChatService mChatService = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
        setContentView(R.layout.activity_main);
        getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);

        init();

//        /* 顯示App icon左側的back鍵 */
//        ActionBar actionBar = getActionBar();
//        actionBar.setDisplayHomeAsUpEnabled(true);

    }

    /*
    初始化
     */
    private void init(){
        // 設置自定義title佈局
        mTitle = (TextView) findViewById(R.id.title_left_text);
        mTitle.setText(R.string.app_name);
        mTitle = (TextView) findViewById(R.id.title_right_text);

        // 獲得一個本地藍牙適配器
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        // 若是適配器爲null,則不支持藍牙
        if (mBluetoothAdapter == null) {
            Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
            finish();
            return;
        }
    }

    /*
    檢測藍牙是否被打開,若是沒有打開,則請求打開,不然就能夠設置一些聊天信息的準備工做
     */
    @Override
    public void onStart() {
        super.onStart();
        if(D) Log.e(TAG, "++ ON START ++");
        // 若是藍牙沒有打開,則請求打開
        // setupChat() will then be called during onActivityResult
        if (!mBluetoothAdapter.isEnabled()) {
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
        } else {// 不然,設置聊天會話
            if (mChatService == null) setupChat();
        }
    }

    /*
    回調函數
     */
    //@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode){
            case REQUEST_ENABLE_BT:
                //在請求打開藍牙時返回的代碼
                if (resultCode == Activity.RESULT_OK) {
                    // 藍牙已經打開,因此設置一個聊天會話
                    setupChat();
                } else {
                    // 請求打開藍牙出錯
                    Log.d(TAG, "BT not enabled");
                    Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            case REQUEST_CONNECT_DEVICE:
                // 當DeviceListActivity返回設備鏈接
                if (resultCode == Activity.RESULT_OK) {
                    // 從Intent中獲得設備的MAC地址
                    String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);//String address = data.getExtras() .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
                    // 獲得藍牙設備對象
                    BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
                    // 嘗試鏈接這個設備
                    mChatService.connect(device);
                }
                break;
        }
    }

    /*
    設置藍牙聊天相關信息
     */
    private void setupChat() {
        Log.d(TAG, "setupChat()");
        // 初始化對話進程
        mConversationArrayAdapter = new ArrayAdapter(this, R.layout.message);
        // 初始化對話顯示列表
        mConversationView = (ListView) findViewById(R.id.in);
        // 設置話顯示列表源
        mConversationView.setAdapter(mConversationArrayAdapter);
        // 初始化編輯框,並設置一個監聽,用於處理按回車鍵發送消息
        mOutEditText = (EditText) findViewById(R.id.edit_text_out);
        mOutEditText.setOnEditorActionListener(mWriteListener);
        // 初始化發送按鈕,並設置事件監聽
        mSendButton = (Button) findViewById(R.id.button_send);
        mSendButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // 取得TextView中的內容來發送消息
                TextView view = (TextView) findViewById(R.id.edit_text_out);
                String message = view.getText().toString();
                sendMessage(message);
            }
        });
        // 初始化BluetoothChatService並執行藍牙鏈接
        mChatService = new BluetoothChatService(this, mHandler);
        // 初始化將要發出的消息的字符串
        mOutStringBuffer = new StringBuffer("");
    }

    // The action listener for the EditText widget, to listen for the return key
    private TextView.OnEditorActionListener mWriteListener =  new TextView.OnEditorActionListener() {
        public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
            // 按下回車鍵而且是按鍵彈起的事件時發送消息
            if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {
                String message = view.getText().toString();
                sendMessage(message);
            }
            if(D) Log.i(TAG, "END onEditorAction");  return true;
        }
    };

    //建立一個菜單
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.option_menu, menu);
        return true;
        //return super.onCreateOptionsMenu(menu);
    }

    //處理菜單事件
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.scan:
            // 啓動DeviceListActivity查看設備並掃描
                Intent serverIntent = new Intent(this, DeviceListActivity.class);//serverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
                return true;
            case R.id.discoverable:
            // 確保設備處於可見狀態
                ensureDiscoverable();
                return true;
        }
        return false;
    }

    /*
    用於使設備處於可見狀態
     */
    private void ensureDiscoverable() {
        if (D) Log.d(TAG, "ensure discoverable");
        //判斷掃描模式是否爲既可被發現又能夠被鏈接
        if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
            //請求可見狀態
            Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            //添加附加屬性,可見狀態的時間
            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
            startActivity(discoverableIntent);
        }
    }

    /*
    在連接某個設備以前,咱們須要開啓一個端口監聽
     */
    @Override
    public synchronized void onResume() {
        super.onResume();
        if(D) Log.e(TAG, "+ ON RESUME +");
        // Performing this check in onResume() covers the case in which BT was
        // not enabled during onStart(), so we were paused to enable it...
        // onResume() will be called when ACTION_REQUEST_ENABLE activity returns.
        if (mChatService != null) {
            // 若是當前狀態爲STATE_NONE,則須要開啓藍牙聊天服務
            if (mChatService.getState() == BluetoothChatService.STATE_NONE) {
                // 開始一個藍牙聊天服務
                mChatService.start();
            }
        }
    }

    private final Handler mHandler=new Handler(){

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch(msg.what){
                case MESSAGE_STATE_CHANGE:
                    if(D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);
                    switch (msg.arg1) {
                        case BluetoothChatService.STATE_CONNECTED:
                            //設置狀態爲已經連接
                            mTitle.setText(R.string.title_connected_to);
                            //添加設備名稱
                            mTitle.append(mConnectedDeviceName);
                            //清理聊天記錄
                            mConversationArrayAdapter.clear();
                            break;
                        case BluetoothChatService.STATE_CONNECTING:
                            //設置正在連接
                            mTitle.setText(R.string.title_connecting);
                            break;
                        case BluetoothChatService.STATE_LISTEN:
                        case BluetoothChatService.STATE_NONE:
                            //處於監聽狀態或者沒有準備狀態,則顯示沒有連接
                            mTitle.setText(R.string.title_not_connected);
                            break;
                    }
                    break;
                case MESSAGE_WRITE:
                    byte[] writeBuf = (byte[]) msg.obj;
                    // 將本身寫入的消息也顯示到會話列表中
                    String writeMessage = new String(writeBuf);
                    mConversationArrayAdapter.add("Me:  " + writeMessage);
                    break;
                case MESSAGE_READ:
                    byte[] readBuf = (byte[]) msg.obj;
                    // 取得內容並添加到聊天對話列表中
                    String readMessage = new String(readBuf, 0, msg.arg1);
                    mConversationArrayAdapter.add(mConnectedDeviceName+":  " + readMessage);
                    break;
                case MESSAGE_DEVICE_NAME:
                  // 保存連接的設備名稱,並顯示一個toast提示
                  mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
                  Toast.makeText(getApplicationContext(), "Connected to "
                        + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
                  break;
                case MESSAGE_TOAST:
                     //處理連接(發送)失敗的消息
                    Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),
                          Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    };

    private void sendMessage(String message) {
        // 檢查是否處於鏈接狀態
        if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
            Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show();
            return;
        }

        // 若是輸入的消息不爲空才發送,不然不發送
        if (message.length() > 0) {
            // Get the message bytes and tell the BluetoothChatService to write
            byte[] send = message.getBytes();
            mChatService.write(send);

            // Reset out string buffer to zero and clear the edit text field
            mOutStringBuffer.setLength(0);
            mOutEditText.setText(mOutStringBuffer);
        }
    }

}
View Code

設備掃描DeviceListActivity

7.device_list.xml該佈局總體由一個線性佈局LinearLayout組成,其中包含了兩個textview中來顯示已經配對的設備和信掃描出來的設備(尚未通過配對)和兩個ListView分別用於顯示已經配對和沒有配對的設備的相關信息。

<?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"
    android:descendantFocusability="blocksDescendants">

    <!-- 已經配對的設備 -->
      <TextView android:id="@+id/title_paired_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="已配對"
        android:visibility="gone"
        android:background="#666"
        android:textColor="#fff"
        android:padding="5dp" />
 
      <!-- 已經配對的設備信息 -->
      <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="未配對"
        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="scan" />

</LinearLayout>
View Code

8.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:clickable="false"
    android:focusableInTouchMode="false"
    android:padding="5dp"
    />
View Code

9.DeviceListActivity.java

package com.example.rain.bluetoothchat;

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.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

import java.util.Set;

/**
 * Created by rain on 2017/2/28.
 */

public class DeviceListActivity extends Activity {

    //debugging
    private static final String TAG="DeviceListActivity";
    private static final boolean D=true;

    //return intrnt extra
    public static String EXTRA_DEVICE_ADDRESS = "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 View.OnClickListener() {
            public void onClick(View v) {
                doDiscovery();
                v.setVisibility(View.GONE);
            }
        });


        //初始化ArrayAdapter,一個是已經配對的設備,一個是新發現的設備
        mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
        mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);

        // 檢測並設置已配對的設備ListView
        ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
        pairedListView.setAdapter(mPairedDevicesArrayAdapter);
        pairedListView.setOnItemClickListener(mDeviceClickListener);

        // 檢查並設置行發現的藍牙設備ListView
        ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
        newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
        newDevicesListView.setOnItemClickListener(mDeviceClickListener);


        // 當一個設備被發現時,須要註冊一個廣播
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        this.registerReceiver(mReceiver, filter);

        // 當顯示檢查完畢的時候,須要註冊一個廣播
        filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        this.registerReceiver(mReceiver, filter);


        // 獲得本地的藍牙適配器
        mBtAdapter = BluetoothAdapter.getDefaultAdapter();

        // 獲得一個已經匹配到本地適配器的BluetoothDevice類的對象集合
        Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();

        // 若是有配對成功的設備則添加到ArrayAdapter
        if (pairedDevices.size() > 0) {
            findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
            for (BluetoothDevice device : pairedDevices) {
                mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                }
        } else {
             //不然添加一個沒有被配對的字符串
            String noDevices = getResources().getText(R.string.none_paired).toString();
            mPairedDevicesArrayAdapter.add(noDevices);
        }
    }

    /*
    要包括藍牙適配器和廣播的註銷操做
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 確保咱們沒有發現,檢測設備
        if (mBtAdapter != null) {
            mBtAdapter.cancelDiscovery();
        }
        // 卸載所註冊的廣播
        this.unregisterReceiver(mReceiver);
    }

    /**
     *請求能被發現的設備
     */
     private void doDiscovery() {
        if (D) Log.d(TAG, "doDiscovery()");

        // 設置顯示進度條
        setProgressBarIndeterminateVisibility(true);
        // 設置title爲掃描狀態
        setTitle(R.string.scanning);

        // 顯示新設備的子標題
        findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);

        // 若是已經在請求現實了,那麼就先中止
        if (mBtAdapter.isDiscovering()) {
            mBtAdapter.cancelDiscovery();
        }

        // 請求從藍牙適配器獲得可以被發現的設備
        mBtAdapter.startDiscovery();
     }

    //監聽掃描藍牙設備
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // 當發現一個設備時
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // 從Intent獲得藍牙設備對象
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // 若是已經配對,則跳過,由於他已經在設備列表中了
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                     //不然添加到設備列表
                    mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                }
                // 當掃描完成以後改變Activity的title
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                 //設置進度條不顯示
                setProgressBarIndeterminateVisibility(false);
                //設置title
                setTitle(R.string.select_device);
                //若是計數爲0,則表示沒有發現藍牙
                if (mNewDevicesArrayAdapter.getCount() == 0) {
                    String noDevices = getResources().getText(R.string.none_found).toString();
                    mNewDevicesArrayAdapter.add(noDevices);
                }
            }
        }
     };

    // ListViews中全部設備的點擊事件監聽
    private AdapterView.OnItemClickListener mDeviceClickListener = new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
            // 取消檢測掃描發現設備的過程,由於內很是耗費資源
            mBtAdapter.cancelDiscovery();

            // 獲得mac地址
            String info = ((TextView) v).getText().toString();
            String address = info.substring(info.length() - 17);

            // 建立一個包括Mac地址的Intent請求
            //Intent intent = new Intent(DeviceListActivity.this, MainActivity.class);
            Intent intent = new Intent();
            intent.putExtra(EXTRA_DEVICE_ADDRESS, address );

            // 設置result並結束Activity
            setResult(Activity.RESULT_OK, intent);//這個常量的值是-1,致使onActivityResult沒有被調用。
            //setResult(Activity.RESULT_FIRST_USER, intent);
            //startActivityForResult(intent, 1);
            finish();
            }
        };

}
View Code

BluetoothChatService

 10.BluetoothChatService.java對於設備的監聽,鏈接管理都將由REQUEST_CONNECT_DEVICE來實現,其中又包括三個主要部分,三個進程分貝是:請求鏈接的監聽線程(AcceptThread)、鏈接一個設備的進程(ConnectThread)、鏈接以後的管理進程(ConnectedThread)。

package com.example.rain.bluetoothchat;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

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

/**
 * Created by rain on 2017/2/28.
 */

public class BluetoothChatService {

    // Debugging
    private static final String TAG = "BluetoothChatService";
    private static final boolean D = true;

    //當建立socket服務時的SDP名稱
    private static final String NAME = "BluetoothChat";

    // 應用程序的惟一UUID
    private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");

    // 本地藍牙適配器
    private final BluetoothAdapter mAdapter;
    //Handler
    private final Handler mHandler;
    //請求連接的監聽線程
    private AcceptThread mAcceptThread;
    //連接一個設備的線程
    private ConnectThread mConnectThread;
    //已經連接以後的管理線程
    private ConnectedThread mConnectedThread;
    //當前的狀態
    private int mState;

    // 各類狀態
    public static final int STATE_NONE = 0;
    public static final int STATE_LISTEN = 1;
    public static final int STATE_CONNECTING = 2;
    public static final int STATE_CONNECTED = 3;

    public BluetoothChatService(Context context, Handler handler) {
         //獲得本地藍牙適配器
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        //設置狀態
        mState = STATE_NONE;
        //設置Handler
        mHandler = handler;
    }

    /*
    狀態同步鎖,同一時刻只有一個線程執行狀態相關代碼
     */
    private synchronized void setState(int state) {
        if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
        mState = state;

        // 狀態更新以後UI Activity也須要更新(what,arg1,arg2)
        mHandler.obtainMessage(MainActivity.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
    }

    public synchronized int getState() {
        return mState;
    }

    public synchronized void start() {
        if (D) Log.d(TAG, "start");

        // 取消任何線程視圖創建一個鏈接
        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

        // 取消任何正在運行的連接
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

        // 啓動AcceptThread線程來監聽BluetoothServerSocket
        if (mAcceptThread == null) {
          mAcceptThread = new AcceptThread();
          mAcceptThread.start();
          }
        //設置狀態爲監聽,,等待連接
        setState(STATE_LISTEN);
     }

    //請求連接的監聽線程
    private class AcceptThread extends Thread {
        // 本地socket服務
        private final BluetoothServerSocket mmServerSocket;

        public AcceptThread() {
            BluetoothServerSocket tmp = null;

            // 建立一個新的socket服務監聽
            try {
                tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
                } catch (IOException e) {
                Log.e(TAG, "listen() failed", e);
                }
            mmServerSocket = tmp;
        }

        public void run() {
            if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);
            setName("AcceptThread");
            BluetoothSocket socket = null;

            // 若是當前沒有連接則一直監聽socket服務
            while (mState != STATE_CONNECTED) {
                try {
                     //若是有請求連接,則接受
                     //這是一個阻塞調用,將之返回連接成功和一個異常
                    socket = mmServerSocket.accept();
                    } catch (IOException e) {
                    Log.e(TAG, "accept() failed", e);
                    break;
                    }

                // 若是接受了一個連接
                if (socket != null) {
                    synchronized (BluetoothChatService.this) {
                        switch (mState) {
                            case STATE_LISTEN:
                                case STATE_CONNECTING:
                                // 若是狀態爲監聽或者正在連接中,,則調用connected來連接
                                connected(socket, socket.getRemoteDevice());
                                break;
                            case STATE_NONE:
                                case STATE_CONNECTED:
                                // 若是爲沒有準備或者已經連接,這終止該socket
                                try {
                                    socket.close();
                                    } catch (IOException e) {
                                    Log.e(TAG, "Could not close unwanted socket", e);
                                    }
                                break;
                            }
                        }
                    }
                }
            if (D) Log.i(TAG, "END mAcceptThread");
        }
        //關閉BluetoothServerSocket
        public void cancel() {
            if (D) Log.d(TAG, "cancel " + this);
            try {
                mmServerSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of server failed", e);
            }
        }
    }

    public synchronized void connect(BluetoothDevice device) {
        if (D) Log.d(TAG, "connect to: " + device);

        // 取消任何連接線程,視圖創建一個連接
        if (mState == STATE_CONNECTING) {
           if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
           }

        // 取消任何正在運行的線程
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

        // 啓動一個連接線程連接指定的設備
        mConnectThread = new ConnectThread(device);
        mConnectThread.start();
        setState(STATE_CONNECTING);
    }

    //連接一個設備的線程
    private class ConnectThread extends Thread {
        //藍牙Socket
            private final BluetoothSocket mmSocket;
        //藍牙設備
            private final BluetoothDevice mmDevice;

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;

            //獲得一個給定的藍牙設備的BluetoothSocket
            try {
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) {
                Log.e(TAG, "create() failed", e);
            }
            mmSocket = tmp;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectThread");
            setName("ConnectThread");

            // 取消可見狀態,將會進行連接
            mAdapter.cancelDiscovery();

            // 建立一個BluetoothSocket連接
            try {
                //一樣是一個阻塞調用,返回成功和異常
                mmSocket.connect();
            } catch (IOException e) {
                 //連接失敗
                connectionFailed();
                // 若是異常則關閉socket
                try {
                    mmSocket.close();
                } catch (IOException e2) {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
                // 從新啓動監聽服務狀態
                BluetoothChatService.this.start();
                return;
            }

            // 完成則重置ConnectThread
            synchronized (BluetoothChatService.this) {
                mConnectThread = null;
            }

            // 開啓ConnectedThread(正在運行中...)線程
            connected(mmSocket, mmDevice);
            }
        //取消連接線程ConnectThread
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }

    //鏈接失敗
    private void connectionFailed() {
        setState(STATE_LISTEN);

        // 發送連接失敗的消息到UI界面
        Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(MainActivity.TOAST, "Unable to connect device");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }

    public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
        if (D) Log.d(TAG, "connected");

        // 取消ConnectThread連接線程
        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

        // 取消全部正在連接的線程
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

        // 取消全部的監聽線程,由於咱們已經連接了一個設備
        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}

        // 啓動ConnectedThread線程來管理連接和執行翻譯
        mConnectedThread = new ConnectedThread(socket);
        mConnectedThread.start();

        // 發送連接的設備名稱到UI Activity界面
        Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_DEVICE_NAME);
        Bundle bundle = new Bundle();
        bundle.putString(MainActivity.DEVICE_NAME, device.getName());
        msg.setData(bundle);
        mHandler.sendMessage(msg);
        //狀態變爲已經連接,即正在運行中
        setState(STATE_CONNECTED);
    }

    //已經連接以後的管理線程
    private class ConnectedThread extends Thread {
        //BluetoothSocket
        private final BluetoothSocket mmSocket;
        //輸入輸出流
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;

        public ConnectedThread(BluetoothSocket socket) {
            Log.d(TAG, "create ConnectedThread");
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            // 獲得BluetoothSocket的輸入輸出流
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, "temp sockets not created", e);
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectedThread");
            byte[] buffer = new byte[1024];
            int bytes;

            // 監聽輸入流
            while (true) {
                try {
                    // 從輸入流中讀取數據
                    bytes = mmInStream.read(buffer);

                    // 發送一個消息到UI線程進行更新
                    mHandler.obtainMessage(MainActivity.MESSAGE_READ, bytes, -1, buffer)
                    .sendToTarget();
                    } catch (IOException e) {
                     //出現異常,則連接丟失
                    Log.e(TAG, "disconnected", e);
                    connectionLost();
                    break;
                    }
            }
        }

        /**
        * 寫入要發送的消息
        * @param buffer  The bytes to write
        */
        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);

                // 將寫的消息同時傳遞給UI界面
                mHandler.obtainMessage(MainActivity.MESSAGE_WRITE, -1, -1, buffer)
                .sendToTarget();
                } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
                }
            }
        //取消ConnectedThread連接管理線程
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }

    //失去鏈接
    private void connectionLost() {
        setState(STATE_LISTEN);

        // 發送失敗消息到UI界面
        Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(MainActivity.TOAST, "Device connection was lost");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }

    //寫入本身要發送出來的消息
    public void write(byte[] out) {
        // Create temporary object
        ConnectedThread r;
        // Synchronize a copy of the ConnectedThread
        synchronized (this) {
             //判斷是否處於已經連接狀態
            if (mState != STATE_CONNECTED) return;
            r = mConnectedThread;
        }
        // 執行寫
        r.write(out);
    }

    //中止全部的線程
    public synchronized void stop() {
        if (D) Log.d(TAG, "stop");
        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
        //狀態設置爲準備狀態
        setState(STATE_NONE);
    }

}
View Code

values

11.strings.xml

<resources>
    <string name="app_name">BluetoothChat</string>
    <string name="none_paired">沒有配對設備</string>
    <string name="none_found">未找到</string>
    <string name="scanning">掃描中</string>
    <string name="select_device">選擇設備</string>
    <string name="not_connected">未鏈接</string>
    <string name="title_connected_to">已鏈接</string>
    <string name="title_connecting">正在鏈接</string>
    <string name="title_not_connected">找不到設備</string>
    <string name="bt_not_enabled_leaving">不支持藍牙設備</string>
</resources>
View Code

運行結果:

 

http://blog.csdn.net/it1039871366/article/details/46533481代碼基原本源於此,只是衆多bug一步一步調出來了很痛苦

http://www.jizhuomi.com/android/course/282.html

http://www.2cto.com/kf/201608/537390.html

 過程當中遇到的問題以及解決辦法

1. You cannot combine custom titles with other title feature

http://www.eoeandroid.com/thread-225717-1-1.html?_dsign=50e7d781

 

2. You need to use a Theme.AppCompat theme (or descendant) with this activity.

http://www.cnblogs.com/jshen/p/3996071.html

 

3. menu items should specify a title

http://stackoverflow.com/questions/27979692/menu-items-should-specify-a-title

 

4. data.getExtras() 報錯

https://zhidao.baidu.com/question/292535079.html

 

5. "Cannot resolve constructor 'intent(anonymous android.view.View.OnClickListener, java.lang.Class(com.example.xxx.buttonexample.emergencyIntent))'.

http://www.dabu.info/android-cannot-resolve-constructor-intent.html

 

6. 關於onActivityResult方法不執行的問題彙總

http://blog.csdn.net/sbvfhp/article/details/26858441

https://zhidao.baidu.com/question/1386501104240487180.html

相關文章
相關標籤/搜索