Quản lý tệp kê khai

Trang này mô tả cách các công cụ xây dựng của chúng tôi nhập tệp kê khai để xác định giao diện và trải nghiệm cuối cùng cho ứng dụng của bạn.

Để xem giới thiệu về tệp kê khai ứng dụng, hãy đọc phần tổng quan về tệp kê khai ứng dụng.

Hợp nhất nhiều tệp kê khai

Tệp APK hoặc Android App Bundle của bạn chỉ có thể chứa một tệp AndroidManifest.xml, nhưng dự án Android Studio có thể chứa một số tệp – do nhóm tài nguyên chính, các biến thể bản dựng và thư viện được nhập vào cung cấp. Vì vậy, khi tạo ứng dụng, bản dựng Gradle sẽ hợp nhất tất cả các tệp kê khai thành một tệp kê khai duy nhất được đóng gói vào ứng dụng của bạn.

Công cụ sáp nhập tệp kê khai kết hợp tất cả các phần tử XML từ mỗi tệp bằng cách theo dõi một số phỏng đoán hợp nhất và bằng cách tuân theo các tuỳ chọn hợp nhất mà bạn đã xác định bằng các thuộc tính XML đặc biệt. Trang này mô tả cách quá trình hợp nhất tệp kê khai hoạt động và cách bạn có thể áp dụng tuỳ chọn hợp nhất để giải quyết các xung đột về việc hợp nhất.

Mẹo: Sử dụng chế độ xem Tệp kê khai đã hợp nhất để xem trước kết quả của tệp kê khai sáp nhập và tìm các lỗi xung đột.

Hợp nhất các mức độ ưu tiên

Công cụ sáp nhập kết hợp tất cả các tệp kê khai vào một tệp bằng cách hợp nhất các tệp này theo tuần tự dựa trên mức độ ưu tiên của từng tệp kê khai. Ví dụ: nếu bạn có ba tệp kê khai, tệp kê khai có mức độ ưu tiên thấp nhất sẽ được hợp nhất vào mức độ ưu tiên cao nhất tiếp theo và sau đó được hợp nhất vào tệp kê khai có mức độ ưu tiên cao nhất, như được minh hoạ trong hình 1.

Hình 1. Quá trình hợp nhất ba tệp kê khai, mức độ ưu tiên thấp nhất (bên trái) thành mức độ ưu tiên cao nhất (bên phải)

Có ba loại tệp kê khai cơ bản mà bạn có thể hợp nhất vào từng tệp khác, và mức độ ưu tiên hợp nhất của các tệp đó như sau (ưu tiên cao nhất trước):

  1. Tệp kê khai cho biến thể bản dựng

    Nếu bạn có nhiều nhóm tài nguyên cho biến thể của mình, mức độ ưu tiên của các tệp kê khai sẽ như sau:

    1. Tệp kê khai biến thể bản dựng (chẳng hạn như src/demoDebug/)
    2. Tệp kê khai loại bản dựng (chẳng hạn như src/debug/)
    3. Tệp kê khai hương vị sản phẩm (chẳng hạn như src/demo/)

      Nếu bạn đang sử dụng nhóm phiên bản, mức độ ưu tiên của tệp kê khai sẽ tương ứng với thứ tự được liệt kê trong thuộc tính flavorDimensions (thứ nhất là mức độ ưu tiên cao nhất).

  2. Tệp kê khai chính cho mô-đun ứng dụng
  3. Tệp kê khai từ một thư viện đi kèm

    Nếu bạn có nhiều thư viện, mức độ ưu tiên của tệp kê khai sẽ khớp với thứ tự phần phụ thuộc (thứ tự xuất hiện trong khối dependencies của Gradle).

Ví dụ: một tệp kê khai thư viện được hợp nhất vào tệp kê khai chính, sau đó tệp kê khai chính được hợp nhất vào tệp kê khai biến thể bản dựng. Lưu ý rằng đây là các ưu tiên hợp nhất cho tất cả nhóm tài nguyên, như được mô tả trong Tạo bản dựng bằng nhóm tài nguyên.

