Kiến thức cơ bản về Cronet

1. Giới thiệu

1ee223bf9e1b75fb.png

Lần cập nhật gần đây nhất: ngày 06 tháng 05 năm 2022

Cronet là bộ phần cứng và phần mềm mạng Chromium được dùng cho các ứng dụng Android dưới dạng thư viện. Cronet tận dụng nhiều công nghệ giúp giảm độ trễ và tăng công suất các yêu cầu về mạng mà ứng dụng của bạn cần để hoạt động.

Thư viện Cronet xử lý các yêu cầu ứng dụng được hàng triệu người sử dụng hằng ngày, chẳng hạn như YouTube, Ứng dụng Google, Google PhotosMaps – Điều hướng và phương tiện công cộng. Cronet là thư viện nối mạng Android được sử dụng nhiều nhất với sự hỗ trợ HTTP3.

Để biết thêm thông tin chi tiết, vui lòng xem trang Các tính năng của Cronet.

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn sẽ thêm tính năng hỗ trợ Cronet vào ứng dụng hiển thị hình ảnh. Ứng dụng này sẽ:

  • Tải Cronet từ Dịch vụ Google Play hoặc quay trở lại an toàn nếu Cronet không khả dụng.
  • Gửi yêu cầu, nhận và xử lý phản hồi bằng Cronet.
  • Hiển thị kết quả trong giao diện người dùng đơn giản.

28b0fcb0fed5d3e0.png

Kiến thức bạn sẽ học được

  • Cách đưa Cronet dưới dạng phần phụ thuộc vào ứng dụng của bạn
  • Cách định cấu hình công cụ Cronet
  • Cách sử dụng Cronet để gửi yêu cầu
  • Cách viết lệnh gọi lại Cronet để xử lý các phản hồi

Lớp học lập trình này tập trung vào việc sử dụng Cronet. Hầu hết các ứng dụng đều được triển khai trước nên bạn sẽ có thể hoàn thành lớp học lập trình này mà cần nhiều kinh nghiệm trong việc phát triển Android. Dù vậy, để tận dụng triệt để những gì học được ở lớp học lập trình này, bạn cần nắm rõ các kiến thức cơ bản về phát triển Android và thư viện Jetpack Compose.

Bạn cần có

2. Lấy mã

Chúng tôi đã đặt mọi thứ bạn cần cho dự án này vào Git repo. Để bắt đầu, hãy sao chép kho lưu trữ và mở mã trong Android Studio.

git clone https://github.com/android/codelab-cronet-basics

3. Thiết lập đường cơ sở

Điểm xuất phát của chúng ta là gì?

Điểm xuất phát của chúng ta là một ứng dụng hiển thị hình ảnh cơ bản được thiết kế cho lớp học lập trình này. Nếu nhấp vào nút Thêm hình ảnh, bạn sẽ thấy một hình ảnh mới được thêm vào danh sách kèm thông tin chi tiết về thời gian tìm nạp hình ảnh từ Internet. Ứng dụng này sử dụng thư viện HTTP tích hợp do Kotlin cung cấp mà không hỗ trợ bất kỳ tính năng nâng cao nào.

Trong cả lớp học lập trình này, chúng ta sẽ mở rộng ứng dụng để dùng Cronet và một số tính năng của ngăn xếp mạng này.

4. Thêm phần phụ thuộc vào tập lệnh Gradle của bạn

Bạn có thể tích hợp Cronet như một thư viện độc lập đi kèm với ứng dụng, hoặc sử dụng Cronet do nền tảng cung cấp. Nhóm phát triển Cronet khuyên bạn nên sử dụng nhà cung cấp Dịch vụ Google Play. Khi sử dụng nhà cung cấp Dịch vụ Google Play, ứng dụng của bạn không cần phải trả chi phí kích thước nhị phân để vận chuyển Cronet (khoảng 5 megabyte), đồng thời nền tảng này đảm bảo luôn cung cấp các bản cập nhật và bản sửa lỗi bảo mật mới nhất.

Bất kể bạn quyết định nhập cách triển khai nào, bạn cũng sẽ cần phải thêm phần phụ thuộc cronet-api để đưa các API Cronet vào.

Mở tệp build.gradle và thêm hai dòng sau vào phần dependencies.

implementation 'com.google.android.gms:play-services-cronet:18.0.1'
implementation 'org.chromium.net:cronet-api:101.4951.41'

5. Cài đặt nhà cung cấp Cronet Dịch vụ Google Play

