安卓蓝牙聊天室(一)

单点聊天(两台设备聊天)

打算做一个真正意义上的聊天室,现在网上找的蓝牙博客基本都是点对点的。

关于蓝牙的基本知识:http://developer.android.com/guide/topics/connectivity/bluetooth.html

相关知识自己可以去网上找,相关操作本文结尾处也有几篇博客可以看一下

基本流程

目录结构

 

 

 

相关代码

 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函数

2.custom_title.xml文件,该布局将title设置为一个相对布局RelativeLayout,其中包含了两个TextView,一个在左边一个在右边,分别用于显示应用程序的标题title和当前的蓝牙配对链接名称

<?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

posted @ 2017-03-14 16:42  下雨天rain  阅读(873)  评论(0编辑  收藏  举报