Android 车机蓝牙开发之环境搭建

作者: RazorZ | 来源:发表于2017-01-16 18:38 被阅读1005次

    蓝牙开发环境搭建

    车机蓝牙开发与手机蓝牙开发最大的不同是连接过程中的角色,而作为以手机为主要适配目标的 Android 源代码中虽然提供了作为客端蓝牙使用的协议栈,但是在原生的 SDK 中并没有直接提供给我们,这也是车机蓝牙开发首先要面对的棘手问题。

    @hide

    阅读过 Android 源代码的同学应该对如下格式的注释不会陌生:

    /**
    * XXXXXXXXXXX
    * @hide
    */
    

    @hide 经常会出现在类、方法甚至变量的注释当中,而被标识的 API 在正常开发过程中是无法直接使用的,这也是我们常说的 被隐藏的 API。甚至不用 Google ,只要随便百度一下,成百上千的文章会告诉你如何使用被 hide 了的类、方法和变量。比较常见的有以下几种:

    1. 通过反射调用。好处是只需要关心自己的业务,坏处是调用复杂、可读性差而且效率很低,建议在只需要使用少量被隐藏的 API 时使用该方法。
    2. 修改源码,把 @hide 删除掉。好处很明显,从此不用苦逼兮兮的使用反射了,坏处就更明显了,首先你需要一份源码和源码编译环境,其次需要了解 Android 源码编译,需要对源码结构很熟悉,有比较大的学习成本,再次是动手修改源码很可能引发连锁反应,导致其他 API 受到影响,增加了测试成本,得不偿失。
    3. 利用 Android 源码编译特性来获取全量的 .jar。车机蓝牙模块目前使用的是这种方法,好处是和当前车机上运行的 Android 系统保持一致,对源码没有侵入,使用起来和方法 2 同样方便等,当然坏处是学习成本高。下面重点介绍这种方法。

    获取全量 frameworks.jar

    三个背景知识

    1. Android 源码编译分为 ROM 和 SDK 两种,ROM 指的是我们常说的刷机包,而 SDK 则是在 Android 开发过程中使用的类、方法、变量、资源、工具等的集合。在开发过程中我们使用 SDK 为我们提供的资源编写程序,利用 SDK 提供的一系列工具编译打包成 .apk 类型的安装包文件,之后就可以把程序部署到手机上,这时我们会发现 .apk 文件少则几 M 多则几十 M ,很明显 SDK 提供给我们开发使用的 android.jar 并没有被包含在 .apk 中,这说明我们 android.jar 只是在编译过程中被使用了,而运行过程中我们调用的 API 则由手机上的系统提供,当我们用反射去调用隐藏的 API ,程序运行过程中如果没有产生崩溃的话,说明手机系统中是存在该 API 的,到这我们可以得出结论,被隐藏的 API 不是被删除,只是被排除在 API docs 之外。
    2. Android 源码编译分为若干的阶段,无论是编译 ROM 还是 SDK ,首先会将代码树上的各个子项目编译成单独的 .jar ,甚至我们可以单独编译其中的一个子项目,感兴趣的同学可以自行了解。所有子项目编译完成后,会根据编译目标的不同,对这些 .jar 进行不同的处理,如果目标为 SDK ,则编译系统会把包含 frameworks.jar 在内的若干个 .jar 组装成我们熟悉的 android.jar ,组装的过程会生成 API docs ,这个东西会决定哪些 API 我们可以使用而哪些不可以,就是在这个时候 @hide 标识发挥了作用,将被标识的 API 从 API docs 中剔除。
    3. Android APP 开发目前使用的 IDE 为 Android Studio(简称 AS ),AS 使用 Gradle 作为编译工具,Gradle 中引用其他类库分为两种情况,compile 和 provided 。compile 表示引用该类库编译工程并将该类库一并编译进最终的 .apk 文件中,而 provided 表示只引用该类库编译工程,但并不将其一并打包进 .apk 文件中。

    具体步骤

    依据以上三个背景知识,我们可以得出获取全量 frameworks.jar 的方法。

    1. 编译车机 ROM 完成后到 ANDROID_SOURCE/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/ 路径下,其中的 classes.jar 即为我们要找的全量 frameworks.jar 。将其拷贝出来并重命名为 frameworks_all.jar
    2. frameworks_all.jar 放到 project 中相关 module 的 libs 目录下,修改 module 目录下的 build.gradle 文件:
    dependencies {
        provided files('libs/frameworks_all.jar')
    }
    
    1. 修改 module 目录下后缀名为 .iml 的配置文件:
    <orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
    <orderEntry type="library" exported="" name="frameworks_all" level="project" />
    

    为:

    <orderEntry type="library" exported="" name="frameworks_all" level="project" />
    <orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
    

    刷新 project 后,我们发现可以 IDE 的自动提示中会包含被隐藏的 API 了,到此开发工作可以开展。.iml 文件是 IDE 自动生成的,每次更新 Gradle 之后都会重新生成一遍,因此这个文件在开发过程中可能会被多次修改。

    1. 步骤 3 保证了我们的开发工作可以正常进行,但是编译会出现错误,原因是 Gradle 编译时优先使用 android.jar 中的 API ,而我们使用的全量 frameworks.jar API 有一部分 android.jar 中是不包含的,因此我们需要让 Gradle 优先使用全量 frameworks.jar 编译我们的项目。修改项目根目录下的 build.gradle 文件:
      buildscript {
         repositories {
            jcenter()
         }
         dependencies {
            classpath 'com.android.tools.build:gradle:2.2.0'
         }
      }
      
      allprojects {
         gradle.projectsEvaluated {
            tasks.withType(JavaCompile) {
               options.compilerArgs.add('-Xbootclasspath/p:chjbluetoothconnection/libs/frameworks_all.jar')
            }
         }
      }
      

    -Xbootclasspath/p:$PATH 指令的含义是通知 java VM 优先加载 PATH 下的 .class 完成编译。同时,使用最新版本的 Gradle ,原因是经过测试,低版本的 Gradle 在执行 -Xbootclasspath/p:$PATH 会出现一些问题,原因不详,但是在新版本里面没有出现,姑且认为是 Gradle 本身的 bug。

    至此全量 frameworks.jar 的引用就完成了。

    获取蓝牙 Pbap 协议栈的支持 .jar

    为了方便开发,Android 提供了若干个蓝牙功能的协议栈,我们称之为 profile 。车机蓝牙开发我们需要用到的 profile 主要有:

    Profile Description
    A2DP Sink 音频相关
    HFP Client 电话相关
    Avrcp Controller 音频控制相关
    Pbap Client 通讯录、通话记录相关

    其中除了 Pbap Client 之外,相关代码都包含在 ANDROID_SOURCE/frameworks/base/core/ 路径下,即会随着 frameworks 主体的编译而编译,也就包含在了我们的全量包里面。

    Pbap Client 包含在 ANDROID_SOURCE/frameworks/opt/ 路径下,该路径下包含着 frameworks 的可选功能,需要手动编译或修改 Android 源码编译脚本。我们选择手动编译。

    具体步骤

    1. 编译车机 ROM 完成后在源码根目录下执行:source build/evnsetup.sh,初始化编译工具
    2. 进入 ANDROID_SOURCE/frameworks/opt/bluetooth/ 路径下执行 mm,对该路径下代码单独编译,生成的 .jar 在 ANDROID_SOURCE/out/target/common/obj/JAVA_LIBRARIES/android.bluetooth.client.pbap_intermediates/ 路径下,将其拷贝出来并重命名为 bluetooth_pbap_client.jar
    3. 进入 ANDROID_SOURCE/frameworks/base/obex/ 路径下执行 mm,对该路径下代码单独编译,生成的 .jar 在 ANDROID_SOURCE/out/target/common/obj/JAVA_LIBRARIES/javax.obex_intermediates/ 路径下,将其拷贝出来并重命名为 javax_obex.jar
    4. 将生成的两个 .jar 文件添加到 module 的 libs 目录下,修改 module 目录下的 build.gradle 文件:
      dependencies {
         compile files('libs/bluetooth_pbap_client.jar')
         compile files('libs/javax_obex.jar')
      }
      

    在这里我们使用的是 compile 形式引用 .jar ,原因是这部分代码在系统中不存在,需要工程自己提供相关类来保证程序正常运行。

    至此蓝牙 Pbap 协议栈的支持 .jar 的引用就完成了。

    相关文章

      网友评论

      • trayliu_小马过河:还有 我有多个framework.jar
      • trayliu_小马过河:修改iml之后,引用framework.jar编译是可以过,但是运行的时候还是会挂掉啊?这个怎么办?就比如修改的setting之类的,现在还在eclipse里,没有迁移倒AS
        RazorZ:@trayliu_小马过河 你的四个 jar 都是哪里来的?是编译源码之后在 out 文件夹下某子文件夹下找到的吗?你使用的 jar ,在 gradle 中是用 provided 还是 compile 添加的依赖?你首先要确定你是用的 jar ,在你程序运行环境中是否存在,如果运行环境中有,那么要使用 provided 添加依赖,如果没有,要使用 compile ,这两个关键字的区别在于是否把 jar 打包进入你的 apk 中。如果你使用 jar 包出现崩溃,可能有两种原因,一种是运行环境中有这个 jar ,同时你还把这个 jar 打包进了你的 apk 中,另一种是运行环境没有这个 jar ,同时你没有把 jar 打包进 apk。
        trayliu_小马过河:@周君宜 我之前没说清
        1. 我用的是AS,做机顶盒开发,除了Setting剩下其他项目都已经顺利迁到AS上了,改iml是为了让framwork.jar的优先级高于sdk 的android.jar,对吧,但我现在build可以过去,运行就会挂
        2.我用eclipse没问题。
        3.我有,而且我有四个jar。
        4.比如以太网设置这块,如果用官方SDK,那就得一路的反射
        RazorZ:1.如果你用的是 eclipse 的话,那应该没有 .iml 文件,这个文件是 idea 系列 IDE 才会有的。
        2.eclipse 使用全量的 framework.jar 应该不需要太多的配置,只要调整一下和 SDK 提供的 android.jar 的引用顺序就可以,具体可以百度,百度就能解决这个问题。
        3.我介绍的获取全量 framework.jar 的前提是要有源代码并且自己编译,如果你要做车机开发,这个应该不是问题。而且按照我描述的路径下,只能找到一个 classes.jar ,详见 具体步骤->1。
        4.全量 framework.jar 中包含的官方 SDK 不包含的方法,只供你开发使用,如果这个全量的 framework.jar 你是通过正确渠道获取的,那其中包含的方法你程序的运行环境应该同样具备,不会出现找不到 XX 方法这样的错误。

      本文标题:Android 车机蓝牙开发之环境搭建

      本文链接:https://www.haomeiwen.com/subject/squpbttx.html