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:
And in a Telegram chat, it looks like this:
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.