單點聊天(兩臺設備聊天)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>
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>
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>
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" />
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>
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); } } }
設備掃描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>
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" />
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(); } }; }
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); } }
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>
運行結果:
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