Định cấu hình chế độ phân phối theo yêu cầu

Mô-đun tính năng cho phép bạn tách một số tính năng và tài nguyên cụ thể khỏi mô-đun cơ sở của ứng dụng rồi đưa chúng vào gói ứng dụng. Thông qua Play Feature Delivery, sau khi cài đặt tệp APK cơ sở của ứng dụng, người dùng có thể làm những việc như tải và cài đặt các thành phần mà họ muốn sử dụng.

Hãy xem xét ví dụ về một ứng dụng nhắn tin văn bản có chức năng chụp ảnh và gửi tin nhắn hình ảnh, nhưng chỉ có một tỷ lệ nhỏ người dùng gửi tin nhắn hình ảnh. Việc đặt tin nhắn hình ảnh làm mô-đun tính năng có thể tải xuống là điều hợp lý. Bằng cách đó, ứng dụng tải xuống ban đầu sẽ nhỏ hơn đối với tất cả người dùng và chỉ những người dùng gửi tin nhắn hình ảnh mới cần tải thành phần bổ sung đó xuống.

Xin lưu ý rằng kiểu mô-đun hoá này đòi hỏi nhiều nỗ lực và có thể bạn phải tái cấu trúc mã nguồn hiện tại của ứng dụng. Vì vậy, hãy cân nhắc kỹ xem những tính năng nào trong ứng dụng sẽ hưởng lợi nhiều nhất khi được đặt thành tính năng tải theo yêu cầu. Để hiểu rõ hơn về những trường hợp sử dụng tối ưu và nguyên tắc với tính năng theo yêu cầu, hãy đọc các phương pháp hay nhất về trải nghiệm người dùng dành cho việc phân phối tính năng theo yêu cầu.

Nếu bạn muốn từng bước mô-đun hoá các tính năng của ứng dụng theo thời gian mà không bật các tuỳ chọn phân phối nâng cao, chẳng hạn như phân phối tính năng theo yêu cầu, thay vào đó hãy định cấu hình phân phối tại thời điểm cài đặt.

Trang này giúp bạn thêm mô-đun tính năng vào dự án ứng dụng và định cấu hình mô-đun đó để phân phối theo yêu cầu. Trước khi bắt đầu, hãy đảm bảo bạn đang sử dụng Android Studio 3.5 trở lên và trình bổ trợ Android cho Gradle 3.5.0 trở lên.

Định cấu hình mô-đun mới cho chế độ phân phối tính năng theo yêu cầu

Cách dễ nhất để tạo một mô-đun tính năng mới là bằng cách sử dụng Android Studio 3.5 trở lên. Vì mô-đun tính năng có phần phụ thuộc vốn có của mô-đun ứng dụng cơ sở, nên bạn chỉ có thể thêm chúng vào những dự án ứng dụng hiện có.

Để thêm mô-đun tính năng vào dự án ứng dụng bằng Android Studio, hãy làm như sau:

  1. Mở dự án ứng dụng trong IDE.
  2. Chọn File (Tệp) > New (Mới) > New Module (Mô-đun mới) trên thanh trình đơn.
  3. Trong hộp thoại Create New Module (Tạo mô-đun mới), hãy chọn Dynamic Feature Module (Mô-đun tính năng động) rồi nhấp vào Next (Tiếp theo).
  4. Trong phần Configure your new module (Định cấu hình mô-đun mới), hãy hoàn thành các bước sau:
    1. Chọn Base application module (Mô-đun ứng dụng cơ sở) cho dự án ứng dụng trên trình đơn thả xuống.
    2. Chỉ định Module name (Tên mô-đun). IDE sử dụng tên này để xác định mô-đun làm dự án phụ Gradle trong tệp cài đặt Gradle. Khi bạn tạo gói ứng dụng, Gradle sử dụng phần tử cuối của tên dự án phụ để chèn thuộc tính <manifest split> trong tệp kê khai của mô-đun tính năng.
    3. Chỉ định package name (tên gói) của mô-đun. Theo mặc định, Android Studio kết hợp tên gói gốc của mô-đun cơ sở và tên mô-đun mà bạn đã chỉ định ở bước trước để làm tên gói.
    4. Chọn Minimum API level (Cấp API tối thiểu) mà bạn muốn mô-đun hỗ trợ. Giá trị này phải khớp với giá trị của mô-đun cơ sở.
  5. Nhấp vào Next (Tiếp theo)
  6. Trong phần Module Download Options (Tuỳ chọn tải mô-đun xuống), hãy hoàn thành các bước sau:

    1. Chỉ định Tên mô-đun (tối đa 50 ký tự). Nền tảng sử dụng tiêu đề này để xác định mô-đun cho người dùng, chẳng hạn như khi xác nhận xem người dùng có muốn tải mô-đun xuống hay không. Vì lý do này, mô-đun cơ sở của ứng dụng phải bao gồm cả tên mô-đun dưới dạng tài nguyên chuỗi mà bạn có thể dịch. Khi tạo mô-đun bằng Android Studio, IDE thêm tài nguyên chuỗi vào mô-đun cơ sở cho bạn và chèn nội dung sau vào tệp kê khai của mô-đun tính năng:

      <dist:module
          ...
          dist:title="@string/feature_title">
      </dist:module>
      
    2. Trong trình đơn thả xuống bên dưới Install-time inclusion (Bao gồm thời gian cài đặt), hãy chọn Do not include module at install-time (Không bao gồm mô-đun tại thời điểm cài đặt). Android Studio sẽ chèn đoạn mã sau vào tệp kê khai của mô-đun để phản ánh lựa chọn của bạn:

      <dist:module ... >
        <dist:delivery>
            <dist:on-demand/>
        </dist:delivery>
      </dist:module>
      
    3. Đánh dấu ô bên cạnh Fusing (Hợp nhất) nếu bạn muốn mô-đun này có sẵn cho thiết bị chạy Android 4.4 (API cấp 20) trở xuống và đi kèm nhiều APK. Điều này có nghĩa là bạn có thể bật hành vi phân phát theo yêu cầu cho mô-đun này và tắt tính năng hợp nhất để bỏ mô-đun này khỏi các thiết bị không hỗ trợ tải xuống và cài đặt tệp APK phân tách. Android Studio sẽ chèn đoạn mã sau vào tệp kê khai của mô-đun để phản ánh lựa chọn của bạn:

      <dist:module ...>
          <dist:fusing dist:include="true | false" />
      </dist:module>
      
  7. Nhấp vào Finish (Hoàn tất).

