草庐IT

【AndroidStudio开发】(三)经典蓝牙+BLE蓝牙搜索

xanadw 2023-05-23 原文

系列文章目录

【AndroidStudio开发】(一):新建页面切换项目

【AndroidStudio开发】(二):加入摇杆控制

【AndroidStudio开发】(三):经典蓝牙+BLE蓝牙搜索

【AndroidStudio开发】(四):蓝牙BLE设备连接


目录

系列文章目录

一、蓝牙的区别?

二、具体实现

1.查看系统app目录下的build.gradle配置

2.获取系统权限

(1)AndroidManifest.xml文件

(2)创建一个文本弹出框的类APP

(3)MainActivity文件

(4)增加一个蓝牙设备列表数据结构

(5)增加一个广播接收类BtReceiver

(6)FirstFragment文件

3.页面设计

(1) fragment_first.xml 文件

(2) 增加activity_listview.xml 文件

(3)activity_main.xml文件

(4)其它文件

4.连接设备调试

(1)连接手机

(2)打开MainActivity文件

(3)实际效果


前言

        上一次我们改造的是第二个页面,在页面中加入摇杆按钮,虽然我们可以拖动界面的按钮到任何一个方向,但是还是处于模拟器的操纵,没有用手机进行真机调试。现在我们需要对第一个页面进行改造,加入我们的蓝牙搜索及连接,这样我们就可以通过蓝牙连接设备,然后通过摇杆遥控,实际控制现实世界的物品。


一、蓝牙的区别?

        蓝牙其实分经典蓝牙和BLE两种,比如我们常见的HC-05、HC-06蓝牙模块都属于经典蓝牙,ESP32自带的蓝牙是属于BLE这种的,BLE是蓝牙协议4.0之后开始升级的,叫做低功耗蓝牙,这两种蓝牙使用过程中是存在区别的。

        第一个,BLE在安卓6.0之后,不仅需要动态获取系统蓝牙权限,还需要获取系统定位权限。

        第二个,蓝牙的搜索模式差不多,但是连接操作和写入操作完全不一样了。搜索的话,都是先通过注册一个蓝牙广播BtReceiver,再通过startDiscovery进行搜索,添加设备都是通过跟系统建立intent来获取搜索到的设备。但在连接和写入方面,经典蓝牙是通过创建一个Scoket,再通过其输入流getInputStream、输出流getOutputStream完成。BLE是通过BluetoothGatt的connectGatt、disconnect完成连接和断连,而发送消息必须要通过UUID获取BluetoothGatt的服务再进行写入。

二、具体实现

1.查看系统app目录下的build.gradle配置

因为创建项目选的是API 28,所以需要修改下面红色的配置。大部分点击搜索按钮后,没有显示任何蓝牙设备的时候,可以看看这个地方配置是否跟API版本一致,手机安卓系统是否支持该版本。

compileSdkVersion 28
buildToolsVersion '28.0.3'

defaultConfig {
    applicationId "com.example.myapplication"
    minSdkVersion 21
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

2.获取系统权限

(1)AndroidManifest.xml文件

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-feature android:name="android.hardware.bluetooth_le"  android:required="true" />

完整的AndroidManifest.xml文件,并增加一个后面的文本框类的关联,android:name=".APP"

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

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-feature android:name="android.hardware.bluetooth_le"  android:required="true" />

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

(2)创建一个文本弹出框的类APP

package com.example.myapplication;

import android.annotation.SuppressLint;
import android.app.Application;
import android.os.Handler;
import android.widget.Toast;

public class APP extends Application {
    private static final Handler sHandler = new Handler();
    private static Toast sToast; // 单例Toast,避免重复创建,显示时间过长

    @SuppressLint("ShowToast")
    @Override
    public void onCreate() {
        super.onCreate();
        sToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
    }

    public static void toast(String txt, int duration) {
        sToast.setText(txt);
        sToast.setDuration(duration);
        sToast.show();
    }

    public static void runUi(Runnable runnable) {
        sHandler.post(runnable);
    }
}

(3)MainActivity文件

        增加静态常量,删除fab按钮,增加蓝牙硬件的检查,蓝牙是否开启,动态获取定位授权。

    private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 检查蓝牙开关
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null) {
            APP.toast("本机没有找到蓝牙硬件或驱动!", 0);
            finish();
            return;
        } else {
            if (!adapter.isEnabled()) {
                //直接开启蓝牙
                adapter.enable();
                //跳转到设置界面
                //startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 112);
            }
        }

        //检查是否支持BLE
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            APP.toast("系统不支持BLE", Toast.LENGTH_SHORT);
            finish();
        }

        // Make sure we have access coarse location enabled, if not, prompt the user to enable it
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            final android.app.AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("定位授权");
            builder.setMessage("请授予位置访问权限,以便此应用可以检测外围设备");
            builder.setPositiveButton(android.R.string.ok, null);
            builder.setOnDismissListener(dialog -> requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION));
            builder.show();
        }

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }

