[mono-android] Custom Gallery App

Ariandle jebusinessmail at gmail.com
Thu Jan 26 15:52:55 UTC 2012


I'm working on a project that essentially is going to be a custom gallery
app. I am having problems with out of memory errors, as the user scrolled
through the images in the gallery.

I use a camera to take the pictures: 

  public class GalleryMenuActivity : Activity
    {
        private ImageButton _takePictureButton;
        private ImageGalleryAdapter _imageAdapter;
        private Gallery _recentPictureGallery;
        private Android.Net.Uri _currentImageUri;
        private List<ImageGalleryAdapter.ImageDetails> _imageList; 

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Request the feature to set a custom window title
            RequestWindowFeature(WindowFeatures.CustomTitle);

            // Load the UI defined in Second.axml
            SetContentView(Resource.Layout.GalleryMenu);

            // Set a custom title for the window
            Window.SetFeatureInt(WindowFeatures.CustomTitle,
Resource.Layout.WindowTitle);

            //Load existing images here if they are needed between runs.
            _imageAdapter = new ImageGalleryAdapter(new
List<ImageGalleryAdapter.ImageDetails>());

            _takePictureButton =
FindViewById<ImageButton>(Resource.Id.TakePictureButton);
            _takePictureButton.Click += OnCaptureImageClicked;
        }


        private void OnCaptureImageClicked(object sender, EventArgs e)
        {
            TakePicture(); 
        }

        private void *TakePicture()*
        {
            // Specify some metadata for the picture
            using (var contentValues = new ContentValues())
            {
              
contentValues.Put(MediaStore.Images.ImageColumnsConsts.Description,
"Image");
                // Specify where to put the image
                _currentImageUri =
ContentResolver.Insert(MediaStore.Images.Media.ExternalContentUri, 
contentValues);
            }

            // Specify the message to send to the OS
            using (var takePictureIntent = new
Intent(MediaStore.ActionImageCapture))
            {
                takePictureIntent.PutExtra(MediaStore.ExtraOutput,
_currentImageUri);
                // Start the requested activity
                StartActivityForResult(takePictureIntent, 1001);
            }
        
        }

Then I capture the activity result. I can get the physical path and the
thumbnail location here. 
ImageDetails is just a small object to hold some details about the image
that gets passed to the adapter: 

   protected override void *OnActivityResult*(int requestCode, Result
resultCode, Intent data)
        {
            base.OnActivityResult(requestCode, resultCode, data);

            if (requestCode == 1001 && resultCode == Result.Ok)
            {
                var imageDetail = new
Adapters.ImageGalleryAdapter.ImageDetails();
                imageDetail.ThumbnailLocation = _currentImageUri.ToString();
              
                String[] projection = { MediaStore.MediaColumns.Data };
                String selection = "";
                String[] selectionArgs = null;

                Android.Database.ICursor mediaCursor =
ContentResolver.Query(_currentImageUri, projection, null, null, null);
                
                    if (mediaCursor != null)
                    {
                        mediaCursor.MoveToFirst();
                        String photoFilePath = mediaCursor.GetString(
                           
mediaCursor.GetColumnIndex(MediaStore.MediaColumns.Data));
                        imageDetail.PhysicalLocation = photoFilePath;
                    }
                
                _imageAdapter.Images.Add(imageDetail);
                _imageAdapter.NotifyDataSetChanged();
                
            }
        }
}