Sau khi Android Studio hoàn tất quá trình tạo mô-đun, bạn nên tự kiểm tra nội dung của mô-đun đó trên ngăn Project (Dự án) (chọn View (Thành phần hiển thị) > Tool Windows (Cửa sổ công cụ) > Project (Dự án) trên thanh trình đơn). Mã, tài nguyên và cách sắp xếp mặc định phải tương tự như mã, tài nguyên và cách sắp xếp của mô-đun ứng dụng chuẩn.

Tiếp theo, bạn sẽ cần triển khai chức năng cài đặt theo yêu cầu bằng thư viện Play Feature Delivery.

Thêm Thư viện Play Feature Delivery vào dự án của bạn

Để có thể bắt đầu, trước tiên bạn cần thêm Thư viện Play Feature Delivery vào dự án của mình.

Yêu cầu mô-đun theo yêu cầu

Khi cần sử dụng mô-đun tính năng, ứng dụng của bạn có thể yêu cầu mô-đun đó khi đang ở nền trước thông qua lớp SplitInstallManager. Khi đưa ra yêu cầu, ứng dụng cần chỉ định tên của mô-đun đã được xác định bằng phần tử split trong tệp kê khai của mô-đun mục tiêu. Khi bạn tạo một mô-đun tính năng sử dụng Android Studio, hệ thống xây dựng sẽ sử dụng Module name (Tên mô-đun) mà bạn cung cấp để chèn thuộc tính này vào tệp kê khai của mô-đun trong thời gian biên dịch. Để biết thêm thông tin, hãy đọc bài viết về tệp kê khai của mô-đun tính năng.

Ví dụ: có một ứng dụng có mô-đun theo yêu cầu để chụp và gửi tin nhắn hình ảnh qua máy ảnh của thiết bị, và mô-đun theo yêu cầu này sẽ chỉ định split="pictureMessages" trong tệp kê khai. Ví dụ sau đây dùng SplitInstallManager để yêu cầu mô-đun pictureMessages (cùng với một mô-đun khác cho một số bộ lọc quảng cáo):

Kotlin

// Creates an instance of SplitInstallManager.
val splitInstallManager = SplitInstallManagerFactory.create(context)

// Creates a request to install a module.
val request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build()

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener { sessionId -> ... }
    .addOnFailureListener { exception ->  ... }

Java

// Creates an instance of SplitInstallManager.
SplitInstallManager splitInstallManager =
    SplitInstallManagerFactory.create(context);

// Creates a request to install a module.
SplitInstallRequest request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build();

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener(sessionId -> { ... })
    .addOnFailureListener(exception -> { ... });

Khi ứng dụng của bạn yêu cầu một mô-đun theo yêu cầu, thư viện Play Feature Delivery sẽ sử dụng chiến lược “fire-and-forget” (phóng và quên). Phương thức này sẽ gửi yêu cầu tải mô-đun xuống nền tảng, nhưng yêu cầu đó không giám sát việc liệu cài đặt có thành công hay không. Để đưa hành trình của người dùng tiến lên sau khi cài đặt hoặc xử lý lỗi một cách linh hoạt, hãy nhớ theo dõi trạng thái yêu cầu.

Lưu ý: Bạn có thể yêu cầu một mô-đun tính năng đã cài đặt trên thiết bị. API sẽ ngay lập tức coi yêu cầu là đã hoàn tất nếu phát hiện mô-đun đã được cài đặt. Ngoài ra, sau khi cài đặt mô-đun, Google Play sẽ tự động cập nhật mô-đun đó. Tức là khi bạn tải một phiên bản mới của gói ứng dụng lên, nền tảng sẽ cập nhật mọi tệp APK đã cài đặt thuộc về ứng dụng của bạn. Để biết thêm thông tin, hãy đọc phần Quản lý bản cập nhật ứng dụng.

Để có quyền truy cập vào mã và tài nguyên của mô-đun, ứng dụng của bạn cần phải bật SplitCompat. Lưu ý rằng không bắt buộc phải sử dụng SplitCompat cho các Ứng dụng Android tức thì.

Hoãn cài đặt các mô-đun theo yêu cầu

Nếu không cần ứng dụng tải xuống và cài đặt một mô-đun theo yêu cầu ngay lập tức, thì bạn có thể hoãn việc cài đặt cho đến khi ứng dụng hoạt động ở chế độ nền. Ví dụ: nếu bạn muốn tải trước một số tài liệu quảng cáo để ra mắt ứng dụng về sau này.

Bạn có thể chỉ định một mô-đun để tải xuống sau sử dụng phương thức deferredInstall(), như thể hiện dưới đây. Ngoài ra, không giống như SplitInstallManager.startInstall(), ứng dụng của bạn không cần ở nền trước để bắt đầu yêu cầu cài đặt chậm.

Kotlin

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(listOf("promotionalFilters"))

Java

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(Arrays.asList("promotionalFilters"));

Các yêu cầu cài đặt chậm là nỗ lực tốt nhất và bạn không thể theo dõi tiến trình của những yêu cầu đó. Vì vậy, trước khi thử truy cập vào một mô-đun mà bạn đã chỉ định để cài đặt chậm, bạn nên kiểm tra để đảm bảo rằng mô-đun đó đã được cài đặt. Nếu bạn muốn mô-đun có sẵn ngay, hãy sử dụng SplitInstallManager.startInstall() để yêu cầu, như thể hiện trong phần trước.

Theo dõi trạng thái của yêu cầu

Để có thể cập nhật thanh tiến trình, kích hoạt ý định sau khi cài đặt hoặc xử lý lỗi yêu cầu đúng cách, bạn cần lắng nghe thông tin cập nhật về trạng thái từ nhiệm vụ SplitInstallManager.startInstall() không đồng bộ. Trước khi bạn có thể bắt đầu nhận thông tin cập nhật cho yêu cầu cài đặt của mình, hãy đăng ký trình nghe và nhận ID phiên cho yêu cầu đó, như thể hiện dưới đây.

Kotlin

// Initializes a variable to later track the session ID for a given request.
var mySessionId = 0

// Creates a listener for request status updates.
val listener = SplitInstallStateUpdatedListener { state ->
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
}