(4)增加一个蓝牙设备列表数据结构LeDeviceListAdapter

package com.example.myapplication;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Created by xanadw on 10/24/2021.
 */
public class LeDeviceListAdapter extends BaseAdapter {
    private ArrayList<BluetoothDevice> deviceArrayList;
    private Context context;
    private LayoutInflater mInflater;
    private List<Integer> mRssis;
    private List<String> mBletype;


    public LeDeviceListAdapter(Context context, ArrayList<BluetoothDevice> devices, List<Integer> rssis, List<String> bletype) {
        this.context = context;
        this.deviceArrayList = devices;
        mInflater = LayoutInflater.from(this.context);
        mRssis = rssis;
        mBletype = bletype;
    }

    @Override
    public int getCount() {
        return deviceArrayList.size();
    }

    @Override
    public BluetoothDevice getItem(int position){
        return deviceArrayList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        MyViewHolder mViewHolder;

        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.activity_listview, parent, false);
            mViewHolder = new MyViewHolder(convertView);
            convertView.setTag(mViewHolder);
        }else {
            mViewHolder = (MyViewHolder) convertView.getTag();
        }

        BluetoothDevice currentListData = getItem(position);

        if (currentListData.getName()==null) {
            mViewHolder.tvname.setText("No name");
        }
        else{
            mViewHolder.tvname.setText(currentListData.getName());
        }
        mViewHolder.tvmac.setText(currentListData.getAddress());
        mViewHolder.tvRssi.setText(String.format("%ddBm", mRssis.get(position)));
        mViewHolder.tvBletype.setText(mBletype.get(position));

        return convertView;
    }

    private class MyViewHolder {
        TextView tvname, tvmac, tvRssi, tvSate, tvBletype;

        public MyViewHolder(View item) {
            tvname = (TextView)item.findViewById(R.id.name);
            tvmac = (TextView)item.findViewById(R.id.mac);
            tvRssi = (TextView)item.findViewById(R.id.rssi);
            tvBletype = (TextView)item.findViewById(R.id.bluetoothtype);
        }
    }

    public void clear() {
        deviceArrayList.clear();
        mRssis.clear();
        mBletype.clear();
    }

    public boolean addDevice (BluetoothDevice device) {
        boolean returnValue;
        if (!deviceArrayList.contains(device)) {
            deviceArrayList.add(device);
            returnValue = true;
        } else {
            returnValue = false;
        }

        return returnValue;
    }

    public void addBound() {
        Set<BluetoothDevice> bondedDevices = BluetoothAdapter.getDefaultAdapter().getBondedDevices();
        if (bondedDevices != null)
            deviceArrayList.addAll(bondedDevices);
    }
}

(5)增加一个广播接收类BtReceiver

这个类放在util包里面,下面这个是目录结构。

package com.example.myapplication.util;

import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;

/**
 * 监听蓝牙广播-各种状态
 */
public class BtReceiver extends BroadcastReceiver {
    private static final String TAG = BtReceiver.class.getSimpleName();
    private final Listener mListener;

