android -- 蓝牙 bluetooth (二) 打开蓝牙

2023-05-16

        4.2的蓝牙打开流程这一部分还是有些变化的,从界面上看蓝牙开关就是设置settings里那个switch开关,widget开关当然也可以,起点不同而已,后续的流程是一样的。先来看systemServer.java的代码,蓝牙服务开启的地方,最后一个else分支是我们关心的,前两个是模拟器的一个测试模式的。

 if (SystemProperties.get("ro.kernel.qemu").equals("1")) {                                          
                Slog.i(TAG, "No Bluetooh Service (emulator)");                                   
            } else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) {                     
                Slog.i(TAG, "No Bluetooth Service (factory test)");                              
            } else {                                                                             
                Slog.i(TAG, "Bluetooth Manager Service");                                        
                bluetooth = new BluetoothManagerService(context);                                
                ServiceManager.addService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, bluetooth);
            }   

         暂且看下bluetoothManagerService的构造方法,代码有点多,我们只看两个地方, loadStoredNameAndAddress()是读取蓝牙打开默认名称的地方,isBluetoothPersistedStateOn()是判断是否已打开蓝牙的,如果已打开,后续操作要执行开启蓝牙的动作,前面那几行注册广播其中就有这个作用。

BluetoothManagerService(Context context) {
        ...一些变量声明初始化...
        IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
        filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
        filter.addAction(Intent.ACTION_USER_SWITCHED);
        registerForAirplaneMode(filter);
        mContext.registerReceiver(mReceiver, filter);
        loadStoredNameAndAddress();
        if (isBluetoothPersistedStateOn()) {
            mEnableExternal = true;
        }
    }

        回到界面开关那个看得着的地方,界面上开关就是BluetoothEnabler.java这个类了,而setBluetoothEnabled()则是具体开关动作。看下代码

 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        // Show toast message if Bluetooth is not allowed in airplane mode
        if (isChecked &&
                !WirelessSettings.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) {
            Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
            // Reset switch to off
            buttonView.setChecked(false);          
        }

        if (mLocalAdapter != null) {
            mLocalAdapter.setBluetoothEnabled(isChecked);
        }
        mSwitch.setEnabled(false);
    }

这里在判断是飞行模式不知道为什么没有return,如果是飞行模式会有提示toast弹出,既然这样源码为什么还要执行下面打开流程呢,也许是个bug?不细究这个了,继续看setBluetoothEnabled()方法做什么了,很明显mLocalAdapter(LocalBluetoothAdapter )只是个过渡,里面的 mAdapter(BluetoothAdapter)才是真正的主角,代码如下:

    public void setBluetoothEnabled(boolean enabled) {
        boolean success = enabled   ? mAdapter.enable() : mAdapter.disable();

        if (success) {
            setBluetoothStateInt(enabled
                ? BluetoothAdapter.STATE_TURNING_ON
                : BluetoothAdapter.STATE_TURNING_OFF);
        } else {
           .........
        }
    }
        在BluetoothAdapter.java里可以看到一个单例模式的应用,主要提供给其它程序调用蓝牙的一些方法用的,外部程序想调用蓝牙的方法就要先用这个

拿到BluetoothAdapter对象,代码也简单看下吧,里面是典型的binder应用。

     public static synchronized BluetoothAdapter getDefaultAdapter() {
        if (sAdapter == null) {
            IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);
            if (b != null) {
                IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);
                sAdapter = new BluetoothAdapter(managerService);
            } else {
                Log.e(TAG, "Bluetooth binder is null");
            }
        }
        return sAdapter;
    }
        此时我们更关心mAdapter.enable()的后续操作,外部其它应用到getDefaultAdapter()也是调用enable(),注意,到了BluetoothAdapter我们已经在framework层了,顺着BluetoothAdapter.java的enable()调用先回到BluetoothManagerService.java的enable(),再进一步来到BluetoothManagerService.java中的handleEnable()