// Registers the listener.
splitInstallManager.registerListener(listener)

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener { sessionId -> mySessionId = sessionId }
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener { exception ->
        // Handle request errors.
    }

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener)

Java

// Initializes a variable to later track the session ID for a given request.
int mySessionId = 0;

// Creates a listener for request status updates.
SplitInstallStateUpdatedListener listener = state -> {
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
};

// Registers the listener.
splitInstallManager.registerListener(listener);

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener(sessionId -> { mySessionId = sessionId; })
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener(exception -> {
        // Handle request errors.
    });

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener);

Xử lý lỗi yêu cầu

Xin lưu ý rằng đôi khi, việc cài đặt các mô-đun tính năng theo yêu cầu có thể không thành công, giống như việc cài đặt ứng dụng không phải lúc nào cũng thành công. Việc không cài đặt được có thể do các vấn đề như dung lượng lưu trữ của thiết bị thấp, không có kết nối mạng hoặc người dùng chưa đăng nhập vào Cửa hàng Google Play. Để biết các nội dung đề xuất về cách xử lý hiệu quả các tình huống này từ góc độ của người dùng, hãy xem các nguyên tắc về trải nghiệm người dùng đối với tính năng phân phối theo yêu cầu.

Ngoài ra, bạn nên xử lý lỗi khi tải xuống hoặc cài đặt một mô-đun sử dụng addOnFailureListener(), như minh hoạ dưới đây:

Kotlin

splitInstallManager
    .startInstall(request)
    .addOnFailureListener { exception ->
        when ((exception as SplitInstallException).errorCode) {
            SplitInstallErrorCode.NETWORK_ERROR -> {
                // Display a message that requests the user to establish a
                // network connection.
            }
            SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> checkForActiveDownloads()
            ...
        }
    }

fun checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .sessionStates
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                // Check for active sessions.
                for (state in task.result) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        }
}

Java

splitInstallManager
    .startInstall(request)
    .addOnFailureListener(exception -> {
        switch (((SplitInstallException) exception).getErrorCode()) {
            case SplitInstallErrorCode.NETWORK_ERROR:
                // Display a message that requests the user to establish a
                // network connection.
                break;
            case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED:
                checkForActiveDownloads();
            ...
    });

void checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .getSessionStates()
        .addOnCompleteListener( task -> {
            if (task.isSuccessful()) {
                // Check for active sessions.
                for (SplitInstallSessionState state : task.getResult()) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        });
}

Bảng dưới đây mô tả các trạng thái lỗi mà ứng dụng của bạn có thể cần xử lý:

Mã lỗi Nội dung mô tả Hành động đề xuất
ACTIVE_SESSIONS_LIMIT_EXCEEDED Yêu cầu bị từ chối vì hiện có ít nhất một yêu cầu đang được tải xuống. Kiểm tra xem có yêu cầu nào vẫn đang tải xuống hay không, như thể hiện trong mẫu ở trên.
MODULE_UNAVAILABLE Google Play không thể tìm thấy mô-đun yêu cầu dựa trên phiên bản đã cài đặt hiện tại của ứng dụng, thiết bị và tài khoản Google Play của người dùng. Nếu người dùng không có quyền truy cập vào mô-đun, hãy thông báo cho họ.
INVALID_REQUEST Google Play đã nhận được yêu cầu nhưng yêu cầu không hợp lệ. Xác minh rằng thông tin có trong yêu cầu là đầy đủ và chính xác.
SESSION_NOT_FOUND Không tìm thấy phiên cho ID phiên nhất định. Nếu bạn đang cố gắng theo dõi trạng thái của yêu cầu theo ID phiên, hãy đảm bảo rằng ID phiên đó là chính xác.
API_NOT_AVAILABLE Thư viện Play Feature Delivery không được hỗ trợ trên thiết bị hiện tại. Điều đó nghĩa là thiết bị không thể tải xuống và cài đặt các tính năng theo yêu cầu. Đối với các thiết bị chạy Android 4.4 (API cấp 20) trở xuống, bạn nên thêm các mô-đun tính năng tại thời điểm cài đặt sử dụng thuộc tính tệp kê khai dist:fusing. Để tìm hiểu thêm, hãy đọc về tệp kê khai mô-đun tính năng.
NETWORK_ERROR Yêu cầu không thành công do lỗi mạng. Nhắc người dùng thiết lập kết nối mạng hoặc đổi sang một mạng khác.
ACCESS_DENIED Ứng dụng không thể đăng ký yêu cầu vì không có đủ quyền. Điều này thường xảy ra khi ứng dụng đang ở chế độ nền. Hãy thử yêu cầu khi ứng dụng trở về nền trước.
INCOMPATIBLE_WITH_EXISTING_SESSION Yêu cầu có chứa một hoặc nhiều mô-đun đã được yêu cầu nhưng chưa được cài đặt. Tạo một yêu cầu mới không bao gồm các mô-đun mà ứng dụng của bạn đã yêu cầu hoặc chờ tất cả các mô-đun hiện được yêu cầu hoàn tất quá trình cài đặt trước khi thử lại yêu cầu.

Xin lưu ý rằng việc yêu cầu mô-đun đã được cài đặt sẽ không giải quyết được lỗi.

SERVICE_DIED Dịch vụ chịu trách nhiệm xử lý yêu cầu đã ngừng hoạt động. Thử gửi lại yêu cầu.

SplitInstallStateUpdatedListener của bạn sẽ nhận được SplitInstallSessionState với mã lỗi này, trạng thái FAILED và ID phiên -1.

INSUFFICIENT_STORAGE Thiết bị không có đủ dung lượng lưu trữ miễn phí để cài đặt mô-đun tính năng. Thông báo cho người dùng rằng họ không có đủ dung lượng lưu trữ để cài đặt tính năng này.
SPLITCOMPAT_VERIFICATION_ERROR, SPLITCOMPAT_EMULATION_ERROR, SPLITCOMPAT_COPY_ERROR SplitCompat không thể tải mô-đun tính năng. Những lỗi này sẽ tự động được giải quyết sau lần khởi động lại ứng dụng tiếp theo.
PLAY_STORE_NOT_FOUND Ứng dụng Cửa hàng Play chưa được cài đặt trên thiết bị. Thông báo cho người dùng biết rằng ứng dụng Cửa hàng Play cần phải tải xuống tính năng này.
APP_NOT_OWNED Bạn chưa cài đặt ứng dụng này cho Google Play và không thể tải tính năng này xuống. Lỗi này chỉ có thể xảy ra đối với lượt cài đặt bị trì hoãn. Nếu bạn muốn người dùng mua ứng dụng trên Google Play, hãy sử dụng startInstall() để có thể có xác nhận người dùng cần thiết.
INTERNAL_ERROR Đã xảy ra lỗi nội bộ trong Cửa hàng Play. Thử gửi lại yêu cầu.

Nếu người dùng yêu cầu tải một mô-đun theo yêu cầu xuống và xảy ra lỗi, hãy cân nhắc việc hiển thị một hộp thoại cung cấp hai tuỳ chọn cho người dùng: Thử lại (thao tác này sẽ thử lại yêu cầu) và Huỷ (thao tác này sẽ huỷ yêu cầu). Để được hỗ trợ thêm, bạn cũng nên cung cấp đường liên kết Trợ giúp để chuyển hướng người dùng đến Trung tâm trợ giúp của Google Play.

Xử lý thông tin cập nhật trạng thái

Sau khi bạn đăng ký trình nghe và ghi lại ID phiên cho yêu cầu của mình, hãy sử dụng StateUpdatedListener.onStateUpdate() để xử lý các thay đổi về trạng thái, như thể hiện dưới đây.

Kotlin

override fun onStateUpdate(state : SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIED) {
       // Retry the request.
       return
    }
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.DOWNLOADING -> {
              val totalBytes = state.totalBytesToDownload()
              val progress = state.bytesDownloaded()
              // Update progress bar.
            }
            SplitInstallSessionStatus.INSTALLED -> {

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
            }
        }
    }
}

