Tệp mở rộng APK

Theo yêu cầu của Google Play, APK ở dạng nén mà người dùng tải xuống không được có kích thước quá 100 MB. Đối với hầu hết ứng dụng, kích thước này thừa đủ để chứa toàn bộ mã và tài sản của ứng dụng. Tuy nhiên, một số ứng dụng cần thêm không gian cho tệp nội dung nghe nhìn, hình ảnh có độ chân thực cao hoặc thành phần lớn khác. Trước đây, nếu kích thước tải xuống đã nén của ứng dụng vượt quá 100 MB, thì bạn phải tự lưu trữ và tải các tài nguyên bổ sung xuống khi người dùng mở ứng dụng. Việc lưu trữ và phân phát các tệp thừa có thể tốn chi phí, và trải nghiệm người dùng thường không được lý tưởng. Để giúp bạn dễ dàng thực hiện quá trình này và người dùng có trải nghiệm dễ chịu hơn, Google Play cho phép bạn đính kèm 2 tệp mở rộng lớn bổ sung cho tệp APK.

Google Play lưu trữ các tệp mở rộng cho ứng dụng và phân phối các tệp đó đến thiết bị mà không tính phí. Các tệp mở rộng được lưu vào vị trí bộ nhớ chia sẻ của thiết bị (thẻ SD hoặc phân vùng có thể kết nối qua cổng USB; còn gọi là bộ nhớ "ngoài") mà ứng dụng của bạn có thể truy cập. Trên hầu hết thiết bị, Google Play tải tệp mở rộng xuống cùng thời điểm tải tệp APK xuống, để ứng dụng có mọi thông tin cần thiết khi người dùng mở ứng dụng lần đầu tiên. Tuy nhiên, trong một số trường hợp, ứng dụng phải tải tệp từ Google Play xuống khi ứng dụng khởi động.

Nếu muốn tránh sử dụng tệp mở rộng và kích thước tải xuống đã nén của ứng dụng lớn hơn 100 MB, thì bạn nên tải ứng dụng lên bằng Android App Bundle (cho phép kích thước tải xuống đã nén lên đến 200 MB). Ngoài ra, do hoạt động sử dụng gói ứng dụng trì hoãn việc tạo tệp APK và đăng nhập vào Google Play, nên người dùng chỉ tải các tệp APK được tối ưu hoá xuống thông qua mã và tài nguyên mà họ cần để chạy ứng dụng. Bạn không cần tạo bản dựng, ký và quản lý nhiều tệp APK hoặc tệp mở rộng, còn người dùng sẽ có thể tải các tệp nhỏ hơn và được tối ưu hoá nhiều hơn xuống.

Tổng quan

Mỗi lần tải tệp APK lên bằng Google Play Console, bạn có thể thêm một hoặc hai tệp mở rộng vào tệp APK. Mỗi tệp có thể có dung lượng tối đa là 2 GB và có thể là bất kỳ định dạng nào bạn chọn, nhưng bạn nên sử dụng một tệp nén để tiết kiệm băng thông trong khi tải xuống. Về mặt lý thuyết, mỗi tệp mở rộng đều đóng một vai trò riêng:

  • Tệp mở rộng chính là dành cho các tài nguyên bổ sung mà ứng dụng của bạn yêu cầu.
  • Tệp bản vá mở rộng là không bắt buộc và dành cho các bản cập nhật nhỏ trong tệp mở rộng chính.

Mặc dù bạn có thể sử dụng hai tệp mở rộng này theo bất kỳ cách nào bạn muốn, nhưng tệp mở rộng chính nên phân phối tài sản chính và nên ít được cập nhật; tệp bản vá mở rộng phải nhỏ hơn và đóng vai trò là "trình cung cấp bản vá", đồng thời được cập nhật sau mỗi bản phát hành chính hoặc nếu cần.

Tuy nhiên, ngay cả khi bản cập nhật ứng dụng chỉ yêu cầu tệp bản vá mở rộng mới, bạn vẫn phải tải một tệp APK mới có versionCode đã cập nhật lên trong tệp kê khai. (Play Console không cho phép bạn tải tệp mở rộng lên một tệp APK hiện có.)

Lưu ý: Tệp bản vá mở rộng giống với tệp mở rộng chính về mặt ngữ nghĩa, bạn có thể sử dụng mỗi tệp theo bất kỳ cách nào mà bạn muốn.

Định dạng tên tệp

Mỗi tệp mở rộng bạn tải lên có thể ở định dạng bất kỳ do bạn chọn (ZIP, PDF, MP4, v.v.). Bạn cũng có thể sử dụng công cụ JOBB để đóng gói và mã hoá một tập hợp tệp tài nguyên và bản vá kế tiếp cho tập hợp đó. Bất kể loại tệp là gì, Google Play đều coi chúng là tệp opaque binary blob và đổi tên tệp bằng lược đồ sau:

[main|patch].<expansion-version>.<package-name>.obb

Có ba thành phần cho lược đồ này:

main hoặc patch
Chỉ định tệp đó là tệp mở rộng chính hay tệp bản vá mở rộng. Mỗi tệp APK chỉ có thể có duy nhất một tệp chính và một tệp bản vá.
<expansion-version>
Đây là một số nguyên khớp với mã phiên bản của tệp APK mà bản mở rộng được liên kết lần đầu tiên (số nguyên này khớp với giá trị android:versionCode của ứng dụng).

"Lần đầu tiên" được nhấn mạnh vì mặc dù Play Console cho phép bạn sử dụng lại tệp mở rộng được tải lên bằng một tệp APK mới, nhưng tên của tệp mở rộng đó sẽ không thay đổi. Play Console giữ lại phiên bản được áp dụng vào lần đầu tiên bạn tải tệp đó lên.

<package-name>
Tên gói kiểu Java của ứng dụng.

Ví dụ: giả sử phiên bản APK là 314159 và tên gói là com.example.app. Nếu bạn tải tệp mở rộng chính lên, tệp đó sẽ được đổi tên thành:

main.314159.com.example.app.obb

Vị trí bộ nhớ

Khi tải tệp mở rộng xuống thiết bị, Google Play sẽ lưu các tệp đó vào vị trí bộ nhớ dùng chung của hệ thống. Để đảm bảo hoạt động bình thường, bạn không được xoá, di chuyển hoặc đổi tên tệp mở rộng. Trong trường hợp ứng dụng phải tự tải xuống qua Google Play, bạn phải lưu các tệp vào đúng vị trí đó.

Phương thức getObbDir() trả về vị trí cụ thể cho các tệp mở rộng ở dạng sau:

<shared-storage>/Android/obb/<package-name>/

Đối với mỗi ứng dụng, thư mục này không bao giờ có nhiều hơn hai tệp mở rộng. Một tệp mở rộng chính và một tệp bản vá mở rộng (nếu cần). Các phiên bản trước đó sẽ bị ghi đè khi bạn cập nhật ứng dụng bằng các tệp mở rộng mới. Kể từ Android 4.4 (API cấp 19), ứng dụng có thể đọc tệp mở rộng OBB mà không cần quyền truy cập vào bộ nhớ ngoài. Tuy nhiên, một số quy trình triển khai Android 6.0 (API cấp 23) trở lên vẫn yêu cầu quyền nên bạn cần khai báo quyền READ_EXTERNAL_STORAGE trong tệp kê khai ứng dụng và yêu cầu quyền trong thời gian chạy như sau:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Đối với Android phiên bản 6 trở lên, bạn cần yêu cầu quyền truy cập vào bộ nhớ ngoài trong thời gian chạy. Tuy nhiên, một số quy trình triển khai của Android không yêu cầu quyền đọc tệp OBB. Đoạn mã sau đây cho biết cách kiểm tra quyền đọc trước khi yêu cầu quyền truy cập vào bộ nhớ ngoài:

Kotlin

val obb = File(obb_filename)
var open_failed = false

try {
    BufferedReader(FileReader(obb)).also { br ->
        ReadObbFile(br)
    }
} catch (e: IOException) {
    open_failed = true
}

if (open_failed) {
    // request READ_EXTERNAL_STORAGE permission before reading OBB file
    ReadObbFileWithPermission()
}

Java

File obb = new File(obb_filename);
 boolean open_failed = false;

 try {
     BufferedReader br = new BufferedReader(new FileReader(obb));
     open_failed = false;
     ReadObbFile(br);
 } catch (IOException e) {
     open_failed = true;
 }

 if (open_failed) {
     // request READ_EXTERNAL_STORAGE permission before reading OBB file
     ReadObbFileWithPermission();
 }

