Vladimir Abazarov
Vladimir Abazarov

Reputation: 333

What is wrong with upload speed of some devices with Multipart OkHttp3/Retrofit 2

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:

Samsung Tab 3

failed with 120 timeout after this: failed after 120 seconds

success after these two, but waiting 1:35 minutes: enter image description here enter image description here

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

Answers (1)

Vladimir Abazarov
Vladimir Abazarov

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.

enter image description here

Upvotes: 5

Related Questions