Java

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) {
       // Retry the request.
       return;
    }
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.DOWNLOADING:
              int totalBytes = state.totalBytesToDownload();
              int progress = state.bytesDownloaded();
              // Update progress bar.
              break;

            case SplitInstallSessionStatus.INSTALLED:

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
        }
    }
}

Các trạng thái có thể có cho yêu cầu cài đặt của bạn được mô tả trong bảng dưới đây.

Trạng thái yêu cầu Nội dung mô tả Hành động đề xuất
ĐANG CHỜ GIẢI QUYẾT Yêu cầu đã được chấp nhận và quá trình tải xuống sẽ sớm bắt đầu. Khởi chạy các thành phần giao diện người dùng, chẳng hạn như thanh tiến trình, để cung cấp phản hồi của người dùng về nội dung tải xuống.
REQUIRES_USER_CONFIRMATION Việc tải xuống cần có xác nhận của người dùng. Trạng thái này thường xảy ra nhất nếu bạn chưa cài đặt ứng dụng này qua Google Play. Nhắc người dùng xác nhận tính năng tải xuống thông qua Google Play. Để tìm hiểu thêm, hãy chuyển đến phần hướng dẫn cách yêu cầu người dùng xác nhận.
TẢI XUỐNG Quá trình tải xuống đang diễn ra. Nếu bạn cung cấp thanh tiến trình để tải xuống, hãy sử dụng phương thức SplitInstallSessionState.bytesDownloaded()SplitInstallSessionState.totalBytesToDownload() để cập nhật giao diện người dùng (xem mã mẫu phía trên bảng này).
ĐÃ TẢI XUỐNG Thiết bị đã tải mô-đun xuống nhưng quá trình cài đặt chưa bắt đầu. Ứng dụng nên bật SplitCompat để có quyền truy cập vào các mô-đun đã tải xuống và tránh xem trạng thái này. Điều này là bắt buộc để truy cập vào mã và tài nguyên của mô-đun tính năng.
ĐANG CÀI ĐẶT Thiết bị đang cài đặt mô-đun. Cập nhật thanh tiến trình. Trạng thái này thường ngắn.
ĐÃ CÀI ĐẶT Mô-đun đã được cài đặt trên thiết bị. Truy cập vào mã và tài nguyên trong mô-đun để tiếp tục hành trình của người dùng.

Nếu mô-đun này dành cho một Ứng dụng Android tức thì chạy trên Android 8.0 (API cấp 26) trở lên, thì bạn cần sử dụng splitInstallHelper để cập nhật các thành phần của ứng dụng bằng mô-đun mới.

KHÔNG THÀNH CÔNG Yêu cầu không thành công trước khi mô-đun được cài đặt trên thiết bị. Nhắc người dùng thử lại hoặc huỷ yêu cầu.
ĐANG HUỶ Thiết bị đang trong quá trình huỷ yêu cầu. Để tìm hiểu thêm, hãy chuyển đến phần hướng dẫn cách huỷ yêu cầu cài đặt.
ĐÃ HUỶ Yêu cầu đã bị huỷ.

Yêu cầu người dùng xác nhận

Trong một số trường hợp, Google Play có thể yêu cầu người dùng xác nhận trước khi đáp ứng yêu cầu tải xuống. Ví dụ: nếu ứng dụng của bạn chưa được Google Play cài đặt hoặc nếu bạn đang cố tải lượng lớn dữ liệu xuống qua dữ liệu di động. Trong những trường hợp như vậy, trạng thái của yêu cầu báo cáo REQUIRES_USER_CONFIRMATION và ứng dụng của bạn cần có sự xác nhận của người dùng thì thiết bị mới có thể tải xuống và cài đặt các mô-đun theo yêu cầu. Để được xác nhận, ứng dụng của bạn cần nhắc người dùng như sau:

Kotlin

override fun onSessionStateUpdate(state: SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher)
    }
    ...
 }

Java

@Override void onSessionStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher);
    }
    ...
 }

Bạn có thể đăng ký trình chạy kết quả hoạt động bằng cách sử dụng hợp đồng ActivityResultContracts.StartIntentSenderForResult tích hợp. Hãy xem Activity Result API (API Kết quả hoạt động).

Trạng thái của yêu cầu được cập nhật tuỳ theo phản hồi của người dùng:

  • Nếu người dùng chấp nhận xác nhận, trạng thái yêu cầu sẽ thay đổi thành PENDING và quá trình tải xuống sẽ tiếp tục.
  • Nếu người dùng từ chối xác nhận, trạng thái yêu cầu sẽ thay đổi thành CANCELED.
  • Nếu người dùng không đưa ra lựa chọn trước khi hộp thoại bị huỷ, trạng thái yêu cầu vẫn là REQUIRES_USER_CONFIRMATION. Ứng dụng của bạn có thể nhắc lại người dùng để hoàn tất yêu cầu đó.

