How to Implement Fast Image Sharing in Your Android Application

In my Android application, I wanted to implement a new functionality: sharing an image of an aircraft. After searching the Internet, I found several methods that all essentially boiled down to one approach — convert the downloaded image and then share it. However, this process was very slow. In this post, I will show you how to do this quickly and efficiently.

Common Method for Image Sharing in Android

The most common way to implement image sharing in Android is as follows:

Bitmap icon = mBitmap;
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/jpeg");
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
icon.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
File f = new File(Environment.getExternalStorageDirectory() + File.separator + "temporary_file.jpg");
try {
    f.createNewFile();
    FileOutputStream fo = new FileOutputStream(f);
    fo.write(bytes.toByteArray());
} catch (IOException e) {                       
        e.printStackTrace();
}
share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///sdcard/temporary_file.jpg"));
startActivity(Intent.createChooser(share, "Share Image"));

However, the call to icon.compress(Bitmap.CompressFormat.JPEG, 100, bytes) is very slow. On my device, it sometimes took 3 seconds or more. My device is a POCO F3, which is not the cheapest or slowest Android smartphone.

Optimizing Image Sharing with Picasso and OkHttpClient

To increase the performance of my code, I used the Picasso library with OkHttpClient under the hood. Here’s how you can configure them with Dagger:

Picasso Configuration

@Provides
@Singleton
@NonNull
Picasso picasso(Application application, @Named("httpClient") OkHttpClient client) {
    LruCache lruCache = new LruCache(MEMORY_CACHE_SIZE);
    return new Picasso.Builder(application)
            .memoryCache(lruCache)
            .downloader(new OkHttp3Downloader(client))
            .indicatorsEnabled(false).build();
}

HttpClient Configuration

@Provides
@Singleton
@NonNull
@Named("httpClient")
OkHttpClient httpClient(Application application) {
    return provideOkHttpClient(application, true, true);
}

Method to Create HttpClient

private OkHttpClient provideOkHttpClient(Application application, boolean followRedirects,
                                         boolean useDiskCache) {
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor()
            .setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient.Builder builder = new OkHttpClient.Builder()
            .connectTimeout(2, TimeUnit.SECONDS)
            .readTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(2, TimeUnit.SECONDS)
            .followRedirects(followRedirects)
            .retryOnConnectionFailure(true)
            .addInterceptor(logging);

    if (useDiskCache) {
        File cache = new File(application.getCacheDir(), "picasso_cache");
        if (!cache.exists()) {
            cache.mkdirs();

        }
        Cache diskCache = new Cache(cache, DISK_CACHE_SIZE);

        builder.cache(diskCache);
    }
    return builder.build();
}

OkHttpClient is configured with a file cache, so I can use already downloaded files in my code.

The following code demonstrates how this works:

httpClient.newCall(new Request.Builder()
            .get()
            .url(medias.get(startPosition).getData())
            .build()
).enqueue(new Callback() {
        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {
            Toast.makeText(GalleryActivity.this, "Failed ot share", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) {
            Log.d(TAG, "Image loaded in: " + (System.currentTimeMillis() - ts) + "ms");

            File downloadedFile = new File(
                    GalleryActivity.this.getExternalCacheDir(),
                    System.currentTimeMillis() + ".jpg"
            );
            try (BufferedSink sink = Okio.buffer(Okio.sink(downloadedFile))) {
                sink.writeAll(response.body().source());

                Uri uri = FileProvider.getUriForFile(
                        GalleryActivity.this,
                        GalleryActivity.this.getPackageName() + ".provider",
                        downloadedFile
                );

                String msg = aircraftName + "\r\nShow it in: https://play.google.com/store/apps/details?id=net.nevinsky.airwar";

                Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
                shareIntent.putExtra(Intent.EXTRA_TEXT, msg);
                shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
                shareIntent.setType("image/png");
                shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

                Log.d(TAG, "Call start 'Share with' activity after " + (System.currentTimeMillis() - ts) + "ms");
                GalleryActivity.this.startActivity(Intent.createChooser(shareIntent, "Share with"));
            } catch (Exception e) {
                Log.e(TAG, e.getMessage(), e);
                Toast.makeText(GalleryActivity.this, "Failed ot share", Toast.LENGTH_SHORT).show();
            }
        }
    });

I use the Okio library by Squareup to transfer byte data from the response to a file. Then, I can use this file’s content provider.

Specifying the Content Provider in AndroidManifest.xml

You must specify this content provider in your AndroidManifest.xml like this:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="net.nevinsky.airwar.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <!-- resource file to create -->
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

File ‘@xml/file_paths’ Configuration

File @xml/file_paths contains the following code:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external_files"
        path="." />
</paths>

Visual Examples

In the application, it will look like this:

Share image from Aviation guide

And in a Telegram chat, it looks like this:

Share image from Aviation guide

By following these steps, you can efficiently share images in your Android application without the slowdowns associated with traditional methods. This approach leverages Picasso and OkHttpClient for optimal performance.