Nếu bạn phải giải nén nội dung của các tệp mở rộng, đừng xoá các tệp mở rộng OBB sau đó và đừng lưu dữ liệu đã giải nén trong cùng một thư mục. Bạn nên lưu các tệp đã giải nén vào thư mục do getExternalFilesDir() chỉ định. Tuy nhiên, nếu có thể, tốt nhất bạn nên sử dụng định dạng tệp mở rộng cho phép bạn đọc tệp trực tiếp thay vì yêu cầu bạn giải nén dữ liệu. Ví dụ: chúng tôi đã cung cấp một dự án thư viện tên là Thư viện zip tệp mở rộng APK. Thư viện này đọc dữ liệu trực tiếp trên tệp ZIP.

Thận trọng: Không giống như tệp APK, người dùng và các ứng dụng khác có thể đọc mọi tệp đã lưu trong vị trí bộ nhớ dùng chung.

Mẹo: Nếu đang đóng gói các tệp nội dung nghe nhìn vào một tệp ZIP thì bạn có thể sử dụng lệnh gọi playback (phát) nội dung nghe nhìn trên các tệp đó kèm theo các tuỳ chọn điều khiển độ lệch và thời lượng (chẳng hạn như MediaPlayer.setDataSource()SoundPool.load()) mà không cần giải nén tệp ZIP. Để làm được việc này, bạn không được nén các tệp nội dung nghe nhìn thêm một lần nữa khi tạo các gói ZIP. Ví dụ: khi sử dụng công cụ zip, bạn nên sử dụng tuỳ chọn -n để chỉ định hậu tố tệp không nên nén:
zip -n .mp4;.ogg main_expansion media_files

Quá trình tải xuống

Trong hầu hết các trường hợp, Google Play tải và lưu các tệp mở rộng ở cùng thời điểm tải tệp APK xuống thiết bị. Tuy nhiên, trong một số trường hợp, Google Play không tải được tệp mở rộng xuống hoặc người dùng có thể đã xoá tệp mở rộng đã tải xuống trước đó. Để xử lý những trường hợp này, ứng dụng phải có khả năng tự tải tệp xuống khi hoạt động chính bắt đầu, bằng cách sử dụng URL do Google Play cung cấp.

Quá trình tải xuống từ cấp cao sẽ diễn ra như sau:

  1. Người dùng chọn cài đặt ứng dụng của bạn qua Google Play.
  2. Nếu có thể tải các tệp mở rộng xuống (trường hợp này xảy ra đối với hầu hết các thiết bị) thì Google Play sẽ tải chúng xuống cùng với APK.

    Nếu không thể tải các tệp mở rộng xuống thì Google Play sẽ chỉ tải tệp APK xuống.

  3. Khi người dùng chạy ứng dụng của bạn, ứng dụng phải kiểm tra xem tệp mở rộng đã được lưu trên thiết bị hay chưa.
    1. Nếu rồi thì ứng dụng đã sẵn sàng hoạt động.
    2. Nếu chưa thì ứng dụng phải tải các tệp mở rộng xuống qua HTTP trên Google Play. Ứng dụng phải gửi yêu cầu đến ứng dụng Google Play qua dịch vụ Cấp phép cho ứng dụng của Google Play. Dịch vụ này trả về tên, kích thước tệp và URL của từng tệp mở rộng. Với thông tin này, bạn sẽ tải các tệp xuống và lưu vào vị trí bộ nhớ thích hợp.

Thận trọng: Điều quan trọng là bạn phải bao gồm mã cần thiết để tải tệp mở rộng xuống từ Google Play trong trường hợp tệp chưa có trên thiết bị khi ứng dụng khởi động. Như đã thảo luận trong phần sau đây về việc Tải tệp mở rộng xuống, chúng tôi cung cấp một thư viện giúp bạn đơn giản hoá quá trình này và thực hiện tải xuống qua một dịch vụ bằng lượng mã tối thiểu.

Danh sách kiểm thử quá trình phát triển

Sau đây là phần tóm tắt về những việc bạn nên làm để sử dụng tệp mở rộng trên ứng dụng của mình:

  1. Trước tiên, hãy xác định xem kích thước tải xuống đã nén của ứng dụng có lớn hơn 100 MB hay không. Không gian lưu trữ rất quý giá và bạn nên giữ cho tổng kích thước tải xuống càng nhỏ càng tốt. Nếu ứng dụng sử dụng hơn 100 MB để cung cấp nhiều phiên bản thành phần đồ hoạ cho nhiều mật độ màn hình, hãy cân nhắc phát hành nhiều APK, trong đó mỗi APK chỉ chứa các thành phần cần thiết cho màn hình mà ứng dụng nhắm mục tiêu. Để có kết quả phù hợp nhất khi phát hành trên Google Play, hãy tải Android App Bundle lên, trong đó có tất cả tài nguyên và mã đã biên dịch của ứng dụng, nhưng trì hoãn quá trình tạo APK và đăng nhập vào Google Play.
  2. Xác định tài nguyên ứng dụng nào cần tách khỏi tệp APK và đóng gói chúng trong một tệp để sử dụng làm tệp mở rộng chính.

    Thường thì bạn chỉ nên sử dụng tệp bản vá mở rộng thứ hai khi thực hiện cập nhật cho tệp mở rộng chính. Tuy nhiên, nếu tài nguyên vượt quá giới hạn 2 GB cho tệp mở rộng chính, thì bạn có thể sử dụng tệp bản vá cho các tài sản còn lại.

  3. Phát triển ứng dụng để ứng dụng đó sử dụng các tài nguyên trên tệp mở rộng trong vị trí bộ nhớ dùng chung của thiết bị.

    Đừng quên rằng bạn không được xoá, di chuyển hoặc đổi tên tệp mở rộng.

    Nếu ứng dụng của bạn không yêu cầu định dạng cụ thể, bạn nên tạo tệp ZIP cho các tệp mở rộng, sau đó đọc các tệp này bằng Thư viện zip mở rộng APK.

  4. Thêm logic vào hoạt động chính của ứng dụng để kiểm tra xem các tệp mở rộng có ở trên thiết bị khi khởi động hay không. Nếu các tệp không có trên thiết bị, hãy sử dụng dịch vụ Cấp phép ứng dụng của Google Play để yêu cầu URL cho các tệp mở rộng, sau đó tải xuống và lưu lại các tệp đó.

    Để giảm lượng lớn các mã cần viết và đảm bảo mang lại trải nghiệm tốt cho người dùng trong quá trình tải xuống, bạn nên sử dụng Thư viện trình tải xuống để thực hiện hoạt động tải xuống.

    Nếu tự xây dựng dịch vụ tải xuống thay vì sử dụng thư viện, hãy lưu ý rằng bạn không được thay đổi tên của các tệp mở rộng và phải lưu chúng vào vị trí bộ nhớ phù hợp.

Sau khi bạn hoàn tất quá trình phát triển ứng dụng, hãy làm theo hướng dẫn để Kiểm thử tệp mở rộng.

Quy tắc và giới hạn

