[Android] [Java] Download images on GCS (Google Cloud Storage) with Stream using Glide

Glide Glide is an image loading & caching library for Android. https://github.com/bumptech/glide

If it is Gradle, you can install it as follows.

build.gradle


repositories {
    mavenCentral()
    google()
}

dependencies {
    implementation 'com.github.bumptech.glide:glide:4.8.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
}

Preparation

Original model

Define a class called GcsImage that specifies the files that exist on GCS with Bucket and PATH.

GcsImage.java


public class GcsImage {

    private final String mBucket;
    private final String mFilename;

    public GcsImage(@NonNull String bucket, @NonNull String filename) {
        mBucket = bucket;
        mFilename = filename;
    }

    public String getBucket() { return mBucket; }

    public String getFilename() { return mFilename; }
}

Glide Module Follow the link below to define a ModelLoader to load the GcsImage. Writing a custom ModelLoader

The first step is to implement the ModelLoader interface. Before we do so, we need to make two decisions:

  1. What type of Model should we handle?
  2. What type of Data should we produce for that Model?

As you can see, ModelLoader specifies the Model and Data type to handle.

Model may be String etc., but this time we will use our own GcsImage. As the Data type, ʻInputStream and ByteBuffer` decoders are provided as standard.

GcsImageLoader.java


public class GcsImageLoader implements ModelLoader<GcsImage, InputStream> {

    private Context mContext;

    public GcsImageLoader(Context context) {
        mContext = context;
    }

    @Nullable
    @Override
    public LoadData<InputStream> buildLoadData(@NonNull GcsImage gcsImage, int width, int height, @NonNull Options options) {
        //If the bucket name and file name are the same, cache data will be used.
        return new LoadData<>(new ObjectKey(gcsImage.getBucket() + '/' + gcsImage.getFilename()), new GcsImageFetcher(gcsImage));
    }

    @Override
    public boolean handles(@NonNull GcsImage gcsImage) {
        return true;
    }

    private class GcsImageFetcher implements DataFetcher<InputStream> {
        private GcsImage gcsImage;

        GcsImageFetcher(GcsImage gcsImage) {
           this.gcsImage = gcsImage;
        }

        @Override
        public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
            try {
                //Describes the actual reading process. Here, we implemented our own class.
                InputStream stream = CloudStorageUtils.downloadToStream(mContext, gcsImage.getBucket(), gcsImage.getFilename());
                callback.onDataReady(stream);
            } catch (IOException e) {
                callback.onLoadFailed(e);
            }
        }

        @Override
        public void cleanup() {}

        @Override
        public void cancel() {}

        @NonNull
        @Override
        public Class<InputStream> getDataClass() {
            return InputStream.class;
        }

        @NonNull
        @Override
        public DataSource getDataSource() {
            return DataSource.REMOTE;
        }
    }

    public static class GcsImageLoaderFactory implements ModelLoaderFactory<GcsImage, InputStream> {
        private Context context;

        public GcsImageLoaderFactory(Context context) {
            this.context = context;
        }

        @NonNull
        @Override
        public ModelLoader<GcsImage, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
            return new GcsImageLoader(context);
        }

        @Override
        public void teardown() {}
    }
}

I also created a method called CloudStorageUtils.downloadToStream () and defined it in the following class.

CloudStorageUtils.java


public class CloudStorageUtils {

    @WorkerThread
    public static InputStream downloadToStream(@NonNull Context context, @NonNull String bucket,
                                               @NonNull String name) throws IOException {
        Storage storage = getStorageService(context);
        Storage.Objects.Get get = storage.objects().get(bucket, name);
        return get.executeMediaAsInputStream();
    }

    @WorkerThread
    @NonNull
    public static Storage getStorageService(@NonNull Context context) throws IOException {
        HttpTransport transport = new NetHttpTransport();
        JsonFactory jsonFactory = new JacksonFactory();
        //The part for acquiring authentication information is also defined in another class.
        Credential credential = GoogleApiCredentialFactory.getCredential(context);
        return new Storage(transport, jsonFactory, credential);
    }
}

GoogleApiCredentialFactory.java


public class GoogleApiCredentialFactory {

    private static Credential sCredential;

    public static synchronized Credential getCredential(@NonNull Context context) {
        if (sCredential == null) {
            //Read the JSON file with Stream and get the Credential.
            try (InputStream is = context.getAssets().open(BuildConfig.GCP_CREDENTIAL)) {
                List<String> scopes = new ArrayList<>();
                scopes.add(StorageScopes.DEVSTORAGE_FULL_CONTROL);
                GoogleCredential credential = GoogleCredential.fromStream(is);
                sCredential = credential.createScoped(scopes);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return sCredential;
    }
}

For BuildConfig.GCP_CREDENTIAL, specify the path to the JSON file that contains the GCP service account key.

Now, let's add the created GcsImageLoader.

GcsImageGlideModule.java


@GlideModule
public class GcsImageGlideModule extends AppGlideModule {

    @Override
    public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
        registry.prepend(GcsImage.class, InputStream.class, new GcsImageLoader.GcsImageLoaderFactory(context));
    }
}

By annotating @GlideModule, Glide will recognize GcsImageGlideModule, which inherits from ʻAppGlideModule`.

As mentioned above, build.gradle needs to describe Annotation Processor.

See also: Module classes and annotations.

Implementation

The program that reads the image on GCS using the GlideModule that I made is as follows. Here, the image is displayed in ʻImageView called mImageView`.

Glide.with(this).load(new GcsImage(bucket, filename)).into(mImageView);

With the above, the image can be displayed safely.

Recommended Posts

[Android] [Java] Download images on GCS (Google Cloud Storage) with Stream using Glide
Try using Firebase Cloud Functions on Android (Java)
Use Java 11 with Google Cloud Functions
Using JupyterLab + Java with WSL on Windows 10
Sobel filter using OpenCV on Android (Java)
Problem of slow processing when using Active Storage with cloud storage (GCS, S3, etc)
[Java] Get images with Google Custom Search API
Try communication using gRPC on Android + Java server
Using Java 8 with Bluemix (on Liberty Runtime & DevOps Service)
I tried using Google Cloud Vision API in Java
Build a Maven in-house repository on Google Cloud Storage
Try image classification using TensorFlow Lite on Android (JAVA)
Comfortable download with JAVA
Download Java with Ansible
Using multiple versions of Java with Brew on Mac + jEnv
I tried using the CameraX library with Android Java Fragment
Upload files to Aspera that comes with IBM Cloud Object Storage (ICOS) using SDK (Java version)