方法,后面要跳转到新类了,贴出来一起看下,这部分好像不同版本还有些出入,不过核心的启动service是一样的,不影响理解。

 private void handleEnable(boolean persist, boolean quietMode) {
 synchronized(mConnection) {
    if ((mBluetooth == null) && (!mBinding)) {
        //Start bind timeout and bind
        Message timeoutMsg=mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
        mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
        mConnection.setGetNameAddressOnly(false);
        Intent i = new Intent(IBluetooth.class.getName());
        if (!mContext.bindService(i, mConnection,Context.BIND_AUTO_CREATE,
                                  UserHandle.USER_CURRENT)) {
            mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
            Log.e(TAG, "Fail to bind to: " + IBluetooth.class.getName());
        } else {
            mBinding = true;
        }
    } 
       下面跑到哪个service里去了呢,在log信息里可以看到"ActivityManager: Start proc com.android.bluetooth for service com.android.bluetooth/.btservice.AdapterService:"

这样的信息,那就是去AdapterService里看看,里面一共有三个enable(),跳转关系不复杂,我们直接看最后一个关键的。

    public synchronized boolean enable(boolean quietMode) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                 "Need BLUETOOTH ADMIN permission");
         if (DBG)debugLog("Enable called with quiet mode status =  " + mQuietmode);
         mQuietmode  = quietMode;
         Message m =
                 mAdapterStateMachine.obtainMessage(AdapterState.USER_TURN_ON);
         mAdapterStateMachine.sendMessage(m);
         return true;
     }
 状态机来了,状态转换图,从一个状态接受命令跳到另一个状态,因为我们是在开启蓝牙,所以先去的 AdapterState.java内部类offstate.java里面找,在这个分支USER_TURN_ON看到mAdapterService.processStart();在这里面可以看到蓝牙遍历下所支持的profile,最后又发出个带AdapterState.STARTED标识的消息

处理在同文件下面的代码里

      case STARTED:   {
        if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STARTED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
        //Remove start timeout
        removeMessages(START_TIMEOUT);

        //Enable
        boolean ret = mAdapterService.enableNative();
        if (!ret) {
            Log.e(TAG, "Error while turning Bluetooth On");
            notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
            transitionTo(mOffState);
        } else {
            sendMessageDelayed(ENABLE_TIMEOUT, ENABLE_TIMEOUT_DELAY);
        } 

看到那个enableNative()函数调用了吧,又要用到JNI了,稍微回头看下前面的代码,我们先从应用界面开关BluetoothEnabler走到framework的BluetoothAdapter,又回到package的adapterService,现在又要去JNI的C++代码了,往常一般是packages -->framework-->下面一层,这次顺序有些颠倒了,不过这不能影响我们跟踪代码,最后

还是要到下面去的。一起往下看吧。

        根据android JNI的函数命名惯例很容易找到enableNative对应的C++函数在packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp里面

static jboolean enableNative(JNIEnv* env, jobject obj) {
   ALOGV("%s:",__FUNCTION__);
   jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;
    int ret = sBluetoothInterface->enable();
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
    return result;
}

代码瞬间简洁了不少,看来更多的故事还在下面,sBluetoothInterface这是什么,直接关系到下一步去哪的问题,看下变量声明,原来是

Const bt_interface_t *sBluetoothInterface = NULL; 再去找在哪初始化,搜索external目录可以找到/external/bluetooth/bluedroid/btif/src/bluetooth.c

    static const bt_interface_t bluetoothInterface = {
    sizeof(bt_interface_t),
    init,
    enable,
    disable,
    .............
    start_discovery,
    cancel_discovery,
    create_bond,
    remove_bond,
    cancel_bond,
   ...............
};
原来在这里,说下怎么找到,直接跳转是不成了,看这个文件夹下的mk文件,那里面有libhardware目录是编译的时候要用到,这个多半在hardware目录里,在这里面很快可以看到bluetooth.h,那里面有最我们要找的结构体定义,头文件找到了,再找同名C文件就快了,好了继续吧看下enable()里是怎么实现的

static int enable( void )
{
    ALOGI("enable");

    /* sanity check */
    if (interface_ready() == FALSE)
        return BT_STATUS_NOT_READY;

    return btif_enable_bluetooth();
}
又是一个新函数,直接跳转,比起刚才的寻觅这太幸福了
bt_status_t btif_enable_bluetooth(void)
{
    BTIF_TRACE_DEBUG0("BTIF ENABLE BLUETOOTH");

    if (btif_core_state != BTIF_CORE_STATE_DISABLED)
    {
        ALOGD("not disabled\n");
        return BT_STATUS_DONE;
    }

    btif_core_state = BTIF_CORE_STATE_ENABLING;

    /* Create the GKI tasks and run them */
    bte_main_enable(btif_local_bd_addr.address);

    return BT_STATUS_SUCCESS;
}
忘了写路径了 好在可以直接跳转,下面是/external/bluetooth/bluedroid/main/bte_main.c,有点长,暂时只关心set_power那部分就好了,
void bte_main_enable(uint8_t *local_addr)
{
    APPL_TRACE_DEBUG1("%s", __FUNCTION__);
    ........................

#if (defined (BT_CLEAN_TURN_ON_DISABLED) && BT_CLEAN_TURN_ON_DISABLED == TRUE)
        APPL_TRACE_DEBUG1("%s  Not Turninig Off the BT before Turninig ON", __FUNCTION__);

#else
        /* toggle chip power to ensure we will reset chip in case
           a previous stack shutdown wasn't completed gracefully */
        bt_hc_if->set_power(BT_HC_CHIP_PWR_OFF);
#endif
        bt_hc_if->set_power(BT_HC_CHIP_PWR_ON);

        bt_hc_if->preload(NULL);
    }

     .............................
}

