blob: ab9931f8d55627e4fdbd868c0241f0748371c3f3 [file] [log] [blame]
<html devsite><head>
<title>版本编号</title>
<meta name="project_path" value="/_project.yaml"/>
<meta name="book_path" value="/_book.yaml"/>
</head>
<body>
<!--
Copyright 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<p>HIDL 要求每个使用 HIDL 编写的接口均必须带有版本编号。HAL 接口一经发布便会被冻结,如果要做任何进一步的更改,都只能在接口的新版本中进行。虽然无法对指定的已发布接口进行修改,但可通过其他接口对其进行扩展。</p>
<h2 id="code-structure">HIDL 代码结构</h2>
<p>
<a href="/reference/hidl/index.html">HIDL 代码按用户定义的类型、接口和软件包进行整理</a></p>
<ul>
<li><strong>用户定义的类型 (UDT)</strong>。HIDL 能够提供对一组基本数据类型的访问权限,这些数据类型可用于通过结构、联合和枚举组成更复杂的类型。UDT 会被传递到接口的方法。可以在软件包级定义 UDT(针对所有接口的通用 UDT),也可以在本地针对某个接口定义 UDT。</li>
<li><strong>接口</strong>。作为 HIDL 的基本构造块,接口由 UDT 和方法声明组成。接口也可以继承自其他接口。</li>
<li><strong>软件包</strong>。整理相关 HIDL 接口及其操作的数据类型。软件包通过名称和版本进行标识,包括以下内容:
<ul>
<li>称为 <code>types.hal</code> 的数据类型定义文件。</li>
<li>零个或多个接口,每个都位于各自的 <code>.hal</code> 文件中。</li>
</ul>
</li>
</ul>
<p>数据类型定义文件 <code>types.hal</code> 中仅包含 UDT(所有软件包级 UDT 都保存在一个文件中)。采用目标语言的表示法可用于软件包中的所有接口。</p>
<h2 id="philosophy">版本编号理念</h2>
<p>针对指定版本(如 <code>1.0</code>)发布后,HIDL 软件包(如 <code>android.hardware.nfc</code>)便不可再改变;您无法对其进行更改。如果要对已发布软件包中的接口进行修改,或要对其 UDT 进行任何更改,都只能在另一个软件包中进行。<em></em></p>
<p>在 HIDL 中,版本编号是在软件包级而非接口级应用,并且软件包中的所有接口和 UDT 共用同一个版本。软件包版本遵循<a href="http://semver.org/" class="external">语义化版本编号</a>规则,不含补丁程序级别和编译元数据组成部分。在指定的软件包中,<strong>minor 版本</strong>更新意味着新版本的软件包向后兼容旧软件包,而 <strong>major 版本</strong>更新意味着新版本的软件包不向后兼容旧软件包。</p>
<p>从概念上来讲,软件包可通过以下方式之一与另一个软件包相关:</p>
<ul>
<li><strong>完全不相关</strong></li>
<li><strong>软件包级向后兼容的可扩展性</strong>。软件包的新 minor 版本升级(下一个递增的修订版本)中会出现这种情况;新软件包拥有与旧软件包一样的名称和 major 版本,但其 minor 版本会更高。从功能上来讲,新软件包是旧软件包的超集,也就是说:
<ul>
<li>父级软件包的顶级接口会包含在新的软件包中,不过这些接口可以在 <code>types.hal</code> 中有新的方法、新的接口本地 UDT(下文所述的接口级扩展)和新的 UDT。</li>
<li>新接口也可以添加到新软件包中。</li>
<li>父级软件包的所有数据类型均会包含在新软件包中,并且可由来自旧软件包中的方法(可能经过了重新实现)来处理。
</li>
<li>新数据类型也可以添加到新软件包中,以供升级的现有接口的新方法使用,或供新接口使用。</li>
</ul>
</li>
<li><strong>接口级向后兼容的可扩展性</strong>。新软件包还可以扩展原始软件包,方法是包含逻辑上独立的接口,这些接口仅提供附加功能,并不提供核心功能。要实现这一目的,可能需要满足以下条件:
<ul>
<li>新软件包中的接口需要依赖于旧软件包的数据类型。</li>
<li>新软件包中的接口可以扩展一个或多个旧软件包中的接口。</li>
</ul>
</li>
<li><strong>扩展原始的向后不兼容性</strong>。这是软件包的一种 major 版本升级,并且新旧两个版本之间不需要存在任何关联。如果存在关联,则这种关联可以通过以下方式来表示:组合旧版本软件包中的类型,以及继承旧软件包中的部分接口。</li>
</ul>
<h2 id="structuring">构建接口</h2>
<p>对于结构合理的接口,要添加不属于原始设计的新类型的功能,应该需要修改 HIDL 接口。反过来,如果您可以或想对接口两侧进行更改以引入新功能,而无需更改接口本身,则说明接口未进行结构化。</p>
<p>Treble 支持单独编译的供应商组件和系统组件,其中设备上的 <code>vendor.img</code> 以及 <code>system.img</code> 可单独编译。<code>vendor.img</code><code>system.img</code> 之间的所有互动都必须具有明确且详尽的定义,以便其能够继续运行多年。这包括许多 API 表面,但主要表面是 HIDL 在 <code>system.img</code>/<code>vendor.img</code> 边界上进行进程间通信时所使用的 IPC 机制。</p>
<h3 id="structuring-requirements">要求</h3>
<p>所有通过 HIDL 传递的数据都必须进行明确的定义。要确保实现和客户端可以继续协同工作(即使进行单独编译或独立开发也不受影响),数据必须符合以下要求:</p>
<ul>
<li>可使用有语义的名称和含义直接以 HIDL 进行描述(使用结构体枚举等)。</li>
<li>可依照 ISO/IEC 7816 等公共标准进行描述。</li>
<li>可依照硬件标准或硬件物理布局进行描述。</li>
<li>如有必要,可以是不透明数据(如公钥、ID 等)。</li>
</ul>
<p>如果使用不透明数据,则只能在 HIDL 接口的一侧读取相关数据。例如,如果 <code>vendor.img</code> 代码为 <code>system.img</code> 上的某个组件提供了一项字符串消息或 <code>vec&lt;uint8_t&gt;</code> 数据,则这项数据不能由 <code>system.img</code> 自行解析,只能传回到 <code>vendor.img</code> 进行解读。<strong>在将值从 <code>vendor.img</code> 传递到 <code>system.img</code> 上的供应商代码或其他设备时,相关数据的格式及其解读方式必须准确描述,并且仍是相应接口的一部分。</strong></p>
<h3 id="structuring-guidelines">准则</h3>
<p>您应该只需使用 .hal 文件即可编写 HAL 实现或客户端(即,您无需查看 Android 源代码或公共标准)。我们建议您指定确切的所需行为。“一个实现可以执行 A 或 B”之类的语句会导致实现与开发实现所使用的客户端之间互相交织。</p>
<h2 id="code-layout">HIDL 代码布局</h2>
<p>HIDL 包括核心软件包和供应商软件包。</p>
<p>核心 HIDL 接口是指由 Google 指定的接口。此类接口所属的软件包以 <code>android.hardware.</code> 开头,并以子系统命名(可能采用嵌套层命名方式)。例如,NFC 软件包命名为 <code>android.hardware.nfc</code>,而摄像头软件包命名为 <code>android.hardware.camera</code>。一般来说,核心软件包的名称为 <code>android.hardware.</code>[<code>name1</code>].[<code>name2</code>]…。HIDL 软件包除了其名称之外,还有版本。例如,软件包 <code>android.hardware.camera</code> 的版本可以是 <code>3.4</code>;这一点非常重要,因为软件包的版本会影响其在源代码树中的位置。
</p>
<p>所有核心软件包都位于编译系统中的 <code>hardware/interfaces/</code> 下。<code>$m.$n</code> 版本的软件包 <code>android.hardware.</code>[<code>name1</code>].[<code>name2</code>]… 位于 <code>hardware/interfaces/name1/name2/</code><code>/$m.$n/</code> 下;<code>3.4</code> 版本的软件包 <code>android.hardware.camera</code> 位于目录 <code>hardware/interfaces/camera/3.4/.</code> 下。 软件包前缀 <code>android.hardware.</code> 和路径 <code>hardware/interfaces/</code> 之间存在硬编码映射。</p>
<p>非核心(供应商)软件包是指由 SoC 供应商或 ODM 开发的软件包。非核心软件包的前缀是 <code>vendor.$(VENDOR).hardware.</code>,其中 <code>$(VENDOR)</code> 是指 SoC 供应商或 OEM/ODM。此前缀映射到源代码树中的路径 <code>vendor/$(VENDOR)/interfaces</code>(该映射也属于硬编码映射)。</p>
<h2 id="fqn">用户定义的类型的完全限定名称</h2>
<p>在 HIDL 中,每个 UDT 都有一个完全限定名称,该名称由 UDT 名称、定义 UDT 的软件包名称,以及软件包版本组成。完全限定名称仅在声明类型的实例时使用,在定义类型本身时不使用。例如,假设 <code>1.0</code> 版本的软件包 <code>android.hardware.nfc,</code> 定义了一个名为 <code>NfcData</code> 的结构体。在声明位置(无论是在 <code>types.hal</code> 中,还是在接口的声明中),声明中仅注明:</p>
<pre class="prettyprint">
struct NfcData {
vec&lt;uint8_t&gt; data;
};
</pre>
<p>声明此类型的实例(无论是在数据结构中,还是作为方法参数)时,请使用完全限定类型名称:</p>
<pre class="prettyprint">android.hardware.nfc@1.0::NfcData</pre>
<p>一般语法是 <code><var>PACKAGE</var>@<var>VERSION</var>::<var>UDT</var></code>,其中:</p>
<ul>
<li><code><var>PACKAGE</var></code> 是 HIDL 软件包的点分隔名称(例如,<code>android.hardware.nfc</code>)。</li>
<li><code><var>VERSION</var></code> 是软件包的点分隔 major.minor 版本格式(例如,<code>1.0</code>)。</li>
<li><code><var>UDT</var></code> 是 HIDL UDT 的点分隔名称。由于 HIDL 支持嵌套式 UDT,并且 HIDL 接口可以包含 UDT(一种嵌套式声明类型),因此采用点来访问名称。</li>
</ul>
<p>例如,如果以下嵌套式声明是在 <code>1.0</code> 版本的软件包 <code>android.hardware.example</code> 内的通用类型文件中定义的:</p>
<pre class="prettyprint">
// types.hal
package android.hardware.example@1.0;
struct Foo {
struct Bar {
// …
};
Bar cheers;
};
</pre>
<p><code>Bar</code> 的完全限定名称为 <code>android.hardware.example@1.0::Foo.Bar</code>。如果嵌套式声明除了位于上述软件包中之外,还位于名为 <code>IQuux</code> 的接口中:</p>
<pre class="prettyprint">
// IQuux.hal
package android.hardware.example@1.0;
interface IQuux {
struct Foo {
struct Bar {
// …
};
Bar cheers;
};
doSomething(Foo f) generates (Foo.Bar fb);
};
</pre>
<p><code>Bar</code> 的完全限定名称为 <code>android.hardware.example@1.0::IQuux.Foo.Bar</code></p>
<p>在上述两种情况下,只有在 <code>Foo</code> 的声明范围内才能使用 <code>Bar</code> 来引用 <code>Bar</code>。在软件包级或接口级,必须通过 <code>Foo</code>:<code>Foo.<code>Bar</code> 来引用 Bar</code>(如上述方法 <code>doSomething</code> 的声明中所示)。或者,您可以更详细地将该方法声明为:</p>
<pre class="prettyprint">
// IQuux.hal
doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);
</pre>
<h2 id="enumeration">完全限定的枚举值</h2>
<p>如果 UDT 是一种枚举类型,则该枚举类型的每个值都会有一个完全限定名称,这些名称以该枚举类型的完全限定名称开头,后跟一个冒号,然后是相应枚举值的名称。例如,假设 <code>1.0</code> 版本的软件包 <code>android.hardware.nfc,</code> 定义了一个枚举类型 <code>NfcStatus</code></p>
<pre class="prettyprint">
enum NfcStatus {
STATUS_OK,
STATUS_FAILED
};
</pre>
<p>则引用 <code>STATUS_OK</code> 时,完全限定名称为:</p>
<pre class="prettyprint">android.hardware.nfc@1.0::NfcStatus:STATUS_OK</pre>
<p>一般语法是 <code><var>PACKAGE</var>@<var>VERSION</var>::<var>UDT</var>:<var>VALUE</var></code>,其中:
</p><ul>
<li><code><var>PACKAGE</var>@<var>VERSION</var>::<var>UDT</var></code> 与枚举类型的完全限定名称完全相同。</li>
<li><code><var>VALUE</var></code> 是值的名称。</li>
</ul>
<h2 id="auto-interference">自动推理规则</h2>
<p>无需指定完全限定的 UDT 名称。UDT 名称可以安全地省略以下各项:</p>
<ul>
<li>软件包,例如 <code>@1.0::IFoo.Type</code></li>
<li>软件包和版本,例如 <code>IFoo.Type</code></li>
</ul>
<aside class="caution"><strong>注意</strong>:不允许使用缺少版本但指定了存在软件包的 UDT 名称。</aside>
<p>HIDL 会尝试使用自动推理规则补全名称(规则号越低,优先级越高)。</p>
<h3 id="rule1">规则 1</h3>
<p>如果未提供任何软件包和版本,则系统会尝试在本地查找名称。例如:</p>
<pre class="prettyprint">
interface Nfc {
typedef string NfcErrorMessage;
send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m);
};
</pre>
<p>系统在本地查找 <code>NfcErrorMessage</code>,并发现了其上方的 <code>typedef</code>。系统还会在本地查找 <code>NfcData</code>,但由于它未在本地定义,因此系统会使用规则 2 和 3。<code>@1.0::NfcStatus</code> 提供了版本,所以规则 1 并不适用。</p>
<h3 id="rule2">规则 2</h3>
<p>如果规则 1 失败,并且完全限定名称的某个组成部分(软件包、版本,或软件包和版本)缺失,则系统会自动使用当前软件包中的信息填充该组成部分。然后,HIDL 编译器会在当前文件(和所有导入内容)中查找自动填充的完全限定名称。以上面的示例来说,假设 <code>ExtendedNfcData</code> 是在声明 <code>NfcData</code> 的同一版本 (<code>1.0</code>) 的同一软件包 (<code>android.hardware.nfc</code>) 中声明的,如下所示:</p>
<pre class="prettyprint">
struct ExtendedNfcData {
NfcData base;
// … additional members
};
</pre>
<p>HIDL 编译器会填上当前软件包中的软件包名称和版本名称,以生成完全限定的 UDT 名称 <code>android.hardware.nfc@1.0::NfcData</code>。由于该名称在当前软件包中已存在(假设它已正确导入),因此它会用于声明。</p>
<p>仅当以下条件之一为 true 时,才会导入当前软件包中的名称:</p>
<ul>
<li>使用 <code>import</code> 语句显式导入相应名称。</li>
<li>相应名称是在当前软件包中的 <code>types.hal</code> 内定义的。</li>
</ul>
<p>如果 <code>NfcData</code> 仅由版本号限定,则遵循相同的过程:</p>
<pre class="prettyprint">
struct ExtendedNfcData {
// autofill the current package name (android.hardware.nfc)
@1.0::NfcData base;
// … additional members
};
</pre>
<h3 id="rule3">规则 3</h3>
<p>如果规则 2 未能生成匹配项(UDT 未在当前软件包中定义),HIDL 编译器会扫描所有导入的软件包,查找是否有匹配项。以上面的示例来说,假设 <code>ExtendedNfcData</code> 是在 <code>1.1</code> 版本的软件包 <code>android.hardware.nfc</code> 中声明的,则 <code>1.1</code> 会按预期导入 <code>1.0</code>(请参阅<a href="#package-ext">软件包级扩展</a>),且定义只会指定 UDT 名称:</p>
<pre class="prettyprint">
struct ExtendedNfcData {
NfcData base;
// … additional members
};
</pre>
<p>编译器查找名称为 <code>NfcData</code> 的所有 UDT,并在 <code>1.0</code> 版本的 <code>android.hardware.nfc</code> 中找到一个,从而生成 <code>android.hardware.nfc@1.0::NfcData</code> 这一完全限定的 UDT。如果针对指定的部分限定 UDT 找到多个匹配项,则 HIDL 编译器会抛出错误。</p>
<h3 id="rule-example">示例</h3>
<p>如果使用规则 2,则当前软件包中定义的导入式类型会比来自其他软件包的导入式类型更受青睐:</p>
<pre class="prettyprint">
// hardware/interfaces/foo/1.0/types.hal
package android.hardware.foo@1.0;
struct S {};
// hardware/interfaces/foo/1.0/IFooCallback.hal
package android.hardware.foo@1.0;
interface IFooCallback {};
// hardware/interfaces/bar/1.0/types.hal
package android.hardware.bar@1.0;
typedef string S;
// hardware/interfaces/bar/1.0/IFooCallback.hal
package android.hardware.bar@1.0;
interface IFooCallback {};
// hardware/interfaces/bar/1.0/IBar.hal
package android.hardware.bar@1.0;
import android.hardware.foo@1.0;
interface IBar {
baz1(S s); // android.hardware.bar@1.0::S
baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback
};
</pre>
<ul>
<li>插入 <strong><code>S</code></strong> 后得到 <code>android.hardware.bar@1.0::S</code>,并可在 <code>bar/1.0/types.hal</code> 中找到它(因为 <code>types.hal</code> 是自动导入的)。</li>
<li>使用规则 2 插入 <strong><code>IFooCallback</code></strong> 后得到 <code>android.hardware.bar@1.0::IFooCallback</code>,但无法找到它,因为 <code>bar/1.0/IFooCallback.hal</code> 不是自动导入的(<code>types.hal</code> 是自动导入的)。因此,规则 3 会将其解析为 <code>android.hardware.foo@1.0::IFooCallback</code>(通过 <code>import android.hardware.foo@1.0;</code> 导入)。</li>
</ul>
<h2 id="types">types.hal</h2>
<p>每个 HIDL 软件包都包含一个 <code>types.hal</code> 文件,该文件中包含参与相应软件包的所有接口共享的 UDT。不论 UDT 是在 <code>types.hal</code> 中还是在接口声明中声明的,HIDL 类型始终是公开的,您可以在这些类型的定义范围之外访问它们。<code>types.hal</code> 并非为了描述软件包的公共 API,而是为了托管软件包内的所有接口使用的 UDT。HIDL 的性质决定了所有 UDT 都是接口的一部分。</p>
<p><code>types.hal</code> 由 UDT 和 <code>import</code> 语句组成。因为 <code>types.hal</code> 可供软件包的每个接口使用(它是一种隐式导入),所以按照定义,这些 <code>import</code> 语句是软件包级的。此外,<code>types.hal</code> 中的 UDT 还可以整合导入的 UDT 和接口。</p>
<p>例如,对于 <code>IFoo.hal</code></p>
<pre class="prettyprint">
package android.hardware.foo@1.0;
// whole package import
import android.hardware.bar@1.0;
// types only import
import android.hardware.baz@1.0::types;
// partial imports
import android.hardware.qux@1.0::IQux.Quux;
// partial imports
import android.hardware.quuz@1.0::Quuz;
</pre>
<p>会导入以下内容:</p>
<ul>
<li><code>android.hidl.base@1.0::IBase</code>(隐式)</li>
<li><code>android.hardware.foo@1.0::types</code>(隐式)</li>
<li><code>android.hardware.bar@1.0</code> 中的所有内容(包括所有接口及其 <code>types.hal</code></li>
<li><code>android.hardware.baz@1.0::types</code> 中的 <code>types.hal</code><code>android.hardware.baz@1.0</code> 中的接口不会被导入)</li>
<li><code>android.hardware.qux@1.0</code> 中的 <code>IQux.hal</code><code>types.hal</code></li>
<li><code>android.hardware.quuz@1.0</code> 中的 <code>Quuz</code>(假设 <code>Quuz</code> 是在 <code>types.hal</code> 中定义的,整个 <code>types.hal</code> 文件经过解析,但除 <code>Quuz</code> 之外的类型都不会被导入)。</li>
</ul>
<h2 id="interface-version">接口级版本编号</h2>
<p>软件包中的每个接口都位于各自的文件中。接口所属的软件包是使用 <code>package</code> 语句在接口的顶部声明的。在软件包声明之后,可以列出零个或多个接口级导入(部分或完整软件包)。例如:</p>
<pre class="prettyprint">package android.hardware.nfc@1.0;</pre>
<p>在 HIDL 中,接口可以使用 <code>extends</code> 关键字从其他接口继承。如果一个接口要扩展另一个接口,那么前者必须有权通过 <code>import</code> 语句访问后者。被扩展的接口(基接口)的名称遵循以上所述的类型名称限定规则。接口只能从一个接口继承;HIDL 不支持多重继承。</p>
<p>下面的升级版本编号示例使用的是以下软件包:</p>
<pre class="prettyprint">
// types.hal
package android.hardware.example@1.0
struct Foo {
struct Bar {
vec&lt;uint32_t&gt; val;
};
};
// IQuux.hal
package android.hardware.example@1.0
interface IQuux {
fromFooToBar(Foo f) generates (Foo.Bar b);
}
</pre>
<h3 id="rules">升级规则</h3>
<p>要定义软件包 <code>package@major.minor</code>,则 A 必须为 true,或 B 中的所有项必须为 true:</p>
<table>
<tbody><tr>
<th width="10%">规则 A</th>
<td>“是起始 minor 版本”:所有之前的 minor 版本(<code>package@major.0</code><code>package@major.1</code><code>package@major.(minor-1)</code>)必须均未定义。
</td>
</tr>
</tbody></table>
<strong></strong>
<table>
<tbody><tr>
<th width="10%">规则 B</th>
<td><p>以下各项均为 true:</p>
<ol>
<li>“以前的 minor 版本有效”:<code>package@major.(minor-1)</code> 必须已定义,并且遵循相同的规则 A(从 <code>package@major.0</code><code>package@major.(minor-2)</code> 均未定义)或规则 B(如果它是从 <code>@major.(minor-2)</code> 升级而来);
<br /><br />
<br /><br />
</li>
<li>“继承至少一个具有相同名称的接口”:存在扩展 <code>package@major.(minor-1)::IFoo</code> 的接口 <code>package@major.minor::IFoo</code>(如果前一个软件包具有接口);
<br /><br />
<br /><br />
</li>
<li>“没有具有不同名称的继承接口”:不得存在扩展 <code>package@major.(minor-1)::IBaz</code><code>package@major.minor::IBar</code>,其中 <code>IBar</code><code>IBaz</code> 是两个不同的名称。如果存在具有相同名称的接口,则 <code>package@major.minor::IBar</code> 必须扩展 <code>package@major.(minor-k)::IBar</code>,以确保不存在 k 较小的 IBar。</li>
</ol>
</td>
</tr>
</tbody></table>
<p>由于规则 A:</p>
<ul>
<li>软件包可以使用任何起始 minor 版本号(例如,<code>android.hardware.biometrics.fingerprint</code> 的起始版本号是 <code>@2.1</code>)。</li>
<li><code>android.hardware.foo@1.0</code> 未定义”这项要求意味着目录 <code>hardware/interfaces/foo/1.0</code> 甚至不应存在。
</li>
</ul>
<p><em></em>不过,规则 A 不会影响软件包名称相同但 major 版本不同的软件包(例如,<code>android.hardware.camera.device</code> 定义了 <code>@1.0</code><code>@3.2</code><code>@3.2</code> 无需与 <code>@1.0</code> 进行交互)。因此,<code>@3.2::IExtFoo</code> 可扩展 <code>@1.0::IFoo</code></p>
<p>如果软件包名称不同,则 <code>package@major.minor::IBar</code> 可从名称不同的接口进行扩展(例如,<code>android.hardware.bar@1.0::IBar</code> 可扩展 <code>android.hardware.baz@2.2::IBaz</code>)。如果接口未使用 <code>extend</code> 关键字显式声明超类型,它将扩展 <code>android.hidl.base@1.0::IBase</code><code>IBase</code> 本身除外)。</p>
<p>必须同时遵循 B.2 和 B.3。例如,即使 <code>android.hardware.foo@1.1::IFoo</code> 扩展 <code>android.hardware.foo@1.0::IFoo</code>,以通过规则 B.2,但如果 <code>android.hardware.foo@1.1::IExtBar</code> 扩展 <code>android.hardware.foo@1.0::IBar</code>,那么这仍不是一次有效的升级。
</p>
<h3 id="uprev">升级接口</h3>
<p><code>android.hardware.example@1.0</code>(在上文中进行了定义)升级到 <code>@1.1</code></p>
<pre class="prettyprint">
// types.hal
package android.hardware.example@1.1;
<strong>import android.hardware.example@1.0;</strong>
// IQuux.hal
package android.hardware.example@1.1
interface IQuux <strong>extends @1.0::IQuux</strong> {
<strong>fromBarToFoo(Foo.Bar b) generates (Foo f);</strong>
}
</pre>
<p>这是 <code>types.hal</code><code>1.0</code> 版本的 <code>android.hardware.example</code> 的软件包级 <code>import</code>。虽然 <code>1.1</code> 版本的软件包中没有添加新的 UDT,但仍需引用 <code>1.0</code> 版本中的 UDT,因此是 <code>types.hal</code> 中的软件包级导入。(借助 <code>IQuux.hal</code> 中的接口级导入可以实现相同的效果。)</p>
<p><code>IQuux</code> 声明中的 <code>extends @1.0::IQuux</code> 内,我们指定了被继承的 <code>IQuux</code> 的版本(需要澄清说明,因为 <code>IQuux</code> 用于声明接口和从接口继承)。由于声明只是名称(会继承位于声明位置处的所有软件包和版本属性),因此澄清说明必须位于基接口的名称中;我们可能也使用了完全限定的 UDT,但这样做是多余的。</p>
<p>新接口 <code>IQuux</code> 不会重新声明它从 <code>@1.0::IQuux</code> 继承的方法 <code>fromFooToBar()</code>;它只会列出它添加的新方法 <code>fromBarToFoo()</code>。在 HIDL 中,<strong>不得</strong>在子接口中重新声明继承的方法,因此 <code>IQuux</code> 接口无法显式声明 <code>fromFooToBar()</code> 方法。</p>
<aside class="key-point"><strong>要点</strong>:在 HIDL 中,每个从基类继承的方法都必须在继承类中显式实现。如果方法实现需要回退到相关基类的方法实现,则回退必须位于实现中。</aside>
<h3 id="conventions">升级规范</h3>
<p>有时接口名称必须重新命名扩展接口。我们建议枚举扩展、结构体和联合采用与其扩展的内容相同的名称,除非它们有足够多的不同之处,有必要使用新名称。例如:</p>
<pre class="prettyprint">
// in parent hal file
enum Brightness : uint32_t { NONE, WHITE };
// in child hal file extending the existing set with additional similar values
enum Brightness : @1.0::Brightness { AUTOMATIC };
// extending the existing set with values that require a new, more descriptive name:
enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };
</pre>
<p>除非方法有必要使用新名称,否则应采用与其扩展的内容相似的名称。例如,<code>@1.1::IFoo</code> 中的方法 <code>foo_1_1</code> 可以取代 <code>@1.0::IFoo</code><code>foo</code> 方法的功能。</p>
<h2 id="package-ext">软件包级版本编号</h2>
<p>HIDL 版本编号在软件包级进行;软件包一经发布,便不可再改变(它的一套接口和 UDT 无法更改)。软件包可通过多种方式彼此建立关系,所有这些关系都可通过接口级继承和构建 UDT 的组合(按构成)来表示。
</p>
<p>不过,有一种类型的关系经过严格定义,且必须强制执行,即软件包级向后兼容的继承。<em></em>在这种情况下,父级软件包是被继承的软件包,而子软件包是扩展父级的软件包。<em></em><em></em>软件包级向后兼容的继承规则如下:</p>
<ol>
<li>父级软件包的所有顶级接口都会被子级软件包中的接口继承。</li>
<li>新接口也可以添加到新软件包中(与其他软件包中其他接口的关系不受限制)。</li>
<li>新数据类型也可以添加到新软件包中,以供升级的现有接口的新方法使用,或供新接口使用。</li>
</ol>
<p>这些规则可以使用 HIDL 接口级继承和 UDT 构成来实现,但需要元级知识才能了解这些关系如何构成向后兼容的软件包扩展。元级知识按以下方式推断:</p>
<aside class="key-point"><strong>要点</strong>:对于 <code>major.minor</code> 版本的软件包 <code>package</code>,如果存在 <code>major.(minor-1)</code> 版本的 <code>package</code>,则 <code>package@major.minor</code> 属于 minor 版本升级,并且必须遵循向后兼容性规则。</aside>
<p>如果软件包符合这一要求,则 <code>hidl-gen</code> 会强制执行向后兼容性规则。</p>
</body></html>