I am new to android. I am displaying images in gridview by using AsyncTask. But there are some issues like:
1: onScroll AsyncTask tasks gets call again.
2: Images are not displaying correctly ( Once I scroll it shows top image and load the original latter).
here is my code:
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater layoutInflater = (LayoutInflater) ctx
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ListRowHolder listRowHolder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.ll_sponsor_list_item,
parent, false);
listRowHolder = new ListRowHolder();
listRowHolder.imgSponsor = (ImageView) convertView
.findViewById(R.id.imggrid_item_image);
convertView.setTag(listRowHolder);
} else {
listRowHolder = (ListRowHolder) convertView.getTag();
}
try {
task = new BitmapWorkerTask(listRowHolder.imgSponsor);
task.image_path = ImageName.get(position);
task.execute(1);
} catch (Exception e) {
if (thisLogger != null) {
thisLogger.error(e.toString());
thisLogger.error("\r\n");
}
}
return convertView;
}
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private String image_path;
public BitmapWorkerTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
#Override
protected Bitmap doInBackground(Integer... params) {
try {
while (running) {
Bitmap picture = BitmapFactory.decodeFile(image_path);
int width = picture.getWidth();
int height = picture.getHeight();
float aspectRatio = (float) width / (float) height;
int newWidth = 98;
int newHeight = (int) (98 / aspectRatio);
Log.v("ImageList", "L");
return picture = Bitmap.createScaledBitmap(picture,
newWidth, newHeight, true);
}
} catch (Exception e) {
if (thisLogger != null) {
thisLogger.error(e.toString());
thisLogger.error("\r\n");
}
}
return null;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
Please let me know whats wrong here.
Related
I want to collect all photos in pictures directory to show them in GridView, so tried with two ways.
1_ without using AsyncTask: this way worked very well but it has a problem which is that the app freezes for seconds to get all photos in specified directory and it's sub directories.
2_using AsyncTask: this way doesn't get any images and the list of images is always empty, I know that I should use Asynctask for processes that take mush time to processed and I don't know what I missed when using AsyncTask.
what I want is to find a way to collect all photos using AsyncTask with app freezing and thanks in advance.
public static class ImagesFragment extends Fragment {
static List<Image> imagesList = new ArrayList<>();
GridViewRecyclerAdapter gridViewRecyclerAdapter;
GridView gridView;
Spinner spinner;
public static File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// imagesList = loadImageData(file);
gridViewRecyclerAdapter = new GridViewRecyclerAdapter(imagesList, getContext());
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_images, container, false);
spinner = view.findViewById(R.id.spinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(getContext(), R.array.spinner, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
switch (position){
case 0:
break;
case 1:
break;
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
gridView = view.findViewById(R.id.grid_view);
gridView.setAdapter(gridViewRecyclerAdapter);
return view;
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static List<Image> loadImageData(File file) {
String fileType;
String extension;
if(file.isDirectory()){
File[] files = file.listFiles();
for(File file1: files){
if(file1.isDirectory())
loadImageData(file1);
else{
extension = MimeTypeMap.getFileExtensionFromUrl(file1.getPath());
if (extension != null) {
fileType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if(fileType != null)
if(fileType.contains("image")){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calculateInSampleSize(options, 500, 500);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(file1.getAbsolutePath(), options);
// bitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight())
imagesList.add(new Image(bitmap, file1.getName(), file1.getPath()));
}
}
}
}
}
else {
extension = MimeTypeMap.getFileExtensionFromUrl(file.getPath());
if (extension != null) {
fileType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if(fileType != null)
if(fileType.contains("image")){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calculateInSampleSize(options, 500, 500);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
imagesList.add(new Image(bitmap, file.getName(), file.getPath()));
}
}
}
return imagesList;
}
}
public static class GridViewRecyclerAdapter extends BaseAdapter {
List<Image> imageList;
Context context;
static int position;
public GridViewRecyclerAdapter(List<Image> imageList, Context context){
this.imageList = imageList;
this.context = context;
}
#Override
public int getCount() {
return imageList.size();
}
#Override
public Object getItem(int position) {
return imageList.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if(convertView == null){
convertView = LayoutInflater.from(context).inflate(R.layout.images_item, parent, false);
viewHolder = new ViewHolder();
viewHolder.imageView = convertView.findViewById(R.id.image_view);
viewHolder.imageView.setTag(imageList.get(position).path);
viewHolder.textView = convertView.findViewById(R.id.text_view);
convertView.setTag(viewHolder);
}
else
viewHolder = (ViewHolder) convertView.getTag();
new LoadImageAsyncTask().execute(ImagesFragment.file.getAbsolutePath());
viewHolder.imageView.setImageBitmap(imageList.get(position).bitmap);
viewHolder.textView.setText(imageList.get(position).title);
GridViewRecyclerAdapter.position = position;
return convertView;
}
public static class ViewHolder{
ImageView imageView;
TextView textView;
}
static class LoadImageAsyncTask extends AsyncTask<String, Void, List<Image>> {
#Override
protected List<Image> doInBackground(String... objects) {
ImagesFragment.imagesList = ImagesFragment.loadImageData(new File(objects[0]));
return ImagesFragment.imagesList;
}
}
}
I am developing an Android application for tablet which will be used as a catalog of many products (around 10 000).
I display them in a gridview, I only display the 250 first, and if I reach the end of these 250 I load 250 more.
My problem is the pictures. My application seems to work when I scroll not quickly, it is smooth, and there is no problems but it can use 300 or 400 MB of RAM, so I guess I'm doing something badly. I heard that I'm supposed to use WeakReferences but I'm not sure how to use them... That was for the first "problem". (It is not really important since the user may only use this application, and if it works very well... But yes, if I can improve it I will try)
The other big problem here is that when I do something like :
Scroll fast to the bottom
Immediately scroll fast to the top
Some pictures of the "bottom" are displayed on the pictures of the "top" (and not temporarly, they change again only if I scroll and come back at them).
What I know actually :
The mechanism of the griview reuse the views. If I have 10 views visibles, When the 5 first are not visibles, they are used for the 5 next ones that will be visible. I don't know how many view are in this "recycler".
This means that when I scroll to the bottom I see the 5 views which were not visible in the place of the 5 next ones for a few moment then the good views are loaded. To not have this bad effect, I set a picture white (the color of my background) using the method setRecyclerListener().
I don't "recycle" / use the good way to load my pictures and recycle them but don't know how to make it better
I use animations, I don't think there is any link with my problem due to them
I load the pictures in AsyncTask, that seems to work, but the picures are always loaded (in fast scroll too) which is not really a good thing
When scrolling bottom then top quickly sometimes I have the wrong pictures displayed. It may happen in other cases.
public class ProduitsAdapter extends ArrayAdapter<Article> {
private final Activity context;
private ArrayList<Article> params;
private Resources res;
static class ViewHolder {
public TextView numero;
public TextView design;
public ImageView image;
public ImageSwitcher imgSwitcher;
public ImageView codeBarre;
public TextView codeBarreText;
public TextView colisage;
public TextView col3;
public TextView col4;
public TextView col5;
public String image_string = "";
public boolean found = true;
public ImageView nouveaute;
}
public ProduitsAdapter(Activity context, ArrayList<Article> params) {
super(context, R.layout.list_single, params);
res = Catalogue.activity.getResources();
this.context = context;
this.params = params;
}
public int getCount() {
return params.size();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
if (rowView == null) {
LayoutInflater inflater = context.getLayoutInflater();
ViewHolder viewHolder = new ViewHolder();
if (Catalogue.type_aff.equals("list")) {
rowView = inflater.inflate(R.layout.list_single, null);
viewHolder.numero = (TextView) rowView
.findViewById(R.id.numero);
viewHolder.design = (TextView) rowView
.findViewById(R.id.designation);
viewHolder.col3 = (TextView) rowView.findViewById(R.id.col3);
viewHolder.col4 = (TextView) rowView.findViewById(R.id.col4);
viewHolder.col5 = (TextView) rowView.findViewById(R.id.col5);
} else {
rowView = inflater.inflate(R.layout.fragment_grid_item, null);
viewHolder.numero = (TextView) rowView
.findViewById(R.id.numero);
viewHolder.design = (TextView) rowView
.findViewById(R.id.designation);
viewHolder.colisage = (TextView) rowView
.findViewById(R.id.grid_colisage);
viewHolder.codeBarre = (ImageView) rowView
.findViewById(R.id.code_barre);
viewHolder.codeBarreText = (TextView) rowView
.findViewById(R.id.code_barre_text);
viewHolder.nouveaute = (ImageView) rowView
.findViewById(R.id.img_nouveaute);
}
viewHolder.image = (ImageView) rowView.findViewById(R.id.img);
rowView.setTag(viewHolder);
}
ViewHolder holder = (ViewHolder) rowView.getTag();
if (Catalogue.type_aff.equals("list")) {
holder.col3.setText(params.get(position).getColonne_pref(3));
holder.col4.setText(params.get(position).getColonne_pref(4));
holder.col5.setText(params.get(position).getColonne_pref(5));
} else {
if (params.get(position).isNouveaute()) {
holder.nouveaute.setImageResource(R.drawable.nouveaute);
holder.nouveaute.setVisibility(ImageView.VISIBLE);
} else
holder.nouveaute.setVisibility(ImageView.GONE);
holder.colisage.setText(params.get(position).getColisage());
if (ProduitsFragment.barcodeVisible) {
holder.codeBarreText.setText(params.get(position)
.getGencodNegoce());
}
}
holder.numero.setText(params.get(position).getCodeArt());
holder.design.setText(params.get(position).getDesignation_pref());
// This is how I store my pictures, because finding one picture in a folder of 10 000 pictures takes too much time
String pattern_deb = holder.numero.getText() + "";
String chemin = Environment.getExternalStorageDirectory().getPath()
+ res.getString(R.string.chemin_app_data)
+ MainActivity.listDossiersSocietes
.get(MainActivity.societeSelected)
+ res.getString(R.string.chemin_dossier_photos);
for (int i = 0; i < pattern_deb.length() - 2; i++) {
chemin += "/" + pattern_deb.charAt(i);
}
chemin += "/";
File f = new File(chemin + params.get(position).getFichierPhoto());
if (f.exists()) {
holder.image_string = f.getAbsolutePath();
new loadImagesFromFileThread(holder, position).execute();
if (!Catalogue.type_aff.equals("list")
&& ProduitsFragment.barcodeVisible)
new loadBarCodeThread(holder, position).execute();
} else {
holder.image.setImageResource(R.drawable.notfound300);
}
return rowView;
}
private BitmapDrawable loadImagesFromFile(ViewHolder holder, int position) {
holder.found = true;
File bmp = new File(holder.image_string);
if (bmp.exists() && !bmp.isDirectory()) {
if (position < ProduitsFragment.gridView.getFirstVisiblePosition()
&& position > ProduitsFragment.gridView
.getLastVisiblePosition()) {
holder.found = false;
return null;
} else {
if (holder.image.getVisibility() == ImageView.VISIBLE) {
holder.found = false;
return null;
}
}
Bitmap myBitmap = BitmapFactory.decodeFile(bmp.getAbsolutePath());
int width = myBitmap.getWidth();
int height = myBitmap.getHeight();
int newWidth = 200;
int newHeight = 200;
if (Catalogue.type_aff.equals("list")) {
newWidth = 40;
newHeight = 40;
} else {
if (Catalogue.nbGrid == 5) {
newWidth = 200;
newHeight = 200;
}
if (Catalogue.nbGrid == 3) {
newWidth = 300;
newHeight = 300;
}
}
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizedBitmap = Bitmap.createBitmap(myBitmap, 0, 0, width,
height, matrix, true);
BitmapDrawable bmd = new BitmapDrawable(context.getResources(),
resizedBitmap);
return bmd;
} else {
holder.found = false;
return null;
}
}
private class loadImagesFromFileThread extends
AsyncTask<Void, Void, BitmapDrawable> {
ViewHolder holder;
int position;
public loadImagesFromFileThread(ViewHolder holder, int position) {
this.holder = holder;
this.position = position;
this.holder.image.setVisibility(ImageView.INVISIBLE);
}
#Override
protected BitmapDrawable doInBackground(Void... params) {
return loadImagesFromFile(holder, position);
}
#Override
protected void onPostExecute(BitmapDrawable bmd) {
if (ProduitsFragment.isNotScrolling) {
if (bmd != null) {
holder.image.setImageDrawable(bmd);
if (holder.image.getVisibility() != ImageView.VISIBLE
&& !(position < ProduitsFragment.gridView
.getFirstVisiblePosition() && position > ProduitsFragment.gridView
.getLastVisiblePosition())) {
Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setInterpolator(new DecelerateInterpolator());
fadeIn.setDuration(500);
fadeIn.setStartOffset(500);
holder.image.startAnimation(fadeIn);
holder.image.setVisibility(ImageView.VISIBLE);
}
} else {
if (position < ProduitsFragment.gridView
.getFirstVisiblePosition()
&& position > ProduitsFragment.gridView
.getLastVisiblePosition()) {
Log.e("DEBUG", "...");
} else {
if (holder.image.getVisibility() != ImageView.VISIBLE) {
if (!holder.found) {
if (Catalogue.type_aff.equals("list")) {
holder.image
.setImageResource(R.drawable.notfound40);
} else {
if (Catalogue.nbGrid == 5) {
holder.image
.setImageResource(R.drawable.notfound200);
}
if (Catalogue.nbGrid == 3) {
holder.image
.setImageResource(R.drawable.notfound300);
}
}
Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setInterpolator(new DecelerateInterpolator());
fadeIn.setDuration(500);
holder.image.startAnimation(fadeIn);
holder.image.setVisibility(ImageView.VISIBLE);
} else {
Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setInterpolator(new DecelerateInterpolator());
fadeIn.setDuration(500);
holder.image.startAnimation(fadeIn);
}
holder.image.setVisibility(ImageView.VISIBLE);
}
}
}
}
}
}
}
I would appreciate any help to correct the problem of the wrong pictures, and if anyone can explain me how to load them properly with some code if possible (without using 300 MB of RAM) that would be really nice.
I try to create a gallery with a grid view, now I have a list that load the images in an asynnc way, is so smooth but the problem is that every time a cell is redraw also the image is redraw, I use cache but nothing, this is my code:
public class GalleryFragment extends Fragment {
View view;
GridView gridview;
ImageAdapter myImageAdapter;
Bitmap mPlaceHolderBitmap;
ArrayList<String> arrayPhotos = new ArrayList<String>();
private LruCache<String, Bitmap> mMemoryCache;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.gallery_layout, container, false);
return view;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityManager am = (ActivityManager) getActivity().getSystemService(Context.ACTIVITY_SERVICE);
int memClass = am.getMemoryClass();
final int cacheSize = 1024 * 1024 * memClass / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in bytes rather than number
// of items.
return bitmap.getByteCount();
}
};
}
#Override
public void onStart (){
super.onStart();
gridview = (GridView) view.findViewById(R.id.gridView);
gridview.setAdapter(new ImageAdapter(getActivity()));
String ExternalStorageDirectoryPath = Environment.getExternalStorageDirectory().getAbsolutePath();
String targetPath = ExternalStorageDirectoryPath + "/test/";
File targetDirector = new File(targetPath);
File[] files = targetDirector.listFiles();
for (File file : files){
arrayPhotos.add(file.getAbsolutePath());
}
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return (Bitmap) mMemoryCache.get(key);
}
public class ImageAdapter extends BaseAdapter {
private Context mContext;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return arrayPhotos.size();
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(300, 300));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
String imageKey = arrayPhotos.get(position);
Bitmap bm = getBitmapFromMemCache(imageKey);
if (bm == null){
loadBitmap(imageKey, i);
}
else {
i.setImageBitmap(bm);
}
return imageView;
}
}
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
public void loadBitmap(String resId, ImageView imageView) {
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
public static boolean cancelPotentialWork(String data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final String bitmapData = bitmapWorkerTask.path;
// If bitmapData is not yet set or it differs from the new data
if (bitmapData == "" || bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
}
else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private String path;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(String... params) {
path = params[0];
Bitmap bitmap = decodeSampledBitmapFromResource(path, 300, 300);
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
public static Bitmap decodeSampledBitmapFromResource(String filePath,int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filePath, options);
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
where is the mistake?
I am using a GridView to display images. The images are downloaded from a feed and added to a BitmapCache. The GridView is inside of a ViewFlipper (which has a ListView as second View). I'm using GridView for the first time but I've worked with Adapters many times when I used ListViews.
At the moment, the feed only delivers two images. But when I start my Fragment containing the GridView I get an OutOfMemoryError caused bei BitmapFactory.decodeStream(). When I took a deeper look into the logcat, I noticed that getView() inside of my Adapter for the GridView is called many many times.
I know that it's nothing special if getView() is called more than once, but the getView()-method in my Adapter gets called over 120 times only for position 0. And I don't really understand why it's called so often. But I'm pretty sure that this caused my memory problem as this method tries to load a bitmap over 100 times in just a few seconds.
As I'm already trying to recycle my view with a ViewHolder I'm quite helpless at the moment and I hope somebody can explain me this massive calls of getView() and/or might give me a hint to solve my problem.
The getView()-mthod of my adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.pictures_grid_item, parent, false);
holder.image = (ImageView) convertView.findViewById(R.id.picturesGridImage);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
holder.image.setImageBitmap(null);
}
Picture picture = (Picture) pictureList.get(position);
String imageUrl = picture.getUrl();
if (!TextUtils.isEmpty(imageUrl)) {
holder.image.setTag(imageUrl);
ImageLoader.getInstance(context).loadImageWithTagCheck(holder.image);
}
return convertView;
}
private static class ViewHolder {
ImageView image;
}
The loadImageWithTagCheck()-method just checks if the image has already been downloaded (which deffinitely should be the case)
The Fragment which holds the View:
public class PicturesFragment extends BaseFragment {
private List<Parcelable> pictureList;
private PicturesGridAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.pictures_fragment, container, false);
// TODO: Remove final after development
final MediaActivity activity = (MediaActivity) getActivity();
pictureList = activity.getPictures();
adapter = new PicturesGridAdapter(activity, pictureList);
GridView gridview = (GridView) view.findViewById(R.id.picturesGrid);
gridview.setAdapter(adapter);
gridview.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
Toast.makeText(activity, "" + position, Toast.LENGTH_SHORT).show();
}
});
return view;
}
}
BTW: I'm not using *wrap_content* anywhere.
Edit:
Here's the code of the imageloader. Ofcourse, the ImageLoader is the problem which causes the outOfMemoryError. But I think that the problem is rather something with the adapter because 120 calls of getView() for position 0 just after creating the view can't be right. And the Adapter is just created once so it's >120 calls in a single instance of my adapter.
(this is a pretty huge and complex project so the "simple" imageloader has a lot of code)
public void loadImageWithTagCheck(final ImageView view) {
final String url = (String) view.getTag();
final Handler uiHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
}
};
if (imageHandler != null) {
imageHandler.post(new Runnable() {
#Override
public void run() {
final Bitmap bmp = getImage(url, view);
uiHandler.post(new Runnable() {
#Override
public void run() {
String tagUrl = (String) view.getTag();
if (tagUrl.equals(url) && bmp != null
&& !bmp.isRecycled()) {
scaleBitmapAndAdjustViewByHeight(view, bmp);
} else if (bmp != null) {
bmp.recycle();
}
}
});
}
});
}
}
private Bitmap getImage(String url, View v) {
Bitmap bmp = null;
if (url != null && !TextUtils.isEmpty(url)) {
String md5Url = Utility.md5(url);
if (cache.containsKey(md5Url)) {
bmp = cache.getBitmap(md5Url);
} else {
HttpGet httpGet = new HttpGet();
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = null;
try {
URI uri = new URI(url);
httpGet.setURI(uri);
response = httpClient.execute(httpGet);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity entity = response.getEntity();
if (entity != null) {
final BufferedInputStream buffIn = new BufferedInputStream(
entity.getContent(), Utils.IO_BUFFER_SIZE);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.outWidth = v.getWidth();
options.outHeight = v.getHeight();
options.inPurgeable = true;
options.inInputShareable = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
bmp = BitmapFactory.decodeStream(buffIn, null,
options);
}
}
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
}
if (bmp != null) {
cache.put(md5Url, bmp);
}
}
}
return bmp;
}
private void scaleBitmapAndAdjustViewByHeight(final ImageView view,
final Bitmap bmp) {
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.getViewTreeObserver().removeOnGlobalLayoutListener(
this);
} else {
view.getViewTreeObserver().removeGlobalOnLayoutListener(
this);
}
// Get current dimensions
int width = bmp.getWidth();
int height = bmp.getHeight();
// Determine how much to scale: the dimension requiring less
// scaling is closer to the its side. This way the image always
// stays inside your bounding box AND either x/y axis touches
// it.
int imageViewHeightFromXMLinPixels = view.getHeight();
float xScale = (float) ((imageViewHeightFromXMLinPixels * 2.75) / width);
float yScale = ((float) imageViewHeightFromXMLinPixels)
/ height;
float scale = (xScale <= yScale) ? xScale : yScale;
// Create a matrix for the scaling and add the scaling data
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
// Create a new bitmap
Bitmap scaledBitmap = Bitmap.createBitmap(bmp, 0, 0, width,
height, matrix, true);
width = scaledBitmap.getWidth(); // re-use
view.setImageBitmap(scaledBitmap);
view.getLayoutParams().width = width;
}
});
view.requestLayout();
}
Get rid of the scaleBitmapAndAdjustViewByHeight(...) method.
Instead, do a simple view.setImageBitmap(bmp).
Why?
scaleBitmapAndAdjustViewByHeight(...) calls view.requestLayout() which probably leads to calling your adapters getView(...) and ends in a deadlock and finally the OutOfMemoryError.
I'm trying to develop an Android 3.1 tablet application.
This app will have a lot of images, and I have followed Processing Bitmaps Off the UI Thread tutorial, but I've done something wrong because I get:
java.lang.ClassCastException: android.widget.AbsListView$LayoutParams cannot be cast to android.widget.Gallery$LayoutParams
This is my code:
I set up Gallery on an Activity
mFactGallery = (Gallery)mView.findViewById(R.id.factGallery);
mFactGallery.setAdapter(new ImageGalleryAdapter(mActivity, ImageView.ScaleType.FIT_END, 180, 90));
ImageGalleryAdapter.java
public class ImageGalleryAdapter extends BaseAdapter
{
private ArrayList<String> mImagesPath;
private Context mContext;
private int mWidth;
private int mHeight;
public ArrayList<String> getmImagesPath()
{
return mImagesPath;
}
public void setmImagesPath(ArrayList<String> mImagesPath)
{
this.mImagesPath = mImagesPath;
}
public void addImage(String imagePath)
{
mImagesPath.add(imagePath);
}
public ImageGalleryAdapter(Context context, ImageView.ScaleType scaleType, int width, int height)
{
mContext = context;
mWidth = width;
mHeight = height;
mScaleType = scaleType;
mImagesPath = new ArrayList<String>();
}
#Override
public int getCount()
{
return mImagesPath.size();
}
#Override
public Object getItem(int position)
{
return position;
}
#Override
public long getItemId(int position)
{
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
// Get a View to display image data
ImageView imageView;
// if it's not recycled, initialize some attributes
if (convertView == null)
{
imageView = new ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new GridView.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
else
{
imageView = (ImageView) convertView;
// Recicla el Bitmap.
Bitmap bm = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
if (bm != null)
bm.recycle();
}
String filePath = mImagesPath.get(position);
if (BitmapTools.cancelPotentialWork(filePath, imageView))
{
String[] params = {filePath, Integer.toString(mWidth), Integer.toString(mHeight)};
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(mContext.getResources(), filePath, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(params);
}
return imageView;
}
}
BitmapWorkerTask.java
public class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap>
{
private final WeakReference<ImageView> imageViewReference;
public String imgPath = "";
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(String... params)
{
imgPath = params[0];
int width = Integer.valueOf(params[1]).intValue();
int height = Integer.valueOf(params[2]).intValue();
return BitmapTools.decodeSampledBitmapFromDisk(imgPath, width, height);
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap)
{
if (isCancelled())
{
bitmap = null;
}
if (imageViewReference != null && bitmap != null)
{
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask =
BitmapTools.getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null)
{
imageView.setImageBitmap(bitmap);
}
}
}
}
AsyncDrawable.java
public class AsyncDrawable extends BitmapDrawable
{
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, String filepath,
BitmapWorkerTask bitmapWorkerTask)
{
super(res, filepath);
bitmapWorkerTaskReference =
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask()
{
return bitmapWorkerTaskReference.get();
}
}
What am I doing wrong?
replace GridView.LayoutParams with Gallery.LayoutParams as follows, that will solve your problem
imageView.setLayoutParams(new Gallery.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
In ImageGalleryAdapter.java, this is wrong:
imageView.setLayoutParams(new GridView.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
Use the correct LayoutParams class, it should be
imageView.setLayoutParams(new Gallery.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
(The superclass of LayoutParams is Gallery instead of GridView)