Android汽车服务篇 CarAudioService
Posted broadview_java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android汽车服务篇 CarAudioService相关的知识,希望对你有一定的参考价值。
一.简介
本文将基于CarService中另一个重要的服务CarAudioservice以及其对应的CarAudioManager介绍汽车音频的相关内容.
在车载上,音频设备的数量还是使用场景都和手机有很大的不同,紧靠android原有的音频服务是无法满足在车内的使用需求的.
因此AAOS对Android原有的音频机制进行了扩充. 在CarService中加入了CarAudioService.对音频设备进行更加细致的管理,以满足车上的使用场景.
二. 音量控制
2.1 音量组
汽车音频中新增了音量组的概念. 所谓音量组,就是将不同用途的声音进行归纳,按组对相关的音量进行控制.
在进一步解释音量组的概念之前,先了解一下音频上下文(Audio Context)和音频属性(Audio Attribute)两个概念.
Android中的每段声音都由相应的应用和声音生成的原因来识别, 然后Android设备会使用这些信息来确认如何呈现声音.
音频属性与音频上下文的具体对应关系如表:
音频上下文 | 音频属性对象 |
---|---|
MUSIC | MEDIA |
VOICE_COMMAND | USAGE_ASSISTANT |
NAVIGATION | ASSISTANCE_NAVIGATION_GUIDEANCE |
CALL | VOICE_COMMUNICATION |
RINGTONE | NOTIFICATION_RINGTONE |
NOTIFICATION | NOTIFICATION |
ALARM | ALARM |
SYSTEM_SOUND | ASSISTANCE_SONIFICATION |
UNKNOWN | UNKNOWN |
也就是说, 应用在使用音频设备时, 需要指定相应的音频属性,而音频属性对应着相关的音频上下文. 音量组就是对音频上下文进行了分组,在同一组内的上下文,音量会同步变化.
而如何对音量组进行分类,则由制造商自己定义,如果制造商没有定义,则使用AAOS的默认分组(Android10中首选以car_audio_configuration.xml文件读取音频配置).
2.2 音量组的定义与加载
音量组通过car_volume_groups.xml文件进行定义,默认的配置文件的源码路径为:
packages/services/Car/service/res/xml/car_volume_groups.xml
<volumeGroups xmlns:car="http://schemas.android.com/apk/res-auto"
car:isDeprecated="true">
<group>
<context car:context="music"/>
<context car:context="call_ring"/>
<context car:context="notification"/>
<context car:context="system_sound"/>
</group>
<group>
<context car:context="navigation"/>
<context car:context="voice_command"/>
</group>
<group>
<context car:context="call"/>
</group>
<group>
<context car:context="alarm"/>
</group>
</volumeGroups>
从XML文件中的内容可以发现CarAudioService默认的音量组分为4组
其中 music, call_ring, notification,system_sound为一组; navigation, voice_command组成另一组; 而call 和 alarm单独为一组.
在同一组中的音频虽然属于不同的上下文,但是它们的音量变化是联动的. 当然,各制造商可以覆盖以上默认定义,对音频上下文进行不同的组合,或者增加分组的数量. 但是需要满足以下条件:
1. 一个音频上下文只能归属于一个组
2. 除去UNKNOWN, 共有8个有效的音量上下文类型,这8个类型都需要进行分组,不能缺省.
3. 上下文对应的底层的总线设备不应该出现在两个组中
4. 在同一组的音频上下文的单次调节的步长度应该一致.
在Android10之后,为了更好地支持多音区音频的特性,修改了汽车音频配置文件的格式.同时文件也不再以Android资源的形式进行配置,而是通过系统构建时复制到指定的路径下进行读取.
PRODUCT_COPY_FILES += \\
vendor/custom/car_audio_configuration.xml: $(TARGET_COPY_OUT_VENDRO)/etc/car_audio_configuration.xml
2.3 音量控制接口
有了音量组,就要通过音量组对音量大小进行控制, 这点和手机不一样, 手机上面使用AudiaManager的setStreamVolume方法进行音量调节,但是在AAOS中该方法很可能是无效的(取决于制造商的配置).
原因是AAOS上建议的是使用车上的硬件放大器完成对音量大小的调节,而非软件混音器. 系统音量的调节需要通过CarAudioManager中提供的接口进行控制. 其中相关的接口都是SystemAPI, 即系统级别的接口,所以普通第三方应用是无法使用的. 也就是说在实际驾驶过程中,第三方应用的音量调节主要依赖系统设置或者相关控制按键(如音量旋钮, 方向盘音量按键)实现.
CarAudioManager实例的获取方法和CarPropertyManager的获取类似,也是通过
Car car = Car.createCar(this);
CarAudioManager carAudioManager = (CarAudioManager)car.getCarManager(Car.AUDIO_SERVICE);
获取到CarAudioManager的实例之后,就可以使用它对音量进行调节了. 以增加某一Group的音量为例子:
int volume = carAudioManager.getGroupVolume(groupID);
carAudioManager.setGroupVolume(groupID, ++volume,
AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND);
通过getGroupVolume可以获取当前组的音量值,在通过setGroupVolume设置新的值.
那么这里有个疑问?如何知道想要调节的音频属性所对应的groupID呢? 因为制造商是可以重新定义分组的,不推荐开发者使用"硬编码"的方式, 为了使应用有较好的通用性,应用态获取音频属性所属的分组. 这里分为两个步骤实现:
1. 通过getVolumeGroupIdForUsage获取该Usage对应的groupId
2. 在利用获取的groupId设置音量
int groupID = carAudioManager.getVolumeGroupIdForUsage(AudioAttributes.USAGE_MEDIA);
调节音量需要申请对应的权限,因此不要忘记在清单文件中增加声明:
<uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
同样的,该权限只有系统特权应用可以使用.
2.4 制造商实现与相关配置
前文提到可以通过overlay机制覆盖CarAudioService默认的音量组实现音量组的自定义. 当然制造商可以做的和需要完成的事情不仅于此. 本节内容就基于CarAudioService的相关实现,对制造商的音频实现和相关配置进行一些补充.
在AAOS中推荐使用硬件放大器来控制音量, 而在Android手机中,支持软件混音器调节音量,这也是Android框架中的默认配置. 因此,如果要改用硬件放大器调节音量, 首先需要覆盖config_useFixedVolume属性.
<bool name="config_useFixedVolume">false</bool>
/frameworks/base/core/res/res/values/config.xml 默认值为false, 该情况下,应用就可以调用AudioManager.setStreamVolume方法调节不同音频属性的音量.
在车载设备上, 我们一般设置为true, 这样子AudioManager.setStreamVolume就不生效了.
继续看一下 CarAudioService的源码(Android10)
public void init()
synchronized (mImplLock)
//注释1
if (mUseDynamicRouting)
// Enumerate all output bus device ports
AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
AudioManager.GET_DEVICES_OUTPUTS);
if (deviceInfos.length == 0)
Log.e(CarLog.TAG_AUDIO, "No output device available, ignore");
return;
SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>();
for (AudioDeviceInfo info : deviceInfos)
Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
info.getId(), info.getAddress(), info.getType()));
if (info.getType() == AudioDeviceInfo.TYPE_BUS)
final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
// See also the audio_policy_configuration.xml,
// the bus number should be no less than zero.
if (carInfo.getBusNumber() >= 0)
busToCarAudioDeviceInfo.put(carInfo.getBusNumber(), carInfo);
Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
setupDynamicRouting(busToCarAudioDeviceInfo);
else
Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
setupLegacyVolumeChangedListener();
// Restore master mute state if applicable
if (mPersistMasterMuteState)
boolean storedMasterMute = Settings.Global.getInt(mContext.getContentResolver(),
VOLUME_SETTINGS_KEY_MASTER_MUTE, 0) != 0;
setMasterMute(storedMasterMute, 0);
注释1处的 mUseDynamicRouting 这个boolean值非常重要, 实际上无论是硬件放大器调节音量还是音量组的划分, 都有一个先决条件, 那就是mUseDynamicRouting的值为true. 该值是在
/frameworks/base/core/res/res/values/config.xml中配置
因为它的默认值为false,所以需要制造商覆盖设置为true. 这一点非常重要,否则汽车音频相关特性都不会被使能.
三. 音频焦点
在汽车音频中,音频焦点(Audio Focus)的处理和手机有所不同, 在具体介绍 AAOS中汽车音频焦点的实现之前, 需要了解一下Android的音频焦点机制
Android 引入音频焦点机制的主要目的是协调多个应用同时播放音频时产生的声音竞争的问题, 从而避免系统中有多个应用同时发声, 而导致声音混杂,影响用户体验.
每个应用在播放音频时需要申请音频焦点,当获得音频焦点成功或音频焦点被抢占后,应用应当根据相关的规则,暂停/继续播放音频.
因此在音频焦点的基础上,为了保证车类音频体验,制造商可以通过在HAL层对不同的音频上下文采用强制性的策略.
在AAOS中, 通过CarAudioFocus,汽车音频定义了属于自己的音频焦点规则. 见下表
竖列代表获得焦点并正在播放中的音频上下文
横行代表申请焦点的音频上下文.
0: 代表焦点获取被拒绝
1: 代表焦点申请成功, 且原持有者失去焦点
2: 代表申请焦点成功,且原焦点保持
Context | Music | Nav | Voice | Ring | Call | Alarm | Notification | System |
---|---|---|---|---|---|---|---|---|
Music | 1 | 2 | 1 | 1 | 1 | 1 | 2 | 2 |
Nav | 2 | 2 | 1 | 2 | 1 | 2 | 2 | 2 |
Voice | 2 | 0 | 2 | 1 | 1 | 0 | 0 | 0 |
Ring | 0 | 2 | 2 | 2 | 2 | 0 | 0 | 2 |
Call | 0 | 2 | 0 | 2 | 2 | 2 | 2 | 0 |
Alarm | 2 | 2 | 1 | 1 | 1 | 2 | 2 | 2 |
Notification | 2 | 2 | 1 | 1 | 1 | 2 | 2 | 2 |
System | 2 | 2 | 1 | 1 | 1 | 2 | 2 | 2 |
举个例子:
如果一个应用正在使用导航(Nav)上下文播放音频, 并获取了音频焦点,这时有一个应用以音乐(Music)上下文申请焦点, 那么申请结果为2 表示的意思: 焦点申请成功, 且导航应用保持焦点.
也就是说: 虽然音乐音频申请焦点成功了,导航音频却不会收到音频焦点丢失的通知
这与手机不同, 手机系统中同时只有一个应用维持音频焦点, 有其他用应用获取了焦点,那么意味着之前的应用失去了焦点.
但是CarAudioFocus中却不是这样, 使用导航上下文和音乐上下文的应用可以同时维持着音频焦点. 这样的设计更符合在汽车上的使用场景, 因为汽车上往往不同音频对应不同的扬声器设备, 汽车本身已经有很好的车内音响环境, 有的时候不同的声音可以同时播放并提供给用户出色的体验感.
Android核心服务解析篇——Android系统的启动
从大的方面来说。Android系统的启动能够分为两个部分:第一部分是Linux核心的启动,第二部分是Android系统的启动。
第一部分主要包含系统引导,核心和驱动程序等,因为它们不属于本篇要讲的内容,这里就不再讨论。
在本篇博客中,我们重点解说Android系统的启动,这一过程主要经过两个阶段。各自是应用的初始化流程与system_service进程及核心服务的创建流程。
1.初始化流程
初始化流程。顾名思义,它完毕Android的一些初始化工作,包含设置必要的环境变量,启动必要的服务进程,挂载必要的设备等,而这些工作将会为整个Android打下坚实的基础。
①应用的初始化流程
在核心启动完毕以后。将进入Android文件系统及应用的初始化流程。此时将会转向运行init.c中的main()函数(路径:/system/core/init/init.c),该函数的运行流程例如以下图所看到的:
以下我们了解一下上图中的注解
注解1:/dev表示设备文件系统或者udev挂载点。/proc用来挂载存放系统过程性信息的虚拟文件系统,/sys用于挂载“sysfs文件系统”。因为前面调用了umask(0)。因此mkdir(“/dev”,0755)得到的权限应该是0755.
注解2:init.rc的解析结果是形成action_list(onkeyword相关的部分),service_list(service_keyword相关的部分)以及action_queue(须要运行的命令或服务),以便兴许流程使用。
注解3:解析/proc/cmdline文件,将当中的属性导入Android系统全局变量。
注解4:
Ⅰget_hardware_name()方法用于解析/proc/cpuinfo文件获取硬件信息,并用于拼接成一个init.<hardware_name>.rc文件。继续解析。
Ⅱ在解析init.rc文件的过程中,系统会依据该文件的内容形成一些须要命令,动作或者触发器的列表并将这些存入在内在中,以便在必要的时候使用。
不同的厂商可能依据不同的硬件需求定制不同的.rc文件,这些.rc文件的名称一般为“init.<hardware_name>.rc”,而解析这些.rc文件的结果相同也会形成一些命令,动作或者触发器的列表,而这些列表将会合并解析init.rc所得的命令和动作的列表中,而且形成终于须要运行的命令和动作。
注解5:加入顺序为:early-init下的全部动作,wait_for_coldboot_done_action,property_init_action。keychord_init_action,console_init_action,set_init_properties_action。init下的动作,property_service_init_action,signal_init_action。check_startup_action,early-boot下的全部动作,boot下的全部动作。queue_property_triggers_action。这些动作组成了开机过程中看到的设备的状态,比方开机动画等。
注解6:这里会启动运行设置属性,创建或挂载动作以及启动服务等操作。
须要注意是的这里启动的服务包含最重要的servicemanager和zygote服务进程。
至此。init进程进入死循环中处理一些消息以等待命令的到来。
在这个过程中。我们将要了解下面知识。
Ⅰ在init执行的过程中产生了很多服务,它们是整个Android的基础。各自是ueventd,console,adbd,servicemanager。vold,netd,debuggerd,ril-daemon,surfaceflinger,zygote,drm。media。bootanim。dbus。bluetoothd,installd,flash_recovery,racoon,mtpd,keystore和dumstate。
Ⅱ整个init的行为甚至整个Android核心的属性都受到启动脚本init.rc的影响。
以下我们就重点介绍zygote的启动行为。具体了解init.rc的语法。
②init.rc的使用方法
Android初始化语言由声明的4个类型组成,它们各自是动作(action),命令(command),服务(service),和选项(option),以#开头的行表示凝视。
动作和服务声明新的一节而且有唯一的名字,全部的命令或者选项属于近期声明的节。
假设下一个动作或者服务的名字已存在(也就是重名),则它将作为错误被忽略。
Ⅰ动作
动作是命令序列。它有一个触发器,用于确定行动应在何时发生。
当发生某一个事件时,它能够匹配到一个动作触发器,而且该动作会被加入到要运行队列的尾部(除非它已经在队列中了)。
队列中的每一个动作是按顺序出列的,详细例如以下所看到的:
on early-init
write /proc/1/oom-adj -16
setcon u:r:init:s0
start ueventd
动作表现为下面的形式:
on <trigger>
<command>
<command>
<command>
.........
触发器是一些字符串。这些字符串可用于匹配一定类型的事件,而且用于触发动作。
下表罗列了一些触发器的定义。
触发器 | 说明 |
boot | 当初始化流程触发的时候。boot是首先被触发的动作(在完毕/init.conf文件载入之后) |
<name>=<value> | 当以<name>命名的属性被设为特定的值<value>时,该触发器发生 |
device-added-<path> | 当加入设备节点时,device-added-<path>定义的触发器执行 |
device-removed-<path> | 当移除设备节点时,device-removed-<path>定义的触发器执行 |
service-exited-<name> | 当指定的服务退出时,service-exited-<name>类型的触发器执行 |
<string> | 自己定义的触发器。可由init代码负责管理 |
Ⅱ命令
命令是组成动作的成员,也就是说。动作由一个个命令组成。下表罗列了动作支持的命令。
命令 | 说明 |
exec <path> [<argument>]* | fork并运行程序(<path>)。 这在程序完毕运行之前将堵塞一切进程,因此最好避免使用exec命令。该命令中两个參数的含义例如以下所看到的。 |
export <name> <value> | 设置名字为<name>的环境变量为<value> |
ifup <interface> | 打开网络接口<interface> |
import <filename> | 解析一个初始化配置文件。导入系统中 |
hostname <name> | 设置主机名 |
chdir <directory> | 改动工作文件夹。它的功能和cd命令一样 |
chmod <octal-mode> <path> | 改动文件的訪问权限 |
chown <owner> <group> <path> | 改动<path>指定的问题的全部者和组 |
chroot <directory> | 改动进程根文件夹为<directory> |
class_start <serviceclass> | 启动<serviceclass>类别的服务。假设它们没有执行的话 |
class_stio <serviceclass> | 停止<serviceclass>类别的服务,假设它们已经处于执行状态的话 |
domainname <name> | 设置域名 |
insmod <path> | 在<path>上安装模块 |
mkdir <path> [mode] [owner] [group] | 创建一个文件夹,当中文件夹路径以及名称由<path>指明。 这里能够通过參数给定文件夹的模式,全部者和组。假设没有提供[mode] [owner] [group]。则用权限755来创建文件夹,而且它属于root用户root组 |
mount <type> <device> <dir> [<mountoption>]* | 尝试在文件夹<dir>上挂载被命名的设备,<device>可能是[email protected]的形式。以便指定名为mtd块的设备。<mountoption>包含ro,rw,remount和noatime等 |
setkey | TBD |
setprop <name> <value> | 设置系统属性<name> 为<value> |
setrlimit <resource> <cur> <max> | 设置指定资源的使用限制 |
start <service> | 启动指定的服务。假设服务还没有执行的话 |
stop <service> | 停止指定的服务,假设服务眼下正在执行的话 |
symlink <target> <path> | 用值<target>来在<path>上创建一个符号链接 |
sysclktz <mins_west_of_gmt> | 设置系统闹钟基准(假设系统闹钟为GMT。则为0) |
trigger <event> | 触发一个事件。用于运行该触发器中的操作 |
write <path> <string> [<string>]* | 在<path>上打开文件而且用write(2)来将一个或多个字符串写到文件上。 |
在init.rc中,Android 定义了若干动作,而且这些动作用于完毕Android的初始化工作。以下以当中一个动作的配置来说明一下:
on fs
mount yaffs2 [email protected] /system
mount yaffs2 [email protected] /system ro remount
mount yaffs2 [email protected] /data nosuid nodev
mount yaffs2 [email protected] /cache nosuid nodev
这个样例配置了一个触发器为fs的动作,它由4条命令组成,这4条命令都使用mount命令挂载设备。
Ⅲ服务
服务是一些程序,当它们退出的时候。init启动而且(选择性地)又一次启动。
服务表现为下面形式:
service <name> <pathname> [<argument>]*
<option>
<option>
...........
当中各个參数的含义例如以下所看到的:
?<name>:为服务指定一个名字。
?<pathname>:指定服务须要运行的文件路径。
?[<argument>]*:启动服务所须要的參数,參数个数能够是0个或者多个。
Ⅳ选项
选项是服务的改动器。能够影响怎样以及何时初始化执行服务。下表罗列了选项列表。
选项 | 说明 |
critical | 这是一个对于设备来说比較关键的服务,假设它在4分钟内退出超过4次,那么设备将又一次启动并进入recovery模式。 |
disabled | 这个服务不能通过类别自己主动启动,它必须通过服务名字来显示启动 |
setenv <name> <value> | 设置启动进程中环境变量(由<name>指定)的值为<value> |
socket <name> <type> <perm> [<user> [<group>]] | 创建名为/dev/socket/<name>的一个Unix域port而且将它的fd传递到被启动的进程上 <type>必须是dgram,stream,seqpacket.设置用户和组的默认值为0 |
user <username> | 在运行该服务之前变换username,假设进程须要Linux的能力,就不能使用该命令 |
group <groupname> [<groupname>]* | 在运行该服务之前变换组名 |
oneshot | 在服务退出时不要又一次启动它 |
class <name> | 为服务指定一个类名。一个被命名的类中的全部服务都能够一起被启动或停止。 假设服务没有通过类选项来指定的话,它是在类default中的 |
onrestart | 当服务又一次启动时。运行一条命令 |
以下以init.rc文件里的配置为例简要说明一个服务的配置:
service zygote /system/bin/app_process -Xzygote /system/bin -zygote
--start-system-server
class main
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
在上面代码中,第一行配置了一个名为zygote的服务,这个服务将会执行/system/bin/app_process,剩余部分为參数(以空格切割)。
剩下的几行代码声明了此服务的选项。
这说明zygote是一个类型为main的服务(classmain)而且它会创建一个socket。这个socket的类型为stream,权限为666(socket zygote stream 666)。当重新启动此服务的时候,须要完毕下面事情。
?写/sys.android_power/request_state为wake
?写/sys/power/state为on
?又一次启动media服务
?又一次启动netd服务
init.rc文件须要在init启动期被解析成系统能够识别的数据结构。
前面我们读懂了init.rc的含义,以下我们就来看看init是怎样保存和组织这些信息的,首先,我们来看看在init中怎样表示动作,服务和命令,例如以下表所看到的:
组件 | 数据结构 | 说明 | |||
列表节点(listnode) |
|
listnode是一个表示位置的数据结构,能够用来定义不同类型节点(比方动作或者服务)的运行顺序 从左側的数据结构中能够看出。这里包括了两个listnode的指针。它们用于指向前一个和后一个将要运行的节点 这些信息将帮助各种节点(动作,服务,以及命令等)组成一个双向循环列表 |
|||
动作(action) |
|
action中包括4个表示节点位置信息的节点,它们分别表示它本身在全部动作中的位置(alist),在加入动作的队列中的位置(qlist),以及在某个触发器中的全部动作列表的位置(tlist) action 数据结构中包括了其它的重要信息,比方动作的名字(name),包括的全部命令列表(commands)以及当前命令 |
|||
服务(service) |
|
这个数据结构中包括了服务的信息。主要包括例如以下内容: ?该服务在全部服务列表中逻辑位置的数据结构“listnode”(slist) ?服务的基本信息。比方服务的名称,进程的相关信息,所须要參数信息等 |
|||
命令(command) |
|
这个数据结构中包含下面内容: ?节点的位置信息(clist) ?命令须要运行的函数的函数指针(func) ?參数信息:nargs和args[1] |
最后。我们通过解析init.rc中的一个片段来说明解析过程。
開始解析之前,须要了解整个解析过程至关重要的一个数据结构,那就是parse_state,它保存了整个解析过程中所处的状态,下图显示了它的“成分”
<<struct>> parse_state |
+*ptr:char +*text:char +line:int +nexttoken:int +*context:void +(*parse_line)(struct parse_state *state,int nargs,char **args):void +*filename:char |
③用init解析整个init.rc文件
如今我们 回到init启动的初期,这里它调用了init_parse_config_file()方法,而这种方法就是解析init.rc文件的入口。
用init解析整个init.rc文件的流程例如以下图所看到的。
以下我们了解一下上图中的注解、
注解1:state是一个被命名为parser_satte的结构体。用于保存当前文件的解析状态信息。包含解析的文件(filename),当前解析的行号(line),当前解析的文字指针(ptr),指示下一个动作的变量(nexttoken)以及解析这一行须要的函数指针(parse_line)等。
注解2:next_token()函数位于/system/core/init/parse.c中。用于分析init.rc文件的内容。它仅仅返回3个状态,各自是:T_EOF(文件结束),T_NEWLINE(一行结束)和T_TEXT(表示遇到第一个空格)。
注解3:init.rc中每一行的信息通过空格被切割为若干段,而这些信息共同组成args[INIT_PARSER_MAXARGS]的内容,并由nargs计数。
比如on fs经过解析后。这一行分为两段(各自是on和fs)。分别存放在args中,计数器的值为2.。
注解4:init.rc的每一行经过切割后,须要分析其类型(由lookup_keyword返回)。/system/core/init/keywords.h中定义了全部关键字的类型。在片段KEYWORD(on,SECTION,0,0)中。on关键字是一个SECTION,有0个(也就是不须要)參数,没有相应的触发函数(也就是最后一个0)。
注解5:state.parse_line是一个函数的指针,能够依据keyword指向两种不同的解析方法——parse_line_service(处理服务的选项)和parse_line_action(处理行为的命令)。依照这个流程,init完毕整个init.rc文件的解析,并生成service_list和action_list。兴许流程所须要的信息将从这两个列表中获取,将须要运行的命令或启动的服务增加action_queue中。这样就完毕了Android系统基础部分的启动。
在启动的过程中,须要特别注意的是,我们通过action_for_each_trigger()方法声明须要运行的命令队列,该方法的代码例如以下所看到的:
void action_for_each_trigger(const char * trigger,void (*func)(struct action *act)){
struct listnode * node。
struct action *act;
list_for_each(node,&action_list){
act=node_to_item(node,struct action,alist);
if(!strcmp(act->name,trigger)){
func(act);
}
}
}
在上述代码中,list_for_each()用于遍历action_list中的每个节点,返回节点在列表中的位置信息,然后通过node_to_item()方法生成一个action的信息,最后运行func()函数。
action_for_each_trigger()方法在init.rc中是这样调用的:
action_for_each_trigger(early-init,action_add_queue_tail);
它的含义是。在action_list中查找名字为early-init的节点。并将其信息通过action_add_queue_tail()方法增加action_queue队列的尾部。
然后在init的无限循环中遍历action_queue中的每个节点。运行它们所包括的命令。
说到这里,我们了解了init怎样对待init.rc文件的内容。以下扩展一下知识,概要介绍一下Android系统中*.rc文件的keyword及其使用需求,例如以下表。
假设你想改动或编写自己的.rc文件,那么请关注下表。
keyword | 类型 | 參数个数 |
capability | OPTION | 0 |
chdir | COMMAND | 1 |
chroot | COMMAND | 1 |
class | OPTION | 0 |
class_start | COMMAND | 1 |
class_stop | COMMAND | 1 |
class_reset | COMMAND | 1 |
console | OPTION | 0 |
critical | OPTION | 0 |
disabled | OPTION | 0 |
domainname | COMMAND | 1 |
exec | COMMAND | 1 |
export | COMMAND | 2 |
group | OPTION | 0 |
hostname | COMMAND | 1 |
ifup | COMMAND | 1 |
insmod | COMMAND | 1 |
import | SECTION | 1 |
keycodes | OPTION | 0 |
mkdir | COMMAND | 1 |
mount | COMMAND | 3 |
on | SECTION | 0 |
oneshot | OPTION | 0 |
onrestart | OPTION | 0 |
restart | COMMAND | 1 |
rm | COMMAND | 1 |
rmdir | COMMAND | 1 |
service | SECTION | 0 |
setenv | OPTION | 2 |
setkey | COMMAND | 0 |
setprop | COMMAND | 2 |
setrlimit | COMMAND | 3 |
socket | OPTION | 0 |
start | COMMAND | 1 |
stop | COMMAND | 1 |
trigger | COMMAND | 1 |
Symlink | COMMAND | 1 |
sysclktz | COMMAND | 1 |
user | OPTION | 0 |
wait | COMMAND | 1 |
write | COMMAND | 2 |
copy | COMMAND | 2 |
chown | COMMAND | 2 |
chmod | COMMAND | 2 |
loglevel | COMMAND | 1 |
load_persist_props | COMMAND | 0 |
ioprio | OPTION | 0 |
2.创建system_service进程
在init进程的启动过程中。比較重要的部分由孵化进程启动system_service进程,以下具体介绍一下这个部分。system_service进程将会为我们创建一些重要的Android核心服务,包含ActivityManagerService,PackageManagerService和PowerManagerService等。这些将成为应用程序的基础。并为应用程序提供必要的接口。
①创建流程
完毕应用程序的初始化之后。init进程将创建一个名叫system_service的重要进程,而我们将在此进程中创建Android核心服务。下图显示了system_process进程以及核心服务的创建过程。
注解1:init进程会按顺序启动各种类型的服务(包含core和main)。首先启动core类型的服务。然后启动main类型的服务。因为孵化服务为main类型,所以它会在core类型的服务之后启动。因此,这里启动用于管理服务的服务——servicemanager。
启动和入口例如以下所看到的。
?启动:service zygote /system/bin/app_process -Xzygote /system/bin --zygote--start-system-server
?入口:/frameworks/base/cmds/app_process/app_main.cpp的main()函数。
注解2:此时转向/frameworks/base/core/jni/AndroidRuntime.cpp的start()函数。
注解3:启动代码例如以下:
jmethodId startMeth=env->GetStaticMethodID(startClass,"main",....);
env->CallStaticVoidMethod(startClass,startMeth,strArray);
此时转向com.android.internal.os.ZygoteInit的main()方法运行。
注解4:
?载入frameworks下的preloaded-classes类。
?载入framework-res.apk下的资源。
注解5:孵化进程的主要目的就是孵化出system_process进程,这个时候流程将转向/frameworks/base/services/java/com/android/server/SystemServer.java的main()方法运行,而自身进入死循环成为守护进程。
注解6:init1()调用本地android_server_SystemServer_init1(/frameworks/base/services/jni/com_android_server_SystemServer.cpp)后,通过libAndroid_servers.So的system_init()函数启动两个服务并启动init2()、
注解7:这里启动并注冊剩余的必需服务(比方包服务和Activity服务等)。终于会启动Launcher来到桌面,至此整个启动过程完毕。
②system_service简单介绍
system_service进程很重要,它创建了很多重要的服务,那么怎样增加system_service中并接受管理呢?详细如以下的代码所看到的:
try{
Slog.i(TAG,"Backup Service");
ServiceManager.addService(Context.BACKUP_SERVICE,new BackupManagerService(context));
}catch(Throwable e){
Slog.e(TAG,"Failure starting Backup Service",e);
}
以上代码展示了system_process怎样将备份服务增加服务管理器中的。当中粗体部分的代码完毕了两件事情:第一,创建备份服务。第二,使用ServiceManager的addService()方法将创建出来的备份服务实例增加服务管理器中加以管理。
下表列出了system_service的服务keyword等知识。
服务keyword | 类 | 备注 |
entropy | EntropyService | 熵服务 |
power | PowerManagerService | 电源管理服务(Context.POWER_SERVICE) |
activity | ActivityManagerService | Activity管理服务 |
telephony.registry | TelephonyRegistry | 电话服务 |
package | PackageManagerService | 包管理服务 |
account | AccountManagerService | 账户管理服务(Context.ACCOUT_SERVICE) |
battery | BatteryService | 电池服务 |
vibrator | VibratorService | 振动服务 |
alarm | AlarmManagerService | 报警服务(Context.ALARM_SERVICE) |
window | WindowManagerService | 窗体服务(Context.WINDOW_SERVICE) |
bluetooth | BluetoothService | 蓝牙服务(BluetoothAdapter.BLUETOOTH_SERVICE) |
statusbar | StatusBarManagerService | 状态栏服务(Context.STATUS_BAR_SERVICE) |
input_method | InputMethodManagerService | 输入法管理服务(Context.INPUT_METHOD_SERVICE) |
location | LocationManagerService | 位置管理服务(Context.LOCATION_SERVICE) |
wallpaper | WallpaperManagerService | 壁纸管理服务(Context.WALLPAPER_SERVICE) |
audio | AudioService | 声音服务(Context.AUDIO_SERVICE) |
user | UserManagerService | 用户管理服务(Context.USER_SERVICE) |
以下以获取声音服务为例介绍获取服务的方法:
AudioService as=(AudioService)context.getSystemService(Context.AUDIO_SERVICE);
此时整个系统也就完毕了启动工作,这也意味着我们能够開始使用Android设备了。
以上是关于Android汽车服务篇 CarAudioService的主要内容,如果未能解决你的问题,请参考以下文章
Android系统篇之----免root实现Hook系统服务拦截方法
Android系统篇之----Binder机制和远程服务调用机制分析
Android Studio版本控制之Git篇(服务器gitblit)