Quan trọng: Cấu hình bản dựng từ tệp build.gradle sẽ ghi đè mọi thuộc tính tương ứng trong tệp kê khai sáp nhập. Ví dụ: minSdkVersion từ tệp build.gradle sẽ ghi đè thuộc tính so khớp trong phần tử tệp kê khai <uses-sdk>. Để tránh gây nhầm lẫn, bạn chỉ cần bỏ phần tử <uses-sdk> và chỉ xác định các thuộc tính này trong tệp build.gradle. Để biết thêm thông tin chi tiết, hãy xem phần Định cấu hình bản dựng.

Hợp nhất giả mạo xung đột

công cụ sáp nhập có thể so khớp mọi phần tử XML từ một tệp kê khai với phần tử tương ứng trong tệp kê khai khác một cách hợp lý. (Để biết chi tiết về cách hoạt động của tính năng so khớp, hãy xem chính sách hợp nhất).

Nếu một phần tử của tệp kê khai có mức độ ưu tiên thấp hơn không khớp với bất cứ phần tử nào trong tệp kê khai có mức độ ưu tiên cao hơn thì phần tử đó sẽ được thêm vào tệp kê khai sáp nhập. Tuy nhiên, nếu một phần tử phù hợp, thì công cụ sáp nhập sẽ cố gắng kết hợp tất cả thuộc tính từ mỗi phần tử đó thành cùng một phần tử. Nếu công cụ này phát hiện thấy cả hai tệp kê khai chứa cùng một thuộc tính có các giá trị khác nhau, thì sẽ xảy ra xung đột hợp nhất.

Bảng 1 mô tả các kết quả có thể xảy ra khi công cụ sáp nhập cố gắng kết hợp tất cả các thuộc tính trong cùng một phần tử.

Bảng 1. Hành vi hợp nhất mặc định cho các giá trị thuộc tính

Thuộc tính mức độ ưu tiên cao Thuộc tính mức độ ưu tiên thấp Kết quả được hợp nhất của thuộc tính
Không có giá trị Không có giá trị Không có giá trị (sử dụng giá trị mặc định)
Giá trị B Giá trị B
Giá trị A Không có giá trị Giá trị A
Giá trị A Giá trị A
Giá trị B Lỗi xung đột – bạn phải thêm một mã đánh dấu quy tắc hợp nhất

Tuy nhiên, có một vài tình huống mà công cụ sáp nhập hoạt động theo cách khác nhau để tránh xung đột khi hợp nhất:

  • Các thuộc tính trong phần tử <manifest> không bao giờ được hợp nhất với nhau mà chỉ sử dụng các thuộc tính từ tệp kê khai có mức độ ưu tiên cao nhất.
  • Thuộc tính android:required trong các phần tử <uses-feature><uses-library> sử dụng HO nên khi áp dụng xung đột, "true" sẽ được áp dụng và tính năng hoặc thư viện mà một tệp kê khai yêu cầu phải luôn bao gồm.
  • Các thuộc tính trong phần tử <uses-sdk> luôn sử dụng giá trị từ tệp kê khai có mức độ ưu tiên cao hơn, ngoại trừ các trường hợp sau:
    • Khi tệp kê khai có mức độ ưu tiên thấp hơn có giá trị minSdkVersion cao hơn, sẽ xảy ra lỗi trừ khi bạn áp dụng quy tắc hợp nhất overrideLibrary.
    • Khi tệp kê khai có mức độ ưu tiên thấp hơn có giá trị targetSdkVersion thấp hơn, công cụ sáp nhập sẽ sử dụng giá trị từ tệp kê khai có mức độ ưu tiên cao hơn, nhưng cũng thêm mọi quyền hệ thống cần thiết để đảm bảo rằng các thư viện đã nhập tiếp tục hoạt động đúng cách (đối với trường hợp trong đó Phiên bản Android cao hơn đã tăng mức độ hạn chế về quyền). Để biết thêm thông tin về hành vi này, hãy xem mục các quyền hệ thống ngầm ẩn.
  • Phần tử <intent-filter> không bao giờ khớp giữa các tệp kê khai. Mỗi phần tử được xem là duy nhất và được thêm vào phần tử mẹ chung trong tệp kê khai sáp nhập.