Thêm các tệp mở rộng APK là tính năng được cung cấp khi bạn tải ứng dụng lên bằng Play Console. Trong lần đầu tiên tải ứng dụng lên hoặc cập nhật một ứng dụng sử dụng tệp mở rộng, bạn phải lưu ý các quy tắc và giới hạn sau:

  1. Mỗi tệp mở rộng không được vượt quá 2 GB.
  2. Để tải các tệp mở rộng của bạn xuống từ Google Play, người dùng phải mua ứng dụng của bạn từ Google Play. Google Play sẽ không cung cấp URL cho các tệp mở rộng nếu ứng dụng được cài đặt bằng các phương pháp khác.
  3. Khi tải xuống từ ứng dụng của bạn, URL mà Google Play cung cấp cho từng tệp là duy nhất cho mỗi lần tải xuống và mỗi URL sẽ hết hạn ngay sau khi được cấp cho ứng dụng của bạn.
  4. Nếu cập nhật ứng dụng bằng một APK mới hoặc tải nhiều APK lên cho cùng một ứng dụng, bạn có thể chọn các tệp mở rộng được tải lên cho APK trước đó. Tên của tệp mở rộng không thay đổi – tên này giữ lại phiên bản nhận được từ APK liên kết từ đầu.
  5. Nếu sử dụng tệp mở rộng kết hợp với nhiều APK để cung cấp các tệp mở rộng khác nhau cho thiết bị khác nhau, bạn vẫn phải tải các APK riêng biệt lên cho từng thiết bị để cung cấp một giá trị versionCode và khai báo các bộ lọc khác nhau cho mỗi APK.
  6. Bạn không thể phát hành bản cập nhật cho ứng dụng bằng cách chỉ thay đổi các tệp mở rộng – bạn phải tải APK mới lên để cập nhật ứng dụng. Nếu nội dung thay đổi chỉ liên quan đến các tài sản trong tệp mở rộng, bạn có thể cập nhật APK chỉ bằng cách thay đổi versionCode (và cũng có thể là versionName).

  7. Không lưu dữ liệu khác vào thư mục obb/ của bạn. Nếu bạn phải giải nén một số dữ liệu, hãy lưu dữ liệu đó vào vị trí mà getExternalFilesDir() chỉ định.
  8. Không xoá hoặc đổi tên tệp mở rộng .obb (trừ khi bạn đang cập nhật). Làm vậy có thể khiến Google Play (hoặc chính ứng dụng của bạn) tải tệp mở rộng xuống nhiều lần.
  9. Khi cập nhật tệp mở rộng theo cách thủ công, bạn phải xoá tệp mở rộng trước đó.

Tải tệp mở rộng xuống

Trong hầu hết trường hợp, Google Play sẽ tải xuống và lưu các tệp mở rộng của bạn về thiết bị tại cùng thời điểm cài đặt hoặc cập nhật APK. Bằng cách này, các tệp mở rộng sẽ sẵn có khi ứng dụng của bạn khởi chạy lần đầu tiên. Tuy nhiên, trong một số trường hợp, ứng dụng phải tải các tệp mở rộng xuống bằng cách yêu cầu các tệp đó từ một URL được cung cấp cho bạn trong một phản hồi từ dịch vụ Cấp phép ứng dụng của Google Play.

Logic cơ bản mà bạn cần để tải các tệp mở rộng xuống là:

  1. Khi ứng dụng của bạn khởi động, hãy tìm các tệp mở rộng trên vị trí bộ nhớ dùng chung (trong thư mục Android/obb/<package-name>/).
    1. Nếu các tệp mở rộng có trong bộ nhớ đó thì bạn đã hoàn thành các bước cần thiết và ứng dụng của bạn có thể tiếp tục.
    2. Nếu tệp mở rộng không có trong bộ nhớ đó:
      1. Hãy yêu cầu bằng cách sử dụng dịch vụ Cấp phép ứng dụng của Google Play để có được URL, các kích thước và tên tệp mở rộng của ứng dụng.
      2. Sử dụng các URL mà Google Play cung cấp để tải các tệp mở rộng xuống và lưu chúng. Bạn phải lưu tệp vào vị trí bộ nhớ dùng chung (Android/obb/<package-name>/) và sử dụng đúng tên tệp theo phản hồi của Google Play.

        Lưu ý: URL mà Google Play cung cấp cho các tệp mở rộng là duy nhất cho mỗi lượt tải xuống và mỗi URL sẽ hết hạn ngay sau khi được cấp cho ứng dụng của bạn.

Nếu ứng dụng của bạn miễn phí (không phải là ứng dụng có tính phí) thì có thể bạn chưa sử dụng dịch vụ Cấp phép ứng dụng. Dịch vụ này chủ yếu được thiết kế để bạn thực thi các chính sách cấp phép cho ứng dụng của mình và đảm bảo rằng người dùng có quyền sử dụng ứng dụng của bạn (anh ấy hoặc cô ấy đã thanh toán chính đáng cho ứng dụng trên Google Play). Để tạo điều kiện cho chức năng tệp mở rộng, dịch vụ cấp phép đã được nâng cấp để cung cấp phản hồi cho ứng dụng của bạn, bao gồm URL của các tệp mở rộng của ứng dụng được lưu trữ trên Google Play. Vì vậy, ngay cả khi ứng dụng của bạn miễn phí đối với người dùng, bạn vẫn cần phải bao gồm cả Thư viện xác minh giấy phép (LVL) để sử dụng các tệp mở rộng APK. Tất nhiên, nếu ứng dụng của bạn miễn phí, bạn không cần xác minh giấy phép mà chỉ cần thư viện thực hiện yêu cầu trả về URL của các tệp mở rộng.

Lưu ý: Cho dù ứng dụng của bạn miễn phí hay không, Google Play chỉ trả về URL tệp mở rộng nếu người dùng đã mua ứng dụng của bạn từ Google Play.

Ngoài LVL, bạn cần một bộ mã tải các tệp mở rộng xuống qua kết nối HTTP và lưu các tệp đó vào vị trí thích hợp trên bộ nhớ chia sẻ của thiết bị. Khi xây dựng quy trình này trong ứng dụng, bạn nên lưu ý một số vấn đề sau:

  • Thiết bị có thể không có đủ dung lượng cho các tệp mở rộng, vì vậy, bạn nên kiểm tra trước khi bắt đầu tải xuống và cảnh báo người dùng nếu không có đủ dung lượng.
  • Việc tải tệp xuống nên diễn ra trong một dịch vụ nền để tránh chặn tương tác của người dùng và cho phép họ rời khỏi ứng dụng trong khi thực hiện quá trình tải xuống.
  • Bạn phải xử lý một cách thoả đáng nhiều lỗi có thể xảy ra trong quá trình yêu cầu và tải xuống.
  • Kết nối mạng có thể thay đổi trong quá trình tải xuống, vì vậy, bạn nên xử lý những điều thay đổi đó và nếu bị gián đoạn, hãy tiếp tục tải xuống khi có thể.
  • Mặc dù quá trình tải xuống diễn ra trong chế độ nền, bạn nên cung cấp một thông báo về tiến trình tải xuống, thông báo cho người dùng khi quá trình tải xuống hoàn tất và đưa họ trở lại ứng dụng của bạn khi được chọn.

Để đơn giản hoá công việc này cho bạn, chúng tôi đã xây dựng Thư viện trình tải xuống. Thư viện này yêu cầu URL của tệp mở rộng thông qua dịch vụ cấp phép, tải tệp mở rộng xuống, thực hiện tất cả tác vụ được liệt kê bên trên và thậm chí cho phép bạn tạm dừng hoạt động và tiếp tục tải xuống. Bằng cách thêm Thư viện trình tải xuống và một phần móc của mã vào ứng dụng, hầu như tất cả công việc tải các tệp mở rộng đã được mã hoá xuống cho bạn. Do đó, để thay bạn cung cấp trải nghiệm người dùng tốt nhất mà không tốn nhiều công sức, bạn nên sử dụng Thư viện trình tải xuống để tải tệp mở rộng xuống. Thông tin trong các phần sau giải thích cách tích hợp thư viện vào ứng dụng.

Nếu bạn muốn phát triển giải pháp riêng để tải các tệp mở rộng xuống thông qua các URL Google Play, bạn phải làm theo tài liệu Cấp phép ứng dụng để thực hiện yêu cầu cấp phép, sau đó truy xuất các url, kích thước và tên tệp mở rộng phần bổ sung phản hồi. Bạn nên sử dụng lớp APKExpansionPolicy (có trong Thư viện xác minh giấy phép) làm chính sách cấp phép, trong đó ghi lại các URL, kích thước và tên tệp mở rộng từ dịch vụ cấp phép.

Giới thiệu về Thư viện trình tải xuống

Để thay bạn sử dụng các tệp mở rộng APK với ứng dụng và cung cấp trải nghiệm người dùng tốt nhất mà không tốn nhiều công sức, bạn nên sử dụng Thư viện trình tải xuống có trong gói Thư viện mở rộng APK Google Play. Thư viện này tải các tệp mở rộng của bạn xuống trong dịch vụ nền, hiển thị thông báo cho người dùng về trạng thái tải xuống, xử lý tình trạng mất kết nối mạng, tiếp tục tải xuống khi có thể và nhiều tính năng khác.

