zoukankan      html  css  js  c++  java
  • 安卓蓝牙聊天室(一)

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

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

    关于蓝牙的基本知识: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() + "
    " + 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() + "
    " + 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

  • 相关阅读:
    程序员编程武器大盘点
    Opencv 视频转为图像序列
    C/C++ 浮点数比较问题
    C/C++ Swap without using extra variable
    C/C++ Quick Sort Algorithm
    LaTeX 插图片
    LaTeX 基本的公式符号命令
    天龙八部服务器端共享内存的设计(1/3)
    天龙八部服务器端共享内存的设计(2/3)
    天龙八部服务器端共享内存的设计(2/3)
  • 原文地址:https://www.cnblogs.com/rainday1/p/6549293.html
Copyright © 2011-2022 走看看