路径在这里/external/bluetooth/bluedroid/hci/src/bt_hci_bdroid.c,看看set_power里面有什么,快到头了

static void set_power(bt_hc_chip_power_state_t state)
{
    int pwr_state;

    BTHCDBG("set_power %d", state);

    /* Calling vendor-specific part */
    pwr_state = (state == BT_HC_CHIP_PWR_ON) ? BT_VND_PWR_ON : BT_VND_PWR_OFF;

    if (bt_vnd_if)
        bt_vnd_if->op(BT_VND_OP_POWER_CTRL, &pwr_state);
    else
        ALOGE("vendor lib is missing!");
}
        这下又有新东西了bt_vnd_if,这个是什么,bt_vendor_interface_t *bt_vnd_if=NULL;和刚才的bt_interface_t 一样,我们希望可以找到它的初始化,那样就可以继续跟踪了,不过看到下面的代码和注释,在源码中我们要绝望了。路径:/external/bluetooth/bluedroid/hci/include/bt_vendor_lib.h

/* Entry point of DLib --
 *      Vendor library needs to implement the body of bt_vendor_interface_t
 *      structure and uses the below name as the variable name. HCI library
 *      will use this symbol name to get address of the object through the
 *      dlsym call.
 */
extern const bt_vendor_interface_t BLUETOOTH_VENDOR_LIB_INTERFACE;

bt_vendor_interface_t *bt_vnd_if=NULL;

 google定义好了接口,具体实现要看vendor厂商来做了,这后面怎么实现就看各家芯片商怎么写了,肯定各有不同,而且这一部分代码一般是不会公开,当然授权购买后除外了。所以在4.2的源码中我们只跟到这里了,那后面会做什么呢,加载驱动和上电这两项肯定要有了,打开蓝牙没这两步怎么行,类似下面的字符串

static const char* BT_DRIVER_MODULE_PATH =    "/system/lib/modules/mbt8xxx.ko";
static const char* BT_DRIVER_MODULE_NAME =     "bt8xxx";
static const char* BT_DRIVER_MODULE_INIT_ARG = " init_cfg=";
static const char* BT_DRIVER_MODULE_INIT_CFG_PATH = "bt_init_cfg.conf";

在有类似下面的动作,insmod加载驱动,rfkill控制上下电,具体厂商具体做法也不同。

ret = insmod(BT_DRIVER_MODULE_PATH, arg_buf);
ret = system("/system/bin/rfkill block all");
       写到这,关于4.2源码的蓝牙打开流程就算结束了,比起4.1之前的代码感觉没有以前的直观,对于vendor那部分的代码大家只能看各自厂商的代码了,一般蓝牙开启后才会上电,这样也比较符合逻辑和节省电量,是否上电可以连上手机用adb shell看sys/class/rfkill目录下的state状态值,有些厂商会把蓝牙和wifi的上电算在一起,这个也是要注意的,小心误判。最后呢,这次文章写得有点仓促,写错的或遗漏地方希望朋友指出来,非常感谢。






  


  

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android -- 蓝牙 bluetooth (二) 打开蓝牙 的相关文章

