Quản lý tệp kê khai

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.

Trang này mô tả cách hoạt động của quá trình hợp nhất tệp kê khai và cách bạn có thể áp dụng phương án hợp nhất để giải quyết các xung đột trong quá trình đó. Để 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 những tệp kê khai được cung cấp bởi các nhóm tài nguyên chính, biến thể bản dựng và thư viện được nhập. Khi tạo ứng dụng, bản dựng Gradle sẽ hợp nhất mọi 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ông cụ sáp nhập tệp kê khai kết hợp toàn bộ phần tử XML trong mỗi tệp bằng cách theo dõi phỏng đoán hợp nhất và tuân thủ các phương án hợp nhất mà bạn đã xác định bằng những thuộc tính XML đặc biệt.

Mẹo: Hãy sử dụng chế độ xemMerged Manifest (Tệp kê khai sáp nhập) được mô tả trong phần tiếp theo để xem trước kết quả của tệp kê khai sáp nhập và tìm 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 toàn bộ tệp kê khai vào một tệp theo thứ 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, sau đó tiếp tục được hợp nhất vào tệp kê khai có mức độ ưu tiên cao nhất như minh hoạ trong hình 1.

Hình 1. Quá trình hợp nhất ba tệp kê khai: từ mức độ ưu tiên thấp nhất thành mức độ ưu tiên cao nhất.

Có ba loại tệp kê khai cơ bản có thể hợp nhất với nhau. Mức độ ưu tiên hợp nhất của những tệp này sẽ theo thứ tự như sau (mức độ ưu tiên cao nhất ở đầu tiên):

  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ể, mức độ ưu tiên của các tệp kê khai sẽ theo thứ tự như sau:

    • Tệp kê khai biến thể bản dựng (chẳng hạn như src/demoDebug/)
    • Tệp kê khai loại bản dựng (chẳng hạn như src/debug/)
    • Tệp kê khai phiên bản 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ự mà mỗi phiên bản đượ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 trong 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ự xuất hiện của những thư viện này 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 này sẽ được hợp nhất vào tệp kê khai biến thể bản dựng. Lưu ý rằng mức độ ưu tiên hợp nhất này được áp dụng cho mọi nhóm tài nguyên theo như mô tả trong phần Tạo bản dựng bằng nhóm tài nguyên.

Lưu ý quan trọng: Cấu hình bản dựng trong 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ụ:, minSdk trong tệp build.gradle hoặc build.gradle.kts sẽ ghi đè thuộc tính tương ứng trong phần tử tệp kê khai <uses-sdk>. Để tránh gây nhầm lẫn, hãy bỏ phần tử <uses-sdk> và chỉ xác định những 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.

Phỏng đoán xung đột hợp nhất

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

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

Bảng 1 mô tả những kết quả có thể xảy ra khi công cụ sáp nhập cố gắng kết hợp mọi thuộc tính vào 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ả 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 sẽ xử lý theo cách riêng để ngăn ngừa xung đột xảy ra trong quá trình 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ỉ có các thuộc tính trong tệp kê khai có mức độ ưu tiên cao nhất mới được sử dụng.
  • Thuộc tính android:required trong các phần tử <uses-feature><uses-library> sử dụng lệnh hợp nhất OR. Nếu có xung đột, "true" được áp dụng và tính năng hoặc thư viện mà tệp kê khai yêu cầu sẽ luôn được đưa vào.
  • Các thuộc tính trong phần tử <uses-sdk> luôn sử dụng giá trị trong tệp kê khai có mức độ ưu tiên cao hơn ngoại trừ những trường hợp sau:
    • Các tệp kê khai không thể hợp nhất khi tệp kê khai có mức độ ưu tiên thấp hơn có giá trị minSdk cao hơn, trừ phi bạn áp dụng quy tắc hợp nhất overrideLibrary.
    • Đối với trường hợp phiên bản Android mới tăng mức độ hạn chế về quyền, khi tệp kê khai có mức độ ưu tiên thấp có giá trị targetSdkVersion thấp hơn, công cụ sáp nhập sẽ sử dụng giá trị trong tệp kê khai có mức độ ưu tiên cao hơn, đồng thời bổ sung các quyền hệ thống cần thiết để đảm bảo những thư viện đã được nhập vào tiếp tục hoạt động đúng cách. Để biết thêm thông tin về hành vi này, hãy xem phần về 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ử sẽ được giữ nguyên và thêm vào phần tử mẹ chung trong tệp kê khai sáp nhập.

