Reputation: 333
I have an App that can record and upload video file.
I have a mechanism that logs onFailure Throwable
on every file upload.
I have noticed that from a thousand devices using the App, there are some that have constant timeouts and have poor success upload ratio.
The problematic device is: Asus ZenPad C 7.0 Z170C
I have bought the device and started testing it, but first lets give you a flavour of what code I use.
In Activity:
FileUploadService service = ServiceGenerator.createService(FileUploadService.class);
File file = new File(getRealPathFromURI(mediaForUpload.mediaUri));
ProgressRequestBody fileBody = new ProgressRequestBody(file,
new ProgressRequestBody.UploadCallbacks() {
@Override
public void onProgressUpdate(int uploadPercentage) {
// set current progress
}
@Override
public void onError() {}
@Override
public void onFinish() {}
});
MultipartBody.Part body = MultipartBody.Part.createFormData(mediaForUpload.mediaNameWithExtension, mediaForUpload.mediaNameWithExtension, fileBody);
Call<ResponseBody> call = service.upload(body);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
if (response.isSuccessful()) {
} else {
//Log
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
//Log
}
});
In ServiceGenerator:
public static <S> S createService(Class<S> serviceClass) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.build();
Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl("https://some.nice.api/")
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.client(client).build();
return retrofit.create(serviceClass);
}
In FileUploadService:
@Multipart
@POST("/somepath")
Call<ResponseBody> upload(@Part MultipartBody.Part file);
So as you can see my timeout is 120 seconds, interesting but this is not enough to successfully upload a 15MB video file without timeout.
I have made couple of test from the Asus Zenpad C 7.0 and Samsung Tab 3 7" just to show you the difference. (The test is on 2.4GHz network, 5 meters from the router; Tab 3 average upload speed: 1MB/s, Zenpad C7 average upload speed: ~30KB/s if miracle occurs)
Screenshots from Android Monitor:
failed with 120 timeout after this:
success after these two, but waiting 1:35 minutes:
I have tested OkHttp3 Multipart upload, just to be sure that this is not Retrofit 2 issue and the results are the same.
Upvotes: 2
Views: 1757
Reputation: 333
After two days of searching for more information I have found the problem.
First you have to know that the default socket factory has a getSendBufferSize() of 524288. In our case this is alot for Asus Zenpad C 7.0.
My steps of finding the solution are:
https://github.com/square/okhttp/issues/1078
and implementing RestrictedSocketFactory
https://gist.github.com/slightfoot/00a26683ea68856ceb50e26c7d8a47d0
After that I have made some modifications to my code:
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.socketFactory(new RestrictedSocketFactory(bufferSize))
.build();
My bufferSize
that is the socket sendBufferSize
is the same size as my DEFAULT_BUFFER_SIZE
from ProgressRequestBody
. I have tested with 256*1024 and 128*1024 and the optimal for C 7.0 is 128*1024. Unfortunately the upload speed for all other devices was reduced with ~30% so I have made a mechanism that apply these changes for couple of devices only.(From my tests the optimal for all other devices is 256*1024 as bufferSize
and DEFAULT_BUFFER_SIZE
)
Here is an image of 17MB video file upload with Asus Zenpad C 7.0 you can see the difference from the first post.
Upvotes: 5