Cronet - Chromium Network Stack for Android

Cronet is a Chromium network stack used internally by Google inside its mobile apps for reliable network requests. Recently, Google made the library available for Android, IOS but the network stack has been part of Chrome web browser for a long time. 

Using Cronet, the requests can be given priorities and responses can be cached in memory or on the disk. Once set the priorities, the server can decide the order to execute the requests and once cached, the future requests are fetched automatically from the cache.

Cronet requests are asynchronous which means it doesn't even block the worker threads. This network stack provides support for HTTP, HTTP2, and QUIC (Quick UDP Internet Connections) protocols. It uses Brotli Compressed Data Format to perform lossless compression. 

Cronet automatically sets the request type to GET or POST based on the presence of the request body or the developer can always manually set the request type to their preferred choice.



Let's Create a Simple Request using Cronet.

1) Add the dependency to module level build.gradle

implementation 'org.chromium.net:cronet-embedded:71.3578.98'

2) Build the Cronet engine using the builder. One can use the builder the set the storage paths or enable QUIC support or Brotli compression or more.

// Build a Cronet engine
val cronetEngine =
    CronetEngine.Builder(this)
        .build()

3) Build a request by passing an url to newUrlRequestBuilder() provided by the Cronet Engine and start the connection.

// Build the request
val request =
    cronetEngine.newUrlRequestBuilder(
       "https://jsonplaceholder.typicode.com/todos/1",
        RequestCallback(),
        Executors.newSingleThreadExecutor()
    ).build()


// Start the request
request.start()

Once, the connection starts, the request lifecycle kicks in. The lifecycles trigger different callbacks which are used to handle the response and the errors. So, let's implement the necessary callbacks.

4) Implement a callback that extends UrlRequest.Callback() and override methods to receive the response.

override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) {
    Log.i(TAG, "Response Started")
    val statusCode = info?.httpStatusCode
    Log.i(TAG, "Status Code $statusCode")
    if (statusCode == 200) {
        // Read the buffer
        request?.read(ByteBuffer.allocateDirect(32 * 1024))
    }
}


override fun onReadCompleted(request: UrlRequest?, info: UrlResponseInfo?, byteBuffer: ByteBuffer?) {
    Log.i(TAG, "Response Completed")


    // Flip the buffer
    byteBuffer?.flip()


    // Convert the byte buffer to a string
    byteBuffer?.let {
        val byteArray = ByteArray(it.remaining())
        it.get(byteArray)
        String(byteArray, Charset.forName("UTF-8"))
    }.apply {
        Log.d(TAG, "Response: $this")
    }


    // Clear the buffer
    byteBuffer?.clear()


    // Read the buffer
    request?.read(byteBuffer)
}


override fun onFailed(request: UrlRequest?, info: UrlResponseInfo?, error: CronetException?) {
    Log.e(TAG, "Response Failed: ${error?.message}")
}


override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
    Log.i(TAG, "Response Succeeded")
}


override fun onRedirectReceived(request: UrlRequest?, info: UrlResponseInfo?, newLocationUrl: String?) {
    Log.i(TAG, "Response Redirect to $newLocationUrl")
    request?.followRedirect()
}


override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
    super.onCanceled(request, info)
    Log.i(TAG, "Response cancelled")
}

Cronet Request Lifecycle:

As previously mentioned, the Cronet lifecycle triggers different callbacks and needs to handled by the developer. In the above discussed simple request, the following callbacks are implemented and triggered at various cycles.

If the provided endpoint contains re-directs, onRedirectReceived callback is triggered. You can follow the re-direct or cancel the request.

Once all the headers are received and all redirects are followed, onResponseStarted callback is triggered.

Unlike retrofit or Volley, Cronet returns the responses to the same callback for 2xx,4xx,5xx response codes. So, the developer needs to check the response code before parsing the response.

Irrespective of the response codes, you still need to call the read method so that the lifecycle can continue its journey and trigger the next callback.

Once onResponseStarted is readonReadCompleted is triggered where you can read the response as byte buffer. In the above example, byte buffers are converted to bytes then to string and printed to logcat. To continue the journey in the lifecycle, call read method. 

This will trigger onSucceeded callback as the Cronet request got successfully completed.

onFailed is called if the Cronet request fails abruptly and onCanceled is triggered if the request is cancelled by calling the cancel method. If so, simply, free the resources.

The below picture summarises the entire lifecycle of a Cronet request.


Cronet request lifecycle
diagram

If you are having trouble understanding the article or want to explore the code yourself, feel free to fork the Cronet Example project on Github.

Popular posts from this blog

How to Read Metadata from AndriodManifest File

How to Change Material Chip Text Size, Text Style and Font

How to Set an Android App as a Default Dialer

Create Assets Folder, Add Files and Read Data From It

Add Spacing to Recycler View Linear Layout Manager Using Item Decoration