Đối với những xung đột còn lại giữa các thuộc tính, bạn sẽ nhận được thông báo lỗi và bạn cần hướng dẫn công cụ sáp nhập 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. Toàn bộ thuộc tính riêng được kết hợp vào trong cùng một phần tử có thể dẫn đến kết quả không mong muốn nếu như trên thực tế, tệp kê khai có mức độ ưu tiên cao hơn 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 không khai báo thuộc tính android:launchMode, tệp này 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 với giá trị khác thì giá trị này sẽ được áp dụng cho tệp kê khai sáp nhập và ghi đè giá trị mặc định. Vì vậy, bạn nên xác định rõ ràng các 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 về tệp kê khai.

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 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ỉ cho những thuộc tính cụ thể trong phần tử đó.

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

Mọi mã đánh dấu đều thuộc không gian tên tools Android, vì vậy, 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">

Mã đánh dấu nút

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

tools:node="merge"
Hợp nhất toàn bộ thuộc tính trong thẻ và phần tử lồng nhau bằng cách sử dụng giải pháp phỏng đoán xung đột hợp nhất trong trường hợp không có xung đột nào xảy ra. Đâ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 những 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ử khỏi tệp kê khai sáp nhập. Được dùng khi bạn phát hiện một phần tử trong tệp kê khai hợp nhất mà mình không cần đến và được cung cấp bởi tệp kê khai có mức độ ưu tiên thấp hơn nằm ngoài tầm kiểm soát của bạn (chẳng hạn như một thư viện đã được nhập vào).

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"
Tương tự như tools:node="remove" nhưng loại bỏ mọi 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. Tức là nếu có một phần tử tương tự trong tệp kê khai có mức độ ưu tiên thấp hơn, hãy bỏ qua và chỉ sử dụng đúng 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ử trong tệp kê khai có mức độ ưu tiên thấp không hoàn toàn khớp với phần tử trong tệp kê khai có mức độ ưu tiên cao hơn (trừ phi đã được giải quyết bằng các điể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>

Các tệp kê khai không thể hợp nhất. Ở chế độ nghiêm ngặt, hai phần tử tệp kê khai phải hoàn toàn giống nhau. Vì vậy, bạn cần áp dụng các mã đánh dấu quy tắc hợp nhất khác để xử lý những khác biệt này. (Nếu không có tools:node="strict", hai tệp này vẫn có thể hợp nhất mà không phạm lỗi, như minh hoạ trong ví dụ về tools:node="merge".)

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

Để chỉ áp dụng quy tắc hợp nhất cho những thuộc tính cụ thể trong một 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 ít nhất một tên thuộc tính, bao gồm cả không gian tên của thuộc tính và được phân tá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. Được dùng khi tệp kê khai có mức độ ưu tiên thấp hơn chứa các thuộc tính mà bạn muốn đảm bảo rằng những thuộc tính này sẽ không được đưa 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 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 trong tệp kê khai. Nói cách khác, hãy luôn giữ lại 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 trong tệp kê khai có mức độ ưu tiên thấp không hoàn toàn khớp với các 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 mọi 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ần phỏng đoán xung đột hợp nhấ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>

Các tệp kê khai không thể hợp nhất. Bạn cần phải áp dụng các mã đánh dấu quy tắc hợp nhất khác để giải quyết xung đột. Đây là hành vi mặc định, vì vậy khi thêm tools:strict="screenOrientation" một cách rõ ràng thì kết quả tương tự cũng xuất hiện.

Bạn cũng có thể áp dụng nhiều điểm đánh dấu cho một phần tử như trong ví dụ 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 cụ thể đã được nhập vào, 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, 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, quy tắc hợp nhất remove sẽ bị bỏ qua.

Lưu ý: Nếu bạn sử dụng quy tắc này với một trong các mã đánh dấu thuộc tính thì quy tắc sẽ được áp dụng cho mọi 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, việc nhập vào một thư viện có giá trị minSdk cao hơn so với tệp kê khai chính sẽ khiến thư viện này không được nhập vào.

Để công cụ sáp nhập bỏ qua xung đột và nhập thư viện mà vẫn duy trì giá trị minSdk 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ể ít nhất là một tên gói thư viện được phân tách bằng dấu phẩy, cho biết những thư viện có thể ghi đè minSdk 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 phạm lỗi liên quan đến thẻ <uses-sdk> và tệp kê khai sáp nhập sẽ giữ minSdk="2" lại qua tệp kê khai ứng dụng.

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

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

Một số tệp API Android mà trước đây ứng dụng có thể truy cập tự do nay bị giới hạn theo 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 yêu cầu quyền truy cập vào những API này, các phiên bản Android gần đây 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 truy cập nếu targetSdkVersion được đặt thành một giá trị thấp hơn so với phiên bản đã được thêm quy định hạn chế. Hành vi này cấp cho ứng dụng quyền ngầm ẩn để cho phép truy cập vào các API. Những tệp kê khai hợp nhất có nhiều giá trị cho targetSdkVersion có thể bị ảnh hưởng.

Nếu tệp kê khai có mức độ ưu tiên thấp chứa giá trị targetSdkVersion thấp hơn cung cấp cho tệp này quyền truy cập ngầm định và tệp kê khai có mức độ ưu tiên cao hơn không được cấp cùng một quyền (do targetSdkVersion bằng hoặc cao hơn so với phiên bản đã được thêm quy định hạn chế), công cụ sáp nhập sẽ 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 đặt 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, 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ê toàn bộ 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

Trước khi tạo ứng dụng, bạn vẫn có thể xem trước giao diện của tệp kê khai sáp nhập. Để xem trước, hãy làm như sau:

  1. Trong Android Studio, hãy mở tệp AndroidManifest.xml.
  2. Nhấp vào thẻ Merged Manifest (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 sáp nhập ở bên trái và thông tin về mỗi tệp kê khai đã sáp nhập ở bên phải, như 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 ở bên trái. Khoá cho mỗi màu được chỉ định trong Manifest Sources(Nguồn tệp kê khai).

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 các phần tử hoặc thuộc tính nào được liệt kê trong phần Other Manifest Files (Tệp kê khai khác).

Để 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ử đó trong ngăn bên trái và thông tin chi tiết sẽ xuất hiện trong phần Merging Log (Nhật ký hợp nhất).

Nếu có xung đột xảy ra, những xung đột này sẽ xuất hiện trong mục Merging Errors (Lỗi hợp nhất) kèm đề xuất giải quyết bằng cách sử dụng các điể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). Để xem các lỗi đó, hãy chọn View > Tool Windows > Event Log (Xem > Cửa sổ công cụ > Nhật ký sự kiện).

Để xem toàn bộ nhật ký 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 trong tệp kê khai với một phần tử tương ứng trong một tệp khác theo 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 theo dạng giá trị thuộc tính riêng (chẳng hạn như android:name) hoặc đặc điểm độc đáo 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, công cụ sẽ hợp nhất hai phần tử 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 mọi 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ó các thuộc tính xung đột với nhau, hãy hợp nhất những thuộc tính này 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 những 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ữ lại
Giữ lại phần tử 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 nhiều nội dung khai báo của cùng một phần tử.

Bảng 3 liệt kê các 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 khoá trùng khớp cho mỗi <manifest>
<category> Hợp nhất Thuộc tính android:name
<data> Hợp nhất Chỉ có một khoá trùng khớp cho mỗi <intent-filter>
<grant-uri-permission> Hợp nhất Chỉ có một khoá trùng khớp cho mỗi <provider>
<instrumentation> Hợp nhất Thuộc tính android:name
<intent-filter> Giữ lại 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 khoá trùng khớp 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 khoá trùng khớp 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 khoá trùng khớp cho mỗi <manifest>
<uses-configuration> Hợp nhất Chỉ có một khoá trùng khớp 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 khoá trùng khớp cho mỗi <manifest>
Phần tử tuỳ chỉnh Hợp nhất Không khớp, công cụ sáp nhập không thể xác định những dữ liệu này và chúng luôn được đưa vào 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 mà đã được xác định trong tệp build.gradle, bạn có thể thực hiện điều này 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:

<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 phần giữ chỗ ${applicationId}. Giá trị này luôn khớp với mã ứng dụng hoàn thiện cho bản dựng hiện tại (bao gồm cả những thay đổi do các biến thể bản dựng thực hiện). Tính năng này rất hữu ích khi bạn muốn sử dụng một không gian tên riêng cho các giá trị nhận dạng, chẳng hạn như hành động theo ý định ngay cả trong 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"
    }
    flavorDimensions "type
    productFlavors {
        free {
            applicationIdSuffix ".free"
            dimension "type"
        }
        pro {
            applicationIdSuffix ".pro"
            dimension "type"
        }
    }
}

Kotlin

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

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.