Để nhận lệnh gọi lại cùng với phản hồi của người dùng, bạn có thể ghi đè ActivityResultCallback như sau.

Kotlin

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult -> {
        // Handle the user's decision. For example, if the user selects "Cancel",
        // you may want to disable certain functionality that depends on the module.
    }
}

Java

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // Handle the user's decision. For example, if the user selects "Cancel",
            // you may want to disable certain functionality that depends on the module.
        }
    });

Huỷ yêu cầu cài đặt

Nếu cần huỷ một yêu cầu trước khi cài đặt, ứng dụng của bạn có thể gọi phương thức cancelInstall() sử dụng ID phiên của yêu cầu như thể hiện dưới đây.

Kotlin

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId)

Java

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId);

Truy cập các mô-đun

Để truy cập vào mã và tài nguyên từ một mô-đun đã tải xuống sau khi tải xuống, ứng dụng của bạn cần bật Thư viện SplitCompat cho cả ứng dụng và từng hoạt động trong mô-đun tính năng mà ứng dụng tải xuống.

Tuy nhiên, bạn nên lưu ý rằng nền tảng sẽ gặp các hạn chế sau đây khi truy cập vào nội dung của một mô-đun, trong một khoảng thời gian nào đó (vài ngày, trong một số trường hợp) sau khi tải mô-đun xuống:

  • Nền tảng này không thể áp dụng bất cứ mục nhập tệp kê khai mới nào được mô-đun giới thiệu.
  • Nền tảng này không thể truy cập vào các tài nguyên của mô-đun đối với các thành phần trên giao diện người dùng của hệ thống, chẳng hạn như thông báo. Nếu bạn cần sử dụng các tài nguyên đó ngay lập tức, hãy cân nhắc đưa các tài nguyên đó vào mô-đun cơ sở của ứng dụng.

Bật SplitCompat

Để ứng dụng của bạn có thể truy cập vào mã và tài nguyên từ một mô-đun đã tải xuống, bạn cần bật SplitCompat dựa vào một trong các phương thức đã mô tả trong các phần sau.

Sau khi bật SplitCompat cho ứng dụng, bạn cũng cần bật SplitCompat cho từng hoạt động trong các mô-đun tính năng mà bạn muốn ứng dụng của mình có quyền truy cập.

Khai báo SplitCompatApplication trong tệp kê khai

Cách đơn giản nhất để bật SplitCompat là khai báo SplitCompatApplication là lớp con Application trong tệp kê khai của ứng dụng như thể hiện dưới đây:

<application
    ...
    android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
</application>

Sau khi ứng dụng được cài đặt trên một thiết bị, bạn có thể tự động truy cập vào mã và tài nguyên từ các mô-đun tính năng đã tải xuống.

Gọi SplitCompat trong thời gian chạy

Bạn cũng có thể bật SplitCompat trong các hoạt động hoặc dịch vụ cụ thể trong thời gian chạy. Bạn phải bật SplitCompat theo cách này để khởi chạy các hoạt động trong các mô-đun tính năng. Để thực hiện việc này, hãy ghi đè attachBaseContext như bên dưới.

Nếu bạn có một lớp Ứng dụng tuỳ chỉnh, hãy thêm lớp này thay vì mở rộng SplitCompatApplication để bật SplitCompat cho ứng dụng, như thể hiện dưới đây:

Kotlin

class MyApplication : SplitCompatApplication() {
    ...
}

Java

public class MyApplication extends SplitCompatApplication {
    ...
}

SplitCompatApplication chỉ ghi đè ContextWrapper.attachBaseContext() để bao gồm SplitCompat.install(Context applicationContext). Nếu không muốn lớp Application của mình mở rộng SplitCompatApplication, bạn có thể ghi đè phương thức attachBaseContext() theo cách thủ công như sau:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this);
}

Nếu mô-đun theo yêu cầu của bạn tương thích với cả ứng dụng tức thì và ứng dụng cần cài đặt, bạn có thể gọi SplitCompat một cách có điều kiện như sau:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this)
    }
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this);
    }
}

Bật SplitCompat cho các hoạt động của mô-đun

Sau khi bật SplitCompat cho ứng dụng cơ sở, bạn cần bật SplitCompat cho từng hoạt động mà ứng dụng tải xuống trong mô-đun tính năng. Để thực hiện việc này, hãy sử dụng phương thức SplitCompat.installActivity() như sau:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this);
}

Truy cập các thành phần được xác định trong mô-đun tính năng

Bắt đầu một hoạt động được xác định trong mô-đun tính năng

Bạn có thể khởi chạy các hoạt động được xác định trong các mô-đun tính năng sử dụng startActivity() sau khi bật SplitCompat.

Kotlin

startActivity(Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...))

Java

startActivity(new Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...));

Thông số đầu tiên đối với setClassName là tên gói của ứng dụng và thông số thứ hai là tên lớp đầy đủ của hoạt động.

Khi có một hoạt động trong mô-đun tính năng mà bạn đã tải xuống theo yêu cầu, bạn phải bật SplitCompat trong hoạt động đó.

Bắt đầu dịch vụ được xác định trong mô-đun tính năng

Bạn có thể khởi chạy các dịch vụ được xác định trong các mô-đun tính năng sử dụng startService() sau khi bật SplitCompat.

Kotlin

startService(Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...))

Java

startService(new Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...));

Xuất thành phần được xác định trong mô-đun tính năng

Bạn không nên bao gồm các thành phần Android đã xuất bên trong các mô-đun tuỳ chọn.

Hệ thống xây dựng sẽ sáp nhập các mục nhập tệp kê khai cho tất cả mô-đun vào mô-đun cơ sở; nếu mô-đun tuỳ chọn chứa một thành phần đã xuất, thì mô-đun đó sẽ có thể truy cập được ngay trước khi cài đặt mô-đun và có thể gây ra sự cố do thiếu mã khi được gọi từ một ứng dụng khác.

Đây không phải là vấn đề đối với các thành phần nội bộ; chỉ ứng dụng mới truy cập được các thành phần đó, vì vậy, ứng dụng có thể kiểm tra xem mô-đun đã được cài đặt trước khi truy cập vào thành phần hay chưa.