Như đã thảo luận ở phần trước, bạn có thể thêm Cronet vào ứng dụng của mình theo nhiều cách. Mỗi cách trong số này đều được tóm tắt bằng Provider, nhằm đảm bảo các đường liên kết cần thiết giữa thư viện và ứng dụng của bạn. Mỗi khi bạn tạo một công cụ Cronet mới, Cronet sẽ xem xét tất cả các nhà cung cấp đang hoạt động và chọn nhà cung cấp tốt nhất để tạo bản sao cho công cụ.

Nhà cung cấp Dịch vụ Google Play thường không có sẵn, do đó, bạn cần cài đặt ứng dụng này trước. Xác định thuộc tính VIỆC CẦN LÀM trong MainActivity và dán đoạn mã sau vào:

val ctx = LocalContext.current
CronetProviderInstaller.installProvider(ctx)

Thao tác này sẽ khởi chạy Tác vụ trong Dịch vụ Play để cài đặt nhà cung cấp theo cách không đồng bộ.

6. Xử lý kết quả cài đặt nhà cung cấp

Bạn đã cài đặt thành công nhà cung cấp... Chờ đã, bạn có chắc đã cài đặt xong? Task không đồng bộ và bạn chưa xử lý kết quả nào. Hãy khắc phục vấn đề đó. Thay lệnh gọi installProvider bằng đoạn mã sau:

CronetProviderInstaller.installProvider(ctx).addOnCompleteListener {
   if (it.isSuccessful) {
       Log.i(LOGGER_TAG, "Successfully installed Play Services provider: $it")
       // TODO(you): Initialize Cronet engine
   } else {
       Log.w(LOGGER_TAG, "Unable to load Cronet from Play Services", it.exception)
   }
}

Để phục vụ cho mục đích của lớp học lập trình này, chúng ta sẽ tiếp tục sử dụng trình tải hình ảnh gốc nếu không tải được Chromium. Nếu hiệu suất kết nối là yếu tố then chốt đối với ứng dụng thì bạn nên cài đặt hoặc cập nhật Dịch vụ Play. Để biết thêm thông tin chi tiết, vui lòng xem tài liệu về CronetProviderInstaller.

Chạy ứng dụng ngay; nếu mọi thứ hoạt động tốt, bạn sẽ thấy câu lệnh nhật ký cho biết đã cài đặt thành công nhà cung cấp.

7. Tạo công cụ Cronet

Công cụ Cronet là đối tượng cốt lõi mà bạn sẽ sử dụng để gửi yêu cầu bằng Cronet. Công cụ này được xây dựng bằng cách sử dụng mẫu Trình tạo, cho phép bạn định cấu hình các tuỳ chọn khác nhau của Chromium. Tạm thời, chúng ta sẽ tiếp tục dùng các tuỳ chọn mặc định. Tạo bản sao của công cụ Cronet mới bằng cách thay thế TODO bằng đoạn mã sau:

val cronetEngine = CronetEngine.Builder(ctx).build()
// TODO(you): Initialize the Cronet image downloader

8. Triển khai lệnh gọi lại Cronet

Bản chất không đồng bộ của Cronet có nghĩa là quá trình xử lý phản hồi được kiểm soát bằng cách sử dụng các lệnh gọi lại, cụ thể là các phiên bản của UrlRequest.Callback. Ở phần này, bạn sẽ triển khai một lệnh gọi lại trình trợ giúp để đọc toàn bộ phản hồi vào bộ nhớ.

Tạo một lớp trừu tượng mới có tên là ReadToMemoryCronetCallback, đặt lớp này mở rộng UrlRequest.Callback, và để Android Studio tự động tạo phương thức giả lập. Lớp mới sẽ có dạng tương tự như đoạn mã sau:

abstract class ReadToMemoryCronetCallback : UrlRequest.Callback() {
   override fun onRedirectReceived(
       request: UrlRequest,
       info: UrlResponseInfo,
       newLocationUrl: String?
   ) {
       TODO("Not yet implemented")
   }

   override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
       TODO("Not yet implemented")
   }

   override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
       TODO("Not yet implemented")
   }

   override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {
       TODO("Not yet implemented")
   }

   override fun onReadCompleted(
       request: UrlRequest,
       info: UrlResponseInfo,
       byteBuffer: ByteBuffer
   ) {
       TODO("Not yet implemented")
   }
}

Các phương thức onRedirectReceived, onSucceededonFailed mang tính tự giải thích, do đó chúng ta sẽ không trình bày chi tiết lúc này. Chúng ta sẽ tập trung vào onResponseStartedonReadCompleted.