    public BtReceiver(Context cxt, Listener listener) {
        mListener = listener;
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//蓝牙开关状态
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//蓝牙开始搜索
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//蓝牙搜索结束

        filter.addAction(BluetoothDevice.ACTION_FOUND);//蓝牙发现新设备(未配对的设备)
        filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);//在系统弹出配对框之前(确认/输入配对码)
        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//设备配对状态改变
        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);//最底层连接建立
        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);//最底层连接断开

        filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); //BluetoothAdapter连接状态
        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); //BluetoothHeadset连接状态
        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); //BluetoothA2dp连接状态
        cxt.registerReceiver(this, filter);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action == null)
            return;
        Log.i(TAG, "===" + action);
        BluetoothDevice dev = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        if (dev != null)
            Log.i(TAG, "BluetoothDevice: " + dev.getName() + ", " + dev.getAddress());
        switch (action) {
            case BluetoothAdapter.ACTION_STATE_CHANGED:
                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
                Log.i(TAG, "STATE: " + state);
                break;
            case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
                break;
            case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                break;

            case BluetoothDevice.ACTION_FOUND:
                short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MAX_VALUE);
                Log.i(TAG, "EXTRA_RSSI:" + rssi);
                mListener.foundDev(dev, rssi);
                break;
            case BluetoothDevice.ACTION_PAIRING_REQUEST: //在系统弹出配对框之前,实现自动配对,取消系统配对框
                /*try {
                    abortBroadcast();//终止配对广播,取消系统配对框
                    boolean ret = dev.setPin("1234".getBytes()); //设置PIN配对码(必须是固定的)
                } catch (Exception e) {
                    e.printStackTrace();
                }*/
                break;
            case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
                Log.i(TAG, "BOND_STATE: " + intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 0));
                break;
            case BluetoothDevice.ACTION_ACL_CONNECTED:
                break;
            case BluetoothDevice.ACTION_ACL_DISCONNECTED:
                break;

            case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED:
                Log.i(TAG, "CONN_STATE: " + intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 0));
                break;
            case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
                Log.i(TAG, "CONN_STATE: " + intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0));
                break;
            case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
                Log.i(TAG, "CONN_STATE: " + intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, 0));
                break;
        }
    }

    public interface Listener {
        void foundDev(BluetoothDevice dev, short devrssi);
    }
}

(6)FirstFragment文件

(I)引入BtReceiver,修改主类

import com.example.myapplication.util.BtReceiver;
public class FirstFragment extends Fragment implements BtReceiver.Listener {

然后根据错误提示需要新增foundDev函数,这个函数是经典蓝牙搜索的结果

    @Override
    public void foundDev(BluetoothDevice dev, short devrssi) {
        mLeDeviceListAdapter.addDevice(dev);
        mRssis.add((int) devrssi);
        mfbletype.add("old");
        mLeDeviceListAdapter.notifyDataSetChanged();
    }

新建蓝牙相关的变量以及一些常量

    private static final String TAG = "FirstFragment";
    private final static int REQUEST_ENABLE_BT = 1; 
   
    //获取主页面
    MainActivity mActivity;

    BluetoothManager btManager;
    BluetoothAdapter btAdapter;
    BluetoothLeScanner btScanner;
    private BtReceiver mBtReceiver;

 (II)获取主页面的Activity,因为我们的first页面是fragment页面,只能用主页面Activity去获取系统设备,第一个OnAttach是从Activity创建Fragment时调用的,第二个是OnDetach是从Fragment销毁返回到Activity时调用的。

    @Override
    public void onAttach(@NonNull Activity  activity) {
        super.onAttach(activity);
        mActivity = (MainActivity) activity;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mActivity = null;
    }

(III)修改onCreateView函数页面创建方式

        // Inflate the layout for this fragment
        View view=inflater.inflate(R.layout.fragment_first, container, false);

        return view;

(IV)判断系统是否支持蓝牙,并获取,并跟蓝牙建立一个Intent数据链接,放在onCreateView函数return前面。