Nếu bạn cần một thành phần đã xuất và muốn nội dung của thành phần đó nằm trong một mô-đun tuỳ chọn, hãy cân nhắc triển khai mẫu proxy. Bạn có thể làm điều đó bằng cách thêm một thành phần đã xuất của proxy vào cơ sở; khi được truy cập, thành phần proxy có thể kiểm tra sự hiện diện của mô-đun chứa nội dung. Nếu có mô-đun, thành phần proxy có thể bắt đầu thành phần nội bộ từ mô-đun thông qua Intent, sẽ chuyển tiếp ý định từ ứng dụng gọi. Nếu không có mô-đun, thành phần có thể tải mô-đun xuống hoặc trả về một thông báo lỗi thích hợp cho ứng dụng gọi.

Truy cập vào mã và tài nguyên từ các mô-đun đã cài đặt

Nếu bật SplitCompat cho ngữ cảnh ứng dụng cơ sở và hoạt động trong mô-đun tính năng thì bạn có thể sử dụng mã và tài nguyên qua mô-đun tính năng dưới dạng một phần của APK cơ sở, sau khi cài đặt mô-đun không bắt buộc.

Truy cập vào mã từ mô-đun khác

Truy cập vào mã cơ sở từ một mô-đun

Những mô-đun khác có thể trực tiếp sử dụng mã bên trong mô-đun cơ sở của bạn. Bạn không cần phải làm gì đặc biệt; chỉ cần nhập và sử dụng các lớp bạn cần.

Truy cập vào mã mô-đun từ một mô-đun khác

Một đối tượng hoặc lớp bên trong một mô-đun không thể truy cập tĩnh trực tiếp từ một mô-đun khác, mà có thể truy cập gián tiếp bằng cách sử dụng phản chiếu.

Bạn nên cảnh giác tần suất xảy ra việc này, do chi phí thực hiện phản ánh. Đối với các trường hợp sử dụng phức tạp, hãy sử dụng các khung chèn phụ thuộc như Dagger 2 để đảm bảo có một lệnh gọi phản ánh duy nhất cho mỗi thời gian hoạt động của ứng dụng.

Để đơn giản hoá các hoạt động tương tác với đối tượng sau khi tạo bản sao, bạn nên xác định một giao diện trong mô-đun cơ sở và quy trình triển khai giao diện đó trong mô-đun tính năng. Ví dụ:

Kotlin

// In the base module
interface MyInterface {
  fun hello(): String
}

// In the feature module
object MyInterfaceImpl : MyInterface {
  override fun hello() = "Hello"
}

// In the base module, where we want to access the feature module code
val stringFromModule = (Class.forName("com.package.module.MyInterfaceImpl")
    .kotlin.objectInstance as MyInterface).hello();

Java

// In the base module
public interface MyInterface {
  String hello();
}

// In the feature module
public class MyInterfaceImpl implements MyInterface {
  @Override
  public String hello() {
    return "Hello";
  }
}

// In the base module, where we want to access the feature module code
String stringFromModule =
   ((MyInterface) Class.forName("com.package.module.MyInterfaceImpl").getConstructor().newInstance()).hello();

Truy cập tài nguyên và tài sản qua mô-đun khác

Sau khi cài đặt mô-đun, bạn có thể truy cập tài nguyên và tài sản trong mô-đun theo cách tiêu chuẩn, nhưng có hai vấn đề:

  • Nếu bạn đang truy cập tài nguyên từ một mô-đun khác, mô-đun sẽ không có quyền truy cập vào mã nhận dạng tài nguyên, mặc dù vẫn có thể truy cập vào tài nguyên đó theo tên. Lưu ý rằng gói cần sử dụng để tham chiếu tài nguyên là gói của mô-đun mà tài nguyên được xác định.
  • Nếu muốn truy cập tài sản hoặc tài nguyên có trong mô-đun mới cài đặt qua một mô-đun đã cài đặt khác của ứng dụng, bạn phải sử dụng ngữ cảnh ứng dụng. Ngữ cảnh của thành phần đang cố truy cập vào tài nguyên sẽ chưa được cập nhật. Ngoài ra, bạn có thể tạo lại thành phần đó (ví dụ: gọi Activity.recreate()) hoặc cài đặt lại SplitCompat trên thành phần đó sau khi cài đặt mô-đun tính năng.

Tải mã gốc trong ứng dụng bằng phương thức phân phối theo yêu cầu

Bạn nên sử dụng ReLinker để tải tất cả thư viện gốc của mình khi phân phối các mô-đun tính năng theo yêu cầu. ReLinker sẽ khắc phục vấn đề tải thư viện gốc sau khi cài đặt mô-đun tính năng. Bạn có thể tìm hiểu thêm về ReLinker trong bài viết Mẹo về JNI Android.

Tải mã gốc từ mô-đun không bắt buộc

Sau khi cài đặt phần phân tách, bạn nên tải mã gốc của phần phân tách thông qua ReLinker. Đối với các ứng dụng tức thì, bạn nên sử dụng phương thức đặc biệt này.

Nếu bạn đang sử dụng System.loadLibrary() để tải mã gốc và thư viện gốc có phần phụ thuộc nằm trên một thư viện khác trong mô-đun, thì trước tiên bạn phải tải thư viện khác đó theo cách thủ công. Nếu bạn đang sử dụng ReLinker, thì thao tác tương đương sẽ là Relinker.recursively().loadLibrary().

Nếu bạn đang sử dụng dlopen() trong mã gốc để tải thư viện được xác định trong mô-đun tuỳ chọn, thì thư viện này sẽ không hoạt động với các đường dẫn thư viện tương đối. Cách tốt nhất là truy xuất đường dẫn tuyệt đối của thư viện từ mã Java thông qua ClassLoader.findLibrary() và sau đó sử dụng đường dẫn này trong lệnh gọi dlopen(). Hãy làm việc này trước khi nhập mã gốc hoặc sử dụng lệnh gọi JNI từ mã gốc của bạn vào Java.

Truy cập ứng dụng Android tức thì đã cài đặt

Sau khi mô-đun Ứng dụng Android tức thì báo cáo là INSTALLED, bạn có thể truy cập vào mã và tài nguyên của mô-đun đó sử dụng Ngữ cảnh của ứng dụng đã làm mới. Ngữ cảnh mà ứng dụng của bạn tạo trước khi cài đặt mô-đun (ví dụ: một mô-đun đã được lưu trữ trong một biến) không chứa nội dung của mô-đun mới. Tuy nhiên, bạn sẽ xem được ngữ cảnh mới, ví dụ: bạn có thể lấy được thông tin này sử dụng createPackageContext.

