Awesome
cronet is a framework that using chromium net to send network request for android
Changelog
Current version 73.0.3653.5 released on 20th Jun 2019.
See details in CHANGELOG.
Examples
I have provided a sample.
See sample here on Github.
To run the sample application, simply clone this repository and use android studio to compile, install it on a connected device.
Feature
- Full platform supports the latest version of TLS.
- The platform supports the latest network protocols such as HTTP/2 and QUIC.
Usage
Maven
<dependency>
<groupId>io.github.lizhangqu</groupId>
<artifactId>cronet-native</artifactId>
<version>73.0.3653.0.6</version>
</dependency>
Gradle
compile 'io.github.lizhangqu:cronet-native:73.0.3653.0.6'
Remote so
The cronet's so file is big, you can use remote mode to reduce the apk size by exclude cronet-so module.
compile ('io.github.lizhangqu:cronet-native:73.0.3653.0.6'){
exclude group: 'io.github.lizhangqu', module: 'cronet-so'
}
And add custom library loader when init cronet.
try {
CronetEngine.Builder myBuilder = new CronetEngine.Builder(this);
myBuilder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 100 * 1024)
.setLibraryLoader(new ChromiumLibraryLoader(this)) //set library to such as ChromiumLibraryLoader impl
.enableHttp2(true)
.enableQuic(false);
Log.i(TAG, "setup");
CronetEngine cronetEngine = myBuilder.build();
} catch (Throwable e) {
}
You should use the httpurlconnection style api for downgrade
public HttpURLConnection createHttpURLConnection(CronetEngine cronetEngine String url) {
try {
return (HttpURLConnection) cronetEngine.openConnection(new URL(url));
} catch (Exception e) {
try {
return (HttpURLConnection) new URL(url).openConnection();
} catch (IOException ex) {
ex.printStackTrace();
}
}
return null;
}
private void sendHeadRequestByHurl() {
InputStream inputStream = null;
try {
HttpURLConnection urlConnection = createHttpURLConnection(cronetEngine, "url");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("HEAD");
urlConnection.getOutputStream().write("a=b&b=c".getBytes());
Map<String, List<String>> headerFields = urlConnection.getHeaderFields();
if (urlConnection.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
InputStream errorStream = urlConnection.getErrorStream();
readInputStream(errorStream);
} else {
inputStream = urlConnection.getInputStream();
readInputStream(inputStream);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
NDK abiFilters
This library add all so default, if you need add only one, you should use ndk abiFilters yourself.
I suggest that you only add abiFilters "armeabi-v7a".
android {
defaultConfig {
ndk {
abiFilters "armeabi-v7a"
// default is no filters
// abiFilters "armeabi"
// abiFilters "armeabi-v7a"
// abiFilters "arm64-v8a"
// abiFilters "x86"
// abiFilters "x86_64"
// abiFilters "mips"
// abiFilters "mips64"
}
}
}
Create Engine
CronetEngine.Builder builder = new CronetEngine.Builder(context);
builder.
enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY,
100 * 1024) // cache
.enableHttp2(true) // Http/2.0 Supprot
.enableQuic(true) // Quic Supprot
.setHostResolver(new HostResolver() {
@Override
public List<InetAddress> resolve(String hostname) throws UnknownHostException {
if (hostname == null)
throw new UnknownHostException("hostname == null");
return Arrays.asList(InetAddress.getAllByName(hostname));
}
}) // custom dns, you can use httpdns here
.enableSDCH(true) // SDCH Supprot
.setLibraryName("cronet"); // lib so name
CronetEngine cronetEngine = builder.build();
//see more config in the code
Use For HttpUrlConnection
You can use the method like OkHttp
URL.setURLStreamHandlerFactory(new OkUrlFactory(new OkHttpClient()));
Cronet also support it.
CronetURLStreamHandlerFactory cronetURLStreamHandlerFactory = new CronetURLStreamHandlerFactory(cronetEngine);
URL.setURLStreamHandlerFactory(cronetURLStreamHandlerFactory);
And then you don't need to modify your java code like this.
try {
URL url = new URL(mEditTextUrl.getText().toString());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
Log.e("TAG", "connection:" + connection);
connection.setDoInput(true);
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
connection.setRequestMethod("GET");
connection.connect();
int responseCode = connection.getResponseCode();
InputStream inputStream = connection.getInputStream();
ByteArrayOutputStream output = new ByteArrayOutputStream();
copy(inputStream, output);
output.close();
inputStream.close();
byte[] bytes = output.toByteArray();
String response = new String(bytes);
Log.e("TAG", "responseCode:" + responseCode);
Log.e("TAG", "response body:" + response);
} catch (IOException e) {
e.printStackTrace();
}
public static long copy(InputStream input, OutputStream output) throws IOException {
return copyLarge(input, output, new byte[2048]);
}
public static long copyLarge(InputStream input, OutputStream output, byte[] buffer)
throws IOException {
long count = 0;
int n = 0;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
count += n;
}
return count;
}
Attentation Please
If you use the HttpURLConnection style api, you must read the inputstream anyway.
static ByteArrayInputStream toByteArrayInputStream(InputStream inputStream) {
if (inputStream != null) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
return new ByteArrayInputStream(outputStream.toByteArray());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return null;
}
static void readInputStream(InputStream inputStream) {
if (inputStream != null) {
try {
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inputStream.read(buffer)) != -1) {
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
InputStream inputStream = null;
try {
inputStream = urlConnection.getInputStream();
} catch (IOException e) {
inputStream = toByteArrayInputStream(urlConnection.getErrorStream());
}
//you must read the inputStream
readInputStream(inputStream);
Send GET Request
UrlRequest.Builder builder = new UrlRequest.Builder(mEditTextUrl.getText().toString(), new UrlRequest.Callback() {
private ByteArrayOutputStream mBytesReceived = new ByteArrayOutputStream();
private WritableByteChannel mReceiveChannel = Channels.newChannel(mBytesReceived);
@Override
public void onRedirectReceived(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo, String s) throws Exception {
Log.i("TAG", "onRedirectReceived");
urlRequest.followRedirect();
}
@Override
public void onResponseStarted(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo) throws Exception {
Log.i("TAG", "onResponseStarted");
urlRequest.read(ByteBuffer.allocateDirect(32 * 1024));
}
@Override
public void onReadCompleted(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo, ByteBuffer byteBuffer) throws Exception {
Log.i("TAG", "onReadCompleted");
byteBuffer.flip();
try {
mReceiveChannel.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
byteBuffer.clear();
urlRequest.read(byteBuffer);
}
@Override
public void onSucceeded(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo) {
Log.i("TAG", "onSucceeded");
Log.i("TAG", String.format("Request Completed, status code is %d, total received bytes is %d",
urlResponseInfo.getHttpStatusCode(), urlResponseInfo.getReceivedBytesCount()));
final String receivedData = mBytesReceived.toString();
final String url = urlResponseInfo.getUrl();
final String text = "Completed " + url + " (" + urlResponseInfo.getHttpStatusCode() + ")";
Log.i("TAG", "text:" + text);
Log.i("TAG", "receivedData:" + receivedData);
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "onSucceeded", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onFailed(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo, UrlRequestException e) {
Log.i("TAG", "onFailed");
Log.i("TAG", "error is: %s" + e.getMessage());
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "onFailed", Toast.LENGTH_SHORT).show();
}
});
}
}, executor, cronetEngine);
builder.build().start();
Send POST Request
public void startWithURL(String url, UrlRequest.Callback callback, Executor executor, String postData) {
UrlRequest.Builder builder = new UrlRequest.Builder(url, callback, executor, mCronetEngine);
applyPostDataToUrlRequestBuilder(builder, executor, postData);
builder.build().start();
}
private void applyPostDataToUrlRequestBuilder(
UrlRequest.Builder builder, Executor executor, String postData) {
if (postData != null && postData.length() > 0) {
builder.setHttpMethod("POST");
builder.addHeader("Content-Type", "application/x-www-form-urlencoded");
builder.setUploadDataProvider(
UploadDataProviders.create(postData.getBytes()), executor);
}
}
And then reuse the callback in Send GET Request
Thanks
- chromium-net-android-porting
- chromium-compile-guide-for-android
- lazy-chromium-net-android-porting-guide
- chromium-gn-build-tools
License
chromium net for android(cronet) is under the BSD license. See the LICENSE file for details.