RIL 重构

Android 7.0 已使用一组子功能对无线接口层 (RIL) 进行重构,从而改进了 RIL 功能。要实现这些功能,合作伙伴需要对代码进行更改;您可以自行决定是否更改,不过我们建议您进行更改。重构更改具有向后兼容性,因此您可以继续使用之前实现的已重构功能。

RIL 重构包括以下改进:

  • RIL 错误代码。 除了现有的 GENERIC_FAILURE 代码之外,还会返回具体的错误代码。这样一来,系统便可以提供关于错误原因的更具体信息,从而提高错误的问题排查效率。
  • RIL 版本管理。 可以更准确、轻松地配置版本信息。
  • 利用唤醒锁定机制的 RIL 通信技术。 改善了设备的电池性能。

您可以实现上述任何或所有改进。有关 RIL 版本管理的更多详情,请参阅 https://android.googlesource.com/platform/hardware/ril/+/master/include/telephony/ril.h 中的代码注释。

实现增强的 RIL 错误代码

几乎所有的 RIL 请求调用在对错误做出响应时都会返回 GENERIC_FAILURE 错误代码。原始设备制造商 (OEM) 返回的所有应求响应都存在这个问题。由于 RIL 调用在因不同的原因而出错时都返回同样的 GENERIC_FAILURE 错误代码,因此很难根据错误报告对问题进行调试。供应商甚至需要花费相当长的时间才能确定是代码的哪一部分返回了 GENERIC_FAILURE 代码。

在 Android 7.x 和更高版本中,OEM 可以返回与当前归类为 GENERIC_FAILURE 的各种不同错误相关联的独特错误代码值。如果 OEM 不希望公开披露自己的自定义错误代码,则可以以一组独特整数(例如从 1 到 x,映射为从 OEM_ERROR_1OEM_ERROR_X)的形式返回错误。供应商应确保返回的每个经掩码处理的这类错误代码都可以映射到代码中具体某种错误原因。相比之前 OEM 返回一般性错误的情况,使用具体的错误代码可加快 RIL 的调试速度,因为要确定 GENERIC_FAILURE 错误代码的确切原因往往需要花费很长时间(有时甚至找不到原因)。

此外,ril.h 针对枚举 RIL_LastCallFailCauseRIL_DataCallFailCause 添加了更多错误代码,以便供应商代码可以避免返回 CALL_FAIL_ERROR_UNSPECIFIEDPDP_FAIL_ERROR_UNSPECIFIED 等一般性错误。

验证增强的 RIL 错误代码

添加新的错误代码来替换 GENERIC_FAILURE 代码后,请确保 RIL 调用返回的是新的错误代码而非 GENERIC_FAILURE

实现增强的 RIL 版本管理

旧版 Android 中的 RIL 版本管理存在问题:版本本身并不精确,报告 RIL 版本时所采用的机制不明晰(导致部分供应商报告的版本不正确),临时的版本估测解决方法往往不准确。

在 Android 7.x 及更高版本中,ril.h 会记录所有 RIL 版本值,描述相应的 RIL 版本,并列出该版本的所有更改。对某个 RIL 版本做出更改时,供应商必须在代码中更新其版本,并在 RIL_REGISTER 中返回该版本。

验证增强的 RIL 版本管理

确保您的 RIL 代码对应的 RIL 版本在 RIL_REGISTER 期间(而非 ril.h 中定义的 RIL_VERSION 中)返回。

实现利用唤醒锁定机制的 RIL 通信技术

RIL 通信中的定时唤醒锁定使用方式并不精确,因而会对电池性能带来负面影响。在 Android 7.x 及更高版本中,您可以对 RIL 请求进行归类并更新代码以针对不同的请求类型以不同方式处理唤醒锁定,从而提高性能。

对 RIL 请求进行归类

RIL 请求既可以是应求请求,也可以是自发请求。供应商应该进一步将应求请求归入以下类别之一:

  • 同步。无需较长时间即可返回响应的请求。例如,RIL_REQUEST_GET_SIM_STATUS
  • 异步。需要相当长时间才能返回响应的请求。例如,RIL_REQUEST_QUERY_AVAILABLE_NETWORKS