Kotlin

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                val newContext = context.createPackageContext(context.packageName, 0)
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                val am = newContext.assets
            }
        }
    }
}

Java

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                AssetManager am = newContext.getAssets();
        }
    }
}

Ứng dụng Android tức thì trên Android 8.0 trở lên

Khi yêu cầu một mô-đun theo yêu cầu cho Ứng dụng Android tức thì trên Android 8.0 (API cấp 26) trở lên, sau khi một yêu cầu cài đặt báo cáo dưới dạng INSTALLED, bạn cần cập nhật ứng dụng theo ngữ cảnh của mô-đun mới qua lệnh gọi tới SplitInstallHelper.updateAppInfo(Context context). Nếu không, ứng dụng chưa biết mã và tài nguyên của mô-đun. Sau khi cập nhật siêu dữ liệu của ứng dụng, bạn nên tải nội dung của mô-đun trong sự kiện luồng chính tiếp theo bằng cách gọi Handler mới, như thể hiện dưới đây:

Kotlin

override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                // You need to perform the following only for Android Instant Apps
                // running on Android 8.0 (API level 26) and higher.
                if (BuildCompat.isAtLeastO()) {
                    // Updates the app’s context with the code and resources of the
                    // installed module.
                    SplitInstallHelper.updateAppInfo(context)
                    Handler().post {
                        // Loads contents from the module using AssetManager
                        val am = context.assets
                        ...
                    }
                }
            }
        }
    }
}

Java

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
            // You need to perform the following only for Android Instant Apps
            // running on Android 8.0 (API level 26) and higher.
            if (BuildCompat.isAtLeastO()) {
                // Updates the app’s context with the code and resources of the
                // installed module.
                SplitInstallHelper.updateAppInfo(context);
                new Handler().post(new Runnable() {
                    @Override public void run() {
                        // Loads contents from the module using AssetManager
                        AssetManager am = context.getAssets();
                        ...
                    }
                });
            }
        }
    }
}

Tải thư viện C/C++

Nếu bạn muốn tải thư viện C/C++ từ một mô-đun mà thiết bị đã tải xuống trong Ứng dụng tức thì, hãy sử dụng SplitInstallHelper.loadLibrary(Context context, String libName), như thể hiện dưới đây:

Kotlin

override fun onStateUpdate(state: SplitInstallSessionState) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.INSTALLED -> {
                // Updates the app’s context as soon as a module is installed.
                val newContext = context.createPackageContext(context.packageName, 0)
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, “my-cpp-lib”)
                ...
            }
        }
    }
}

Java

public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.INSTALLED:
                // Updates the app’s context as soon as a module is installed.
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, “my-cpp-lib”);
                ...
        }
    }
}

Các hạn chế đã biết

  • Bạn không thể sử dụng WebView trên Android trong một hoạt động truy cập vào các tài nguyên hoặc tài sản từ một mô-đun tuỳ chọn. Nguyên nhân là do không tương thích giữa WebView và SplitCompat trên API Android cấp 28 trở xuống.
  • Bạn không thể lưu các đối tượng ApplicationInfo của Android, nội dung của các đối tượng đó hoặc các đối tượng có chứa đối tượng đó vào bộ nhớ đệm trong ứng dụng. Bạn phải luôn tìm nạp những đối tượng này khi cần trong ngữ cảnh ứng dụng. Việc lưu vào bộ nhớ đệm các đối tượng như vậy có thể khiến ứng dụng gặp sự cố khi cài đặt mô-đun tính năng.

Quản lý các mô-đun đã cài đặt

Để kiểm tra xem mô-đun tính năng nào hiện đã được cài đặt trên thiết bị, bạn có thể gọi SplitInstallManager.getInstalledModules(), sẽ trả về Set<String> tên của các mô-đun đã cài đặt như thể hiện dưới đây.

Kotlin

val installedModules: Set<String> = splitInstallManager.installedModules

Java

Set<String> installedModules = splitInstallManager.getInstalledModules();

Gỡ cài đặt các mô-đun

Bạn có thể yêu cầu thiết bị gỡ cài đặt các mô-đun bằng cách gọi SplitInstallManager.deferredUninstall(List<String> moduleNames), như thể hiện dưới đây.

Kotlin

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(listOf("pictureMessages", "promotionalFilters"))

Java

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(Arrays.asList("pictureMessages", "promotionalFilters"));

Việc gỡ cài đặt mô-đun không xảy ra ngay lập tức. Điều đó nghĩa là thiết bị sẽ gỡ cài đặt các mô-đun này ở chế độ nền khi cần để tiết kiệm không gian lưu trữ. Bạn có thể xác nhận rằng thiết bị đã xoá một mô-đun bằng cách gọi SplitInstallManager.getInstalledModules() và kiểm tra kết quả, như mô tả trong phần trước.

Tải tài nguyên ngôn ngữ bổ sung xuống

Với các gói ứng dụng, các thiết bị chỉ tải xuống mã và tài nguyên cần thiết để chạy ứng dụng của bạn. Vì vậy, đối với tài nguyên ngôn ngữ, thiết bị của người dùng chỉ tải xuống tài nguyên ngôn ngữ của ứng dụng khớp với một hoặc nhiều ngôn ngữ hiện được chọn trong phần cài đặt của thiết bị.

Nếu muốn ứng dụng của mình có quyền truy cập vào các tài nguyên ngôn ngữ bổ sung (ví dụ: để triển khai một bộ chọn ngôn ngữ trong ứng dụng), bạn có thể sử dụng Thư viện Play Feature Delivery để tải các tài nguyên đó xuống theo yêu cầu. Quá trình này tương tự như cách tải mô-đun tính năng xuống, như minh hoạ dưới đây.

Kotlin

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply()
...

// Creates a request to download and install additional language resources.
val request = SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build()

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request)

Java

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply();
...

// Creates a request to download and install additional language resources.
SplitInstallRequest request =
    SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build();

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request);

Yêu cầu được xử lý như thể đó là yêu cầu đối với một mô-đun tính năng. Tức là bạn có thể theo dõi trạng thái yêu cầu như cách bạn thường làm.