Để triển khai quá trình tải tệp mở rộng xuống bằng Thư viện trình tải xuống, bạn chỉ cần:

  • Mở rộng một lớp con Service đặc biệt và lớp con BroadcastReceiver, mỗi lớp chỉ yêu cầu bạn nhập một vài dòng mã.
  • Thêm một số logic vào hoạt động chính để kiểm tra xem các tệp mở rộng đã được tải xuống hay chưa, và nếu chưa, gọi quy trình tải xuống và hiển thị giao diện người dùng tiến trình.
  • Triển khai giao diện gọi lại với một vài phương thức trong hoạt động chính để nhận cập nhật về tiến trình tải xuống.

Các phần sau giải thích cách thiết lập ứng dụng bằng Thư viện trình tải xuống.

Chuẩn bị sử dụng Thư viện trình tải xuống

Để sử dụng Thư viện trình tải xuống, bạn cần tải hai gói xuống từ Trình quản lý SDK và thêm các thư viện phù hợp vào ứng dụng.

Trước tiên, hãy mở Trình quản lý SDK Android (Tools > SDK Manager)(Công cụ > Trình quản lý SDK) và trong mục Appearance & Behavior > System Settings > Android SDK (Giao diện và hành vi > Cài đặt hệ thống > SDK Android), chọn thẻ SDK Tools (Công cụ SDK) để chọn và tải xuống:

  • Gói Thư viện cấp phép của Google Play
  • Gói Thư viện mở rộng APK Google Play

Tạo một mô-đun thư viện mới cho Thư viện xác minh giấy phép và Thư viện trình tải xuống. Với mỗi thư viện:

  1. Chọn File > New > New Module (Tệp > Mới > Mô-đun mới).
  2. Trong cửa sổ Tạo mô-đun mới, hãy chọn Android Library, (Thư viện Android) rồi chọn Next (Tiếp theo).
  3. Chỉ định tên ứng dụng/Thư viện như "Thư viện giấy phép Google Play" và "Thư viện trình tải xuống Google Play", chọn cấp SDK tối thiểu, rồi chọn Finish (Hoàn tất).
  4. Chọn File > Project Structure (Tệp > Cấu trúc dự án).
  5. Chọn tab Thuộc tính và trong Kho lưu trữ thư viện, hãy nhập thư viện từ thư mục <sdk>/extras/google/ (play_licensing/ cho Thư viện xác minh giấy phép hoặc play_apk_expansion/downloader_library/ cho Thư viện trình tải xuống).
  6. Chọn OK để tạo mô-đun mới.

Lưu ý: Thư viện trình tải xuống phụ thuộc vào Thư viện xác minh giấy phép. Hãy nhớ thêm Thư viện xác minh giấy phép vào thuộc tính dự án của Thư viện trình tải xuống.

Hoặc trên một dòng lệnh, hãy cập nhật dự án của bạn để bao gồm các thư viện:

  1. Thay đổi các thư mục thành thư mục <sdk>/tools/.
  2. Thực thi android update project bằng tuỳ chọn --library để thêm cả LVL và Thư viện trình tải xuống vào dự án. Ví dụ:
    android update project --path ~/Android/MyApp \
    --library ~/android_sdk/extras/google/market_licensing \
    --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
    

Khi thêm cả Thư viện xác minh giấy phép và Thư viện trình tải xuống vào ứng dụng, bạn sẽ có thể nhanh chóng tích hợp khả năng tải các tệp mở rộng xuống từ Google Play. Định dạng mà bạn chọn cho các tệp mở rộng và cách bạn đọc chúng từ bộ nhớ dùng chung là hoạt động thực thi riêng mà bạn nên cân nhắc dựa trên nhu cầu của ứng dụng.

Mẹo: Gói mở rộng Apk bao gồm một ứng dụng mẫu cho biết cách sử dụng Thư viện trình tải xuống trong một ứng dụng. Ứng dụng mẫu này sử dụng một thư viện thứ ba có sẵn trong gói Mở rộng Apk có tên là Thư viện ZIP mở rộng APK. Nếu định sử dụng các tệp ZIP cho tệp mở rộng, bạn nên thêm Thư viện mã mở rộng APK vào ứng dụng của mình. Để biết thêm thông tin, hãy xem phần bên dưới về cách sử dụng Thư viện Zip mở rộng APK.

Khai báo sự cho phép của người dùng

Để tải các tệp mở rộng xuống, Thư viện trình tải xuống cần có một số quyền mà bạn phải khai báo trong tệp kê khai của ứng dụng. Các quyền như sau:

<manifest ...>
    <!-- Required to access Google Play Licensing -->
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />

    <!-- Required to download files from Google Play -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- Required to keep CPU alive while downloading files
        (NOT to keep screen awake) -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <!-- Required to poll the state of the network connection
        and respond to changes -->
    <uses-permission
        android:name="android.permission.ACCESS_NETWORK_STATE" />

    <!-- Required to check whether Wi-Fi is enabled -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

    <!-- Required to read and write the expansion files on shared storage -->
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

Lưu ý: Theo mặc định, Thư viện trình tải xuống yêu cầu API cấp 4, nhưng Thư viện zip mở rộng APK yêu cầu API cấp 5.

Triển khai dịch vụ trình tải xuống

Để thực hiện việc tải xuống trong nền, Thư viện trình tải xuống cung cấp lớp con Service riêng, gọi là DownloaderService mà bạn nên mở rộng. Ngoài việc tải các tệp mở rộng xuống cho bạn, DownloaderService cũng:

  • Đăng ký một BroadcastReceiver để theo dõi các thay đổi kết nối mạng của thiết bị (truyền phát CONNECTIVITY_ACTION) để tạm dừng việc tải xuống khi cần (chẳng hạn như do mất kết nối) và tiếp tục tải xuống khi có thể (có kết nối).
  • Lên lịch chuông báo RTC_WAKEUP để thử tải xuống lại đối với các trường hợp dịch vụ bị ngừng.
  • Xây dựng một Notification tuỳ chỉnh hiển thị tiến trình tải xuống và bất cứ lỗi hoặc thay đổi trạng thái nào.
  • Cho phép ứng dụng tạm dừng và tiếp tục quá trình tải xuống theo cách thủ công.
  • Xác minh rằng bộ nhớ dùng chung đã được gắn và sẵn có, các tệp chưa tồn tại, và có đủ dung lượng trước khi tải tệp mở rộng xuống. Sau đó, hãy thông báo cho người dùng nếu có bất kỳ thông tin nào trong đó không chính xác.

Bạn chỉ cần tạo một lớp trong ứng dụng sẽ mở rộng lớp DownloaderService và ghi đè ba phương pháp cung cấp thông tin chi tiết về ứng dụng:

getPublicKey()
Hàm này phải trả về một chuỗi là khoá công khai RSA được mã hoá Base64 cho tài khoản nhà xuất bản của bạn, sẵn có từ trang hồ sơ trên Play Console (xem phần Cài đặt để cấp phép).
getSALT()
Hàm này phải trả về một mảng các byte ngẫu nhiên mà Policy cấp phép sử dụng để tạo một Obfuscator. Dữ liệu ngẫu nhiên đảm bảo rằng tệp SharedPreferences được làm rối có chứa dữ liệu cấp phép của bạn sẽ là tệp duy nhất và không thể phát hiện.
getAlarmReceiverClassName()
Hàm này phải trả về tên lớp của BroadcastReceiver trong ứng dụng sẽ nhận được thông báo cho biết cần bắt đầu lại quá trình tải xuống (có thể xảy ra nếu dịch vụ tải xuống dừng đột ngột).

Ví dụ: sau đây là quá trình thực thi DownloaderService đầy đủ:

Kotlin

// You must use the public key belonging to your publisher account
const val BASE64_PUBLIC_KEY = "YourLVLKey"
// You should also modify this salt
val SALT = byteArrayOf(
        1, 42, -12, -1, 54, 98, -100, -12, 43, 2,
        -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
)

class SampleDownloaderService : DownloaderService() {

    override fun getPublicKey(): String = BASE64_PUBLIC_KEY

    override fun getSALT(): ByteArray = SALT

    override fun getAlarmReceiverClassName(): String = SampleAlarmReceiver::class.java.name
}

Java

public class SampleDownloaderService extends DownloaderService {
    // You must use the public key belonging to your publisher account
    public static final String BASE64_PUBLIC_KEY = "YourLVLKey";
    // You should also modify this salt
    public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98,
            -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
    };

    @Override
    public String getPublicKey() {
        return BASE64_PUBLIC_KEY;
    }

    @Override
    public byte[] getSALT() {
        return SALT;
    }

    @Override
    public String getAlarmReceiverClassName() {
        return SampleAlarmReceiver.class.getName();
    }
}