On to the adapter, and where the problem is.

 public class ImageGalleryAdapter : BaseAdapter
    {
        private List<ImageDetails> _images;

        /// <summary>
        /// Initializes a new instance of the ImageGalleryAdapter
        /// </summary>
        /// 
        /// 
        public *ImageGalleryAdapter*(List<ImageDetails> imageSources)
        {
            _images = imageSources;
        }


        /// <summary>
        /// Gets a specific item from the list
        /// </summary>
        /// 
        /// <returns></returns>
        public override Object *GetItem*(int position)
        {
            // return new Java.Lang.String(_images.ElementAt(position));
            return position;
        }

        /// <summary>
        /// Returns the ImageDetails, which is an extention of details about
the loaded images
        /// </summary>
        /// 
        /// <returns></returns>
        public ImageDetails *GetItemAtPosition*(int position)
        {
            return _images[position];
        }

        /// <summary>
        /// Gets the ID of a specific item
        /// </summary>
        /// 
        /// <returns></returns>
        public override long *GetItemId*(int position)
        {
            return position;
        }

        /// <summary>
        /// Gets the view for a specific image
        /// </summary>
        /// 
        /// 
        /// 
        /// <returns></returns>
        public override View *GetView*(int position, View convertView,
ViewGroup parent)
        {
            var itemView = convertView;
         
            // Create a new item view when an existing one could not be
recycled
            if (itemView == null)
            {
                using (var inflater =
(LayoutInflater)Application.Context.GetSystemService(Context.LayoutInflaterService))
                {
                    itemView =
inflater.Inflate(Resource.Layout.ImageGalleryItem, null);
                  
                }
            }

            using (var imageView =
itemView.FindViewById<ImageView>(Resource.Id.ContentImageView))
            {
                using (var bitmap =
*DecodeBitmapFile*(_images.ElementAt(position).ThumbnailLocation))
                {
                  //   Attach an image to the item 
                    imageView.SetImageBitmap(bitmap);
                }
            }
            return itemView;
        }

        /// <summary>
        /// Gets the number of images 
        /// </summary>
        public override int *Count*
        {
            get { return _images.Count; }
        }

        /// <summary>
        /// Gets the images that are being displayed
        /// </summary>
        public List<ImageDetails> Images { get { return _images; } }

        /// <summary>
        /// Decodes a thumbnail for the specified image
        /// </summary>
        /// 
        /// <returns></returns>
        private Bitmap *DecodeBitmapFile*(string fileUri)
        {
            // The Image ID is at the end of the image URI.
            int imageId =
Int32.Parse(fileUri.Substring(fileUri.LastIndexOf('/') + 1));
   
           // *Request a small thumbnail  Here is where the out of memory
Error happens*
            return
MediaStore.Images.Thumbnails.GetThumbnail(Application.Context.ContentResolver,
imageId, ThumbnailKind.MiniKind, new BitmapFactory.Options() { InSampleSize
= 1 });
        }

        private Drawable *CreateDrawableFromUrl*(String url)
        {
            return Drawable.CreateFromPath(url);
        }

        public struct *ImageDetails*
        {
            public string ImageName { get; set; }
            public string ThumbnailLocation { get; set; }
            public string PhysicalLocation { get; set; }
        }


So the error happens when *DecodeBitmapFile* is called on the
*imageView.SetImageBitmap*, but only after the user has either (a) scrolled
back and forth many times (which fires the *GetView*), or (b) adds too many
images (which fires the *GetView*).  There seems to be some threshold that
gets exceeded with the method I am using here. Is there any other way to
load dynamic images without running into this? Or am I way off base with
this approach?

The images should be fairly small, as they come from 
*MediaStore.Images.Thumbnails*, and I have set the *BitmapFactory.Options()
{ InSampleSize = 1})*. I believe it has something to do with
imageView.SetImageBitmap(bitmap) being called so many times, but I don't
know if there is a way around that, as I understand it the method is called
to generate the references for the images that appear on the screen as the
user scrolls. 

I have even called itemView.DestroyDrawingCache(); within the using{} for
the inflater and called    imageView.DestroyDrawingCache() in the imageView
using{}.

If anyone has any suggestions I would really appreciate some help here.
These out of memory errors are really becoming a roadblock for this app of
mine. 

--
View this message in context: http://mono-for-android.1047100.n5.nabble.com/Custom-Gallery-App-tp5433213p5433213.html
Sent from the Mono for Android mailing list archive at Nabble.com.


More information about the Monodroid mailing list