2
\$\begingroup\$

I've got a class called Movie and within this class I use an AsyncTask to get a Bitmap from a url passed in the constructor.

To store the image I use new DownloadImage().execute(url).get();. However I'm concerned about this blocking the UI Thread as now its a synchronous call. Is there another way I can go about saving the image in the Bitmap variable?

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.Image;
import android.os.AsyncTask;
import android.widget.ImageView;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutionException;

public class Movie
{
    //Variable for a movie
    private String name;
    private String rating;
    private String genre;
    private String plot;
    private String pathToMovie;

    private int movieId;
    private int runtime;
    private int year;

    private Bitmap image;

    public Movie(String name, String rating, String genre, String plot, String pathToMovie, int movieId, int runtime, int year, String imagePath) throws ExecutionException, InterruptedException
    {
        //Set the variables
        this.name = name;
        this.rating = rating;
        this.genre = genre;
        this.plot = plot;
        this.pathToMovie = pathToMovie;
        this.movieId = movieId;
        this.runtime = runtime;
        this.year = year;

        //Download the image from the imagePath
        this.image = getImageFromUrl(imagePath);
    }

    /*
     * A function to download the image from the imagePath provided
     */
    private Bitmap getImageFromUrl(String url) throws ExecutionException, InterruptedException
    {
        return new DownloadImage().execute(url).get();
    }

    //Define getters
    public String getName(){ return this.name; }
    public String getRating(){ return this.rating; }
    public String getGenre(){ return this.genre; }
    public String getPlot(){ return this.plot; }
    public String getPathToMovie(){ return this.pathToMovie; }
    public int getMovieId(){ return this.movieId; }
    public int getRuntime(){ return this.runtime; }
    public int getYear(){ return this.year; }
    public Bitmap getImage(){ return  this.image; }

    //Set up a AsyncTask to download the images from the url
    private class DownloadImage extends AsyncTask<String, Void, Bitmap>
    {
        @Override
        protected Bitmap doInBackground(String... params)
        {
            //Get the image from the url
            try
            {
                //Create a new URL based on the string provided
                URL url = new URL(params[0]);

                //Create a new HTTP connection from the URL
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setDoInput(true);
                connection.connect();

                //Get the content of the page
                InputStream inputStream = connection.getInputStream();

                //Use a BitmapFactory to convert the InputStream into a bitmap
                return BitmapFactory.decodeStream(inputStream);

            }
            catch (IOException e)
            {
                e.printStackTrace();
                return null;
            }
        }
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ I would suggest you either save the Bitmap to a variable and use it in onPostExecute() or just pass it as a variable to publishProgress() and override onProgressUpdate() where you use that Bitmap again. Trying to get around this will force you to make the call synchronous. \$\endgroup\$
    – GiantTree
    Commented Apr 10, 2015 at 20:06

1 Answer 1

1
\$\begingroup\$

You could try to write your app in callback passing style. That means methods that take a while (e.g. methods for fetching data from the web) take a callback argument that contains the code that will run once the operation is finished.

This program uses two ExecutorServices. One processes everything in one single thread. The other one runs stuff in parallel. All the code you run with the parallel ExecutorService should never access any objects that are modified by the rest of the program unless you know how to do synchronization correctly. But you don't need any synchronization at all here. Just ensure all the code that modifies the state of your program runs only in the single threaded ExecutorService and everything will be fine. You may notice that final is used a lot in this code. That's because variables that are implicitly passed to an anonymous class need to be final.

class Tools {
  public final static ExecutorService async = Executors.newSingleThreadExecutor();
  public final static ExecutorService parallel = Executors.newFixedThreadPool(8);

  public static void shutdown() {
    Tools.async.shutdown();
    Tools.parallel.shutdown();
  }

  public static void getImageFromUrl(final String urlString, final Async<Bitmap> async) {
    Tools.parallel.execute(new Runnable() {
      public void run() {
        try {
          System.out.println(Thread.currentThread() + " - Loading image: " + urlString);
          async.send(null);
          /*
          URL url = new URL(urlString);
          HttpURLConnection connection = (HttpURLConnection) url.openConnection();
          connection.setDoInput(true);
          connection.connect();
          InputStream inputStream = connection.getInputStream();
          async.send(BitmapFactory.decodeStream(inputStream));
          */
        } catch(Exception e) {
          async.error(e);
        }
      }
    });
  }
}

abstract class Async<T> {
  abstract protected void accept(T t);
  public void send(final T t) {
    final Async<T> self = this;
    Tools.async.execute(new Runnable() {
      public void run() {
        self.accept(t);
      }
    });
  }

  public void error(Throwable err) {
    err.printStackTrace();
  }
}

class Ref<T> {
  private T value;
  public Ref(T value) {
    this.value = value;
  }
  public T get() {
    return value;
  }
  public void set(T value) {
    this.value = value;
  }
}

class Test {
  public static void main(String[] args) {
    Tools.async.execute(new Runnable() {
      public void run() {
        mainProgram();
      }
    });
  }

  private static void mainProgram() {
    final ArrayList<Movie> movies = new ArrayList<>();
    final Ref<Integer> numImages = new Ref<>(10);
    for(int i = 0; i < 10; i++) {
      final int imageNr = i;
      Tools.getImageFromUrl("http://image-website/image" + i, new Async<Bitmap>() {
        public void accept(Bitmap image) {
          System.out.println(Thread.currentThread() + " - Creating movie object for image: " + imageNr);
          movies.add(new Movie("", "", "", "", "", 0, 0, 0, image));
          numImages.set(numImages.get() - 1);
          if(numImages.get() == 0) {
            allMoviesLoaded(movies);
          }
        }
      });
    }
  }

  private static void allMoviesLoaded(ArrayList<Movie> images) {
    System.out.println("All movies loaded");
    Tools.shutdown(); //ends the program
  }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.