Lưu ý: Bạn phải cập nhật giá trị BASE64_PUBLIC_KEY thành khoá công khai thuộc về tài khoản nhà xuất bản của bạn. Bạn có thể tìm thấy khoá trong Play Console trong mục thông tin hồ sơ của mình. Điều này cần thiết ngay cả khi bạn kiểm thử các tệp tải xuống của mình.

Hãy nhớ khai báo dịch vụ trong tệp kê khai:

<app ...>
    <service android:name=".SampleDownloaderService" />
    ...
</app>

Triển khai trình nhận thông báo

Để theo dõi tiến trình tải tệp xuống và khởi động lại quá trình tải xuống nếu cần, DownloaderService sẽ lập lịch biểu cho một thông báo RTC_WAKEUP để cung cấp một Intent cho một BroadcastReceiver trong ứng dụng của bạn. Bạn phải thiết lập BroadcastReceiver để gọi API từ Thư viện trình tải xuống nhằm kiểm tra trạng thái của tệp tải xuống và khởi động lại quá trình tải xuống nếu cần.

Bạn chỉ cần ghi đè phương thức onReceive() để gọi DownloaderClientMarshaller.startDownloadServiceIfRequired().

Ví dụ:

Kotlin

class SampleAlarmReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        try {
            DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    context,
                    intent,
                    SampleDownloaderService::class.java
            )
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
    }
}

Java

public class SampleAlarmReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            DownloaderClientMarshaller.startDownloadServiceIfRequired(context,
                intent, SampleDownloaderService.class);
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Lưu ý rằng đây là lớp mà bạn phải trả tên về trong phương thức getAlarmReceiverClassName() của dịch vụ (xem mục trước).

Hãy nhớ khai báo trình nhận trong tệp kê khai:

<app ...>
    <receiver android:name=".SampleAlarmReceiver" />
    ...
</app>

Bắt đầu tiến trình tải xuống

Hoạt động chính trong ứng dụng của bạn (hoạt động được biểu tượng trình chạy bắt đầu) chịu trách nhiệm xác minh xem các tệp mở rộng đã có trên thiết bị hay chưa và bắt đầu tải xuống nếu chưa có.

Cần thực hiện các quy trình sau để bắt đầu tải xuống bằng Thư viện trình tải xuống:

  1. Kiểm tra xem tệp đã được tải xuống hay chưa.

    Thư viện trình tải xuống bao gồm một số API trong lớp Helper để hỗ trợ quá trình này:

    • getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
    • doesFileExist(Context c, String fileName, long fileSize)

    Ví dụ: ứng dụng mẫu được cung cấp trong gói Mở rộng Apk sẽ gọi phương thức sau trong phương thức onCreate() của hoạt động để kiểm tra xem các tệp mở rộng có trên thiết bị hay chưa:

    Kotlin

    fun expansionFilesDelivered(): Boolean {
        xAPKS.forEach { xf ->
            Helpers.getExpansionAPKFileName(this, xf.isBase, xf.fileVersion).also { fileName ->
                if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false))
                    return false
            }
        }
        return true
    }
    

    Java

    boolean expansionFilesDelivered() {
        for (XAPKFile xf : xAPKS) {
            String fileName = Helpers.getExpansionAPKFileName(this, xf.isBase,
                xf.fileVersion);
            if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false))
                return false;
        }
        return true;
    }
    

    Trong trường hợp này, mỗi đối tượng XAPKFile chứa số phiên bản và kích thước tệp của một tệp mở rộng đã biết và một giá trị boolean để xem tệp có phải là tệp mở rộng chính hay không. (Hãy xem lớp SampleDownloaderActivity của ứng dụng mẫu để biết thông tin chi tiết).

    Nếu phương thức này trả về giá trị là false thì ứng dụng phải bắt đầu tải xuống.

  2. Bắt đầu tải xuống bằng cách gọi phương thức tĩnh DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass).

    Phương thức này sẽ lấy các thông số sau:

    • context: Context của ứng dụng của bạn.
    • notificationClient: Một PendingIntent để bắt đầu hoạt động chính. Tệp này được dùng trong NotificationDownloaderService tạo để hiển thị tiến trình tải xuống. Khi người dùng chọn thông báo, hệ thống sẽ gọi PendingIntent bạn cung cấp tại đây và mở hoạt động cho thấy tiến trình tải xuống (thường là cùng hoạt động đã bắt đầu quá trình tải xuống).
    • serviceClass: Đối tượng Class để triển khai DownloaderService bắt buộc phải khởi động dịch vụ và bắt đầu tải xuống nếu cần.

    Phương thức này trả về một số nguyên cho biết liệu người dùng có cần phải tải xuống hay không. Các giá trị có thể là:

    • NO_DOWNLOAD_REQUIRED: Được trả về nếu các tệp đã tồn tại hoặc đã đang được tải xuống.
    • LVL_CHECK_REQUIRED: Được trả về nếu cần xác minh giấy phép để lấy các URL của tệp mở rộng.
    • DOWNLOAD_REQUIRED: Được trả về nếu đã biết các URL của tệp mở rộng, nhưng chưa tải các tệp xuống.

    Hành vi của LVL_CHECK_REQUIREDDOWNLOAD_REQUIRED về cơ bản là giống nhau và thường bạn không cần lo ngại về các hành vi này. Trong hoạt động chính của bạn có chức năng gọi startDownloadServiceIfRequired(), bạn chỉ cần kiểm tra xem phản hồi có phải là NO_DOWNLOAD_REQUIRED hay không. Nếu phản hồi không phải là NO_DOWNLOAD_REQUIRED, Thư viện trình tải xuống sẽ bắt đầu tải xuống và bạn nên cập nhật giao diện người dùng hoạt động của mình để hiển thị tiến trình tải xuống (xem bước tiếp theo). Nếu phản hồi NO_DOWNLOAD_REQUIRED, các tệp sẽ sẵn có và ứng dụng của bạn có thể khởi động.

    Ví dụ:

    Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        // Check if expansion files are available before going any further
        if (!expansionFilesDelivered()) {
            val pendingIntent =
                    // Build an Intent to start this activity from the Notification
                    Intent(this, MainActivity::class.java).apply {
                        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
                    }.let { notifierIntent ->
                        PendingIntent.getActivity(
                                this,
                                0,
                                notifierIntent,
                                PendingIntent.FLAG_UPDATE_CURRENT
                        )
                    }
    
            // Start the download service (if required)
            val startResult: Int = DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    this,
                    pendingIntent,
                    SampleDownloaderService::class.java
            )
            // If download has started, initialize this activity to show
            // download progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // This is where you do set up to display the download
                // progress (next step)
                ...
                return
            } // If the download wasn't necessary, fall through to start the app
        }
        startApp() // Expansion files are available, start the app
    }
    

    Java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Check if expansion files are available before going any further
        if (!expansionFilesDelivered()) {
            // Build an Intent to start this activity from the Notification
            Intent notifierIntent = new Intent(this, MainActivity.getClass());
            notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                                    Intent.FLAG_ACTIVITY_CLEAR_TOP);
            ...
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                    notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
            // Start the download service (if required)
            int startResult =
                DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                            pendingIntent, SampleDownloaderService.class);
            // If download has started, initialize this activity to show
            // download progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // This is where you do set up to display the download
                // progress (next step)
                ...
                return;
            } // If the download wasn't necessary, fall through to start the app
        }
        startApp(); // Expansion files are available, start the app
    }
    
  3. Khi phương thức startDownloadServiceIfRequired() trả về bất cứ nội dung gì không phải là NO_DOWNLOAD_REQUIRED, hãy tạo một bản sao của IStub bằng cách gọi DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService). IStub liên kết hoạt động của bạn với dịch vụ trình tải xuống để hoạt động của bạn nhận lệnh gọi lại về tiến trình tải xuống.

    Để tạo bản sao IStub bằng cách gọi hàm CreateStub(), bạn phải chuyển một hoạt động triển khai của giao diện IDownloaderClient và hoạt động triển khai DownloaderService cho hàm. Phần tiếp theo về Nhận tiến trình tải xuống thảo luận về giao diện IDownloaderClient mà bạn thường nên triển khai trong lớp Activity để có thể cập nhật giao diện người dùng hoạt động khi trạng thái tải xuống thay đổi.

    Chúng tôi khuyến nghị bạn nên gọi CreateStub() để tạo bảo sao IStub trong phương thức onCreate() của hoạt động sau khi startDownloadServiceIfRequired() bắt đầu tải xuống.

    Ví dụ: trong mã mẫu trước đó của onCreate(), bạn có thể phản hồi kết quả startDownloadServiceIfRequired() như sau:

    Kotlin

            // Start the download service (if required)
            val startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    this@MainActivity,
                    pendingIntent,
                    SampleDownloaderService::class.java
            )
            // If download has started, initialize activity to show progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // Instantiate a member instance of IStub
                downloaderClientStub =
                        DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService::class.java)
                // Inflate layout that shows download progress
                setContentView(R.layout.downloader_ui)
                return
            }
    

    Java

            // Start the download service (if required)
            int startResult =
                DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                            pendingIntent, SampleDownloaderService.class);
            // If download has started, initialize activity to show progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // Instantiate a member instance of IStub
                downloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
                        SampleDownloaderService.class);
                // Inflate layout that shows download progress
                setContentView(R.layout.downloader_ui);
                return;
            }
    

    Sau khi phương thức onCreate() trả về, hoạt động của bạn sẽ nhận được một lệnh gọi tới onResume(), đây là nơi bạn nên gọi connect() trên IStub, truyền hoạt động đó đến Context của ứng dụng của bạn. Ngược lại, bạn nên gọi disconnect() trong lệnh gọi lại onStop() của hoạt động.

    Kotlin

    override fun onResume() {
        downloaderClientStub?.connect(this)
        super.onResume()
    }
    
    override fun onStop() {
        downloaderClientStub?.disconnect(this)
        super.onStop()
    }
    

    Java

    @Override
    protected void onResume() {
        if (null != downloaderClientStub) {
            downloaderClientStub.connect(this);
        }
        super.onResume();
    }
    
    @Override
    protected void onStop() {
        if (null != downloaderClientStub) {
            downloaderClientStub.disconnect(this);
        }
        super.onStop();
    }
    

    Việc gọi hàm connect() trên IStub sẽ liên kết hoạt động của bạn với DownloaderService, hoạt động của bạn sẽ nhận được lệnh gọi lại liên quan đến các thay đổi trạng thái tải xuống thông qua giao diện IDownloaderClient.

