前言


上篇文章我们介绍到了开发经典蓝牙和单片机通讯的过程,安卓通讯之《蓝牙单片机通讯助手》①集成工作 ,我们这里还要兼容最新的安卓6.0及以上的系统,因为从6.0以后的权限机制和以往的不一样,我们需要在代码中向用户询问权限。而且在6.0运行蓝牙,还需要加上获取到此刻的地理位置的权限,这是谷歌加的~~,所以我们先把运行的权限弄好先,再扫描设备、连接设备和双向通讯。


权限问题(兼容安卓6.0及以上)


很多小伙伴问我,为什么你以前写的安卓高版本的蓝牙App现在在高版本的安卓机就不可以获取到附近的蓝牙设备啦?这也是我上面提到的:在6.0运行蓝牙,还需要加上获取到此刻的地理位置的权限。所以我们先把权限弄好。我下面是用郭神封装好的权限代码。你们如有不懂,去CSDN搜索郭霖6.0就有当天的直播权限封装视频解说。


private void checkpermission() {
        //判断是否APi等于或大于23,即6.0
        if(Build.VERSION.SDK_INT >= 23){
            //所需要的权限加进去
            requestRuntimePermission(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION
                    ,Manifest.permission.ACCESS_FINE_LOCATION
                    ,Manifest.permission.BLUETOOTH
                    ,Manifest.permission.BLUETOOTH_ADMIN}, new PermissionListener() {

                //权限回调
                @Override
                public void onGranted() {
                    //全部都授权就跳转下个Activity
                    Log.i("权限全部都同意了","-----=="); 
                                           }
                  //某个没有被授权则强制退出程序
                @Override
                public void onDenied(List<String> deniedPermission) {
                    for(String persmission:deniedPermission){
                        Toast.makeText(SplashActivity.this,"基于你的安卓版本大于6.0,未授予该权限导致不能运行,强制退出:"+persmission,Toast.LENGTH_LONG).show();

                    } }
            });
        }else {
            //手机在6.0以下,则不需要获取到地理位置权限,直接跳过
       }
    }

打开蓝牙扫描工作


打开蓝牙工作,相信大家都会,用户在此打开蓝牙的是否,要用 intent回调。在这里,我们还需要写进一个蓝牙广播接收器,来监听系统发出来的已经搜索到的附近蓝牙设备,并且获取该数据的名字和蓝牙地址。


代码部分说明:

我这里用的是一个listVIew控件来展示当前可以连接的蓝牙设备。点击某一项,则把该项的蓝牙名字和id传下去到一个Activity的通讯工作。中间的是一个进度条之类的,自定义控件。


Android蓝牙开启扫描流程源码 蓝牙扫描助手_通讯

package com.example.xuhong.bluetoothassistant_master;

import android.app.Activity;
import android.app.ListActivity;
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.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;
import android.os.Process;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;

import com.example.xuhong.bluetoothassistant_master.adapter.DeviceListAdapter;
import com.example.xuhong.bluetoothassistant_master.ui.WhorlView;


public class DeviceScanActivity extends ListActivity {

    private TextView mtv_show;
    // 调试用
    private static final String TAG = "DeviceScanActivity";

    // 开启蓝牙请求码
    private static final int REQUEST_ENABLE = 0;

    // 停止扫描蓝牙消息头
    private static final int WHAT_CANCEL_DISCOVERY = 1;

    // 判断蓝牙列表
    private static final int WHAT_DEVICE_UPDATE = 2;

    // 扫描间隔时间
    private static final int SCAN_PERIOD = 30 * 1000;

    //实例化Adapter
    private DeviceListAdapter mLeDeviceListAdapter = null;

     // 蓝牙适配器
    private BluetoothAdapter mBluetoothAdapter = null;

