New version of ImageLoad more efficient

This commit is contained in:
Pierre 2017-11-18 11:35:13 +01:00
parent 3a54768224
commit a1d791b232
8 changed files with 433 additions and 25 deletions

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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<View, Thread> 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);
}
}
}

View File

@ -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<String, Thread> 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();
}
}
}

View File

@ -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.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -9,6 +9,8 @@ import android.support.v4.content.ContextCompat;
import android.util.Log; import android.util.Log;
import android.widget.ImageView; import android.widget.ImageView;
import org.communiquons.android.comunic.client.data.Utilities;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -23,10 +25,13 @@ import java.net.URL;
/** /**
* Web image loader and renderer * 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 * @author Pierre HUBERT
* Created by pierre on 11/8/17. * Created by pierre on 11/8/17.
*/ */
@Deprecated
public class ImageLoadTask extends AsyncTask<Void, Void, Void> { public class ImageLoadTask extends AsyncTask<Void, Void, Void> {
/** /**
@ -44,11 +49,6 @@ public class ImageLoadTask extends AsyncTask<Void, Void, Void> {
*/ */
private Context mContext; 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 * Image file object
*/ */
@ -80,12 +80,12 @@ public class ImageLoadTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... param) { protected Void doInBackground(Void... param) {
//Determine the file name for the view //Determine the file name for the view
String filename = get_file_name(url); String filename = ImageLoadUtils.get_file_name(url);
if (filename == null) { if (filename == null) {
Log.e("ImageLoadTask", "Couldn't generate file storage name !"); Log.e("ImageLoadTask", "Couldn't generate file storage name !");
return null; //An error occured 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 //Try to open the file
img_file = new File(mContext.getCacheDir(), full_filename); img_file = new File(mContext.getCacheDir(), full_filename);
@ -177,24 +177,13 @@ public class ImageLoadTask extends AsyncTask<Void, Void, Void> {
return true; 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 * Create cache images files parent directory if it does not exist
* *
* @return True in case of success * @return True in case of success
*/ */
private boolean create_parent_directory(){ 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 //Check if parent directory already exists
if(parent.exists()) if(parent.exists())

View File

@ -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);
}
}

View File

@ -1,6 +1,7 @@
package org.communiquons.android.comunic.client.data.friendsList; package org.communiquons.android.comunic.client.data.friendsList;
import android.app.Activity; import android.app.Activity;
import android.os.AsyncTask;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -11,7 +12,8 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.communiquons.android.comunic.client.R; 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; import java.util.ArrayList;
@ -51,8 +53,7 @@ public class FriendsAdapter extends ArrayAdapter<FriendUser> {
//Update user account image //Update user account image
ImageView user_image = listItemView.findViewById(R.id.fragment_friendslist_item_accountimage); ImageView user_image = listItemView.findViewById(R.id.fragment_friendslist_item_accountimage);
new ImageLoadTask(getContext(), friendUser.getUserInfo().getAcountImageURL(), user_image) ImageLoadManager.load(getContext(), friendUser.getUserInfo().getAcountImageURL(), user_image);
.execute();
//Update user name //Update user name
TextView user_name = listItemView.findViewById(R.id.fragment_friendslist_item_fullname); TextView user_name = listItemView.findViewById(R.id.fragment_friendslist_item_fullname);

View File

@ -14,7 +14,7 @@ import android.widget.TextView;
import org.communiquons.android.comunic.client.R; import org.communiquons.android.comunic.client.R;
import org.communiquons.android.comunic.client.data.Account.AccountUtils; import org.communiquons.android.comunic.client.data.Account.AccountUtils;
import org.communiquons.android.comunic.client.data.DatabaseHelper; 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.GetUsersInfos;
import org.communiquons.android.comunic.client.data.UsersInfo.UserInfo; import org.communiquons.android.comunic.client.data.UsersInfo.UserInfo;