Đang nhận tiến trình tải xuống

Để nhận thông tin cập nhật về tiến trình tải xuống và tương tác với DownloaderService, bạn phải triển khai giao diện IDownloaderClient của Thư viện trình tải xuống. Thông thường, hoạt động bạn sử dụng để bắt đầu tải xuống sẽ triển khai giao diện này để hiển thị tiến trình tải xuống và gửi yêu cầu đến dịch vụ.

Các phương thức thuộc giao diện bắt buộc cho IDownloaderClient là:

onServiceConnected(Messenger m)
Sau khi tạo bản sao IStub trong hoạt động, bạn sẽ nhận được một lệnh gọi đến phương thức này, lệnh này sẽ chuyển một đối tượng Messenger được kết nối với bản sao của DownloaderService. Để gửi các yêu cầu đến dịch vụ, chẳng hạn như tạm dừng và tiếp tục tải xuống, bạn phải gọi hàm DownloaderServiceMarshaller.CreateProxy() để nhận giao diện IDownloaderService được kết nối với dịch vụ.

Cách triển khai được khuyến nghị như sau:

Kotlin

private var remoteService: IDownloaderService? = null
...

override fun onServiceConnected(m: Messenger) {
    remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
        downloaderClientStub?.messenger?.also { messenger ->
            onClientUpdated(messenger)
        }
    }
}

Java

private IDownloaderService remoteService;
...

@Override
public void onServiceConnected(Messenger m) {
    remoteService = DownloaderServiceMarshaller.CreateProxy(m);
    remoteService.onClientUpdated(downloaderClientStub.getMessenger());
}

Sau khi khởi tạo đối tượng IDownloaderService, bạn có thể gửi các lệnh đến dịch vụ trình tải xuống, chẳng hạn như tạm dừng và tiếp tục quá trình tải xuống (requestPauseDownload()requestContinueDownload()).

onDownloadStateChanged(int newState)
Dịch vụ tải xuống gọi hàm này khi có thay đổi về trạng thái tải xuống, chẳng hạn như bắt đầu hoặc hoàn tất việc tải xuống.

Giá trị newState sẽ là một trong nhiều giá trị có thể được chỉ định trong một trong các hằng số STATE_* của lớp IDownloaderClient.

Để cung cấp thông báo hữu ích cho người dùng, bạn có thể yêu cầu một chuỗi tương ứng cho từng trạng thái bằng cách gọi Helpers.getDownloaderStringResourceIDFromState(). Thao tác này sẽ trả về mã nhận dạng tài nguyên cho một trong các chuỗi trong gói Thư viện trình tải xuống. Ví dụ: chuỗi "Tạm dừng tải xuống vì bạn đang chuyển vùng" tương ứng với STATE_PAUSED_ROAMING.

onDownloadProgress(DownloadProgressInfo progress)
Dịch vụ tải xuống gọi hàm này để cung cấp một đối tượng DownloadProgressInfo mô tả nhiều thông tin khác nhau về tiến trình tải xuống, bao gồm thời gian ước tính còn lại, tốc độ hiện tại, tiến trình tổng thể và tổng số để bạn có thể cập nhật giao diện người dùng cho tiến trình tải xuống.

Lưu ý: Để biết ví dụ về các lệnh gọi lại cập nhật giao diện người dùng của tiến trình tải xuống, hãy xem SampleDownloaderActivity trong ứng dụng mẫu được cung cấp cùng với gói Mở rộng Apk.

Một số phương thức công khai cho giao diện IDownloaderService có thể hữu ích là:

requestPauseDownload()
Tạm dừng quá trình tải xuống.
requestContinueDownload()
Tiếp tục tải một tệp đã tạm dừng xuống.
setDownloadFlags(int flags)
Đặt tuỳ chọn người dùng cho những loại mạng mà bạn có thể tải tệp xuống. Cách triển khai hiện tại hỗ trợ một cờ FLAGS_DOWNLOAD_OVER_CELLULAR, tuy nhiên bạn có thể thêm cờ khác. Theo mặc định, cờ này không được bật, vì vậy, người dùng phải sử dụng Wi-Fi để tải tệp mở rộng xuống. Có thể bạn muốn cung cấp một tuỳ chọn người dùng để cho phép tải xuống qua mạng di động. Trong trường hợp đó, bạn có thể gọi:

Kotlin

remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
    ...
    setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR)
}

Java

remoteService
    .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);

Sử dụng APKExpansionPolicy

Nếu quyết định xây dựng dịch vụ tải xuống của riêng bạn thay vì sử dụng Thư viện trình tải xuống của Google Play, bạn vẫn nên sử dụng APKExpansionPolicy được cung cấp trong Thư viện xác minh giấy phép. Lớp APKExpansionPolicy gần giống với lớp ServerManagedPolicy (có sẵn trong Thư viện xác minh giấy phép của Google Play) nhưng có thêm tính năng xử lý bổ sung cho những ứng dụng khác của phản hồi tệp mở rộng APK.

Lưu ý: Nếu bạn sử dụng Thư viện trình tải xuống như đã thảo luận trong phần trước, thư viện sẽ thực hiện tất cả hoạt động tương tác với APKExpansionPolicy nên bạn không phải sử dụng trực tiếp lớp này.

Lớp này bao gồm các phương thức để giúp bạn nhận được thông tin cần thiết về các tệp mở rộng có sẵn:

  • getExpansionURLCount()
  • getExpansionURL(int index)
  • getExpansionFileName(int index)
  • getExpansionFileSize(int index)

Để biết thêm thông tin về cách sử dụng APKExpansionPolicy khi bạn không sử dụng Thư viện trình tải xuống, hãy xem tài liệu về cách Thêm giấy phép vào Ứng dụng, trong đó giải thích cách triển khai chính sách cấp phép giống chính sách này.

Đọc tệp mở rộng

Sau khi lưu các tệp mở rộng APK trên thiết bị, cách bạn đọc tệp sẽ phụ thuộc vào loại tệp bạn đã sử dụng. Như đã thảo luận trong phần tổng quan, tệp mở rộng của bạn có thể là bất kỳ loại tệp nào bạn muốn, nhưng được đổi tên bằng một định dạng tên tệp cụ thể và được lưu vào <shared-storage>/Android/obb/<package-name>/.