Đối với tất cả xung đột khác giữa các thuộc tính, bạn sẽ gặp lỗi và bạn phải hướng dẫn công cụ sáp nhập cách giải quyết lỗi đó bằng cách thêm thuộc tính đặc biệt vào tệp kê khai có mức độ ưu tiên cao hơn (xem phần tiếp theo về mã đánh dấu quy tắc hợp nhất).

Không phụ thuộc vào giá trị thuộc tính mặc định. Bởi vì tất cả thuộc tính duy nhất được kết hợp thành cùng một phần tử, nên điều này có thể dẫn đến kết quả không mong muốn nếu tệp kê khai có mức độ ưu tiên cao hơn thực sự phụ thuộc vào giá trị mặc định của thuộc tính mà không khai báo. Ví dụ: nếu tệp kê khai có mức độ ưu tiên cao hơn không khai báo thuộc tính android:launchMode, thì tệp kê khai sẽ sử dụng giá trị mặc định là "standard"; nhưng nếu tệp kê khai có mức độ ưu tiên thấp hơn khai báo thuộc tính này với giá trị khác thì giá trị đó sẽ được áp dụng cho tệp kê khai sáp nhập (ghi đè giá trị mặc định). Vì vậy bạn nên xác định rõ ràng từng thuộc tính theo ý mình. (Giá trị mặc định cho mỗi thuộc tính được đề cập trong Tài liệu tham khảo tại mục.)

Mã đánh dấu quy tắc hợp nhất

Mã đánh dấu quy tắc hợp nhất là một thuộc tính XML mà bạn có thể sử dụng để thể hiện mong muốn của mình về cách giải quyết các xung đột hợp nhất hoặc xoá các phần tử và thuộc tính không mong muốn. Bạn có thể áp dụng một mã đánh dấu cho toàn bộ phần tử hoặc chỉ các thuộc tính cụ thể trong một phần tử.

Khi hợp nhất hai tệp kê khai, công cụ sáp nhập tìm các mã đánh dấu này trong tệp kê khai có mức độ ưu tiên cao hơn.

Tất cả mã đánh dấu đều thuộc tools vùng chứa tên Android, do đó, trước tiên bạn phải khai báo không gian tên này trong phần tử <manifest> như minh hoạ dưới đây:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp"
    xmlns:tools="http://schemas.android.com/tools">

Điểm đánh dấu nút

Để áp dụng quy tắc hợp nhất cho toàn bộ phần tử XML (đối với tất cả thuộc tính trong một phần tử tệp kê khai nhất định và cho tất cả thẻ con của phần tử đó), hãy sử dụng các thuộc tính sau:

tools:node="merge"
Hợp nhất tất cả thuộc tính trong thẻ này và tất cả phần tử lồng nhau nếu không có xung đột bằng cách sử dụng phỏng đoán xung đột hợp nhất. Đây là hành vi mặc định cho các phần tử.

Tệp kê khai có mức độ ưu tiên thấp:

<activity android:name="com.example.ActivityOne"
    android:windowSoftInputMode="stateUnchanged">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Tệp kê khai có mức độ ưu tiên cao:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:node="merge">
</activity>

Kết quả tệp kê khai sáp nhập:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    android:windowSoftInputMode="stateUnchanged">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
tools:node="merge-only-attributes"
Chỉ hợp nhất các thuộc tính trong thẻ này; không hợp nhất các phần tử lồng nhau.

Tệp kê khai có mức độ ưu tiên thấp:

<activity android:name="com.example.ActivityOne"
    android:windowSoftInputMode="stateUnchanged">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <data android:type="image/*" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Tệp kê khai có mức độ ưu tiên cao:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:node="merge-only-attributes">
</activity>

Kết quả tệp kê khai sáp nhập:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    android:windowSoftInputMode="stateUnchanged">
</activity>
tools:node="remove"
Xoá phần tử này khỏi tệp kê khai sáp nhập. Mặc dù có vẻ như bạn chỉ nên xoá phần tử này, nhưng việc này là cần thiết khi bạn phát hiện một phần tử trong tệp kê khai sáp nhập mà bạn không cần và phần tử đó do mức độ ưu tiên thấp hơn cung cấp tệp kê khai nằm ngoài tầm kiểm soát của bạn (chẳng hạn như một thư viện đã nhập).