onResponseStarted được gọi sau khi Cronet gửi yêu cầu và nhận được tất cả các tiêu đề phản hồi, nhưng trước khi bắt đầu đọc nội dung. Cronet không tự động đọc toàn bộ nội dung như một số thư viện khác (chẳng hạn như Volley). Thay vào đó, Cronet dùng UrlRequest.read() để đọc đoạn tiếp theo của nội dung vào một bộ đệm mà bạn cung cấp. Sau khi đọc xong đoạn nội dung phản hồi, Cronet sẽ gọi phương thức onReadCompleted. Quá trình này lặp lại cho đến khi không còn dữ liệu nào để đọc.

39d71a5e85f151d8.png

Hãy bắt đầu triển khai chu kỳ đọc. Đầu tiên, hãy tạo luồng đầu ra cho mảng byte mới và một kênh sử dụng luồng đó. Chúng ta sẽ sử dụng kênh này làm bồn lưu trữ dữ liệu cho nội dung phản hồi.

private val bytesReceived = ByteArrayOutputStream()
private val receiveChannel = Channels.newChannel(bytesReceived)

Tiếp theo, hãy triển khai phương thức onReadCompleted để sao chép dữ liệu từ bộ đệm byte vào bồn lưu trữ dữ liệu và gọi lần đọc tiếp theo.

// The byte buffer we're getting in the callback hasn't been flipped for reading,
// so flip it so we can read the content.
byteBuffer.flip()
receiveChannel.write(byteBuffer)

// Reset the buffer to prepare it for the next read
byteBuffer.clear()

// Continue reading the request
request.read(byteBuffer)

Để hoàn tất vòng lặp đọc nội dung, hãy thực hiện lệnh gọi cho lần đọc đầu tiên từ phương thức gọi lại onResponseStarted. Lưu ý là bạn cần sử dụng bộ đệm byte trực tiếp qua Cronet. Mặc dù dung lượng của bộ đệm không phải là mục đích quan trọng của lớp học lập trình này, nhưng 16 KiB là giá trị mặc định phù hợp cho hầu hết các mục đích sử dụng trong quá trình sản xuất.

request.read(ByteBuffer.allocateDirect(BYTE_BUFFER_CAPACITY_BYTES))

Hoàn tất ngay các nhiệm vụ còn lại trong lớp học. Bạn không cần phải quan tâm đến lệnh chuyển hướng, do đó chỉ cần làm theo cách giống như trình duyệt web của bạn là được.

override fun onRedirectReceived(
   request: UrlRequest, info: UrlResponseInfo?, newLocationUrl: String?
) {
   request.followRedirect()
}

Cuối cùng, chúng ta cần xử lý các phương thức onSucceededonFailed. onFailed khớp với chữ ký bạn muốn cung cấp cho người dùng của lệnh gọi lại trong trình trợ giúp, để có thể xoá định nghĩa và cho phép mở rộng các lớp ghi đè phương thức này. onSucceeded phải truyền phần nội dung xuống dòng dưới dạng một mảng byte. Thêm một phương thức trừu tượng mới với phần nội dung trong chữ ký.

abstract fun onSucceeded(
   request: UrlRequest, info: UrlResponseInfo, bodyBytes: ByteArray)

Sau đó, hãy đảm bảo phương thức onSucceeded mới được gọi chính xác khi yêu cầu hoàn tất thành công.

final override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
   val bodyBytes = bytesReceived.toByteArray()
   onSucceeded(request, info, bodyBytes)
}

Huzzah, vậy là bạn đã tìm hiểu cách triển khai lệnh gọi lại Cronet!

9. Triển khai trình tải hình ảnh xuống

Hãy dùng lệnh gọi lại mà chúng ta đã tạo ở phần trước để triển khai trình tải hình ảnh dựa trên Cronet.

Tạo một lớp mới có tên là CronetImageDownloader, triển khai giao diện ImageDownloader và nhận CronetEngine làm tham số hàm dựng của lớp đó.

class CronetImageDownloader(val engine: CronetEngine) : ImageDownloader {
   override suspend fun downloadImage(url: String): ImageDownloaderResult {
       TODO("Not yet implemented")
   }
}

Để triển khai phương thức downloadImage, bạn cần tìm hiểu cách tạo các yêu cầu Cronet. Dễ thôi – hãy gọi phương thức newUrlRequestBuilder() của CronetEngine. Phương thức này sử dụng URL, một bản sao của lớp gọi lại và trình thực thi chạy các phương thức gọi lại.

val request = engine.newUrlRequestBuilder(url, callback, executor)