Bất kể bạn đọc tệp theo cách nào, bạn phải luôn kiểm tra trước để chắc rằng bộ nhớ ngoài có sẵn để đọc. Có thể người dùng sẽ lắp bộ nhớ vào một máy tính qua USB hoặc đã tháo thẻ SD.

Lưu ý: Khi ứng dụng của bạn khởi động, bạn phải luôn kiểm tra xem dung lượng bộ nhớ ngoài có đủ và có thể đọc được hay không bằng cách gọi getExternalStorageState(). Hàm này sẽ trả về một trong những chuỗi có thể hiển thị trạng thái của bộ nhớ ngoài. Để bộ nhớ ngoài của bạn có thể đọc được thì giá trị trả về phải là MEDIA_MOUNTED.

Lấy tên tệp

Như đã mô tả trong phần tổng quan, tệp mở rộng APK của bạn được lưu dưới một định dạng tên tệp cụ thể:

[main|patch].<expansion-version>.<package-name>.obb

Để biết vị trí và tên của các tệp mở rộng, bạn nên sử dụng các phương thức getExternalStorageDirectory()getPackageName() để tạo đường dẫn đến tệp của mình.

Dưới đây là một phương thức bạn có thể sử dụng trong ứng dụng để nhận một mảng chứa đường dẫn đầy đủ đến cả hai tệp mở rộng:

Kotlin

fun getAPKExpansionFiles(ctx: Context, mainVersion: Int, patchVersion: Int): Array<String> {
    val packageName = ctx.packageName
    val ret = mutableListOf<String>()
    if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
        // Build the full path to the app's expansion files
        val root = Environment.getExternalStorageDirectory()
        val expPath = File(root.toString() + EXP_PATH + packageName)

        // Check that expansion file path exists
        if (expPath.exists()) {
            if (mainVersion > 0) {
                val strMainPath = "$expPath${File.separator}main.$mainVersion.$packageName.obb"
                val main = File(strMainPath)
                if (main.isFile) {
                    ret += strMainPath
                }
            }
            if (patchVersion > 0) {
                val strPatchPath = "$expPath${File.separator}patch.$mainVersion.$packageName.obb"
                val main = File(strPatchPath)
                if (main.isFile) {
                    ret += strPatchPath
                }
            }
        }
    }
    return ret.toTypedArray()
}

Java

// The shared path to all app expansion files
private final static String EXP_PATH = "/Android/obb/";

static String[] getAPKExpansionFiles(Context ctx, int mainVersion,
      int patchVersion) {
    String packageName = ctx.getPackageName();
    Vector<String> ret = new Vector<String>();
    if (Environment.getExternalStorageState()
          .equals(Environment.MEDIA_MOUNTED)) {
        // Build the full path to the app's expansion files
        File root = Environment.getExternalStorageDirectory();
        File expPath = new File(root.toString() + EXP_PATH + packageName);

        // Check that expansion file path exists
        if (expPath.exists()) {
            if ( mainVersion > 0 ) {
                String strMainPath = expPath + File.separator + "main." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strMainPath);
                if ( main.isFile() ) {
                        ret.add(strMainPath);
                }
            }
            if ( patchVersion > 0 ) {
                String strPatchPath = expPath + File.separator + "patch." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strPatchPath);
                if ( main.isFile() ) {
                        ret.add(strPatchPath);
                }
            }
        }
    }
    String[] retArray = new String[ret.size()];
    ret.toArray(retArray);
    return retArray;
}

Bạn có thể gọi phương thức này bằng cách truyền Context của ứng dụng và phiên bản tệp mở rộng mong muốn cho phương thức.

Có nhiều cách để bạn xác định số phiên bản của tệp mở rộng. Một cách đơn giản là lưu phiên bản trong tệp SharedPreferences khi quá trình tải xuống bắt đầu, bằng cách truy vấn tên tệp mở rộng bằng phương thức getExpansionFileName(int index) của lớp APKExpansionPolicy. Sau đó, bạn có thể nhận mã phiên bản bằng cách đọc tệp SharedPreferences khi muốn truy cập vào tệp mở rộng.

Để biết thêm thông tin về cách đọc từ bộ nhớ dùng chung, hãy xem tài liệu về Bộ nhớ dữ liệu.

Sử dụng Thư viện zip tệp mở rộng APK

Gói mở rộng Apk Google Market bao gồm một thư viện có tên là Thư viện APK mở rộng APK (nằm trong <sdk>/extras/google/google_market_apk_expansion/zip_file/). Đây là thư viện không bắt buộc giúp bạn đọc các tệp mở rộng khi chúng được lưu dưới dạng tệp ZIP. Thư viện này cho phép bạn dễ dàng đọc tài nguyên từ các tệp mở rộng ZIP dưới dạng hệ thống tệp ảo.

Thư viện zip mở rộng APK bao gồm các lớp và API sau:

APKExpansionSupport
Cung cấp một số phương thức để truy cập vào tên tệp mở rộng và tệp ZIP:
getAPKExpansionFiles()
Phương thức tương tự ở trên trả lại đường dẫn tệp đầy đủ đến cả hai tệp mở rộng.
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
Trả về một ZipResourceFile thể hiện cả tệp chính và tệp bản vá. Điều này nghĩa là, nếu bạn chỉ định cả mainVersionpatchVersion, thì thao tác này sẽ trả về một ZipResourceFile cung cấp quyền đọc tất cả dữ liệu, trong đó dữ liệu của tệp bản vá được hợp nhất trên tệp chính.
ZipResourceFile
Là một tệp ZIP trên bộ nhớ dùng chung và thực hiện tất cả công việc để cung cấp một hệ thống tệp ảo dựa trên các tệp ZIP của bạn. Bạn có thể nhận một bản sao bằng cách sử dụng hàm APKExpansionSupport.getAPKExpansionZipFile() hoặc với ZipResourceFile bằng cách truyền bản sao đó tới đường dẫn đến tệp mở rộng. Lớp này bao gồm nhiều phương thức hữu ích, nhưng bạn thường không cần truy cập vào hầu hết các phương thức đó. Sau đây là một số phương thức quan trọng:
getInputStream(String assetPath)
Cung cấp InputStream để đọc một tệp trong tệp ZIP. assetPath phải là đường dẫn đến tệp mong muốn, tương ứng với thư mục gốc của nội dung tệp ZIP.
getAssetFileDescriptor(String assetPath)
Cung cấp một AssetFileDescriptor cho một tệp trong tệp ZIP. assetPath phải là đường dẫn đến tệp mong muốn, tương ứng với thư mục gốc của nội dung tệp ZIP. Thông tin này sẽ hữu ích với một số API Android yêu cầu có AssetFileDescriptor, chẳng hạn như một số API MediaPlayer.
APEZProvider
Phần lớn các ứng dụng không cần dùng lớp này. Lớp này xác định một ContentProvider sẽ sắp xếp dữ liệu từ các tệp ZIP thông qua một nhà cung cấp nội dung Uri để cung cấp quyền truy cập vào tệp cho một số API Android nhất định mong muốn quyền truy cập Uri vào các tệp nội dung nghe nhìn. Ví dụ: lớp này hữu ích khi bạn muốn phát một video có VideoView.setVideoURI().

Bỏ qua quá trình nén ZIP cho tệp nội dung nghe nhìn

Nếu bạn đang sử dụng các tệp mở rộng để lưu trữ tệp nội dung nghe nhìn, thì tệp ZIP vẫn cho phép bạn sử dụng lệnh gọi phát nội dung nghe nhìn trên Android cung cấp các tuỳ chọn kiểm soát độ lệch và độ dài (chẳng hạn như hàm MediaPlayer.setDataSource()SoundPool.load()). Để thực hiện việc này, bạn không được nén thêm trên các tệp nội dung nghe nhìn khi tạo các gói ZIP. Ví dụ: khi sử dụng công cụ zip, bạn nên sử dụng tuỳ chọn -n để chỉ định hậu tố tệp không nên được nén:

zip -n .mp4;.ogg main_expansion media_files

Đọc trên tệp ZIP

Khi sử dụng Thư viện zip mở rộng, việc đọc trên một tệp từ tệp ZIP thường yêu cầu:

Kotlin

// Get a ZipResourceFile representing a merger of both the main and patch files
val expansionFile =
        APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion)