Tệp kê khai có mức độ ưu tiên thấp:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="cow"
      android:value="@string/moo"/>
  <meta-data android:name="duck"
      android:value="@string/quack"/>
</activity-alias>

Tệp kê khai có mức độ ưu tiên cao:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="cow"
      tools:node="remove"/>
</activity-alias>

Kết quả tệp kê khai sáp nhập:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="duck"
      android:value="@string/quack"/>
</activity-alias>
tools:node="removeAll"
Cũng giống như tools:node="remove", nhưng loại bỏ tất cả phần tử khớp với loại phần tử này (trong cùng một phần tử mẹ).

Tệp kê khai có mức độ ưu tiên thấp:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="cow"
      android:value="@string/moo"/>
  <meta-data android:name="duck"
      android:value="@string/quack"/>
</activity-alias>

Tệp kê khai có mức độ ưu tiên cao:

<activity-alias android:name="com.example.alias">
  <meta-data tools:node="removeAll"/>
</activity-alias>

Kết quả tệp kê khai sáp nhập:

<activity-alias android:name="com.example.alias">
</activity-alias>
tools:node="replace"
Thay thế hoàn toàn phần tử có mức độ ưu tiên thấp hơn. Nghĩa là nếu có một phần tử phù hợp trong tệp kê khai có mức độ ưu tiên thấp hơn, hãy bỏ qua và sử dụng phần tử này chính xác như cách phần tử đó xuất hiện trong tệp kê khai.

Tệp kê khai có mức độ ưu tiên thấp:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="cow"
      android:value="@string/moo"/>
  <meta-data android:name="duck"
      android:value="@string/quack"/>
</activity-alias>

Tệp kê khai có mức độ ưu tiên cao:

<activity-alias android:name="com.example.alias"
    tools:node="replace">
  <meta-data android:name="fox"
      android:value="@string/dingeringeding"/>
</activity-alias>

Kết quả tệp kê khai sáp nhập:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="fox"
      android:value="@string/dingeringeding"/>
</activity-alias>
tools:node="strict"
Không tạo được bản dựng bất cứ khi nào phần tử này trong tệp kê khai có mức độ ưu tiên thấp hơn không khớp chính xác với phần tử đó trong tệp kê khai có mức độ ưu tiên cao hơn (trừ khi đã được giải quyết bằng mã đánh dấu quy tắc hợp nhất khác). Thao tác này sẽ ghi đè các phỏng đoán xung đột hợp nhất. Ví dụ: nếu tệp kê khai có mức độ ưu tiên thấp hơn chỉ chứa thuộc tính bổ sung, việc tạo bản dựng sẽ không thành công (trong khi đó, hành vi mặc định sẽ thêm thuộc tính bổ sung vào tệp kê khai sáp nhập).

Tệp kê khai có mức độ ưu tiên thấp:

<activity android:name="com.example.ActivityOne"
    android:windowSoftInputMode="stateUnchanged">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Tệp kê khai có mức độ ưu tiên cao:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:node="strict">
</activity>

Điều này gây ra lỗi hợp nhất tệp kê khai. Hai phần tử tệp kê khai này không được khác nhau khi ở chế độ nghiêm ngặt. Vì vậy, bạn phải áp dụng mã đánh dấu quy tắc hợp nhất khác để giải quyết các khác biệt này. (Thông thường, hai tệp này sẽ hợp nhất với nhau như như minh hoạ trong ví dụ trên về tools:node="merge".)

Mã đánh dấu thuộc tính

Thay vào đó, để chỉ áp dụng quy tắc hợp nhất cho các thuộc tính cụ thể trong thẻ tệp kê khai, hãy sử dụng các thuộc tính sau. Mỗi thuộc tính chấp nhận một hoặc nhiều tên thuộc tính (bao gồm cả không gian tên của thuộc tính), phân cách bằng dấu phẩy.

tools:remove="attr, ..."
Xoá các thuộc tính đã chỉ định khỏi tệp kê khai sáp nhập. Mặc dù có vẻ như bạn có thể chỉ cần xoá các thuộc tính này, nhưng bạn cần sử dụng thuộc tính này khi tệp kê khai có mức độ ưu tiên thấp hơn các thuộc tính này và bạn cần đảm bảo rằng chúng không chuyển vào tệp kê khai sáp nhập.

