Screenshot of custom view in Android generates blank image - android

I have a view MyCustomView which extends FrameLayout. The view creates image views dynamically within.
private void setModel(Model m) {
for (Element elem : m.getImageElements()) {
elem.iv = new ImageView();
addView(elem.iv);
}
...
// Load images into the image views
int count = 0;
for (Element elem : m.getImageElements()) {
Picasso.with(mContext)
.load("file://" + elem.photo.getLocalUri())
.skipMemoryCache()
.noFade()
.into(elem.iv, new Callback() {
#Override
public void onSuccess() {
// When all images have been downloaded successfully
if (++count == m.getImageElements().size) {
generatePreview(this, m.getId());
}
}
#Override
public void onError() {
}
});
}
}
private static Drawable generatePreview(View targetView, int id) {
Bitmap b = Bitmap.createBitmap(targetView.getLayoutParams().width,
targetView.getLayoutParams().height,
Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
targetView.draw(c);
File sdCard = Environment.getExternalStorageDirectory();
File file = new File(sdCard, "image-preview" + id + ".jpg");
FileOutputStream fos;
try {
fos = new FileOutputStream(file);
b.compress(Bitmap.CompressFormat.JPEG, 95,fos);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
I am using the above MyCustomView inside a RecyclerView.
#Override
public void onBindViewHolder(final MyViewHolder holder) {
...
holder.mCustomView.setModel(model);
...
}
The problem is for some rows the preview image is coming out as blank (transparent) which seems to indicate that during draw of MyCustomView in generatePreview the images of elements within MyCustomView weren't drawn yet even though Picasso onSuccess got called.
I have tried putting the generatePreview block in a postDelayed and calling it after 1000 ms but that also fails for some rows.

Related

What is the right coding about Image Loading?

I'm solving my problem about Image Loader and I have some problems..
What I want is to show many images (about 400) in GridView(or ListView).
I don't want to use the Library like Picasso, Glide like that.
and Here is the problem.
When I call the method which convert from url to bitmap?
3.1. before setAdapter, then pass the bitmap array.
3.2. while getView.
two things are working well. but too much slow... maybe cuz of the times to call URLConnection..
Could anyone help me about these problem? How can I speed up? or are there any other solution without Open Source.
Here is my Source.
Now, 3-1.
ShowImage
private void showImages(ArrayList<String> imgUrls) {
ArrayList<Bitmap> bitmaps = new ArrayList<>();
for (int i = 0; i < imgUrls.size(); i++) {
try {
String img_path = imgUrls.get(i);
Bitmap bitmap = new UriToBitmapAsyncTask().execute(img_path).get();
bitmaps.add(bitmap);
} catch (Exception e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
CustomAdapter adapter = new CustomAdapter(getApplicationContext(),R.layout.row,bitmaps);
gridView.setAdapter(adapter);
}
and This is the customAdapter's GetView
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = inflator.inflate(rowLayout, parent, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.imageView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.imageView.setImageBitmap(bitmaps.get(position));
return convertView;
}
You should really take Reinventing the wheel to heart but if you really want to toture yourself an Approach could be:
use a ThreadPoolExecutor to fetch more images at once, you should read up how to use them
implement a way to cancel threads who load a img for a griditem which isn't displayed anymore
use two sets of data a thumbnail which loads faster for the grid view and a real image which gets loaded when the user clicks on the grid
dont't forget to use a LRU caching method or your device will run out of memory depending on the images
Don't use ArrayList to store bitmaps. Bitmaps usually take consumes a lot of memory. Try using LRUCache like this way,
public class TCImageLoader implements ComponentCallbacks2 {
private TCLruCache cache;
public TCImageLoader(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE);
int maxKb = am.getMemoryClass() * 1024;
int limitKb = maxKb / 8; // 1/8th of total ram
cache = new TCLruCache(limitKb);
}
public void display(String url, ImageView imageview, int defaultresource) {
imageview.setImageResource(defaultresource);
Bitmap image = cache.get(url);
if (image != null) {
imageview.setImageBitmap(image);
}
else {
new SetImageTask(imageview).execute(url);
}
}
private class TCLruCache extends LruCache<String, Bitmap> {
public TCLruCache(int maxSize) {
super(maxSize);
}
#Override
protected int sizeOf(ImagePoolKey key, Bitmap value) {
int kbOfBitmap = value.getByteCount() / 1024;
return kbOfBitmap;
}
}
private class SetImageTask extends AsyncTask<String, Void, Integer> {
private ImageView imageview;
private Bitmap bmp;
public SetImageTask(ImageView imageview) {
this.imageview = imageview;
}
#Override
protected Integer doInBackground(String... params) {
String url = params[0];
try {
bmp = getBitmapFromURL(url);
if (bmp != null) {
cache.put(url, bmp);
}
else {
return 0;
}
} catch (Exception e) {
e.printStackTrace();
return 0;
}
return 1;
}
#Override
protected void onPostExecute(Integer result) {
if (result == 1) {
imageview.setImageBitmap(bmp);
}
super.onPostExecute(result);
}
private Bitmap getBitmapFromURL(String src) {
try {
URL url = new URL(src);
HttpURLConnection connection
= (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
Bitmap myBitmap = BitmapFactory.decodeStream(input);
return myBitmap;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
}
#Override
public void onLowMemory() {
}
#Override
public void onTrimMemory(int level) {
if (level >= TRIM_MEMORY_MODERATE) {
cache.evictAll();
}
else if (level >= TRIM_MEMORY_BACKGROUND) {
cache.trimToSize(cache.size() / 2);
}
}
}
get a instance of TCImageLoader and call display method appropriately.

Downloading multiple pictures with Picasso

I'm trying to download multiple pictures using picasso. here's my code:
for(int i=1; i <=20; i++){
String url = img_url + i + "/profile.jpg";
String img_dir = img_dir + i;
Picasso.with(this).load(url).into(picassoImageTarget(getApplicationContext(),img_dir, img_name));
}
Url of the site looks like this:
site.com/img/equipment/1/profile.jpg,
site.com/img/equipment/2/profile.jpg,
site.com/img/equipment/3/profile.jpg
and so on ...
i tried
Picasso.with(this).load(url).into(picassoImageTarget(getApplicationContext(),img_dir, img_name));
without the for loop and it is working. images are not download when i place it inside the loop.
here's my Target
private Target picassoImageTarget(Context context, final String imageDir, final String imageName) {
Log.d("picassoImageTarget", " picassoImageTarget");
ContextWrapper cw = new ContextWrapper(context);
final File directory = cw.getDir(imageDir, Context.MODE_PRIVATE); // path to /data/data/yourapp/app_imageDir
return new Target() {
#Override
public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) {
new Thread(new Runnable() {
#Override
public void run() {
final File myImageFile = new File(directory, imageName); // Create image file
FileOutputStream fos = null;
try {
fos = new FileOutputStream(myImageFile);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Log.i("image", "image saved to >>>" + myImageFile.getAbsolutePath());
}
}).start();
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
if (placeHolderDrawable != null) {}
}
};
}
please help. thanks.
Targets are held in WeakReferences.
You need to hold a reference to the Targets you want to keep to prevent them from being garbage collected.
Maybe your code would look something like:
final class MyLoader {
final ArrayList<Target> targets = new ArrayList<>(20);
void load(...) {
for(...) {
Target target = picassoImageTarget(...);
targets.add(target);
picasso.load(...).into(target); // TODO: Maybe remove from list when complete.
}
}
}

Decoding image in AsyncTask

I'm new to android development and I'm a bit confused regarding decoding and scaling bitmaps using AsyncTask. I've read the android development site and it didn't help my understanding. My question is how exactly is this done? My goal is to decode and scale multiple images and placed them in ImageViews.
Do I code the decoding and scaling within a doInBackground method? Any help will be great appreciated!
Yes, you should do anything you can on the doInBackground. Only when your image is ready than use "return" to call onPostExecute and in onPostExecute you should set the bitmap to the imageView.
This code sample is from my recipe app, and should illustrate what you are asking. The images are read in as base64 encoded data from a web service, so need to be decoded before they can be shown within a ListView.
The code first of all checks for cached images, if not it decodes them in the background using AsyncTask, and writes the result to cache for future use.
The decoding is done in doInBackground, and the display/write to cache is done in onPostExecute.
Hole this helps.
mRecipeAdapter = new ArrayAdapter<Recipe>(getActivity(), R.layout.listrow, mData2){
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row;
if (null == convertView) {
row = mInflater.inflate(R.layout.recipe_listrow, null);
} else {
row = convertView;
}
TextView tv = (TextView) row.findViewById(R.id.recipe_title);
ImageView iv = (ImageView) row.findViewById(R.id.recipe_image);
iv.setImageBitmap(null);
Recipe r = getItem(position);
tv.setText(r.title);
File cacheFile = new File(mCacheDir, ""+r.identity.hashCode());
if (cacheFile.exists()){
FileInputStream fis;
try {
fis = new FileInputStream(cacheFile);
Bitmap bmp = BitmapFactory.decodeStream(fis);
iv.setImageBitmap(bmp);
bmp = null;
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} else {
// Get image asynchronously from database
new AsyncImages(iv).execute(r);
}
return row;
}
class AsyncImages extends AsyncTask<Recipe, Void, Recipe> {
private static final long MAX_SIZE = 5242880L; // 5MB
private final WeakReference<ImageView> imageViewReference;
public AsyncImages(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
#Override
protected Recipe doInBackground(Recipe... r) {
Recipe recipe = r[0];
double heapRemaining = Runtime.getRuntime().freeMemory(); //amount available in heap
Log.i("MEM REM", Double.toString(heapRemaining));
String imageString = SchwartzRecipesBaseActivity.getThumbnailImageFor(recipe.identity);
recipe.thumb = StringToBitMap(imageString);
imageString = null;
return recipe;
}
protected void onPostExecute(Recipe r) {
if (isCancelled()) {
r.thumb = null;
return;
}
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(r.thumb);
// Write to cache file
// Make space if needed
long sz = getDirSize();
if (sz>MAX_SIZE){
makeSomeSpace(r.thumb.getByteCount());
}
File cacheFile = new File(mCacheDir, ""+r.identity.hashCode());
try {
// Create a file at the file path, and open it for writing obtaining the output stream
cacheFile.createNewFile();
FileOutputStream fos = new FileOutputStream(cacheFile);
// Write the bitmap to the output stream (and thus the file) in PNG format (lossless compression)
r.thumb.compress(CompressFormat.JPEG, 100, fos);
// Flush and close the output stream
fos.flush();
fos.close();
} catch (Exception e) {
// Log anything that might go wrong with IO to file
Log.e("CACHE", "Error when saving image to cache. ", e);
}
}
}
}
private void makeSomeSpace(long bytes) {
long bytesDeleted = 0;
File[] files = mCacheDir.listFiles();
Arrays.sort(files, new Comparator<File>(){
public int compare(File f1, File f2)
{
return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
} });
for (File file : files) {
bytesDeleted += file.length();
file.delete();
if (bytesDeleted >= bytes) {
break;
}
}
}
private long getDirSize() {
if (mCacheDir == null) return 0;
long size = 0;
File[] files = mCacheDir.listFiles();
for (File file : files) {
if (file.isFile()) {
size += file.length();
}
}
return size;
}
}
private Bitmap StringToBitMap(String encodedString) {
try{
byte [] encodeByte=Base64.decode(encodedString,Base64.DEFAULT);
Bitmap bitmap=BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.length);
return bitmap;
}catch(Exception e){
e.getMessage();
return null;
}
}
};
setListAdapter(mRecipeAdapter);
Do all the heavy loading stuff in the doInBackground method, and just set the image afterwards in the onPostExecute method.
Enable the strict mode (http://developer.android.com/reference/android/os/StrictMode.html) to make sure you have done everything right (will write a warning into your logcat in case you are doing something on the UI thread you shouldn't be doing):
public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}
But I highly recommend to use a library like volley or Picasso whenever you are working with images from external sources. Take a look at the following question: Local image caching solution for Android: Square Picasso vs Universal Image Loader

Gridview performance with HttpResponseCache

I'm using a gridview to display hundreds of images (perhaps even a few thousand). The images are located on a server and I'm caching the images using HttpResponseCache. The problem I'm having is that when I swipe down through the gridview, the recycled views are showing 3 or more images, per child view, before finally settling on the correct image. It seems to be a result of the callback methods returning all the requested images. How can I get a gridview to not have this giant swoosh of activity when scrolling up/down.
getView method of my custom adapter
public View getView(int position, View convertView, ViewGroup parent) {
View v;
if (convertView == null) {
v = li.inflate(R.layout.folder_button, null);
} else {
v = convertView;
}
TextView tv = (TextView)v.findViewById(R.id.tvFolderButtonTitle);
tv.setText(mBaseItems[position].Name);
tv.setTextColor(Color.WHITE);
ImageView iv = (ImageView)v.findViewById(R.id.ivFolderButtonImage);
iv.setLayoutParams(new LinearLayout.LayoutParams(folderWidth_, folderHeight_));
iv.setScaleType(ImageView.ScaleType.FIT_XY);
String imageUrl = "http://path.to.image";
api_.GetImageAsync(imageUrl, new GetImageStreamCallback(iv), false);
return v;
}
callback method that sets the image.
public class GetImageStreamCallback implements IApiCallback {
private ImageView currentImageView;
public GetImageStreamCallback(ImageView imageView) {
currentImageView = imageView;
}
public void Execute(Object data) {
if (data != null) {
try {
Bitmap image = (Bitmap) data;
currentImageView.setImageBitmap(image);
} catch (Exception e) {
Log.i("Exception", "Error getting image");
}
}
}
}
custom AsyncTask called from api_.GetImageAsync above
public class AsyncRequestImage extends AsyncTask<String,String,Object > {
HttpURLConnection connection_;
InputStream inStream_;
IApiCallback callback_;
boolean ignoreCache_;
public AsyncRequestImage(IApiCallback callback, boolean ignoreCache) {
this.callback_ = callback;
this.ignoreCache_ = ignoreCache;
}
#Override
protected Object doInBackground(String... uri) {
Bitmap image;
if (ignoreCache_) {
image = acquireImage(uri[0], true);
} else {
image = acquireImage(uri[0], false);
if (image == null)
image = acquireImage(uri[0], true);
}
return image;
}
#Override
protected void onPostExecute(Object image) {
callback_.Execute(image);
}
private Bitmap acquireImage(String url, boolean ignoreCache) {
try {
URL _url = new URL(url);
connection_ = (HttpURLConnection) _url.openConnection();
connection_.addRequestProperty("Accept-Encoding", "gzip");
if (ignoreCache) {
connection_.setRequestProperty("Cache-Control", "max-age=0");
} else {
connection_.addRequestProperty("Cache-Control", "only-if-cached");
}
connection_.connect();
String encoding = connection_.getContentEncoding();
// Determine if the stream is compressed and uncompress it if needed.
if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
try {
inStream_ = new GZIPInputStream(connection_.getInputStream());
} catch (FileNotFoundException e) {
}
} else {
try {
inStream_ = connection_.getInputStream();
} catch (FileNotFoundException e) {
}
}
if (inStream_ != null) {
try {
Bitmap image = BitmapFactory.decodeStream(inStream_);
return image;
} catch (java.lang.OutOfMemoryError oom) {
FileLogger.getFileLogger().ReportInfo("UrlConnection: Bitmap creation failed. Out of memory");
}
}
} catch (IOException e) {
if (e != null && e.getMessage() != null) {
Log.i("AsyncRequestImage doInBackground:",e.getMessage());
}
} finally {
connection_.disconnect();
}
return null;
}
}
Part of the issue I was having was due to an unoptimized BaseAdapter.GetView
Also when the user initiated a fling gesture, I was still trying to load all the images as the views passed by.
This article! Provided a detailed description and solution for each of the mistakes I was making. Also in that article is a link to source code that provides a method to stop loading images until the fling gesture has finished.

How to efficiently store bitmaps in Android?

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.

Categories

Resources