异步应求 RIL 请求可能需要相当长的时间。在收到来自供应商代码的确认信息后,RIL Java 会释放唤醒锁定,这可能会使应用处理器从闲置状态转为挂起状态。当收到来自供应商代码的响应时,RIL Java(应用处理器)会重新获取唤醒锁定,并对响应进行处理,然后恢复闲置状态。从闲置状态转为挂起状态再返回闲置状态这一流程可能会非常耗电。

如果响应时间不够长,则与进入挂起状态(通过释放唤醒锁定,然后在系统返回响应时唤醒)相比,在整个响应时间内保持唤醒锁定并停留在闲置状态可能会更省电。供应商应使用平台专用功率测量工具来确定时间“T”在以下情况下的阈值:当应用处理器停留在闲置状态时(在整个时间“T”内)的耗电量多于从闲置状态转为挂起状态再返回闲置状态(在同一时间“T”内)的耗电量时。“T”已知时,所用处理时间比“T”多的 RIL 命令可归类为“异步”,而其余命令则可归类为“同步”。

RIL 通信示例情景

以下图表展示了常见的 RIL 通信示例情景,并提供了修改用于处理 RIL 应求请求和自发请求的代码的解决方案。

注意:要详细了解如何实现以下图表中使用的函数,请参阅 ril.cpp 中的 acquireWakeLock()decrementWakeLock()clearWakeLock() 方法。

示例情景:RIL 请求和应求异步响应

在此示例情景中,如果 RIL 应求响应(例如,对 RIL_REQUEST_GET_AVAILABLE_NETWORKS 的响应)预计需要花费相当长的时间,则唤醒锁定会在应用处理器端保持较长时间。此外,调制解调器问题也会导致长时间的等待。

图 1. RIL 应求异步响应。

解决方案 1:调制解调器针对 RIL 请求和异步响应保持唤醒锁定。

图 2. 调制解调器保持唤醒锁定。
  1. RIL 请求已发送,调制解调器会获取唤醒锁定来处理该请求。
  2. 调制解调器会发送确认信息,导致 Java 端的唤醒锁定计数器的数值递减;如果唤醒锁定计数器的值为 0,则释放唤醒锁定。

    注意:由于确认信息很快就可以收到,因此请求确认 (request-ack) 序列的唤醒锁定超时时长应短于当前使用的超时时长。

  3. 处理请求后,调制解调器会向供应商代码发送一个中断,该代码会获取唤醒锁定并向 ril.cpp 发送响应。随后,ril.cpp 会获取唤醒锁定并向 Java 端发送响应。
  4. 当响应到达 Java 端时,Java 会获取唤醒锁定,并将响应返回至调用程序。
  5. 该响应经过所有模块处理后,系统会通过套接字将确认信息发送回 ril.cpp,后者随后就会释放第 3 步中获取的唤醒锁定。

解决方案 2:调制调解器不会保持唤醒锁定,且响应会很快返回(同步 RIL 请求和响应)。同步和异步行为会针对特定 RIL 命令硬编码,并在每次调用中单独确定。

图 3. 调制解调器未保持唤醒锁定。
  1. 通过在 Java 端调用 acquireWakeLock() 发送 RIL 请求。
  2. 供应商代码不需要获取唤醒锁定,且可快速处理请求并返回响应。
  3. 当 Java 端收到响应时,会调用 decrementWakeLock();这样一来,唤醒锁定计数器的数值便会减少,如果其值为 0,则释放唤醒锁定。

示例情景:RIL 自发响应

在此示例情景中,RIL 自发响应中会标有唤醒锁定类型标记,该标记指明了是否需要针对供应商响应获取唤醒锁定。如果响应中标有标记,则系统会设置一个定时唤醒锁定,并会通过套接字将响应发送至 Java 端。当定时器到期时,系统会释放唤醒锁定。对于不同的 RIL 自发响应,定时唤醒锁定可能会过长或过短。

图 4. RIL 自发响应。

解决方案:从相应的 Java 代码将确认信息发送至原生端 (ril.cpp),而不是在发送自发响应时在原生端保持定时唤醒锁定。

图 5. 使用确认信息取代定时唤醒锁定。

验证重新设计的唤醒锁定

验证 RIL 调用已被标明为“同步”或“异步”调用。由于电池耗电量可能因硬件/平台而异,因此供应商应进行一些内部测试,以确定针对异步调用使用新的唤醒锁定语义是否可以节省电量。