Tệp kê khai có mức độ ưu tiên thấp:

<activity android:name="com.example.ActivityOne"
    android:windowSoftInputMode="stateUnchanged">

Tệp kê khai có mức độ ưu tiên cao:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:remove="android:windowSoftInputMode">

Kết quả tệp kê khai sáp nhập:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait">
tools:replace="attr, ..."
Thay thế các thuộc tính được chỉ định trong tệp kê khai có mức độ ưu tiên thấp hơn bằng các thuộc tính từ tệp kê khai này. Nói cách khác, hãy luôn giữ các giá trị của tệp kê khai có mức độ ưu tiên cao hơn.

Tệp kê khai có mức độ ưu tiên thấp:

<activity android:name="com.example.ActivityOne"
    android:theme="@oldtheme"
    android:exported="false"
    android:windowSoftInputMode="stateUnchanged">

Tệp kê khai có mức độ ưu tiên cao:

<activity android:name="com.example.ActivityOne"
    android:theme="@newtheme"
    android:exported="true"
    android:screenOrientation="portrait"
    tools:replace="android:theme,android:exported">

Kết quả tệp kê khai sáp nhập:

<activity android:name="com.example.ActivityOne"
    android:theme="@newtheme"
    android:exported="true"
    android:screenOrientation="portrait"
    android:windowSoftInputMode="stateUnchanged">
tools:strict="attr, ..."
Không tạo được bản dựng bất cứ khi nào các thuộc tính này trong tệp kê khai có mức độ ưu tiên thấp hơn không khớp chính xác với thuộc tính đó trong tệp kê khai có mức độ ưu tiên cao hơn. Đây là hành vi mặc định cho tất cả các thuộc tính, ngoại trừ những thuộc tính có hành vi đặc biệt như mô tả trong phỏng đoán xung đột.

Tệp kê khai có mức độ ưu tiên thấp:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="landscape">
</activity>

Tệp kê khai có mức độ ưu tiên cao:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:strict="android:screenOrientation">
</activity>

Điều này gây ra lỗi hợp nhất tệp kê khai. Bạn phải áp dụng mã đánh dấu quy tắc hợp nhất khác để giải quyết xung đột. (Lưu ý: Đây là hành vi mặc định, do đó ví dụ ở trên sẽ có kết quả tương tự nếu bạn xoá tools:strict="screenOrientation".)

Bạn cũng có thể áp dụng nhiều mã đánh dấu cho một phần tử như sau.

Tệp kê khai có mức độ ưu tiên thấp:

<activity android:name="com.example.ActivityOne"
    android:theme="@oldtheme"
    android:exported="false"
    android:allowTaskReparenting="true"
    android:windowSoftInputMode="stateUnchanged">

Tệp kê khai có mức độ ưu tiên cao:

<activity android:name="com.example.ActivityOne"
    android:theme="@newtheme"
    android:exported="true"
    android:screenOrientation="portrait"
    tools:replace="android:theme,android:exported"
    tools:remove="android:windowSoftInputMode">

Kết quả tệp kê khai sáp nhập:

<activity android:name="com.example.ActivityOne"
    android:theme="@newtheme"
    android:exported="true"
    android:allowTaskReparenting="true"
    android:screenOrientation="portrait">

Bộ chọn mã đánh dấu

Nếu bạn chỉ muốn áp dụng mã đánh dấu quy tắc hợp nhất cho một thư viện nhập cụ thể, hãy thêm thuộc tính tools:selector cùng với tên gói thư viện.

Ví dụ: với tệp kê khai sau đây, quy tắc hợp nhất remove chỉ được áp dụng khi tệp kê khai có mức độ ưu tiên thấp hơn nằm trong thư viện com.example.lib1.

<permission android:name="permissionOne"
    tools:node="remove"
    tools:selector="com.example.lib1">

Nếu tệp kê khai có mức độ ưu tiên thấp hơn đến từ nguồn khác, thì quy tắc hợp nhất remove sẽ bị bỏ qua.

