RIL 重构

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

RIL 重构包括以下改进:

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

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

实现增强的 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_REGISTER 期间是否返回您的 RIL 代码对应的 RIL 版本,而非 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 后,就可以将所用处理时间多于 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 端将唤醒锁计数器的值减 1;如果唤醒锁计数器的值为 0,则 Java 端释放唤醒锁。

    注意:由于确认信息应该很快就能收到,因此请求确认 (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(),使得唤醒锁计数器的值减 1,并在计数器的值为 0 时释放唤醒锁。

场景:RIL 自发响应

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

图 4. RIL 自发响应。

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

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

验证重新设计的唤醒锁

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