From a1d791b232930833818e943638f3d89328e95df0 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sat, 18 Nov 2017 11:35:13 +0100 Subject: [PATCH] New version of ImageLoad more efficient --- .../data/ImageLoad/ImageDownloadRunnable.java | 71 ++++++++ .../ImageLoad/ImageLoadApplyRunnable.java | 50 ++++++ .../data/ImageLoad/ImageLoadManager.java | 108 ++++++++++++ .../data/ImageLoad/ImageLoadRunnable.java | 160 ++++++++++++++++++ .../data/{ => ImageLoad}/ImageLoadTask.java | 31 ++-- .../client/data/ImageLoad/ImageLoadUtils.java | 29 ++++ .../data/friendsList/FriendsAdapter.java | 7 +- .../client/fragments/UserInfosFragment.java | 2 +- 8 files changed, 433 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageDownloadRunnable.java create mode 100644 app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadApplyRunnable.java create mode 100644 app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadManager.java create mode 100644 app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadRunnable.java rename app/src/main/java/org/communiquons/android/comunic/client/data/{ => ImageLoad}/ImageLoadTask.java (87%) create mode 100644 app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadUtils.java diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageDownloadRunnable.java b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageDownloadRunnable.java new file mode 100644 index 0000000..4227b86 --- /dev/null +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageDownloadRunnable.java @@ -0,0 +1,71 @@ +package org.communiquons.android.comunic.client.data.ImageLoad; + +import org.communiquons.android.comunic.client.data.Utilities; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * Download an image and save it into a file + * + * @author Pierre HUBERT + * Created by pierre on 11/18/17. + */ + +class ImageDownloadRunnable implements Runnable { + + private String url; + private File dest; + + /** + * Instantiate the class for a specific image + * + * @param url The URL were the image can be retrieved + * @param dest The destination file to the image + */ + ImageDownloadRunnable(String url, File dest){ + this.url = url; + this.dest = dest; + } + + + /** + * Perform the download + */ + @Override + public void run() { + try { + //Open the file for writing + if(!dest.createNewFile()) + return; + OutputStream os = new FileOutputStream(dest, false); + + //Open the connection + URL urlObj = new URL(url); + HttpURLConnection conn = (HttpURLConnection) urlObj.openConnection(); + + conn.setDoInput(true); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + + conn.connect(); + + //Get input stream + InputStream is = conn.getInputStream(); + + //Transfer bytes + Utilities.InputToOutputStream(is, os); + + os.close(); + is.close(); + conn.disconnect(); + + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadApplyRunnable.java b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadApplyRunnable.java new file mode 100644 index 0000000..3c8714b --- /dev/null +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadApplyRunnable.java @@ -0,0 +1,50 @@ +package org.communiquons.android.comunic.client.data.ImageLoad; + +import android.graphics.Bitmap; +import android.support.annotation.UiThread; +import android.widget.ImageView; + +/** + * This runnable apply a bitmap on an image view. + * + * This runnable is intended to be run on the UI thread + * + * @author Pierre HUBERT + * Created by pierre on 11/18/17. + */ +class ImageLoadApplyRunnable implements Runnable { + + /** + * The bitmap to appply + */ + private Bitmap bitmap; + + /** + * The target image view + */ + private ImageView imageView; + + /** + * Construct the class + * + * @param imageView The target image view + * @param bitmap The bitmap to apply + */ + ImageLoadApplyRunnable(ImageView imageView, Bitmap bitmap){ + + //Save the values + this.bitmap = bitmap; + this.imageView = imageView; + } + + /** + * This operation should be run only on the UI Thread + */ + @Override + @UiThread + public void run() { + + //Apply the image + imageView.setImageBitmap(bitmap); + } +} diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadManager.java b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadManager.java new file mode 100644 index 0000000..d6b1e4e --- /dev/null +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadManager.java @@ -0,0 +1,108 @@ +package org.communiquons.android.comunic.client.data.ImageLoad; + +import android.content.Context; +import android.util.ArrayMap; +import android.view.View; +import android.widget.ImageView; + +/** + * Image load manager + * + * @author Pierre HUBERT + * Created by pierre on 11/18/17. + */ + +public class ImageLoadManager { + + /** + * The list of running operations + */ + private static ArrayMap threads = null; + + /** + * Private constructor + */ + private static void construct(){ + + //Initializate threads list + threads = new ArrayMap<>(); + + } + + /** + * Check wether the class has been already initialized or not + * + * @return True if the class is already initialized + */ + private static boolean is_constructed(){ + return threads != null; + } + + /** + * Create a new operation + * + * @param context The context of the application + * @param url The URL of the image to load + * @param imageView The target view for the image + */ + public static void load(Context context, String url, ImageView imageView){ + + //Initializate class if required + if(!is_constructed()) + construct(); + + //Remove any previously existing operation + remove(imageView); + + //Create the new thread + Thread thread = new Thread(new ImageLoadRunnable(context, imageView, url)); + threads.put(imageView, thread); + + //Run it + thread.start(); + } + + /** + * Remove (and kill) a thread associated to a view + * + * @param view The target view + */ + public static void remove(ImageView view){ + + //Check if the view has an associated thread + if(threads.containsKey(view)){ + + Thread thread = threads.get(view); + + //Kill the thread if it is alive + if(thread != null){ + if(thread.isAlive()) + thread.interrupt(); + } + + //Remove the thread from the list + threads.remove(view); + + } + + //Clean the list + clean(); + } + + /** + * Clean the list of pending operations + */ + private static void clean(){ + + //Get the list of threads + for(View view : threads.keySet()){ + + if(threads.get(view) != null) + //Check if the associated thread with the view is still alive or not + if(!threads.get(view).isAlive()) + threads.remove(view); + + } + + } +} diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadRunnable.java b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadRunnable.java new file mode 100644 index 0000000..9be6cbe --- /dev/null +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadRunnable.java @@ -0,0 +1,160 @@ +package org.communiquons.android.comunic.client.data.ImageLoad; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.ArrayMap; +import android.util.Log; +import android.widget.ImageView; + +import java.io.File; +import java.io.FileInputStream; + +/** + * Image loading runnable + * + * The advantages of this manager are the following for image loading : it avoid a single file to be + * downloaded several times and it several images to be downloaded at once + * + * @author Pierre HUBERT + * Created by pierre on 11/18/17. + */ + +class ImageLoadRunnable implements Runnable { + + /** + * An array map with all the pending images associated with their URLs + */ + private static ArrayMap pendingOperation = null; + + /** + * The context of the operation + */ + private Context mContext; + + /** + * Target view for the image + */ + private ImageView imageView; + + /** + * The URL of the image + */ + private String url; + + /** + * The file object of the image + */ + private File file; + + /** + * Constructor of the runnable + * + * @param context The context of the application + * @param imageView The imageView of the image + * @param url The URL of the image + */ + ImageLoadRunnable(Context context, ImageView imageView, String url){ + + //Check if the list of pending operations has to be initialized or not + if(pendingOperation == null) + pendingOperation = new ArrayMap<>(); + + //Save the values + this.imageView = imageView; + this.url = url; + this.mContext = context; + } + + @Override + public void run() { + + //Determine the filename for the requested URL + String filename = ImageLoadUtils.IMAGE_CACHE_DIRECTORY + ImageLoadUtils.get_file_name(url); + + //Create file object + file = new File(mContext.getCacheDir(),filename); + + //Check no thread is already running for the following image + if(!pendingOperation.containsKey(url)){ + + //Check if a file exist or not + if(file.exists()){ + + //Then the file can be loaded in a bitmap + load_image(); + return; + } + else { + + //Create the thread and start it + Thread thread = new Thread(new ImageDownloadRunnable(url, file)); + pendingOperation.put(url, thread); + thread.start(); + } + } + + //Get the thread + Thread operation = pendingOperation.get(url); + + //If we couldn't get the thread, this is an error + if(operation == null){ + Log.e("ImageLoadManagerRunnabl", "run : Couldn't get thread !"); + return; + } + + if(operation.isAlive()) { + try { + //Wait for the thread to finish + operation.join(); + + } catch (InterruptedException e) { + e.printStackTrace(); + return; + } + } + + //Remove the thread from the pending list + pendingOperation.remove(url); + + //Load the image + load_image(); + } + + /** + * Once the image was downloaded (if it wasn't already) load the image into a bitmpa object + * The apply to the final image view + */ + private void load_image(){ + + //Check if the file exists + if(!file.exists()){ + Log.e("ImageLoadManagerRunnabl", "load_image : file does not exists but it should !"); + return; + } + + try { + //Load the image + FileInputStream is = new FileInputStream(file); + Bitmap bitmap = BitmapFactory.decodeStream(is); + is.close(); + + //Check if bitmap failed to read + if(bitmap == null){ + //Return error + Log.e("ImageLoadManagerRunnabl", "Image file could not be read, therefore it was" + + "deleted"); + + //Delete file + file.delete(); + return; + } + + //Apply the bitmap on the image view (register operation) + imageView.post(new ImageLoadApplyRunnable(imageView, bitmap)); + + } catch (Exception e){ + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoadTask.java b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadTask.java similarity index 87% rename from app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoadTask.java rename to app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadTask.java index 02167a8..1e649ce 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoadTask.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadTask.java @@ -1,4 +1,4 @@ -package org.communiquons.android.comunic.client.data; +package org.communiquons.android.comunic.client.data.ImageLoad; import android.content.Context; import android.graphics.Bitmap; @@ -9,6 +9,8 @@ import android.support.v4.content.ContextCompat; import android.util.Log; import android.widget.ImageView; +import org.communiquons.android.comunic.client.data.Utilities; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -23,10 +25,13 @@ import java.net.URL; /** * Web image loader and renderer * + * Warning : Using ImageLoadTask is a quite bad idea to load multiple images, + * ImageLoad class should be preferred + * * @author Pierre HUBERT * Created by pierre on 11/8/17. */ - +@Deprecated public class ImageLoadTask extends AsyncTask { /** @@ -44,11 +49,6 @@ public class ImageLoadTask extends AsyncTask { */ private Context mContext; - /** - * The main folder in the cache directory that stores the file - */ - private final String IMAGE_CACHE_DIRECTORY = "img_cache/"; - /** * Image file object */ @@ -80,12 +80,12 @@ public class ImageLoadTask extends AsyncTask { protected Void doInBackground(Void... param) { //Determine the file name for the view - String filename = get_file_name(url); + String filename = ImageLoadUtils.get_file_name(url); if (filename == null) { Log.e("ImageLoadTask", "Couldn't generate file storage name !"); return null; //An error occured } - String full_filename = IMAGE_CACHE_DIRECTORY + filename; + String full_filename = ImageLoadUtils.IMAGE_CACHE_DIRECTORY + filename; //Try to open the file img_file = new File(mContext.getCacheDir(), full_filename); @@ -177,24 +177,13 @@ public class ImageLoadTask extends AsyncTask { return true; } - - /** - * Get the file name, based on the URL name - * - * @param url The URL of the file - * @return The name of the file, composed of characters that can be used in filename - */ - private String get_file_name(String url){ - return Utilities.sha1(url); - } - /** * Create cache images files parent directory if it does not exist * * @return True in case of success */ private boolean create_parent_directory(){ - File parent = new File(mContext.getCacheDir(), IMAGE_CACHE_DIRECTORY); + File parent = new File(mContext.getCacheDir(), ImageLoadUtils.IMAGE_CACHE_DIRECTORY); //Check if parent directory already exists if(parent.exists()) diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadUtils.java b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadUtils.java new file mode 100644 index 0000000..3ff24a3 --- /dev/null +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/ImageLoad/ImageLoadUtils.java @@ -0,0 +1,29 @@ +package org.communiquons.android.comunic.client.data.ImageLoad; + +import org.communiquons.android.comunic.client.data.Utilities; + +/** + * Image loading utilities + * + * @author Pierre HUBERT + * Created by pierre on 11/18/17. + */ + +class ImageLoadUtils { + + /** + * The main folder in the cache directory that stores the file + */ + static final String IMAGE_CACHE_DIRECTORY = "img_cache/"; + + /** + * Get the file name, based on the URL name + * + * @param url The URL of the file + * @return The name of the file, composed of characters that can be used in filename + */ + static String get_file_name(String url){ + return Utilities.sha1(url); + } + +} diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/friendsList/FriendsAdapter.java b/app/src/main/java/org/communiquons/android/comunic/client/data/friendsList/FriendsAdapter.java index c7664a2..e614fdf 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/friendsList/FriendsAdapter.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/friendsList/FriendsAdapter.java @@ -1,6 +1,7 @@ package org.communiquons.android.comunic.client.data.friendsList; import android.app.Activity; +import android.os.AsyncTask; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.LayoutInflater; @@ -11,7 +12,8 @@ import android.widget.ImageView; import android.widget.TextView; import org.communiquons.android.comunic.client.R; -import org.communiquons.android.comunic.client.data.ImageLoadTask; +import org.communiquons.android.comunic.client.data.ImageLoad.ImageLoadManager; +import org.communiquons.android.comunic.client.data.ImageLoad.ImageLoadTask; import java.util.ArrayList; @@ -51,8 +53,7 @@ public class FriendsAdapter extends ArrayAdapter { //Update user account image ImageView user_image = listItemView.findViewById(R.id.fragment_friendslist_item_accountimage); - new ImageLoadTask(getContext(), friendUser.getUserInfo().getAcountImageURL(), user_image) - .execute(); + ImageLoadManager.load(getContext(), friendUser.getUserInfo().getAcountImageURL(), user_image); //Update user name TextView user_name = listItemView.findViewById(R.id.fragment_friendslist_item_fullname); diff --git a/app/src/main/java/org/communiquons/android/comunic/client/fragments/UserInfosFragment.java b/app/src/main/java/org/communiquons/android/comunic/client/fragments/UserInfosFragment.java index 63850e8..0232f36 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/fragments/UserInfosFragment.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/fragments/UserInfosFragment.java @@ -14,7 +14,7 @@ import android.widget.TextView; import org.communiquons.android.comunic.client.R; import org.communiquons.android.comunic.client.data.Account.AccountUtils; import org.communiquons.android.comunic.client.data.DatabaseHelper; -import org.communiquons.android.comunic.client.data.ImageLoadTask; +import org.communiquons.android.comunic.client.data.ImageLoad.ImageLoadTask; import org.communiquons.android.comunic.client.data.UsersInfo.GetUsersInfos; import org.communiquons.android.comunic.client.data.UsersInfo.UserInfo;