Lưu ý: Nếu bạn sử dụng thuộc tính này với một trong các mã đánh dấu thuộc tính, thì thuộc tính này sẽ áp dụng cho tất cả các thuộc tính được chỉ định trong điểm đánh dấu.

Ghi đè <uses-sdk> cho các thư viện đã nhập

Theo mặc định, khi nhập một thư viện có giá trị minSdkVersion cao hơn so với tệp kê khai chính, sẽ xảy ra lỗi và không thể nhập thư viện. Để công cụ sáp nhập bỏ qua xung đột này và nhập thư viện trong khi vẫn giữ giá trị minSdkVersion thấp hơn của ứng dụng, hãy thêm thuộc tính overrideLibrary vào thẻ <uses-sdk>. Giá trị thuộc tính có thể là một hoặc nhiều tên gói thư viện (được phân tách bằng dấu phẩy), cho biết các thư viện có thể ghi đè minSdkVersion của tệp kê khai chính.

Ví dụ: nếu tệp kê khai chính của ứng dụng áp dụng overrideLibrary như sau:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.app"
          xmlns:tools="http://schemas.android.com/tools">
  <uses-sdk tools:overrideLibrary="com.example.lib1, com.example.lib2"/>
...

Sau đó, bạn có thể hợp nhất tệp kê khai sau mà không gặp lỗi liên quan đến thẻ <uses-sdk> và tệp kê khai sáp nhập giữ lại minSdkVersion="2" từ tệp kê khai ứng dụng.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.lib1">
   <uses-sdk android:minSdkVersion="4" />
...

Các quyền ngầm của hệ thống ngầm ẩn

Một số API Android mà trước đây ứng dụng có thể truy cập tự do đã bị giới hạn bởi quyền hệ thống trong các phiên bản Android gần đây. Để tránh làm hỏng các ứng dụng cần truy cập vào các API này, các phiên bản Android gần đây sẽ cho phép ứng dụng tiếp tục truy cập vào các API đó mà không cần quyền nếu đã thiết lập targetSdkVersion thành giá trị thấp hơn phiên bản đã được thêm quy định hạn chế vào. Hành vi này cấp cho ứng dụng một quyền ngầm ẩn để cho phép ứng dụng truy cập vào các API. Do đó, điều này có thể ảnh hưởng đến các tệp kê khai sáp nhập có các giá trị khác nhau cho targetSdkVersion theo cách sau.

Nếu tệp kê khai có mức độ ưu tiên thấp hơn có giá trị thấp hơn cho targetSdkVersion cung cấp cho nó quyền truy cập ngầm định, và tệp kê khai có mức độ ưu tiên cao hơnkhông có cùng quyền ngầm định (vìtargetSdkVersion bằng hoặc cao hơn phiên bản mà bạn đã thêm quy định hạn chế) thì công cụ sáp nhập thêm quyền hệ thống vào tệp kê khai đã sáp nhập một cách rõ ràng.

Ví dụ: nếu ứng dụng của bạn thiết lập targetSdkVersion thành 4 trở lên và nhập thư viện có targetSdkVersion được thiết lập thành 3 trở xuống, thì công cụ sáp nhập sẽ thêm quyền WRITE_EXTERNAL_STORAGE cho tệp kê khai sáp nhập. Bảng 2 liệt kê tất cả các quyền mà bạn có thể thêm vào tệp kê khai sáp nhập.

Bảng 2. Danh sách các quyền mà công cụ sáp nhập có thể thêm vào tệp kê khai sáp nhập

Khai báo tệp kê khai có mức độ ưu tiên thấp hơn Đã thêm các quyền vào tệp kê khai sáp nhập
targetSdkVersion từ 3 trở xuống WRITE_EXTERNAL_STORAGE, READ_PHONE_STATE
targetSdkVersion từ 15 trở xuống và sử dụng READ_CONTACTS READ_CALL_LOG
targetSdkVersion từ 15 trở xuống và sử dụng WRITE_CONTACTS WRITE_CALL_LOG

Kiểm tra tệp kê khai sáp nhập và tìm xung đột

