so I made an app which communicates with JSON on the web. It fetches images and texts from the JSON.
And now I got a task to make this app accessible without an internet connection.
it should be like this:
The first time it's launched, the app has to check if there's any internet connection or not. If not, pop up a dialog box 'Please check your internet connection'. If there's any, the app is opened and it has to download the images and texts from the JSON and save them into an external storage
The next time when the app is opened, when there's no internet it will load the images and text files from the external storage. And each time it's connected to the internet, it will download the files and replace the previous files.
Can anybody provide me a solution by modifying these classes below maybe?
public class ImageThreadLoader {
private static final String TAG = "ImageThreadLoader";
// Global cache of images.
// Using SoftReference to allow garbage collector to clean cache if needed
private final HashMap<String, SoftReference<Bitmap>> Cache = new HashMap<String, SoftReference<Bitmap>>();
private final class QueueItem {
public URL url;
public ImageLoadedListener listener;
}
private final ArrayList<QueueItem> Queue = new ArrayList<QueueItem>();
private final Handler handler = new Handler(); // Assumes that this is started from the main (UI) thread
private Thread thread;
private QueueRunner runner = new QueueRunner();;
/** Creates a new instance of the ImageThreadLoader */
public ImageThreadLoader() {
thread = new Thread(runner);
}
/**
* Defines an interface for a callback that will handle
* responses from the thread loader when an image is done
* being loaded.
*/
public interface ImageLoadedListener {
public void imageLoaded(Bitmap imageBitmap );
}
/**
* Provides a Runnable class to handle loading
* the image from the URL and settings the
* ImageView on the UI thread.
*/
private class QueueRunner implements Runnable {
public void run() {
synchronized(this) {
while(Queue.size() > 0) {
final QueueItem item = Queue.remove(0);
// If in the cache, return that copy and be done
if( Cache.containsKey(item.url.toString()) && Cache.get(item.url.toString()) != null) {
// Use a handler to get back onto the UI thread for the update
handler.post(new Runnable() {
public void run() {
if( item.listener != null ) {
// NB: There's a potential race condition here where the cache item could get
// garbage collected between when we post the runnable and it's executed.
// Ideally we would re-run the network load or something.
SoftReference<Bitmap> ref = Cache.get(item.url.toString());
if( ref != null ) {
item.listener.imageLoaded(ref.get());
}
}
}
});
} else {
final Bitmap bmp = readBitmapFromNetwork(item.url);
if( bmp != null ) {
Cache.put(item.url.toString(), new SoftReference<Bitmap>(bmp));
// Use a handler to get back onto the UI thread for the update
handler.post(new Runnable() {
public void run() {
if( item.listener != null ) {
item.listener.imageLoaded(bmp);
}
}
});
}
}
}
}
}
}
/**
* Queues up a URI to load an image from for a given image view.
*
* #param uri The URI source of the image
* #param callback The listener class to call when the image is loaded
* #throws MalformedURLException If the provided uri cannot be parsed
* #return A Bitmap image if the image is in the cache, else null.
*/
public Bitmap loadImage( final String uri, final ImageLoadedListener listener) throws MalformedURLException {
// If it's in the cache, just get it and quit it
if( Cache.containsKey(uri)) {
SoftReference<Bitmap> ref = Cache.get(uri);
if( ref != null ) {
return ref.get();
}
}
QueueItem item = new QueueItem();
item.url = new URL(uri);
item.listener = listener;
Queue.add(item);
// start the thread if needed
if( thread.getState() == State.NEW) {
thread.start();
} else if( thread.getState() == State.TERMINATED) {
thread = new Thread(runner);
thread.start();
}
return null;
}
/**
* Convenience method to retrieve a bitmap image from
* a URL over the network. The built-in methods do
* not seem to work, as they return a FileNotFound
* exception.
*
* Note that this does not perform any threading --
* it blocks the call while retrieving the data.
*
* #param url The URL to read the bitmap from.
* #return A Bitmap image or null if an error occurs.
*/
public static Bitmap readBitmapFromNetwork( URL url ) {
InputStream is = null;
BufferedInputStream bis = null;
Bitmap bmp = null;
try {
URLConnection conn = url.openConnection();
conn.connect();
is = conn.getInputStream();
bis = new BufferedInputStream(is);
bmp = BitmapFactory.decodeStream(bis);
} catch (MalformedURLException e) {
Log.e(TAG, "Bad ad URL", e);
} catch (IOException e) {
Log.e(TAG, "Could not get remote ad image", e);
} finally {
try {
if( is != null )
is.close();
if( bis != null )
bis.close();
} catch (IOException e) {
Log.w(TAG, "Error closing stream.");
}
}
return bmp;
}
}
and
public class ProjectAdapter extends ArrayAdapter<Project> {
int resource;
String response;
Context context;
private final static String TAG = "MediaItemAdapter";
private ImageThreadLoader imageLoader = new ImageThreadLoader();
//Initialize adapter
public ProjectAdapter(Context context, int resource, List<Project> items) {
super(context, resource, items);
this.resource=resource;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
TextView textTitle;
final ImageView image;
Project pro = getItem(position);
LinearLayout projectView;
//Inflate the view
if(convertView==null)
{
projectView = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater vi;
vi = (LayoutInflater)getContext().getSystemService(inflater);
vi.inflate(resource, projectView, true);
}
else
{
projectView = (LinearLayout) convertView;
}
try {
textTitle = (TextView)projectView.findViewById(R.id.txt_title);
image = (ImageView)projectView.findViewById(R.id.image);
} catch( ClassCastException e ) {
Log.e(TAG, "Your layout must provide an image and a text view with ID's icon and text.", e);
throw e;
}
Bitmap cachedImage = null;
try {
cachedImage = imageLoader.loadImage(pro.smallImageUrl, new ImageLoadedListener() {
public void imageLoaded(Bitmap imageBitmap) {
image.setImageBitmap(imageBitmap);
notifyDataSetChanged(); }
});
} catch (MalformedURLException e) {
Log.e(TAG, "Bad remote image URL: " + pro.smallImageUrl, e);
}
textTitle.setText(pro.project_title);
if( cachedImage != null ) {
image.setImageBitmap(cachedImage);
}
return projectView;
}
}
Thank you!
Create a database with the names and paths of the downloaded images. Upon onCreate() (or wherever you want to do the check), read the database and check if it's empty or not. If not, then use the images.
Related
I'm downloading multiple images through AsyncTask. It is working well when there are 2-3 images; but, when there are more images, many AsyncTask instances are created, and are causing errors. Do anyone have an idea how to overcome this problem?
Can you add your code here?
Maybe you can try to use onPostExecute function of your Asynctask to start a new one like:
protected void onPostExecute() {
doInBackground();
}
or you can trigger an activity function by your onPostExecute() function to start download " next picture " so it will only create one asynctask for one time.
From Android Doc: Common view components such as ListView and GridView
introduce another issue when used in conjunction with the AsyncTask as
demonstrated in the previous section. In order to be efficient with
memory, these components recycle child views as the user scrolls. If
each child view triggers an AsyncTask, there is no guarantee that when
it completes, the associated view has not already been recycled for
use in another child view. Furthermore, there is no guarantee that the
order in which asynchronous tasks are started is the order that they
complete.
Handle Concurrency
http://android-developers.blogspot.in/2010/07/multithreading-for-performance.html
public class ImageDownloader {
private static final String LOG_TAG = "ImageDownloader";
public enum Mode { NO_ASYNC_TASK, NO_DOWNLOADED_DRAWABLE, CORRECT }
private Mode mode = Mode.NO_ASYNC_TASK;
/**
* Download the specified image from the Internet and binds it to the provided ImageView. The
* binding is immediate if the image is found in the cache and will be done asynchronously
* otherwise. A null bitmap will be associated to the ImageView if an error occurs.
*
* #param url The URL of the image to download.
* #param imageView The ImageView to bind the downloaded image to.
*/
public void download(String url, ImageView imageView) {
resetPurgeTimer();
Bitmap bitmap = getBitmapFromCache(url);
if (bitmap == null) {
forceDownload(url, imageView);
} else {
cancelPotentialDownload(url, imageView);
imageView.setImageBitmap(bitmap);
}
}
/*
* Same as download but the image is always downloaded and the cache is not used.
* Kept private at the moment as its interest is not clear.
private void forceDownload(String url, ImageView view) {
forceDownload(url, view, null);
}
*/
/**
* Same as download but the image is always downloaded and the cache is not used.
* Kept private at the moment as its interest is not clear.
*/
private void forceDownload(String url, ImageView imageView) {
// State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys.
if (url == null) {
imageView.setImageDrawable(null);
return;
}
if (cancelPotentialDownload(url, imageView)) {
switch (mode) {
case NO_ASYNC_TASK:
Bitmap bitmap = downloadBitmap(url);
addBitmapToCache(url, bitmap);
imageView.setImageBitmap(bitmap);
break;
case NO_DOWNLOADED_DRAWABLE:
imageView.setMinimumHeight(156);
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
task.execute(url);
break;
case CORRECT:
task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
imageView.setMinimumHeight(156);
task.execute(url);
break;
}
}
}
/**
* Returns true if the current download has been canceled or if there was no download in
* progress on this image view.
* Returns false if the download in progress deals with the same url. The download is not
* stopped in that case.
*/
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null) {
String bitmapUrl = bitmapDownloaderTask.url;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel(true);
} else {
// The same URL is already being downloaded.
return false;
}
}
return true;
}
/**
* #param imageView Any imageView
* #return Retrieve the currently active download task (if any) associated with this imageView.
* null if there is no such task.
*/
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
Bitmap downloadBitmap(String url) {
final int IO_BUFFER_SIZE = 4 * 1024;
// AndroidHttpClient is not allowed to be used from the main thread
final HttpClient client = (mode == Mode.NO_ASYNC_TASK) ? new DefaultHttpClient() :
AndroidHttpClient.newInstance("Android");
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w("ImageDownloader", "Error " + statusCode +
" while retrieving bitmap from " + url);
return null;
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
try {
inputStream = entity.getContent();
// return BitmapFactory.decodeStream(inputStream);
// Bug on slow connections, fixed in future release.
return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
} finally {
if (inputStream != null) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (IOException e) {
getRequest.abort();
Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
} catch (IllegalStateException e) {
getRequest.abort();
Log.w(LOG_TAG, "Incorrect URL: " + url);
} catch (Exception e) {
getRequest.abort();
Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
} finally {
if ((client instanceof AndroidHttpClient)) {
((AndroidHttpClient) client).close();
}
}
return null;
}
/*
* An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
*/
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
#Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int b = read();
if (b < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
/**
* The actual AsyncTask that will asynchronously download the image.
*/
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
/**
* Actual download method.
*/
#Override
protected Bitmap doInBackground(String... params) {
url = params[0];
return downloadBitmap(url);
}
/**
* Once the image is downloaded, associates it to the imageView
*/
#Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
addBitmapToCache(url, bitmap);
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
// Change bitmap only if this process is still associated with it
// Or if we don't use any bitmap to task association (NO_DOWNLOADED_DRAWABLE mode)
if ((this == bitmapDownloaderTask) || (mode != Mode.CORRECT)) {
imageView.setImageBitmap(bitmap);
}
}
}
}
/**
* A fake Drawable that will be attached to the imageView while the download is in progress.
*
* <p>Contains a reference to the actual download task, so that a download task can be stopped
* if a new binding is required, and makes sure that only the last started download process can
* bind its result, independently of the download finish order.</p>
*/
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
super(Color.BLACK);
bitmapDownloaderTaskReference =
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
public void setMode(Mode mode) {
this.mode = mode;
clearCache();
}
/*
* Cache-related fields and methods.
*
* We use a hard and a soft cache. A soft reference cache is too aggressively cleared by the
* Garbage Collector.
*/
private static final int HARD_CACHE_CAPACITY = 10;
private static final int DELAY_BEFORE_PURGE = 10 * 1000; // in milliseconds
// Hard cache, with a fixed maximum capacity and a life duration
private final HashMap<String, Bitmap> sHardBitmapCache =
new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) {
#Override
protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) {
if (size() > HARD_CACHE_CAPACITY) {
// Entries push-out of hard reference cache are transferred to soft reference cache
sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
return true;
} else
return false;
}
};
// Soft cache for bitmaps kicked out of hard cache
private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache =
new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2);
private final Handler purgeHandler = new Handler();
private final Runnable purger = new Runnable() {
public void run() {
clearCache();
}
};
/**
* Adds this bitmap to the cache.
* #param bitmap The newly downloaded bitmap.
*/
private void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (sHardBitmapCache) {
sHardBitmapCache.put(url, bitmap);
}
}
}
/**
* #param url The URL of the image that will be retrieved from the cache.
* #return The cached bitmap or null if it was not found.
*/
private Bitmap getBitmapFromCache(String url) {
// First try the hard reference cache
synchronized (sHardBitmapCache) {
final Bitmap bitmap = sHardBitmapCache.get(url);
if (bitmap != null) {
// Bitmap found in hard cache
// Move element to first position, so that it is removed last
sHardBitmapCache.remove(url);
sHardBitmapCache.put(url, bitmap);
return bitmap;
}
}
// Then try the soft reference cache
SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);
if (bitmapReference != null) {
final Bitmap bitmap = bitmapReference.get();
if (bitmap != null) {
// Bitmap found in soft cache
return bitmap;
} else {
// Soft reference has been Garbage Collected
sSoftBitmapCache.remove(url);
}
}
return null;
}
/**
* Clears the image cache used internally to improve performance. Note that for memory
* efficiency reasons, the cache will automatically be cleared after a certain inactivity delay.
*/
public void clearCache() {
sHardBitmapCache.clear();
sSoftBitmapCache.clear();
}
/**
* Allow a new delay before the automatic cache clear is done.
*/
private void resetPurgeTimer() {
purgeHandler.removeCallbacks(purger);
purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
}
}
you can use Vinci android Library its testet and support Concurrency and its build on top
Builder Pattern
Singleton Pattern
Callback Pattern
and its handle downloading multi Images by her self and its easy to use .
if you wanna get a result of every download request :
Vinci
.base(context)
.process()
.load(uri,
new Request() {
#Override
public void onSuccess(Bitmap bitmap) {
viewHolder.Writer.setImageBitmap(bitmap);
//or
//do some thing with bitmap
}
#Override
public void onFailure(Throwable e) {
Log.e(e.getClass().getSimpleName(), e.getMessage());
}
});
if you wanna show image in ImageView you can use it like this
Vinci
.base(context)
.process()
.load(uri)
.view(imageView);
for use it in RecycleView see this wiki page
Simply use the Picasso library, to load the images into the ImageViews, as this is very simple to use and also supports concurrency very easily.
Example:
Picasso.with(this.context).load(url_of_image).into(imageView);
I'm making an Android app that needs to allow client to maintain the resources from their server which would include strings, drawables etc.
I've already created a mechanism for downloading a zip file with all these files, and they're able to change strings pretty easy, I've also created a mechanism that allows the client to change bg color for UI controls, to change width, height etc but I have a feeling that there must be a better way to create all this.
So I believe the real question is:
What's the best practice to create a custom theme, deploy it on server, make the app download it, and apply it to app afterwards?
I know how to create custom theme and how to deploy it with the app, and how to apply it during runtime, but the problem here is that resources are pre-compiled and once you create APK there's no way for developer to change them which would be required in order to add new themes/drawables/styles/strings.
Do I need to create a custom mechanism for all this (loading images, styles, strings etc from the file system) and to apply them during runtime by creating my own controls that would do that in constructor for example or is there a way to do this properly :)? ( how does Swiftkey do this with all the keyboard themes, and how do similar apps do it allowing the users to download theme and apply it after that )?
I'm sorry if I didn't see similar question, I really tried to find an answer myself during past 2 days, but I failed to find anything useful, so this is my last chance to get a constructive answer :).
The closest to solution i need was this answer: Changing app theme at runtime using using external theme file but I've already made that functionality, and i know i can change colors like that, but the problem is that i would like to be able to change things like borders, on button pressed state etc that require resources other than simple color value :(.
Thanks heaps!
P.S. I've also read about the extension files so is that something i need to consider while thinking about this, or do i need to look elsewhere? The problem with obb files is that they must be deployed over PlayStore and that's not "perfect" for the client because they need to pack it by using jobb, and to deploy it to PlayStore which is too technical for them, so they would prefer creating a zip file, putting it on server, and the app should do the rest :).
I've finally decided to solve this by making a custom system for handling drawables, strings etc so now i have a custom class called "ResourceManager" that handles what needs to be loadad and how, and themes are distributed as a zip file which app downloads, extracts and later uses.
I had to compile nine patch images by myself before putting them in zip file, and I did that using "abrc" from here: http://forum.xda-developers.com/showthread.php?t=785012
I've also created a simple bash script that goes recursively through custom folder and compiles all nine patch images with abrc.
I've also created a simple helper in the ResourceManager that checks and tells me the screen density so i can normally support images in hdpi, xhdpi etc densities, and finally i don't re-create the images every time i need them, i save them in a static list of HashMap so i can reuse the ones I've already created and that way i hope to prevent wasting too much phone's memory :).
OK that's all in short lines, if anyone has any questions please let me know, i'll be glad to share this experience with anyone.
Cheers!
============ EDIT ============
Here's the class I ended up writing for this purpose (it downloads the file, checks for it's version, loads strings from JSON file rather than strings.xml etc)
NOTE: This is not a full class so some parts are missing, but I think it's more than enough to get the idea how I solved all this :)
/**
* Created by bojank on 7/28/2014.
* Class that handles custom resources downloaded from server
*/
public class ResourceManager {
// List of ninePatchImages in the application
private static ArrayList<HashMap<String, NinePatchDrawable>> ninePatchHashMaps;
private static ArrayList<HashMap<String, Drawable>> imagesHashMaps;
private static ImageLoader imageLoader;
// Context for methods
public static Context ctx;
// JSONObject with all strings
private static JSONObject joString;
// JSONObject with all styles
private static JSONObject joStyles;
// String with current active lang code
private static String currentLanguage;
private static String sdcardPath;
// Private consturctor to prevent creating a class instance
private ResourceManager() {
}
/**
* Method that returns a translated string for given key
*
* #param key String
* #return String
*/
public static String getString(String module, String key) {
String output = ""; //String.format("[%s - %s]", module, key);
try {
if (getStringsFile() != null && getStringsFile().getJSONObject(module).has(key))
output = getStringsFile().getJSONObject(module).getString(key);
} catch (Exception e) {
// Force some default language if proper json file is missing for newly added language
currentLanguage = "en-US";
Helper.saveLocale(currentLanguage, ctx);
Helper.logError("ErrorFetchingString", e);
}
return output;
}
/**
* Method that returns JSONObject with string resources
* #return JSONObject
* #throws JSONException
*/
public static JSONObject getStringsFile() throws JSONException {
if (joString == null) {
String stringFileName = getResourcesPath() + "languages/" + getCurrentLanguage() + "/values.json";
String languageFile = Helper.readJsonFile(stringFileName);
if (languageFile != null) {
joString = new JSONObject(Helper.readJsonFile(stringFileName));
} else {
return null;
}
}
return joString.getJSONObject("strings");
}
/**
* Method that returns current language ("sr", "en"...)
* #return String
*/
public static String getCurrentLanguage() {
if (currentLanguage == null)
currentLanguage = Helper.getCurrentLanguage(ctx);
return currentLanguage;
}
/**
* Method that resets joString object and currentLanguage on language change
*/
public static void resetLanguage() {
joString = null;
currentLanguage = null;
}
/**
* Method that resets joStyles object on theme change
*/
public static void resetStyle() {
joStyles = null;
}
/**
* Method that deletes a directory from filesystem
* #param path File
* #return boolean
*/
public static boolean deleteDirectory(File path) {
if( path.exists() ) {
File[] files = path.listFiles();
for(int i=0; i<files.length; i++) {
if(files[i].isDirectory()) {
deleteDirectory(files[i]);
}
else {
files[i].delete();
}
}
}
return(path.delete());
}
/**
* Method that get's the version of assets file
* #param url String
*/
public static String getAssetsVersion(String url) throws IOException {
Helper.logInfo("REQUEST URL:", url);
OkHttpClient client = new OkHttpClient();
// set connection timeut to 5min
client.setConnectTimeout(1, TimeUnit.MINUTES);
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
/**
* Method that downloads assets file from server
* #param url String
* #return String
* #throws IOException
*/
public static String getAssetsFile(String url) throws IOException {
Helper.logInfo("REQUEST URL:", url);
OkHttpClient client = new OkHttpClient();
// set connection timeut to 5min
client.setConnectTimeout(1, TimeUnit.MINUTES);
Request request = new Request.Builder()
.url(url)
.header("User-Agent", MyApplication.USER_AGENT)
.build();
Response response = client.newCall(request).execute();
InputStream inputStreamFile = response.body().byteStream();
try {
// Output stream
String outputFileName = Environment.getExternalStorageDirectory().toString() + "/assets.zip";
File deleteFile = new File(outputFileName);
deleteFile.delete();
OutputStream output = new FileOutputStream(outputFileName);
byte data[] = new byte[1024];
int count;
// writing data to file
while ((count = inputStreamFile.read(data)) != -1)
output.write(data, 0, count);
// flushing output
output.flush();
// closing streams
output.close();
inputStreamFile.close();
return outputFileName;
} catch (Exception e) {
Helper.logError("Download Resursa", e);
return "ERROR";
}
}
public static void setStyle(View v, String styleName) {
try {
if (styleName == null || styleName.equals("")) {
if (v instanceof EditText)
processStyle(v, getStylesFile().getJSONObject("EditText"));
} else
processStyle(v, getStylesFile().getJSONObject(styleName));
} catch (Exception e) {
Helper.logError("Setting Styles", e);
}
}
private static void setBackground(View v, Drawable d) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
v.setBackgroundDrawable(d);
} else {
v.setBackground(d);
}
}
public static JSONObject getStylesFile() throws JSONException {
if (joStyles == null) {
String stylesFileName = getResourcesPath() + "styles/properties.json";
joStyles = new JSONObject(Helper.readJsonFile(stylesFileName));
}
return joStyles;
}
public static void processStyle(View v, JSONObject joStyle) {
if(joStyle != null) {
try {
// used for layout margins
LinearLayout.LayoutParams layoutParams = null;
if (Helper.isValidParameter(joStyle, "backgroundColor"))
v.setBackgroundColor(Color.parseColor(joStyle.getString("backgroundColor")));
if (Helper.isValidParameter(joStyle, "backgroundImage"))
setBackground(v, loadNinePatchFromFilesystem(getImagesPath() + joStyle.getString("backgroundImage")));
if (v instanceof TextView) {
applyTextViewParameters(v, joStyle);
} else if (v instanceof ListView) {
if (Helper.isValidParameter(joStyle, "dividerColor")) {
((ListView) v).setDivider(new ColorDrawable(Color.parseColor(joStyle.getString("dividerColor"))));
((ListView) v).setDividerHeight(Helper.convertDpToPixel(1));
}
if (Helper.isValidParameter(joStyle, "dividerHeight")) {
((ListView) v).setDividerHeight(Helper.convertDpToPixel(joStyle.getInt("dividerHeight")));
}
} else if (v instanceof UnderlinePageIndicator) {
if (Helper.isValidParameter(joStyle, "backgroundColor")) {
v.setBackgroundColor(Color.parseColor(joStyle.getString("backgroundColor")));
}
if (Helper.isValidParameter(joStyle, "selectedColor")) {
((UnderlinePageIndicator) v).setSelectedColor(Color.parseColor(joStyle.getString("selectedColor")));
}
} else if (v instanceof StyleableBackground) {
if (Helper.isValidParameter(joStyle, "backgroundColor")) {
View background = v.findViewById(R.id.llBackground);
if (background != null) {
background.setBackgroundColor(Color.parseColor(joStyle.getString("backgroundColor")));
}
}
if (Helper.isValidParameter(joStyle, "borderTopColor")) {
View topBorder = v.findViewById(R.id.llTopBorder);
if (topBorder != null) {
topBorder.setBackgroundColor(Color.parseColor(joStyle.getString("borderTopColor")));
if (Helper.isValidParameter(joStyle, "borderTopHeight")) {
topBorder.setMinimumHeight(Helper.convertDpToPixel(joStyle.getInt("borderTopHeight")));
}
}
}
if (Helper.isValidParameter(joStyle, "borderBottomColor")) {
View bottomBorder = v.findViewById(R.id.llBottomBorder);
if (bottomBorder != null) {
bottomBorder.setBackgroundColor(Color.parseColor(joStyle.getString("borderBottomColor")));
if (Helper.isValidParameter(joStyle, "borderBottomHeight")) {
bottomBorder.setMinimumHeight(Helper.convertDpToPixel(joStyle.getInt("borderBottomHeight")));
}
}
}
if (Helper.isValidParameter(joStyle, "backgroundImage")) {
ImageView ivBackgroundImage = (ImageView) v.findViewById(R.id.ivBackgroundImage);
if (ivBackgroundImage != null) {
BitmapDrawable d = (BitmapDrawable) ResourceManager.loadImageFromFilesystem(ResourceManager.getImagesPath() + joStyle.getString("backgroundImage"));
d.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
d.setGravity(Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL);
setBackground(ivBackgroundImage, d);
}
}
}
if(Helper.isValidParameter(joStyle, "width"))
v.setMinimumWidth(joStyle.getInt("width"));
if(Helper.isValidParameter(joStyle, "height"))
v.setMinimumHeight(joStyle.getInt("height"));
if(Helper.isValidParameter(joStyle, "padding"))
v.setPadding(joStyle.getInt("padding"), joStyle.getInt("padding"), joStyle.getInt("padding"), joStyle.getInt("padding"));
if(Helper.isValidParameter(joStyle, "paddingLeft"))
v.setPadding(joStyle.getInt("paddingLeft"), v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom());
if(Helper.isValidParameter(joStyle, "paddingTop"))
v.setPadding(v.getPaddingLeft(), joStyle.getInt("paddingTop"), v.getPaddingRight(), v.getPaddingBottom());
if(Helper.isValidParameter(joStyle, "paddingRight"))
v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), joStyle.getInt("paddingRight"), v.getPaddingBottom());
if(Helper.isValidParameter(joStyle, "paddingBottom"))
v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), joStyle.getInt("paddingBottom"));
if(Helper.isValidParameter(joStyle, "margin")) {
layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
layoutParams.setMargins(joStyle.getInt("margin"), joStyle.getInt("margin"), joStyle.getInt("margin"), joStyle.getInt("margin"));
}
if(Helper.isValidParameter(joStyle, "marginLeft")) {
layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
layoutParams.setMargins(joStyle.getInt("marginLeft"), layoutParams.topMargin, layoutParams.rightMargin, layoutParams.bottomMargin);
}
if(Helper.isValidParameter(joStyle, "marginTop")) {
layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
layoutParams.setMargins(layoutParams.leftMargin, joStyle.getInt("marginTop"), layoutParams.rightMargin, layoutParams.bottomMargin);
}
if(Helper.isValidParameter(joStyle, "marginRight")) {
layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin, joStyle.getInt("marginRight"), layoutParams.bottomMargin);
}
if(layoutParams != null)
v.setLayoutParams(layoutParams);
RelativeLayout.LayoutParams relativeLayoutParams = null;
if (Helper.isValidParameter(joStyle, "alignParentTop") && joStyle.getBoolean("alignParentTop")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
}
if (Helper.isValidParameter(joStyle, "alignParentLeft") && joStyle.getBoolean("alignParentLeft")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
}
if (Helper.isValidParameter(joStyle, "alignParentBottom") && joStyle.getBoolean("alignParentBottom")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
}
if (Helper.isValidParameter(joStyle, "alignParentRight") && joStyle.getBoolean("alignParentRight")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
}
if(Helper.isValidParameter(joStyle, "marginLeft")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.setMargins(joStyle.getInt("marginLeft"), relativeLayoutParams.topMargin, relativeLayoutParams.rightMargin, relativeLayoutParams.bottomMargin);
}
if(Helper.isValidParameter(joStyle, "marginTop")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.setMargins(relativeLayoutParams.leftMargin, joStyle.getInt("marginTop"), relativeLayoutParams.rightMargin, relativeLayoutParams.bottomMargin);
}
if(Helper.isValidParameter(joStyle, "marginRight")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.setMargins(relativeLayoutParams.leftMargin, relativeLayoutParams.topMargin, joStyle.getInt("marginRight"), relativeLayoutParams.bottomMargin);
}
if (relativeLayoutParams != null) {
v.setLayoutParams(relativeLayoutParams);
}
} catch (Exception e) {
Helper.logError("", e);
}
}
}
public static String getSdcardPath() {
if(sdcardPath == null)
sdcardPath = ctx.getApplicationInfo().dataDir;
return sdcardPath;
}
public static String getResourcesPath() {
return getSdcardPath() + "/resources/";
}
public static String getCSSPath() {
return getResourcesPath() + "default.css";
}
public static String getImagesPath() {
return getResourcesPath() + "images/" + ResourceConstants.getScreenDPI(ctx) + "/";
}
public static String getImagesPathNoDpi() {
return getResourcesPath() + "images/";
}
public static NinePatchDrawable loadNinePatchFromFilesystem(String filename) {
if(ninePatchHashMaps == null)
ninePatchHashMaps = new ArrayList<HashMap<String, NinePatchDrawable>>();
// check if we already have this filename so we can reuse it
for (int i = 0; i < ninePatchHashMaps.size(); i++) {
HashMap<String, NinePatchDrawable> row = ninePatchHashMaps.get(i);
if(row.containsKey(filename))
return row.get(filename);
}
NinePatchDrawable patchy = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeFile(filename, options);
byte[] chunk = bitmap.getNinePatchChunk();
boolean result = NinePatch.isNinePatchChunk(chunk);
if (result)
patchy = new NinePatchDrawable(bitmap, chunk, new Rect(), null);
} catch (Exception e){
Helper.logError("NinePatchLoading",e);
}
if(patchy != null) {
HashMap<String, NinePatchDrawable> drawableImage = new HashMap<String, NinePatchDrawable>();
drawableImage.put(filename, patchy);
ninePatchHashMaps.add(drawableImage);
}
return patchy;
}
public static Drawable loadImageFromFilesystem(String filename) {
if(imagesHashMaps == null)
imagesHashMaps = new ArrayList<HashMap<String, Drawable>>();
// check if we already have this filename so we can reuse it
for (int i = 0; i < imagesHashMaps.size(); i++) {
HashMap<String, Drawable> row = imagesHashMaps.get(i);
if(row.containsKey(filename))
return row.get(filename);
}
Drawable image = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeFile(filename, options);
if(bitmap == null)
bitmap = BitmapFactory.decodeFile(filename.replace(ResourceConstants.getScreenDPI(ctx) + "/", ""), options);
image = new BitmapDrawable(bitmap);
} catch (Exception e){
Helper.logError("ImageLoadingError",e);
}
if(image != null) {
HashMap<String, Drawable> drawableImage = new HashMap<String, Drawable>();
drawableImage.put(filename, image);
imagesHashMaps.add(drawableImage);
}
return image;
}
}
I'm wondering what happened to this on. My app loads image using the url.
I have successfully done the loading and displaying. The problem is,
the image when displayed, displays the wrong image...
It's better to show it in image:
At first, while other images are still being loaded, the correct images are displayed[TOP IMAGE].
But after all images are loaded, img2 duplicates and replaces img1 but still using the
dimensions of img1?
this is the code i use when loading the image:
public class DrawableBackgroundDownloader {
private final Map<String, Drawable> mCache = new HashMap<String, Drawable>();
private final LinkedList <Drawable> mChacheController = new LinkedList <Drawable> ();
private ExecutorService mThreadPool;
private final Map<ImageView, String> mImageViews = Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
public static int MAX_CACHE_SIZE = 80;
public int THREAD_POOL_SIZE = 3;
/**
* Constructor
*/
public DrawableBackgroundDownloader() {
mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
}
/**
* Clears all instance data and stops running threads
*/
public void Reset() {
ExecutorService oldThreadPool = mThreadPool;
mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
oldThreadPool.shutdownNow();
mChacheController.clear();
mCache.clear();
mImageViews.clear();
}
/**
* Load the drawable associated to a url and assign it to an image, you can set a placeholder to replace this drawable.
* #param url Is the url of the image.
* #param imageView The image to assign the drawable.
* #param placeholder A drawable that is show during the image is downloading.
*/
public void loadDrawable(final String url, final ImageView imageView,Drawable placeholder) {
if(!mImageViews.containsKey(url))
mImageViews.put(imageView, url);
Drawable drawable = getDrawableFromCache(url);
// check in UI thread, so no concurrency issues
if (drawable != null) {
//Log.d(null, "Item loaded from mCache: " + url);
imageView.setImageDrawable(drawable);
} else {
imageView.setImageDrawable(placeholder);
queueJob(url, imageView, placeholder);
}
}
/**
* Return a drawable from the cache.
* #param url url of the image.
* #return a Drawable in case that the image exist in the cache, else returns null.
*/
public Drawable getDrawableFromCache(String url) {
if (mCache.containsKey(url)) {
return mCache.get(url);
}
return null;
}
/**
* Save the image to cache memory.
* #param url The image url
* #param drawable The drawable to save.
*/
private synchronized void putDrawableInCache(String url,Drawable drawable) {
int chacheControllerSize = mChacheController.size();
if (chacheControllerSize > MAX_CACHE_SIZE)
mChacheController.subList(0, MAX_CACHE_SIZE/2).clear();
mChacheController.addLast(drawable);
mCache.put(url, drawable);
}
/**
* Queue the job to download the image.
* #param url Image url.
* #param imageView The ImageView where is assigned the drawable.
* #param placeholder The drawable that is show during the image is downloading.
*/
private void queueJob(final String url, final ImageView imageView,final Drawable placeholder) {
/* Create handler in UI thread. */
final Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
String tag = mImageViews.get(imageView);
if (tag != null && tag.equals(url)) {
if (imageView.isShown())
if (msg.obj != null) {
imageView.setImageDrawable((Drawable) msg.obj);
} else {
imageView.setImageDrawable(placeholder);
//Log.d(null, "fail " + url);
}
}
}
};
mThreadPool.submit(new Runnable() {
public void run() {
final Drawable bmp = downloadDrawable(url);
// if the view is not visible anymore, the image will be ready for next time in cache
if (imageView.isShown())
{
Message message = Message.obtain();
message.obj = bmp;
//Log.d(null, "Item downloaded: " + url);
handler.sendMessage(message);
}
}
});
}
/**
* Method that download the image
* #param url The url image.
* #return Returns the drawable associated to this image.
*/
private Drawable downloadDrawable(String url) {
try {
InputStream is = getInputStream(url);
Drawable drawable = Drawable.createFromStream(is, url);
putDrawableInCache(url,drawable);
return drawable;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* This method manage the connection to download the image.
* #param urlString url of the image.
* #return Returns an InputStream associated with the url image.
* #throws MalformedURLException
* #throws IOException
*/
private InputStream getInputStream(String urlString) throws MalformedURLException, IOException {
URL url = new URL(urlString);
URLConnection connection;
connection = url.openConnection();
connection.setUseCaches(true);
connection.connect();
InputStream response = connection.getInputStream();
return response;
}
}
Also, when i used that code above to download my image, i've noticed, that in my other activity, after all the images are being loaded, the horizontal scroll (devsmart) no longer work.
When i tried scrolling before the images are loaded, it still work. So i tried to use another one, following this, the scrolling works but my images cant be loaded because of leak issues...
Can anyone share idea regarding this problems. Any help will be appreciated.
So, I've decided to use this library, UrlImageViewHelper by koush.
This solved my problem. The loading of image seems stable than mine.
I've been visiting Stack Overflow for many years and it is the first time that I can't find any post that can solve my problem (at least I didn't see any).
I have a GridView with a custom adapter, which I have overridden to return a custom view made by an ImageView and a TextView.
I load the images after JSON parsing them from URLs with an AsyncTask, storing all the info into an ArrayList in the doInBackground() method and calling notifyDataSetChanged() in the onPostExecute() method. Everything's fine.
Now my problem is that when I launch the activity it takes a time of 5-10 seconds before the grid view will create and present itself to the user in entity. I'm wondering if there is a way to show the grid view with the text info first and then each image will load. Is this possible or not because they are both created in the same method?
#Override
public View getView(int arg0, View arg1, ViewGroup arg2) {
View v = null;
if (arg1 == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.custom_product_view, null);
} else {
v = arg1;
}
iv = (ImageView) v.findViewById(R.id.product_image);
imageLoader.DisplayImage(products.get(arg0).getImage(), iv);
TextView tv = (TextView) v.findViewById(R.id.product_price);
tv.setText(products.get(arg0).getPrice());
return v;
}
I also must inform you as you can see from the DisplayImage() method that I have implemented this lazy loading: Lazy load of images in ListView. It works fine but the thing is that it loads the whole view again. What I want to do is launch the activity, load caption first and then the image will load when it finishes downloading. With this code here, it just lazy loads the whole View that every cell of the grid view contains. I earned some seconds because I don't download all the images at once like before but still it's not what I'm searching for.
Thanks a lot.
Follow this approach.
First, create a custom WebImageView class as follows.
public class WebImageView extends ImageView {
private Drawable placeholder, image;
public WebImageView(Context context) {
super(context);
}
public WebImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public WebImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setPlaceholderImage(Drawable drawable) {
placeholder = drawable;
if (image == null) {
setImageDrawable(placeholder);
}
}
public void setPlaceholderImage(int resid) {
placeholder = getResources().getDrawable(resid);
if (image == null) {
setImageDrawable(placeholder);
}
}
public void setImageUrl(String url) {
DownloadTask task = new DownloadTask();
task.execute(url);
}
private class DownloadTask extends AsyncTask<String, Void, Bitmap> {
#Override
protected Bitmap doInBackground(String... params) {
String url = params[0];
try {
URLConnection conn = (new URL(url)).openConnection();
InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayBuffer baf = new ByteArrayBuffer(50);
int current = 0;
while ((current=bis.read()) != -1) {
baf.append((byte)current);
}
byte[] imageData = baf.toByteArray();
return BitmapFactory.decodeByteArray(imageData, 0, imageData.length);
} catch (Exception e) {
return null;
}
}
#Override
protected void onPostExecute(Bitmap result) {
image = new BitmapDrawable(result);
if (image != null) {
setImageDrawable(image);
}
}
}
}
Next, in Activity use the above custom ImageView as follows:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WebImageView imageView = (WebImageView) findViewById(R.id.webimage);
imageView.setPlaceholderImage(R.drawable.ic_launcher);
imageView.setImageUrl("http://www.google.co.in/images/srpr/logo3w.png");
}
In brief, you are setting a placeholder image for the ImageView which gets replaced by the actual image when download completes. So the GridView will render immediately without delay.
Implementation Details:
So in your custom view (with an image + text) instead of using a simple ImageView, use WebImageView as shown above. When you get the JSON response set the TextView with the caption and the WebImageView with the image url.
So the caption will display immediately and the Image will load lazily.
I have used the below class to implement the Lazy loading of the images it works awesome for me . You try it also.
ImageLoader
/**
* This is class for display image in lazy-loading way.
*/
public class ImageLoader
{
private static final String TAG = ImageLoader.class.getSimpleName();
private InputStream m_is = null;
private OutputStream m_os = null;
private Bitmap m_bitmap = null;
private String m_imagePath;
private File m_cacheDir;
private WeakHashMap<String, Bitmap> m_cache = new WeakHashMap<String, Bitmap>();
/**
* Makes the background thread low priority. This way it will not affect the
* UI performance.<br>
* Checks the Device SD card exits or not and assign path according this
* condition.
*
* #param p_context
* activity context
*/
public ImageLoader(Context p_context)
{
/**
* Make the background thread low priority. This way it will not affect
* the UI performance
*/
m_imageLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);
/**
* Check the Device SD card exits or not and assign path according this
* condition.
*/
if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
{
m_imagePath = Environment.getExternalStorageDirectory() + "/Android/data/" + p_context.getPackageName();
m_cacheDir = new File(m_imagePath);
}
else
{
m_cacheDir = new File(p_context.getDir("Cache", Context.MODE_PRIVATE), "Cache");
}
if (!m_cacheDir.exists())
m_cacheDir.mkdirs();
}
/**
* Check Image exits on HashMap or not.If exist then set image to ImageView
* else send request in the queue.
*
* #param p_url
* image Url
* #param p_imageView
* image container
* #param p_prgBar
* progressbar that is displayed till image is not download from
* server.
*/
public void DisplayImage(String p_url, ImageView p_imageView, ProgressBar p_prgBar) throws CustomException
{
if (m_cache.containsKey(p_url))
{
p_prgBar.setVisibility(View.GONE);
p_imageView.setVisibility(View.VISIBLE);
p_imageView.setImageBitmap(m_cache.get(p_url));
}
else
{
queueImage(p_url, p_imageView, p_prgBar);
}
}
/**
* Clear old task from the queue and add new image downloading in the queue.
*
* #param p_url
* image Url
* #param p_imageView
* image container
* #param p_prgBar
* progressbar that is displayed till image is not download from
* server.
*/
private void queueImage(String p_url, ImageView p_imageView, ProgressBar p_prgBar) throws CustomException
{
try
{
m_imagesQueue.Clean(p_imageView);
ImageToLoad m_photoObj = new ImageToLoad(p_url, p_imageView, p_prgBar);
synchronized (m_imagesQueue.m_imagesToLoad)
{
m_imagesQueue.m_imagesToLoad.push(m_photoObj);
m_imagesQueue.m_imagesToLoad.notifyAll();
}
/**
* start thread if it's not started yet
*/
if (m_imageLoaderThread.getState() == Thread.State.NEW)
m_imageLoaderThread.start();
}
catch (CustomException c)
{
throw c;
}
catch (Throwable t)
{
CustomLogHandler.printErrorlog(t);
throw new CustomException(TAG + " Error in queueImage(String p_url, ImageView p_imageView, ProgressBar p_prgBar) of ImageLoader", t);
}
}
/**
* Checks in SD card for cached file.If bitmap is not available then will
* download it from Url.
*
* #param p_url
* imgae Url
* #return bitmap from Cache or from server.
*/
private Bitmap getBitmap(String p_url) throws CustomException
{
System.gc();
String m_fileName = String.valueOf(p_url.hashCode());
File m_file = new File(m_cacheDir, m_fileName);
// from SD cache
m_bitmap = decodeFile(m_file);
if (m_bitmap != null)
return m_bitmap;
// from web
try
{
Bitmap m_bitmap = null;
int m_connectionCode = 0;
m_connectionCode = HttpConnection.getHttpUrlConnection(p_url).getResponseCode();
if (m_connectionCode == HttpURLConnection.HTTP_OK)
{
m_is = new URL(p_url).openStream();
m_os = new FileOutputStream(m_file);
FileIO.copyStream(m_is, m_os);
m_os.close();
m_os = null;
m_bitmap = decodeFile(m_file);
m_is.close();
m_is = null;
HttpConnection.getHttpUrlConnection(p_url).disconnect();
}
return m_bitmap;
}
catch (CustomException c)
{
throw c;
}
catch (Throwable t)
{
CustomLogHandler.printErrorlog(t);
throw new CustomException(TAG + " Error in getBitmap(String p_url) of ImageLoader", t);
}
}
/**
* Decodes the Image file to bitmap.
*
* #param p_file
* Image file object
* #return decoded bitmap
*/
private Bitmap decodeFile(File p_file) throws CustomException
{
try
{
// decode image size
Bitmap m_retBmp = null;
System.gc();
int m_scale = 1;
if (p_file.length() > 400000)
{
m_scale = 4;
}
else if (p_file.length() > 100000 && p_file.length() < 400000)
{
m_scale = 3;
}
// decode with inSampleSize
if (p_file.exists())
{
BitmapFactory.Options m_o2 = new BitmapFactory.Options();
m_o2.inSampleSize = m_scale;
m_retBmp = BitmapFactory.decodeFile(p_file.getPath(), m_o2);
}
return m_retBmp;
}
catch (Throwable t)
{
CustomLogHandler.printErrorlog(t);
throw new CustomException(TAG + " Error in decodeFile(File p_file) of ImageLoader", t);
}
}
/**
* Stores image information
*/
private class ImageToLoad
{
public String m_url;
public ImageView m_imageView;
public ProgressBar m_prgBar;
public ImageToLoad(String p_str, ImageView p_img, ProgressBar p_prgBar)
{
m_url = p_str;
m_imageView = p_img;
m_imageView.setTag(p_str);
m_prgBar = p_prgBar;
}
}
ImagesQueue m_imagesQueue = new ImagesQueue();
/**
* This is method to stop current running thread.
*/
public void stopThread()
{
m_imageLoaderThread.interrupt();
}
/**
* Stores list of image to be downloaded in stack.
*/
class ImagesQueue
{
private Stack<ImageToLoad> m_imagesToLoad = new Stack<ImageToLoad>();
/**
* Removes all instances of this ImageView
*
* #param p_ivImage
* imageView
*/
public void Clean(ImageView p_ivImage) throws CustomException
{
try
{
for (int m_i = 0; m_i < m_imagesToLoad.size();)
{
if (m_imagesToLoad.get(m_i).m_imageView == p_ivImage)
m_imagesToLoad.remove(m_i);
else
m_i++;
}
}
catch (Throwable t)
{
CustomLogHandler.printErrorlog(t);
throw new CustomException(TAG + " Error in Clean(ImageView p_image) of ImageLoader", t);
}
}
}
/**
*
* This is class waits until there are any images to load in the queue.
*/
class ImagesLoader extends Thread
{
public void run()
{
try
{
while (true)
{
if (m_imagesQueue.m_imagesToLoad.size() == 0)
synchronized (m_imagesQueue.m_imagesToLoad)
{
m_imagesQueue.m_imagesToLoad.wait();
}
if (m_imagesQueue.m_imagesToLoad.size() != 0)
{
ImageToLoad m_imageToLoadObj;
synchronized (m_imagesQueue.m_imagesToLoad)
{
m_imageToLoadObj = m_imagesQueue.m_imagesToLoad.pop();
}
Bitmap m_bmp = getBitmap(m_imageToLoadObj.m_url);
m_cache.put(m_imageToLoadObj.m_url, m_bmp);
if (((String) m_imageToLoadObj.m_imageView.getTag()).equals(m_imageToLoadObj.m_url))
{
BitmapDisplayer m_bmpdisplayer = new BitmapDisplayer(m_bmp, m_imageToLoadObj.m_imageView, m_imageToLoadObj.m_prgBar);
Activity m_activity = (Activity) m_imageToLoadObj.m_imageView.getContext();
m_activity.runOnUiThread(m_bmpdisplayer);
}
}
if (Thread.interrupted())
break;
}
}
catch (InterruptedException e)
{
/*
* allow thread to exit
*/
}
catch (Throwable t)
{
CustomLogHandler.printErrorlog(t);
}
}
}
ImagesLoader m_imageLoaderThread = new ImagesLoader();
/**
* This class Used to display bitmap in the UI thread
*/
class BitmapDisplayer implements Runnable
{
Bitmap m_bmp;
ImageView m_imageView;
ProgressBar m_prgBar;
public BitmapDisplayer(Bitmap p_bmp, ImageView p_imgview, ProgressBar p_prgBar)
{
m_bmp = p_bmp;
m_imageView = p_imgview;
m_prgBar = p_prgBar;
}
public void run()
{
if (m_bmp != null)
{
m_imageView.setImageBitmap(m_bmp);
m_prgBar.setVisibility(View.GONE);
m_imageView.setVisibility(View.VISIBLE);
}
}
}
}
Use the above class as below:
First you need to put the ProgressBar in your custom layout where you have your ImageView as below:
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/RelativeImagelayout">
<ProgressBar android:id="#+id/Progress"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginTop="10dp"/>
<ImageView
android:id="#+id/ivImage"
android:layout_width="80dp"
android:layout_height="90dp"
android:layout_marginTop="10dp"
android:clickable="false"/>
</RelativeLayout>
In your adapter class create the instance of the ImageLoader class and use it as below in your getView method:
ImageView m_ibImage = (ImageView) v.findViewById(R.id.ivImage);
ProgressBar m_pbProgress = (ProgressBar) v.findViewById(R.id.Progress);
if (products.get(arg0).getImage().toString().equals(null)
|| products.get(arg0).getImage().toString().equals(""))
{
m_pbProgress.setVisibility(View.INVISIBLE);
m_ibImage.setVisibility(View.VISIBLE);
}
else if (!products.get(arg0).getImage().toString().equals(null))
{
m_imgLoader.DisplayImage(products.get(arg0).getImage(), m_ibImage,
m_pbProgress);
}
I hope it will help you.
Thanks
The answer you mentioned of is not good, in my opinion. For example if you have 50 images, when the user scrolls up/ down the entire list, that sample project will spawn 50 threads. That is bad for mobile devices like a cell phone. A side note, his concept "lazy list" is different to the one that Android SDK defines. For a sample code of lazy loading list view, have a look at:
[Android SDK]/samples/android-x/ApiDemos/src/com/example/android/apis/view/List13.java
where x is API level. You can test the compiled app in any emulators, open the app API Demos > Views > Lists > 13. Slow Adapter.
About your current approach. You shouldn't use AsyncTask to download images. The documentation says:
AsyncTasks should ideally be used for short operations (a few seconds at the most.)
You should instead:
Use a service to download images in background. Note that services run on main UI thread, so to avoid of NetworkOnMainThreadException, you need something like Thread in your service.
Use a content provider to manage the downloaded images on SD card. For instance you keep the map of original URLs to corresponding files downloaded.
Along with the content provider, use a CursorAdapter for your grid view, and loaders for your activity/ fragment which hosts the grid view.
Basically, in the first time the user opens your activity, you create new adapter and set it to the grid view. So it has a connection with the content provider. Then you start the service to check and download the images. For every image downloaded, you insert it into the content provider. The provider notifies any observers about changes ― your activity/ fragment (the loader) receives the notification and updates UI.
I'm building a relatively basic news-reader app that involves displaying news in a custom listview (Image + Title + Short Description per list element).
My question is How can I store the images I download from the server and then attach them to the listview? The images will be relatively small, 200 X 200 usually, in .jpeg format.
It's not so much a question of how as much as "how to do it efficiently", as I'm already noticing lag in lower-end phones when using the default "ic_launcher" icon instead of bitmaps.
Would it be faster to store them as files or into the news database along with other news data when the app starts and syncs up the news or cache them...?
How should I go about this?
better you can do it's use SoftReference via an ImageManager class.
In you ListAdpater getView() method call the displayImage() method of ImageManager.
ImageManager Coding Exemple :
public class ImageManagerExemple {
private static final String LOG_TAG = "ImageManager";
private static ImageManagerExemple instance = null;
public static ImageManagerExemple getInstance(Context context) {
if (instance == null) {
instance = new ImageManagerExemple(context);
}
return instance;
}
private HashMap<String, SoftReference<Bitmap>> imageMap = new HashMap<String, SoftReference<Bitmap>>();
private Context context;
private File cacheDir;
private ImageManagerExemple(Context context) {
this.context = context;
// Find the dir to save cached images
String sdState = android.os.Environment.getExternalStorageState();
if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
File sdDir = android.os.Environment.getExternalStorageDirectory();
cacheDir = new File(sdDir,"data/yourappname");
} else {
cacheDir = context.getCacheDir();
}
if(!cacheDir.exists()) {
cacheDir.mkdirs();
}
}
/**
* Display web Image loading thread
* #param imageUrl picture web url
* #param imageView target
* #param imageWaitRef picture during loading
*/
public void displayImage(String imageUrl, ImageView imageView, Integer imageWaitRef) {
String imageKey = imageUrl;
imageView.setTag(imageKey);
if(imageMap.containsKey(imageKey) && imageMap.get(imageKey).get() != null) {
Bitmap bmp = imageMap.get(imageKey).get();
imageView.setImageBitmap(bmp);
} else {
queueImage(imageUrl, imageView);
if(imageWaitRef != null)
imageView.setImageResource(imageWaitRef);
}
}
private void queueImage(String url, ImageView imageView) {
ImageRef imgRef=new ImageRef(url, imageView);
// Start thread
Thread imageLoaderThread = new Thread(new ImageQueueManager(imgRef));
// Make background thread low priority, to avoid affecting UI performance
imageLoaderThread.setPriority(Thread.NORM_PRIORITY-1);
imageLoaderThread.start();
}
private Bitmap getBitmap(String url) {
String filename = String.valueOf(url.hashCode());
File f = new File(cacheDir, filename);
try {
// Is the bitmap in our cache?
Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());
if(bitmap != null) return bitmap;
// Nope, have to download it
bitmap = ImageServerUtils.pictureUrlToBitmap(url);
// save bitmap to cache for later
writeFile(bitmap, f);
return bitmap;
} catch (Exception ex) {
ex.printStackTrace();
Log.e(LOG_TAG, ""+ex.getLocalizedMessage());
return null;
} catch (OutOfMemoryError e) {
Log.e(LOG_TAG, "OutOfMemoryError : "+e.getLocalizedMessage());
e.printStackTrace();
return null;
}
}
private void writeFile(Bitmap bmp, File f) {
if (bmp != null && f != null) {
FileOutputStream out = null;
try {
out = new FileOutputStream(f);
//bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
bmp.compress(Bitmap.CompressFormat.JPEG, 80, out);
} catch (Exception e) {
e.printStackTrace();
}
finally {
try { if (out != null ) out.close(); }
catch(Exception ex) {}
}
}
}
private class ImageRef {
public String imageUrl;
public ImageView imageView;
public ImageRef(String imageUrl, ImageView i) {
this.imageUrl=imageUrl;
this.imageView=i;
}
}
private class ImageQueueManager implements Runnable {
private ImageRef imageRef;
public ImageQueueManager(ImageRef imageRef) {
super();
this.imageRef = imageRef;
}
#Override
public void run() {
ImageRef imageToLoad = this.imageRef;
if (imageToLoad != null) {
Bitmap bmp = getBitmap(imageToLoad.imageUrl);
String imageKey = imageToLoad.imageUrl;
imageMap.put(imageKey, new SoftReference<Bitmap>(bmp));
Object tag = imageToLoad.imageView.getTag();
// Make sure we have the right view - thread safety defender
if (tag != null && ((String)tag).equals(imageKey)) {
BitmapDisplayer bmpDisplayer = new BitmapDisplayer(bmp, imageToLoad.imageView);
Activity a = (Activity)imageToLoad.imageView.getContext();
a.runOnUiThread(bmpDisplayer);
}
}
}
}
//Used to display bitmap in the UI thread
private class BitmapDisplayer implements Runnable {
Bitmap bitmap;
ImageView imageView;
public BitmapDisplayer(Bitmap b, ImageView i) {
bitmap=b;
imageView=i;
}
#Override
public void run() {
if(bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
The trick to getting smooth ListView scrolling without stutter is to not update it in any way, shape or form while the user is scrolling it. Afaik, this is essentially how iOS manages to get its ListViews that smooth: it disallows any changes to it (and the UI in general) while the user has his finger on it.
Just comment out any code that changes your ListView while leaving all the bitmap loading code intact, and you'll see that the actual loading of the bitmaps in the background doesn't really impact performance at all. The problem is that the UI thread can't keep up with view updates and scrolling at the same time.
You can achieve the same thing by using a OnScrollListener that blocks all updates to the ListView while the User is scrolling it. As soon as the user stops, you can sneak in all pending updates.
For added performance, try not to use notifyDataSetChanged but iterate over the views of the ListView and only update the views that have actually changed.