Nếu ứng dụng của bạn không yêu cầu tài nguyên ngôn ngữ bổ sung ngay lập tức, thì bạn có thể trì hoãn quá trình cài đặt cho thời điểm ứng dụng chạy ở chế độ nền, như minh hoạ dưới đây.

Kotlin

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Java

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

Truy cập vào các tài nguyên ngôn ngữ đã tải xuống

Để có quyền truy cập vào các tài nguyên ngôn ngữ đã tải xuống, ứng dụng của bạn cần chạy SplitCompat.installActivity() phương thức trong phương thức attachBaseContext() của mỗi hoạt động yêu cầu quyền truy cập vào các tài nguyên đó, như thể hiện dưới đây.

Kotlin

override fun attachBaseContext(base: Context) {
  super.attachBaseContext(base)
  SplitCompat.installActivity(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
  super.attachBaseContext(base);
  SplitCompat.installActivity(this);
}

Đối với mỗi hoạt động bạn muốn sử dụng tài nguyên ngôn ngữ mà ứng dụng của mình đã tải xuống, hãy cập nhật ngữ cảnh cơ sở và đặt ngôn ngữ mới thông qua Configuration:

Kotlin

override fun attachBaseContext(base: Context) {
  val configuration = Configuration()
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
  val context = base.createConfigurationContext(configuration)
  super.attachBaseContext(context)
  SplitCompat.install(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
  Configuration configuration = new Configuration();
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
  Context context = base.createConfigurationContext(configuration);
  super.attachBaseContext(context);
  SplitCompat.install(this);
}

Để những thay đổi này có hiệu lực, bạn phải tạo lại hoạt động sau khi ngôn ngữ mới được cài đặt và sẵn sàng sử dụng. Bạn có thể sử dụng phương thức Activity#recreate().

Kotlin

when (state.status()) {
  SplitInstallSessionStatus.INSTALLED -> {
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate()
  }
  ...
}

Java

switch (state.status()) {
  case SplitInstallSessionStatus.INSTALLED:
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate();
  ...
}

Gỡ cài đặt các tài nguyên ngôn ngữ bổ sung

Tương tự như mô-đun tính năng, bạn có thể gỡ cài đặt tài nguyên bổ sung bất cứ lúc nào. Trước khi yêu cầu gỡ cài đặt, dưới đây là cách bạn có thể xác định ngôn ngữ nào đang được cài đặt.

Kotlin

val installedLanguages: Set<String> = splitInstallManager.installedLanguages

Java

Set<String> installedLanguages = splitInstallManager.getInstalledLanguages();

Sau đó, bạn có thể quyết định các ngôn ngữ nào cần gỡ cài đặt bằng cách sử dụng phương thức deferredLanguageUninstall(), như thể hiện dưới đây.

Kotlin

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Java

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

Kiểm thử cục bộ các lượt cài đặt mô-đun

Thư viện Play Feature Delivery hỗ trợ bạn kiểm thử cục bộ khả năng thực hiện những việc sau của ứng dụng mà không cần kết nối với Cửa hàng Play:

Trang này mô tả cách triển khai tệp APK phân tách của ứng dụng cho thiết bị kiểm thử để Play Feature Delivery tự động sử dụng tệp APK đó nhằm mô phỏng hoạt động yêu cầu, tải xuống và cài đặt các mô-đun qua Cửa hàng Play.

Mặc dù không cần thực hiện bất cứ thay đổi nào đối với logic của ứng dụng, nhưng bạn cần đáp ứng các yêu cầu sau:

Tạo một bộ tệp APK

Nếu bạn chưa làm như vậy, hãy tạo APK phân tách của ứng dụng như sau:

  1. Tạo gói ứng dụng cho ứng dụng của bạn sử dụng một trong những phương thức sau:
  2. Dùng bundletool để tạo bộ tệp APK cho tất cả cấu hình thiết bị qua lệnh sau:

    bundletool build-apks --local-testing
      --bundle my_app.aab
      --output my_app.apks
    

Cờ --local-testing bao gồm siêu dữ liệu trong tệp kê khai của tệp APK, cho phép thư viện Play Feature Delivery biết cách sử dụng tệp APK phân tách cục bộ để kiểm thử quá trình cài đặt các mô-đun tính năng mà không cần kết nối với Cửa hàng Play.

Triển khai ứng dụng trên thiết bị

Sau khi bạn tạo bộ tệp APK bằng cờ --local-testing, hãy dùng bundletool để cài đặt phiên bản cơ sở của ứng dụng rồi chuyển tệp APK bổ sung tới bộ nhớ cục bộ của thiết bị. Bạn có thể thực hiện cả hai thao tác bằng lệnh sau:

bundletool install-apks --apks my_app.apks

Sau khi bạn khởi động ứng dụng và hoàn tất luồng người dùng để tải xuống và cài đặt mô-đun tính năng, Thư viện Play Feature Delivery sử dụng tệp APK mà bundletool đã chuyển sang bộ nhớ cục bộ của thiết bị.

Mô phỏng lỗi mạng

Để mô phỏng quá trình cài đặt mô-đun qua Cửa hàng Play, Thư viện Play Feature Delivery sử dụng phương thức thay thế cho SplitInstallManager, có tên là FakeSplitInstallManager, để yêu cầu mô-đun đó. Khi bạn sử dụng bundletool cùng với cờ --local-testing để tạo bộ tệp APK rồi triển khai chúng cho thiết bị kiểm thử, bộ tệp đưa vào siêu dữ liệu hướng dẫn Thư viện Play Feature Delivery tự động chuyển đổi lệnh gọi API của ứng dụng để gọi FakeSplitInstallManager, thay vì SplitInstallManager.

FakeSplitInstallManager bao gồm cờ boolean mà bạn có thể bật để mô phỏng lỗi mạng vào lần tiếp theo ứng dụng của bạn yêu cầu cài đặt mô-đun. Để truy cập FakeSplitInstallManager trong các kiểm thử của mình, bạn có thể nhận bản sao của kiểm thử đó bằng cách sử dụng FakeSplitInstallManagerFactory, như thể hiện dưới đây:

Kotlin

// Creates an instance of FakeSplitInstallManager with the app's context.
val fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context)
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true)

Java

// Creates an instance of FakeSplitInstallManager with the app's context.
FakeSplitInstallManager fakeSplitInstallManager =
    FakeSplitInstallManagerFactory.create(context);
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true);