Ngay cả trước khi tạo bản dựng ứng dụng, bạn có thể xem trước giao diện của tệp kê khai sáp nhập bằng cách mở tệp AndroidManifest.xml trong Android Studio, sau đó nhấp vào thẻ Tệp kê khai sáp nhập ở cuối trình chỉnh sửa.

Chế độ xem Tệp kê khai sáp nhập cho thấy kết quả của tệp kê khai đã hợp nhất ở bên trái và thông tin về từng tệp kê khai đã hợp nhất ở bên phải, như được minh hoạ trong hình 2. Phần tử được hợp nhất từ tệp kê khai có mức độ ưu tiên thấp hơn được làm nổi bật bằng các màu khác nhau ở bên trái. Khoá cho mỗi màu được chỉ định trong Nguồn tệp kê khai ở bên phải.

Hình 2. Chế độ xem Tệp kê khai sáp nhập

Các tệp kê khai là một phần của bản dựng nhưng không đóng góp cho các phần tử hoặc thuộc tính nào được liệt kê trong phần Tệp kê khai khác ở bên phải.

Để xem thông tin về nguồn gốc của một phần tử, hãy nhấp vào phần tử đó ở bên trái và thông tin chi tiết xuất hiện trong Nhật ký sáp nhập ở bên phải.

Nếu có xung đột xảy ra, các xung đột đó sẽ xuất hiện trong mục Lỗi sáp nhập ở bên phải và đề xuất cách giải quyết xung đột bằng cách sử dụng mã đánh dấu quy tắc hợp nhất. Các lỗi cũng được in trong cửa sổ Event Log (Nhật ký sự kiện) (chọn View > Tool Windows >Event Log (Xem > Cửa sổ công cụ > Nhật ký sự kiện)).

Nếu muốn xem nhật ký đầy đủ của cây quyết định hợp nhất, bạn có thể tìm tệp nhật ký trong thư mục build/outputs/logs/ của mô-đun có tên manifest-merger-buildVariant-report.txt.

Hợp nhất các chính sách

Công cụ sáp nhập tệp kê khai có thể so khớp mọi phần tử XML từ một tệp kê khai với một phần tử tương ứng trong một tệp khác một cách hợp lý. Trình hợp nhất so khớp mỗi phần tử bằng cách sử dụng "khoá so khớp": giá trị thuộc tính duy nhất (chẳng hạn như android:name) hoặc giá trị duy nhất tự nhiên của thẻ (ví dụ: chỉ có thể có một phần tử <supports-screen>). Nếu hai tệp kê khai có cùng một phần tử XML, thì công cụ này sẽ hợp nhất hai phần tử lại với nhau bằng một trong ba chính sách hợp nhất:

Hợp nhất
Kết hợp tất cả các thuộc tính không xung đột vào cùng một thẻ rồi hợp nhất các phần tử con theo chính sách hợp nhất tương ứng. Nếu có thuộc tính nào xung đột với nhau, hãy hợp nhất các thuộc tính đó bằng mã đánh dấu quy tắc hợp nhất.
Chỉ hợp nhất phần tử con
Không kết hợp hoặc hợp nhất các thuộc tính (chỉ giữ lại các thuộc tính do tệp kê khai có mức độ ưu tiên cao nhất cung cấp) và hợp nhất các phần tử con theo chính sách hợp nhất.
Giữ
Giữ nguyên phần tử "nguyên trạng" và thêm phần tử đó vào phần tử mẹ chung trong tệp đã hợp nhất. Bạn chỉ nên sử dụng thuộc tính này khi có thể chấp nhận một số nội dung khai báo của cùng một phần tử.

Bảng 1 liệt kê từng loại phần tử, loại chính sách hợp nhất được sử dụng, và khoá được dùng để xác định mức trùng khớp phần tử giữa hai tệp kê khai.

Bảng 3. Chính sách hợp nhất phần tử tệp kê khai và khoá trùng khớp