        btManager = (BluetoothManager)mActivity.getSystemService(Context.BLUETOOTH_SERVICE);
        btAdapter = btManager.getAdapter();
        if (btAdapter == null || !btAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }
        btScanner = btAdapter.getBluetoothLeScanner();

(V)FirstFragment继续增加变量

    final ArrayList<BluetoothDevice> availableBT = new ArrayList<>();
    private static List<String> mfbletype;
    private static List<Integer> mRssis;
    private static LeDeviceListAdapter mLeDeviceListAdapter;
    private static BluetoothGatt firstGatt = null;
    private static String conbletype = "";
    private static BluetoothDevice condev = null;

(VI)增加界面显示搜索到的设备,同样放置在onCreateView函数的return之前。

        mLeDeviceListAdapter = new LeDeviceListAdapter(mActivity.getApplicationContext(), availableBT, mRssis, mfbletype);
        final ListView listView_available = view.findViewById(R.id.listview2);
        listView_available.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                firstGatt = null;
                conbletype = mfbletype.get(position);
                condev = mLeDeviceListAdapter.getItem(position);

                if (btAdapter.isDiscovering()) {
                    btAdapter.cancelDiscovery();
                }

                NavHostFragment.findNavController(FirstFragment.this)
                        .navigate(R.id.action_FirstFragment_to_SecondFragment);
            }
        });

(VII)增加一个蓝牙扫描设备的按钮,蓝牙扫描是最消耗能量的动作了,不符合BLE的思想所以一般手机都设置了30秒内扫描5次的限制,所以改成手动触发扫描,一次扫描时间挺长的,靠近一点等一等就会扫描到你要连接的蓝牙设备。

增加一个变量和常量

private Handler mHandler;
private static final long SCAN_PERIOD = 15000;
        mHandler = new Handler();
        mfbletype = new ArrayList<>();
        mRssis = new ArrayList<>();
        
        Button scan = view.findViewById(R.id.scan);
        scan.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                APP.toast("Scanning...", Toast.LENGTH_SHORT);
                mLeDeviceListAdapter.clear();
                mfbletype.clear();
                mRssis.clear();

                conbletype = "";
                condev = null;


                if (!btAdapter.isDiscovering()) {
                    btAdapter.startDiscovery();
                }

                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        stopScanning();

                    }
                },SCAN_PERIOD);

                scanLeDevice(true);

                mLeDeviceListAdapter.notifyDataSetChanged();
                listView_available.setAdapter(mLeDeviceListAdapter);
            }
        });

(VIII)onViewCreated这个原来用来控制页面一(蓝牙搜索页面)跳转到页面二(按钮控制页面)函数删除,之后采用点击对应的蓝牙设备直接跳转到页面二,然后增加缺少的一些函数,

添加已搜索到的设备到列表的回调函数leScanCallback,这个是BLE蓝牙搜索的结果。


    private final ScanCallback leScanCallback = new ScanCallback() {
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            if (!(mActivity == null || mActivity.isFinishing())) {
                mActivity.runOnUiThread(() -> {
                    if (mLeDeviceListAdapter.addDevice(result.getDevice())) {
                        mRssis.add(result.getRssi());
                        mfbletype.add("ble");
                    }
                    mLeDeviceListAdapter.notifyDataSetChanged();
                });
            }
        }
    };

添加停止扫描函数stopScanning

    public void stopScanning() {
        Log.d(TAG, "stopping scanning" );

        AsyncTask.execute(new Runnable() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void run() {
                btScanner.stopScan(leScanCallback);
            }
        });
    }

添加扫描函数scanLeDevice

    private void scanLeDevice(final boolean enable) {
        if (enable) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    stopScanning();

                }
            },SCAN_PERIOD);

            startScanning();
        } else {
            try {
                stopScanning();
            } catch (Exception e) {
            }
        }
    }