// Get an input stream for a known file inside the expansion file ZIPs
expansionFile.getInputStream(pathToFileInsideZip).use {
    ...
}

Java

// Get a ZipResourceFile representing a merger of both the main and patch files
ZipResourceFile expansionFile =
    APKExpansionSupport.getAPKExpansionZipFile(appContext,
        mainVersion, patchVersion);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

Mã trên đây cung cấp quyền truy cập vào bất kỳ tệp nào tồn tại trong tệp mở rộng chính hoặc tệp bản vá mở rộng, bằng cách đọc từ bản đồ hợp nhất tất cả tệp từ cả hai tệp. Bạn chỉ cần cung cấp cho phương thức getAPKExpansionFile() ứng dụng android.content.Context của bạn và số phiên bản của cả tệp mở rộng chính cũng như tệp bản vá mở rộng.

Nếu muốn đọc một tệp mở rộng cụ thể, bạn có thể sử dụng hàm khởi tạo ZipResourceFile với đường dẫn đến tệp mở rộng mong muốn:

Kotlin

// Get a ZipResourceFile representing a specific expansion file
val expansionFile = ZipResourceFile(filePathToMyZip)

// Get an input stream for a known file inside the expansion file ZIPs
expansionFile.getInputStream(pathToFileInsideZip).use {
    ...
}

Java

// Get a ZipResourceFile representing a specific expansion file
ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

Để biết thêm thông tin về cách sử dụng thư viện này cho các tệp mở rộng, hãy xem lớp SampleDownloaderActivity của ứng dụng mẫu, bao gồm mã bổ sung để xác minh các tệp đã tải xuống bằng CRC. Hãy lưu ý rằng nếu bạn sử dụng mẫu này làm cơ sở cho hoạt động triển khai của riêng mình, thì bạn phải khai báo kích thước byte của tệp mở rộng trong mảng xAPKS.

Kiểm thử tệp mở rộng

Trước khi phát hành ứng dụng, bạn cần kiểm thử hai việc sau: Đọc tệp mở rộng và tải tệp xuống.

Kiểm thử lượt đọc tệp

Trước khi tải ứng dụng lên Google Play, bạn phải kiểm thử khả năng đọc tệp của ứng dụng từ bộ nhớ dùng chung. Bạn chỉ cần thêm tệp vào vị trí thích hợp trên bộ nhớ dùng chung trên thiết bị và chạy ứng dụng của mình:

  1. Hãy tạo thư mục phù hợp trên bộ nhớ dùng chung trên thiết bị của bạn để Google Play lưu tệp.

    Ví dụ: nếu tên gói là com.example.android thì bạn cần tạo thư mục Android/obb/com.example.android/ trên bộ nhớ dùng chung. (Cắm thiết bị thử nghiệm vào máy tính để gắn bộ nhớ dùng chung và tạo thư mục này theo cách thủ công.)

  2. Thêm tệp mở rộng vào thư mục đó theo cách thủ công. Hãy đảm bảo rằng bạn đổi tên các tệp sao cho phù hợp với định dạng tên tệp Google Play sẽ sử dụng.

    Ví dụ: bất kể loại tệp là gì thì tệp mở rộng chính cho ứng dụng com.example.android sẽ là main.0300110.com.example.android.obb. Mã phiên bản có thể là bất kỳ giá trị nào bạn muốn. Chỉ cần nhớ rằng:

    • Tệp mở rộng chính luôn bắt đầu bằng main, còn tệp bản vá bắt đầu bằng patch.
    • Tên gói luôn khớp với tên APK mà bạn đính kèm tệp trên Google Play.
  3. Bây giờ (các) tệp mở rộng đã có trên thiết bị, bạn có thể cài đặt và chạy ứng dụng để kiểm thử (các) tệp mở rộng của mình.

Dưới đây là một số lời nhắc về việc xử lý các tệp mở rộng:

  • Không xoá hoặc đổi tên tệp mở rộng .obb (kể cả khi bạn giải nén dữ liệu tới một vị trí khác). Làm vậy có thể khiến Google Play (hoặc chính ứng dụng của bạn) tải tệp mở rộng xuống nhiều lần.
  • Không lưu dữ liệu khác vào thư mục obb/ của bạn. Nếu bạn phải giải nén một số dữ liệu, hãy lưu dữ liệu đó vào vị trí mà getExternalFilesDir() chỉ định.

Kiểm thử tiến trình tải tệp xuống

Vì ứng dụng của bạn đôi khi phải tải các tệp mở rộng xuống theo cách thủ công khi mở lần đầu, điều quan trọng là bạn phải kiểm thử quá trình này để đảm bảo rằng ứng dụng có thể truy vấn thành công các URL, hãy tải các tệp xuống và lưu chúng vào thiết bị.

Để kiểm thử việc triển khai quy trình tải xuống thủ công trong ứng dụng của mình, bạn có thể xuất bản quy trình đó lên kênh thử nghiệm nội bộ để chỉ những người kiểm thử được uỷ quyền mới có thể thực hiện. Nếu mọi thứ hoạt động như dự kiến, ứng dụng của bạn sẽ bắt đầu tải các tệp mở rộng xuống ngay khi hoạt động chính bắt đầu.

Lưu ý: Trước đây, bạn có thể kiểm thử ứng dụng bằng cách tải một phiên bản "nháp" chưa xuất bản lên. Chức năng này không còn được hỗ trợ. Thay vào đó, bạn phải phát hành tệp này lên một kênh thử nghiệm nội bộ, khép kín hoặc công khai. Để biết thêm thông tin, hãy xem phần Các ứng dụng nháp không còn được hỗ trợ.

Cập nhật ứng dụng

Một trong những lợi ích tuyệt vời của việc sử dụng tệp mở rộng trên Google Play là khả năng cập nhật ứng dụng mà không cần tải lại tất cả thành phần ban đầu xuống. Vì Google Play cho phép bạn cung cấp hai tệp mở rộng với mỗi APK, bạn có thể sử dụng tệp thứ hai làm "bản vá" để cung cấp nội dung cập nhật và tài sản mới. Việc này sẽ giúp người dùng không cần tải lại tệp mở rộng chính có thể có kích thước lớn và đắt đỏ.

Về mặt kỹ thuật, tệp bản vá mở rộng giống với tệp mở rộng chính và cả hệ thống Android lẫn Google Play đều không thực hiện việc vá lỗi giữa các tệp mở rộng và bản vá chính. Mã ứng dụng của bạn phải tự thực hiện mọi bản vá cần thiết.

Nếu bạn sử dụng tệp ZIP làm tệp mở rộng, Thư viện ZIP mở rộng APK đi kèm trong gói Mở rộng Apk bao gồm khả năng hợp nhất tệp bản vá với tệp mở rộng chính.

Lưu ý: Ngay cả khi bạn chỉ cần thực hiện thay đổi cho tệp bản vá mở rộng, bạn vẫn phải cập nhật APK để Google Play thực hiện cập nhật. Nếu bạn không yêu cầu thay đổi mã trong ứng dụng, bạn chỉ cần cập nhật versionCode trong tệp kê khai.

Miễn là bạn không thay đổi tệp mở rộng chính liên kết với APK trong Play Console, những người dùng đã cài đặt ứng dụng của bạn trước đây sẽ không tải tệp mở rộng chính xuống. Người dùng hiện tại chỉ nhận được APK đã cập nhật và tệp bản vá mở rộng mới (giữ lại tệp mở rộng chính trước đó).

Dưới đây là một số vấn đề cần lưu ý về các bản cập nhật cho các tệp mở rộng:

  • Ứng dụng của bạn chỉ được có hai tệp mở rộng cùng một lúc. Một tệp mở rộng chính và một tệp bản vá mở rộng. Trong quá trình cập nhật một tệp, Google Play sẽ xoá phiên bản trước đó (do đó cũng phải xoá ứng dụng của bạn khi cập nhật thủ công).
  • Khi thêm tệp bản vá mở rộng, hệ thống Android không thực sự vá tệp mở rộng hoặc ứng dụng chính của bạn. Bạn phải thiết kế ứng dụng của mình để hỗ trợ dữ liệu bản vá. Tuy nhiên, gói Apk Mở rộng bao gồm một thư viện để sử dụng tệp ZIP dưới dạng tệp mở rộng, hợp nhất dữ liệu từ tệp bản vá vào tệp mở rộng chính để bạn có thể dễ dàng đọc tất cả dữ liệu của tệp mở rộng.