Phần tử Chính sách hợp nhất Khoá trùng khớp
<action> Hợp nhất Thuộc tính android:name
<activity> Hợp nhất Thuộc tính android:name
<application> Hợp nhất Chỉ có một cho mỗi <manifest>
<category> Hợp nhất Thuộc tính android:name
<data> Hợp nhất Chỉ có một cho mỗi <intent-filter>
<grant-uri-permission> Hợp nhất Chỉ có một cho mỗi <provider>
<instrumentation> Hợp nhất Thuộc tính android:name
<intent-filter> Giữ Không khớp; cho phép có nhiều khai báo trong phần tử mẹ
<manifest> Chỉ hợp nhất phần tử con Chỉ có một cho mỗi tệp
<meta-data> Hợp nhất Thuộc tính android:name
<path-permission> Hợp nhất Chỉ có một cho mỗi <provider>
<permission-group> Hợp nhất Thuộc tính android:name
<permission> Hợp nhất Thuộc tính android:name
<permission-tree> Hợp nhất Thuộc tính android:name
<provider> Hợp nhất Thuộc tính android:name
<receiver> Hợp nhất Thuộc tính android:name
<screen> Hợp nhất Thuộc tính android:screenSize
<service> Hợp nhất Thuộc tính android:name
<supports-gl-texture> Hợp nhất Thuộc tính android:name
<supports-screen> Hợp nhất Chỉ có một cho mỗi <manifest>
<uses-configuration> Hợp nhất Chỉ có một cho mỗi <manifest>
<uses-feature> Hợp nhất Thuộc tính android:name (nếu không có thì thuộc tính android:glEsVersion)
<uses-library> Hợp nhất Thuộc tính android:name
<uses-permission> Hợp nhất Thuộc tính android:name
<uses-sdk> Hợp nhất Chỉ có một cho mỗi <manifest>
Phần tử tuỳ chỉnh Hợp nhất Không khớp; những dữ liệu này không xác định với công cụ sáp nhập do đó chúng luôn có trong tệp kê khai sáp nhập

Chèn biến thể bản dựng vào tệp kê khai

Nếu cần chèn các biến vào tệp AndroidManifest.xml được xác định trong tệp build.gradle, bạn có thể thực hiện bằng thuộc tính manifestPlaceholders. Thuộc tính này sẽ liên kết các cặp khoá-giá trị, như minh hoạ ở đây:

Groovy

android {
    defaultConfig {
        manifestPlaceholders = [hostName:"www.example.com"]
    }
    ...
}

Kotlin

android {
    defaultConfig {
        manifestPlaceholders["hostName"] = "www.example.com"
    }
    ...
}

Sau đó, bạn có thể chèn một trong các phần giữ chỗ vào tệp kê khai dưới dạng giá trị thuộc tính như sau:

<intent-filter ... >
    <data android:scheme="https" android:host="${hostName}" ... />
    ...
</intent-filter>

Theo mặc định, các công cụ bản dựng cũng cung cấp mã ứng dụng của ứng dụng trong trình giữ chỗ ${applicationId}. Giá trị này luôn khớp với mã ứng dụng cuối cùng cho bản dựng hiện tại (bao gồm các thay đổi theo biến thể bản dựng. Điều này sẽ hữu ích khi bạn muốn sử dụng một vùng chứa tên duy nhất cho các giá trị nhận dạng, chẳng hạn như hành động theo ý định, ngay cả giữa các biến thể bản dựng.

Ví dụ: nếu tệp build.gradle của bạn trông giống như sau:

Groovy

android {
    defaultConfig {
        applicationId "com.example.myapp"
    }
    productFlavors {
        free {
            applicationIdSuffix ".free"
        }
        pro {
            applicationIdSuffix ".pro"
        }
    }
}

Kotlin

android {
    defaultConfig {
        applicationId = "com.example.myapp"
    }
    productFlavors {
        create("free") {
            applicationIdSuffix = ".free"
        }
        create("pro") {
            applicationIdSuffix = ".pro"
        }
    }
}

Sau đó, bạn có thể chèn mã ứng dụng vào tệp kê khai như sau:

<intent-filter ... >
    <action android:name="${applicationId}.TRANSMOGRIFY" />
    ...
</intent-filter>

Và kết quả tệp kê khai khi bạn tạo phiên bản sản phẩm "miễn phí" là:

<intent-filter ... >
   <action android:name="com.example.myapp.free.TRANSMOGRIFY" />
    ...
</intent-filter>

Để biết thêm thông tin, hãy đọc Thiết lập mã ứng dụng.