添加开始扫描函数startScanning

    public void startScanning() {
        //scan.setVisibility(View.INVISIBLE);
        //dc.setVisibility(View.VISIBLE);
        AsyncTask.execute(() -> btScanner.startScan(leScanCallback));
    }

(IX)onCreateView函数添加广播接收

        mBtReceiver = new BtReceiver(mActivity.getApplicationContext(), this);//注册蓝牙广播
        BluetoothAdapter.getDefaultAdapter().startDiscovery();

(X)onCreateView函数添加按钮的可见性

        Button button_first = view.findViewById(R.id.button_first);
        button_first.setVisibility(View.INVISIBLE);

3.页面设计

        上面的代码添加完毕后,还是存在一些报错,那是因为我们还缺少一些页面的组件元素。

(1) fragment_first.xml 文件

删除textview_first,包含activity_listview页面

<include layout="@layout/activity_listview" />

增加available、listview2、scan元素

    <TextView
        android:text="Available Devices"
        android:id="@+id/available"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <ListView
        android:id="@+id/listview2"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/available"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="30dp"/>

    <Button
        android:id="@+id/scan"
        android:text="Scan Devices"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginBottom="30dp"/>

修改button_first元素

    <Button
        android:id="@+id/button_first"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/next"
        android:layout_toStartOf="@+id/scan"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/scan"
        android:layout_marginBottom="30dp"/>

(2) 增加activity_listview.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#ffffff"
    android:gravity="center_vertical"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingBottom="8dp"
        android:paddingLeft="12dp"
        android:paddingRight="12dp"
        android:paddingTop="8dp">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical">

            <TextView
                android:id="@+id/name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:singleLine="true"
                android:textSize="17sp" />

            <TextView
                android:id="@+id/mac"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@+id/name"
                android:ellipsize="end"
                android:singleLine="true"
                android:textColor="#999999"
                android:textSize="12sp" />

            <TextView
                android:id="@+id/rssi"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@+id/mac"
                android:text=""
                android:textColor="#303030"
                android:textSize="12sp" />

            <TextView
                android:id="@+id/bluetoothtype"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@+id/rssi"
                android:ellipsize="end"
                android:singleLine="true"
                android:textSize="12sp" />
        </LinearLayout>
    </LinearLayout>

</LinearLayout>

(3)activity_main.xml文件

删除id为fab的图标

(4)其它文件

content_main.xml文件不用修改,fragment_second.xml保持上次的修改。

完整的项目地址--【AndroidStudio】经典蓝牙+BLE蓝牙搜索APP

4.连接设备调试

(1)连接手机

手机需要打开开发人员选项,开发人员选项大部分是通过点击关于手机的版本号6次以上打开的,再去系统和更新里面找到开发人员选项,然后将开发人员选项、USB调试打开。

 然后USB连接电脑后,选择传输文件选项。然后AndroidStudio里面就会显示手机的型号了。

(2)打开MainActivity文件

        右键选择Run MainActivity,不要直接点击Run和小三角键,会直接运行另外一个主类app。运行一次后你就可以直接点击小三角键了。

(3)实际效果

        请求打开蓝牙,定位权限获取,第一次获取位置信息权限之后,除非删除APP才会再次请求获取定位权限。

 

         点击Scan按钮,开启蓝牙扫描,扫描结果如下图,点击任何一个设备都会跳转至页面2。

   

         但要注意的是这个地方点击是没有加入连接蓝牙设备的,由于篇幅的原因,这个部分留到下一次再写。

        再附上完整的项目地址--【AndroidStudio】经典蓝牙+BLE蓝牙搜索APP


 总结

        蓝牙连接和控制属于AndroidStudio前期入门里面比较难的一类的了,后面可能还有屏幕的渲染,横竖屏的切换。下一次蓝牙连接文章都会涉及到。

有关【AndroidStudio开发】(三)经典蓝牙+BLE蓝牙搜索的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  3. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  4. ruby-on-rails - Nokogiri:使用 XPath 搜索 <div> - 2

    我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll

  5. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  6. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  7. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  8. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  9. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  10. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

随机推荐