Chúng ta biết URL này từ tham số downloadImage. Đối với trình thực thi, chúng ta sẽ tạo một trường trên toàn thực thể.

private val executor = Executors.newSingleThreadExecutor()

Cuối cùng, chúng ta sẽ sử dụng cấu hình triển khai lệnh gọi lại trình trợ giúp từ phần trước để triển khai callback. Chúng ta sẽ không đi vào chi tiết quá trình triển khai vì nó thuộc chủ đề Coroutine trong Kotlin. Bạn có thể coi cont.resumereturn từ phương thức downloadImage.

Đồng thời, việc triển khai downloadImage của bạn sẽ giống như đoạn mã sau.

override suspend fun downloadImage(url: String): ImageDownloaderResult {
   val startNanoTime = System.nanoTime()
   return suspendCoroutine {
       cont ->
       val request = engine.newUrlRequestBuilder(url, object: ReadToMemoryCronetCallback() {
       override fun onSucceeded(
           request: UrlRequest,
           info: UrlResponseInfo,
           bodyBytes: ByteArray) {
           cont.resume(ImageDownloaderResult(
               successful = true,
               blob = bodyBytes,
               latency = Duration.ofNanos(System.nanoTime() - startNanoTime),
               wasCached = info.wasCached(),
               downloaderRef = this@CronetImageDownloader))
       }

       override fun onFailed(
           request: UrlRequest,
           info: UrlResponseInfo,
           error: CronetException
       ) {
           Log.w(LOGGER_TAG, "Cronet download failed!", error)
           cont.resume(ImageDownloaderResult(
               successful = false,
               blob = ByteArray(0),
               latency = Duration.ZERO,
               wasCached = info.wasCached(),
               downloaderRef = this@CronetImageDownloader))
       }
   }, executor)
       request.build().start()
   }
}

10. Thao tác cuối cùng

Hãy quay lại thành phần kết hợp MainDisplay và xử lý VIỆC CẦN LÀM cuối cùng bằng trình tải hình ảnh chúng ta vừa tạo ra.

imageDownloader = CronetImageDownloader(cronetEngine)

Vậy là chúng ta đã thực hiện xong. Hãy thử chạy ứng dụng. Bạn sẽ thấy các yêu cầu của mình được định tuyến thông qua trình tải hình ảnh Cronet.

11. Tuỳ chỉnh

Bạn có thể tuỳ chỉnh hành vi yêu cầu ở cả cấp yêu cầu và công cụ. Chúng ta sẽ minh hoạ điều này bằng cách lưu vào bộ nhớ đệm, nhưng cũng có nhiều tuỳ chọn khác. Để biết thêm thông tin chi tiết, vui lòng xem tài liệu UrlRequest.BuilderCronetengine.Builder.

Để bật tính năng lưu vào bộ nhớ đệm ở cấp công cụ, hãy dùng phương thức enableHttpCache của trình tạo. Ở ví dụ bên dưới, chúng ta sẽ sử dụng bộ nhớ đệm của bộ nhớ trong. Để biết các lựa chọn có sẵn khác, vui lòng xem tài liệu. Sau đó, việc tạo công cụ Cronet sẽ trở thành:

val cronetEngine = CronetEngine.Builder(ctx)
   .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 10 * 1024 * 1024)
   .build()

Chạy ứng dụng và thêm một vài hình ảnh. Những hình ảnh được thêm nhiều lần sẽ có độ trễ ngắn hơn đáng kể, và giao diện người dùng phải cho biết chúng đã được lưu vào bộ nhớ đệm.

Bạn có thể ghi đè chức năng này theo từng yêu cầu. Hãy xâm nhập một chút vào trình tải Cronet và tắt bộ nhớ đệm cho hình ảnh Mặt trời, ở đây là hình ảnh đầu tiên trong danh sách URL.

if (url == CronetCodelabConstants.URLS[0]) {
   request.disableCache()
}

request.build().start()

Giờ thì hãy chạy lại ứng dụng. Bạn sẽ nhận thấy hình ảnh mặt trời hiện không được lưu vào bộ nhớ đệm.

d9d0163c96049081.png

12. Kết luận

Xin chúc mừng, bạn đã đi đến phần cuối của lớp học lập trình! Ở học phần này, bạn đã tìm hiểu các khái niệm cơ bản về cách sử dụng Cronet.

Để tìm hiểu thêm về Cronet, vui lòng xem hướng dẫn dành cho nhà phát triểnmã nguồn. Ngoài ra, vui lòng đăng ký blog dành cho các Nhà phát triển Android để là người đầu tiên biết tin tức về Cronet và Android nói chung.