     // 进度条
    private WhorlView mWhorlView = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_device_scan);
        init();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mLeDeviceListAdapter = new DeviceListAdapter(this);
        // 设置列表适配器,注:调用此方法必须继承ListActivity
        setListAdapter(mLeDeviceListAdapter);
        scanDevice(true);
        mWhorlView.setVisibility(View.VISIBLE);
    }

    @Override
    protected void onPause() {
        scanDevice(false);
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        unregReceiver();
        super.onDestroy();
    }


    //回调函数
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if ( resultCode == Activity.RESULT_CANCELED) {
            finish();
            Process.killProcess(Process.myPid());
        }

        switch (requestCode){
            case REQUEST_ENABLE:
                if ( mBluetoothAdapter.isEnabled()) {
                    registerReceiver();
                    scanDevice(true);
                }

                break;

        }
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);

        BluetoothDevice device = mLeDeviceListAdapter.getDevice(position);

        if (device == null) {
            return;
        }

        Intent intent = new Intent(this, MainActivity.class);
        Bundle bundle = new Bundle();
        bundle.putParcelable("device", device);
        intent.putExtras(bundle);

        scanDevice(false);
        startActivity(intent);
        finish();
    }

    /**
     * 消息处理者
     */
    private Handler mHandler = new Handler(new Callback() {

        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {

            case WHAT_DEVICE_UPDATE:
                mLeDeviceListAdapter.addDevice((BluetoothDevice) msg.obj);
                // 刷新列表
                mLeDeviceListAdapter.notifyDataSetChanged();
                break;

            case WHAT_CANCEL_DISCOVERY:
                mWhorlView.setVisibility(View.GONE);
                mtv_show.setText("搜索完毕!");
                break;
                default:
                break;
            }
            return false;
        }
    });

    /**
     * 初始化
     */
    private void init() {
        mtv_show= (TextView) findViewById(R.id.tv_show);
        mWhorlView = (WhorlView) findViewById(R.id.whorl_view);
        // 开启动画
        mWhorlView.start();

        // 初始化本地蓝牙设备
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        // 检测蓝牙设备是否开启,如果未开启,发起Intent并回调
        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableIntent, REQUEST_ENABLE);
        }

        registerReceiver();
    }

    /**
     * 是否扫描蓝牙设备
     */
    private void scanDevice(boolean enable) {
        if (enable) {

            Log.d(TAG, "[1]--> startDiscovery()");

            // 开启扫描
            mBluetoothAdapter.startDiscovery();

            // 延时30s后取消扫描动作
            mHandler.postDelayed(new Runnable() {

                @Override
                public void run() {
                    mBluetoothAdapter.cancelDiscovery();

                    Log.d(TAG, "[2]--> cancelDiscovery()");

                    // 发送消息
                    mHandler.sendEmptyMessage(WHAT_CANCEL_DISCOVERY);
                }
            }, SCAN_PERIOD);
        } else {
            Log.d(TAG, "[3]--> cancelDiscovery()");
            // 停止扫描
            mBluetoothAdapter.cancelDiscovery();
        }
    }

    /**
     * 注册广播接收器
     */
    private void registerReceiver() {
        registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
    }

    /**
     * 注销广播接收器
     */
    private void unregReceiver() {
        if (mReceiver != null) {
            unregisterReceiver(mReceiver);
        }
    }

    /**
     * 广播接收器接收返回的蓝牙信息
     */
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            //未配对的设备
            if (BluetoothDevice.ACTION_FOUND == action) {

                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

                Log.d(TAG, "[4] --> " + device.getName() + "------" + device.getAddress());

                if (device != null) {
                    //发送消息
                    mHandler.sendMessage(mHandler.obtainMessage(WHAT_DEVICE_UPDATE, device));
                }
            }
        }
    };
}


通讯工作


我这里用的是一个spinner控件和一个button,怎么使用去看看相关博文,我这里只是供用户选择发送到哪一个数据。因为单片机通讯都是基本的 十六进制 0x00格式,所以我这里只是规范下发送的格式,你们也可以完全按照自己的想法设计UI。


Android蓝牙开启扫描流程源码 蓝牙扫描助手_通讯_02


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

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;

import android.os.Handler;
import android.os.Message;
import android.util.Log;

import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.ToggleButton;

import com.example.xuhong.bluetoothassistant_master.adapter.ChatListAdapter;
import com.example.xuhong.bluetoothassistant_master.data.ChatListData;
import com.example.xuhong.bluetoothassistant_master.util.Toaster;

// 2016.09.15.   徐宏 编写
public class MainActivity extends Activity  {


    private ListView mChatListView;

    //列表
    private List<ChatListData> mList = new ArrayList<>();

    private ChatListAdapter chatListAdapter;

    private static final String TAG = "MainActivity";
    // uuid
    private static final String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";

    private Spinner mSpinner;

    private String[] data = new String[]{"选择 0x01", "选择 0x02", "选择 0x03", "选择       0x04", "选择 0x05", "选择 0x06", "选择 0x07" , "选择 0x08", "选择 0x09"};


    private Byte sendData = null;
    private String mRecieve =null;