随机推荐

  • Linux好用的管道命令

    Linux好用的管道命令 文章目录 Linux好用的管道命令1 选取命令grepcut 分割 2 排序命令sortwcuniq 3 划分命令 split4 参数代换xargs5 数据处理工具awk 6 sed工具7 参考 1 选取命令 gr
  • 关于Hadoop集群物理及虚拟内存的检测的设置说明

    关于Hadoop集群物理及虚拟内存的检测的设置说明 文章目录 关于Hadoop集群物理及虚拟内存的检测的设置说明写在前面正文不能关闭对物理内存的检测关闭对虚拟内存的检测 参考 写在前面 Linux xff1a CentOS7 5Java x
  • Arrays.stream().boxed()的使用

    Arrays stream boxed 的使用 文章目录 Arrays stream boxed 的使用0 写在前面1 Arrays stream 的使用算法 xff1a 代码 xff1a 输出结果 xff1a 2 boxed 的使用box
  • Flume中 File Channel 的优化

    Flume中 File Channel 的优化 文章目录 Flume中 File Channel 的优化File Channel 的特点File Channel 的优化索引索引备份 Flume官方优化设计概述 xff08 Overview
  • 数仓采集通道的设计

    数仓采集通道的设计 文章目录 数仓采集通道的设计写在前面方案一 xff1a 方案二 xff1a 方案三 xff1a 最终方案 写在前面 离线和实时数仓共用一套数据采集通道系统数据采集存储到HDFS上完全分布式 xff08 三台节点 xff0
  • Hive命令使用记录

    Hive命令使用记录 文章目录 Hive命令使用记录操作一些常用的Bash Shell 命令 xff1a 操作HDFS 平台相关的命令 xff1a 查看当前使用的数据库创建表的时候通过location 指定数据存储位置 xff0c 加载数据
  • Navicat远程连接Linux的MySQL服务Error10061的解决方案

    Navicat远程连接Linux的MySQL服务Error10061的解决方案 文章目录 Navicat远程连接Linux的MySQL服务Error10061的解决方案写在前面解决方法 写在前面 Linux xff1a Ubuntu Kyl
  • 棋盘覆盖问题(Java)

    棋盘覆盖问题 xff08 Java xff09 文章目录 棋盘覆盖问题 xff08 Java xff09 1 问题描述2 算法设计思路3 代码实现4 复杂度分析5 参考 1 问题描述 在一个2k 2k个方格组成的棋盘中 若恰有一个方格与其他
  • android -- 蓝牙 bluetooth (一) 入门

    前段时间在 网上看了一些关于android蓝牙的文章 xff0c 发现大部分是基于老版本 xff08 4 1以前含4 1 xff09 的源码 xff0c 虽然无碍了解蓝牙的基本原理和工作流程 xff0c 但对着4 2 2的代码看起来总是有些
  • 最优二叉搜索树问题(Java)

    最优二叉搜索树问题 xff08 Java xff09 文章目录 最优二叉搜索树问题 xff08 Java xff09 1 前置介绍2 算法设计思路2 1 最优二叉搜索树的结构2 2 一个递归算法2 3 计算最优二叉搜索树的期望搜索代价 3
  • 大数据量一次性导入MongoDB

    大数据量一次性导入MongoDB 文章目录 大数据量一次性导入MongoDB0 写在前面1 前置芝士2 mongoimport命令导入JSON文件数据失败3 db COLLECTION count 返回值不正确4 数据导入不完全5 参考资料
  • Strassen矩阵乘法问题(Java)

    Strassen矩阵乘法问题 xff08 Java xff09 文章目录 Strassen矩阵乘法问题 xff08 Java xff09 1 前置介绍3 代码实现4 复杂度分析5 参考资料 1 前置介绍 矩阵乘法是线性代数中最常见的问题之一
  • 线性时间选择(Top K)问题(Java)

    线性时间选择 xff08 Top K xff09 问题 xff08 Java xff09 文章目录 线性时间选择 xff08 Top K xff09 问题 xff08 Java xff09 1 前置介绍2 分治法求解3 代码实现4 复杂度分
  • HBase查询一张表的数据条数的方法

    HBase查询一张表的数据条数的方法 文章目录 HBase查询一张表的数据条数的方法0 写在前面1 HBase Shell的count命令2 Scan操作获取数据条数3 执行Mapreduce任务4 Hive与HBase整合5 协处理器Co
  • 校园论坛设计(Java)——介绍篇

    校园论坛设计 xff08 Java xff09 文章目录 校园论坛设计 xff08 Java xff09 0 写在前面1 项目介绍2 项目背景3 项目功能介绍3 1 总体设计图3 2 帖子模块3 3 学习模块3 4 个人信息模块3 5 数据
  • 单源最短路径问题(Java)

    单源最短路径问题 xff08 Java xff09 文章目录 单源最短路径问题 xff08 Java xff09 1 问题描述2 算法思路3 代码实现4 算法正确性和计算复杂性4 1 贪心选择性质4 2 最优子结构性质4 3 计算复杂性 5
  • 回溯法(Java)

    回溯法 xff08 Java xff09 文章目录 回溯法 xff08 Java xff09 1 引言2 回溯法2 1 定义2 2 使用场合2 3 基本做法2 4 具体做法2 5 常见例子 3 比较4 问题的解空间4 1 介绍4 2 解空间
  • 校园论坛(Java)——环境配置篇

    校园论坛 Java 环境配置篇 文章目录 校园论坛 Java 环境配置篇 1 写在前面 2 新建Maven项目 2 1 引入相关依赖 2 2 配置Tomcat环境 3 项目发布测试 4 项目代码 5 参考资料 1 写在前面 Windows版
  • 校园论坛(Java)—— 帖子模块

    校园论坛 Java 帖子模块 文章目录 校园论坛 Java 帖子模块 1 写在前面 2 系统结构设计 2 1 各个页面之间的调用关系 2 2 普通帖子中各层的设计 3 用户浏览普通帖子功能的实现 3 1 帖子发布和查看以及回复功能系统 3
  • android -- 蓝牙 bluetooth (二) 打开蓝牙

    4 2的蓝牙打开流程这一部分还是有些变化的 xff0c 从界面上看蓝牙开关就是设置settings里那个switch开关 xff0c widget开关当然也可以 xff0c 起点不同而已 xff0c 后续的流程是一样的 先来看systemS