    private ArrayAdapter<String>  adapter;
    //获得系统的适配器
    private BluetoothDevice mBluetoothDevice = null;
    //创建socket
    private BluetoothSocket mSocket = null;
    //io流
    private OutputStream mOutS = null;
    private static  final  int CONNECT_SUCCED =10;
    private static final  int  mRecieve_SUCCED = 20;
    private InputStream input  =null;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //全屏显示
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_main);
        initview();
    }

    private void initview() {
        mSpinner = (Spinner) findViewById(R.id.mSpinner);
        mChatListView = (ListView)findViewById(R.id.mChatListView);

        chatListAdapter=new ChatListAdapter(MainActivity.this,mList);
        mChatListView.setAdapter(chatListAdapter);


        adapter= new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data);
        mSpinner.setAdapter(adapter);
        mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int id, long l) {
                switch (id){
                    case 0:
                        sendData=1;
                        Log.i(TAG,"点击0");
                        break;
                    case 1:
                        sendData=2;
                        Log.i(TAG,"点击1");
                        break;
                    case 2:
                        sendData=3;
                        Log.i(TAG,"点击2");
                        break;
                    case 3:
                        sendData=4;
                        Log.i(TAG,"点击3");
                        break;
                    case 4:
                        sendData=5;
                        Log.i(TAG,"点击4");
                        break;
                    case 5:
                        sendData=6;
                        Log.i(TAG,"点击5");
                        break;
                    case 6:
                        sendData=7;
                        Log.i(TAG,"点击6");
                        break;
                    case 7:
                        sendData=8;
                        Log.i(TAG,"点击7");
                        break;
                    case 8:
                        sendData=9;
                        Log.i(TAG,"点击8");
                        break;

                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
    }

    //添加右边文本
    private void addRightItem(String text) {
        ChatListData date = new ChatListData();
        date.setType(ChatListAdapter.VALUE_RIGHT_TEXT);
        date.setText(text);
        mList.add(date);
        //通知adapter刷新
        adapter.notifyDataSetChanged();
        //滚动到底部
        mChatListView.setSelection(mChatListView.getBottom());
    }


    //添加左边文本
    private void addLeftItem(String text) {
        ChatListData date = new ChatListData();
        date.setType(ChatListAdapter.VALUE_LEFT_TEXT);
        date.setText(text);
        mList.add(date);
        //通知adapter刷新
        adapter.notifyDataSetChanged();
        //滚动到底部
        mChatListView.setSelection(mChatListView.getBottom());
    }


    public void btn_send(View v){
        writeStream(sendData);
        addRightItem(sendData+"");
    }

    @Override
    protected void onStart() {
        super.onStart();
        initDevice();
    }


    @Override
    protected void onStop() {
        close();
        super.onStop();
    }

    private void initDevice() {
        Bundle bundle = getIntent().getExtras();
        if (bundle != null) {
            mBluetoothDevice = bundle.getParcelable("device");
            if (mBluetoothDevice != null) {
                new Thread(mConnRun).start();
            }
        }
    }

    private Runnable mConnRun = new Runnable() {

        @Override
        public void run() {
            connect();
        }
    };
    private void connect() {

        UUID uuid = UUID.fromString(SPP_UUID);

        try {
            mSocket = mBluetoothDevice.createRfcommSocketToServiceRecord(uuid);
        } catch (IOException e) {
            if (mSocket != null) {
                try {
                    mSocket.close();
                } catch (IOException e1) {
                    Log.e(TAG, e1.getMessage());
                }
            }
        }

        try {
            mSocket.connect();
        } catch (IOException e) {
            if (mSocket != null) {
                try {
                    mSocket.close();
                } catch (IOException e1) {
                    Log.e(TAG, e1.getMessage());
                }
            }
        }

        try {
            mOutS = mSocket.getOutputStream();
            input=mSocket.getInputStream();
            handler.sendEmptyMessage(CONNECT_SUCCED);
        } catch (IOException e) {
            if (mOutS != null) {
                try {
                    mOutS.close();
                } catch (IOException e1) {
                    Log.e(TAG, e.getMessage());
                }
            }

            if (mSocket != null) {
                try {
                    mSocket.close();
                } catch (IOException e1) {
                    Log.e(TAG, e.getMessage());
                }
            }
        }
    }


    private void close() {
        if (mOutS != null) {
            try {
                mOutS.close();
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }
        }

        if (mSocket != null) {
            try {
                mSocket.close();
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }
        }
    }


    private void writeStream(byte data) {
        try {
            if (mOutS != null) {
                mOutS.write(data);
                Log.i(TAG,"输出---->>>是:"+data);
                mOutS.flush();
            }
        } catch (IOException e) {

            runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    Toaster.shortToastShow(MainActivity.this, "连接超时");
                    MainActivity.this.finish();
                }
            });
        }
    }


    public Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (msg.what) {
                case 1:
                    break;
                case  mRecieve_SUCCED:
                    addLeftItem(mRecieve);
                    break;
                case CONNECT_SUCCED:
                    Log.i(TAG,"接收成功");
                     new MyThread().start(); //开启下面的线程
                    break;

            }
        }
    };

//新开的一个接收的线程
    class MyThread extends Thread{
        @Override
        public void run() {
            while (true){
            try {
              //获取到的数据
                int read = input.read();
                //因为不允许在子线程更新UI所以用handler
                 handler.sendEmptyMessage(mRecieve_SUCCED);
                 Log.i(TAG+"数据是",Integer.toHexString(read));
                } catch (IOException e) {
                    e.printStackTrace();
                Log.i(TAG,"异常"+e);
                }

            }}

        }




    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        if(keyCode== KeyEvent.KEYCODE_BACK){
            AlertDialog.Builder alertDialog=new AlertDialog.Builder(MainActivity.this);
            alertDialog.setTitle("确认!");
            alertDialog.setMessage("        确定退出智能锁控制吗");
            alertDialog.setPositiveButton("否",new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface arg0, int arg1) {

                }
            });
            alertDialog.setNegativeButton("是", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface arg0, int arg1) {

                    MainActivity.this.finish();
                }
            });
            alertDialog.show();
        }
        return false;
    }

}

运行效果:

Android蓝牙开启扫描流程源码 蓝牙扫描助手_通讯_03


完美通讯~~