I have an Activity with an ImagePagerAdapter (extends of FragmentStatePagerAdapter) that has this getItem method:
#Override
public Fragment getItem(int position) {
Log.d(LOGTAG, "------------>mUserPicturesList.get("+position+").getFilename(): " + mUserPicturesList.get(position).getFilename());
return UserDetailFragment.newInstance(mUserPicturesList.get(position).getFilename());
}
The fragment that is instantiated has this onCreateView:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate and locate the main ImageView
final View v = inflater.inflate(R.layout.image_detail_fragment, container, false);
mImageView = (ImageView) v.findViewById(R.id.imageView);
mProgressPicturePager = (ProgressBar) v.findViewById(R.id.progress_picture_pager);
String imageUrl = WApp.PHOTO_URL + mImageUrl + "?type=user_gallery_big_img";
Picasso picasso = Picasso.with(getActivity());
picasso.setDebugging(true);
picasso.load(imageUrl)
.placeholder(R.drawable.no_picture_man_big)
.error(android.R.drawable.stat_notify_error)
.into(mImageView, new Callback() {
#Override
public void onSuccess() {
mProgressPicturePager.setVisibility(View.GONE);
}
#Override
public void onError() {
Log.d(LOGTAG, "picasso load error");
mProgressPicturePager.setVisibility(View.GONE);
}
});
return v;
}
The Problem:
When the ImagePager load first time, some times, Picasso call onError, showing the .error drawable.
If I press on back button and go back to the Activity that has the ImagePager, Picasso load the picture correctly.
If the ImagePager has two or more pictures and I swipe between the pictures, those are loaded correctly some times without exit and reenter to the ImagePager.
The Theories:
I think that it could be a problem of cache, but after many searches, I bet that the problem is in the weak reference of the Picasso.
Keep in mind that the problem only appears the FIRST TIME I load the activity that have the ImagePager.
In another place, currently Picasso works fine in listView with an adapter loading the pictures at first time. Calling Picasso inside the getView method of Adapter class.
Visited links
How would an anonymous class get GC'd in picasso on Android?
ViewPager unable to load images lazily with Picasso
http://square.github.io/picasso/
https://plus.google.com/communities/109244258569782858265/stream/885843f4-c8b5-4851-9de1-b0374121dfa3
Use of Target in Picasso on Adapter
https://github.com/square/picasso/pull/349
Thanks in advance.
The problem was solved in the Picasso 2.3.0.
The fix is in the Picasso changelog:
Requests will now be automatically replayed if they failed due to network errors.
I hope this save you many hours.
Related
The following is my pager adapter class, It gets called every time user goes to event Activity. There are around 20 images in this activity, I fetched them from the server and save them locally in my app. So next time it does not hit the service, but still it takes time to render these 20 images, Is there a way I can also cache these images, so on second run it does not call instantiateItem 20 times.
public class MynPagerAdapter extends PagerAdapter {
private Context mContext;
#Override
public Object instantiateItem(ViewGroup container, final int position) {
LayoutInflater layoutInflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemView = layoutInflater.inflate(R.layout.team_event_homescreen,container,false);
ImagesLoader.getInstance(mContext).getImage("MyName").setBmpToImageView(homeImageView);
ImagesLoader.getInstance(mContext).getImage(currentEvent.oppLogoName).setBmpToImageView(awayImageView);
}
}
Kindly guide me a better way to handle this.
I am not sure of ImageLoader but in Glide the image generally remains in memory even after the activity destroyed. May be using the applicationcontext in glide with method can load images instantaneously on second run of activity
I have a ViewPager as the row of a RecyclerView. It is like a "featured products" row.
I set the adapter of the ViewPager in the onBindViewHolder of the RecyclerView. ViewPager contains a TextView and an ImageView. ImageView is loaded from an URL via Glide in instantiateItem. The list of items in the ViewPager is 4.
The problem is, the ImageViews of the first two items in the ViewPager are not loaded. If I swipe the ViewPager to the 3rd item and back, I see the first ImageView successfully.
TextViews work fine. The problem is only with the images.
If I debug the code, I observe that the code block that belongs to Glide is reached.
If I use the ViewPager as a standalone view in the fragment (not as a row of the RecyclerView) I observe no problems.
There is a similar question:
Image not loading in first page of ViewPager (PagerAdapter)
But that unfortunately does not apply to my case. I declare my variables locally and with the final modifier already.
PagerAdapter's instantiateItem is like follows:
#Override
public Object instantiateItem(ViewGroup container, int position) {
final LayoutInflater inflater = (LayoutInflater) container.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View v = inflater.inflate(viewpager_layout, container, false);
final ImageView imgProduct = (ImageView) v.findViewById(R.id.imgProduct);
final TextView lblName = (TextView) v.findViewById(R.id.lblName);
final Product product = data.get(position);
lblName.setText(product.name);
Glide
.with(ctx)
.load(product.url)
.asBitmap()
.thumbnail((float) 0.4)
.placeholder(R.drawable.placeholder)
.error(R.drawable.placeholder)
.animate(R.animator.fade_in)
.into(imgProduct);
}
RecyclerView's onBindViewHolder looks like:
#Override
public void onBindViewHolder(final DataObjectHolder holder, int position) {
int listType = getItemViewType(position);
final ProductList item = data.get(position);
if (listType == 0) {
final List<Product> lstProducts = GetProducts(item.products);
final MyPagerAdapter myAdapter = new MyPagerAdapter(ctx, lstProducts);
holder.viewPager.setAdapter(myAdapter);
myAdapter.notifyDataSetChanged(); // this changes nothing also..
}
else {
// removed..
}
}
I work with the AppCompat library by the way.
All suggestions are welcome. Thank you.
Glide sets images asynchronously and helps to avoid any lag in UI thread. If there are many images it does take some time for the image to set in, but it doesn't cause any lag.
It's a simple Asynchronous request issue, when you swipe to the third tab and come back, till the data would have come and come and then it's gets binded with the UI.
I had faced the similar issue in my project.
I am assuming that you are using a new fragment for each view pager
One thing you can do is...
1.While creating fragment in viewPager in **getItem()**, set a tag with fragment.
2. create a viewPager **addOnPageChangeListener** and on page selected, get that fragment by tag and check if image is loaded or not.
3. If not loaded, then show some loader, and wait for the response, after response hide the loader
I have a FragmentActivity in the first tab of a TabHost, and the FragmentActivity itself holds a ViewPager.
The ViewPager's setAdapter() method sets a FragmentPagerAdapter with a set of Fragments. The goal is to have swipeable images in the first pane of the TabHost.
To test it, I was loading a bunch of images that I had kept locally in the project's drawable directory. Everything worked beautifully.
After having tested this initial setup, I'm downloading a bunch of image URLs' off a REST web service. I want these images to load lazily in the ViewPager, and I tried calling Picasso's load() method in three places:
The onCreateView() method of the ViewPager's Fragments (the same place where I was earlier loading images from the local drawable directory).
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View myFragmentView = inflater.inflate(R.layout.layout_fragment, container, false);
ImageView imageView = (ImageView)getView().findViewById(R.id.fragment_image);
String url = getArguments().getString(IMAGE_RESOURCE_URL);
Context context = getActivity();
Picasso.with(context).load(url).fit().into(imageView);
return myFragmentView;
}
The onViewCreated() method of the ViewPager's Fragments.
#Override
public void onViewCreated(View view, Bundle savedInstanceState){
Context context = getActivity();
String url = getArguments().getString(IMAGE_RESOURCE_URL);
ImageView imageView = (ImageView)view.findViewById(R.id.fragment_image);
Picasso.with(context).load(url).fit().into(imageView);
}
The onInstantiateItem() method of the FragmentPagerAdapter.
#Override
public Object instantiateItem(ViewGroup container, int position) {
View v = inflater.inflate(R.layout.layout_fragment, container, false);
Context context = Tab1Activity.this;
String url = getItem(position).getArguments().getString("IMAGE_RESOURCE_URL");
ImageView imageView = (ImageView)v.findViewById(R.id.fragment_image);
Picasso.with(context).load(url).fit().into(imageView);
return v;
}
None of these methods worked. I know that its not a problem with Picasso because the other day I tried using Picasso in a ListView and it worked like a charm. What am I doing wrong ?
Your first approach should work fine... if you implement it correctly. I would expect your code to crash with a NullPointerException.
Replace:
ImageView imageView = (ImageView)getView().findViewById(R.id.fragment_image);
with:
ImageView imageView = (ImageView)myFragmentView.findViewById(R.id.fragment_image);
Your lazy loader should work in the context of the
Fragment.createView()
where the Fragment is one of a collection being paged by the ViewPager and the FragmentpagerAdapter
That is your option one.
I use another lazyloader that manages local memcache and local filesys cache for the bitmaps needed to load the images. at the time of the "OnCreateView()" it will go to the network to get the URL for the loader if the bitmap is not already cached.
I have a gridview which displays two column imageviews. I am loading these images with an async task (see this post Lazy load of images in ListView )
But when i am scrolling gridview , the images in positions are mixing. For example 14th image shows 1th image , i think view is trying to show old image before async task finishes.
My code :
public Content getItem(int position) {
return contents.get(position);
}
public long getItemId(int position) {
return position;
}
#Override
public boolean hasStableIds() { return true; }
public View getView(int position, View convertView, ViewGroup parent) {
Thumbnail contentView;
Content current = contents.get(position);
if (convertView == null) {
contentView = new Thumbnail(mContext);
}
else {
contentView = (Thumbnail) convertView;
}
contentView.title = current.getTitle();
contentView.image = current.getImage();
contentView.link = current.getLink();
contentView.init();
return contentView;
}
Init function
TextView titleText = (TextView)super.findViewById(R.id.titleText);
titleText.setText(title);
ImageView imageControl = (ImageView)super.findViewById(R.id.thumbImage);
DrawableManager loadImage = new DrawableManager(); loadImage.fetchDrawableOnThread(imgUrl, imageControl);
Waiting for your help
Thanks
It happens because of resource reusing. What you should do:
First, just set some kind of default image to your imageView (contentView.image.setImageResource(DEFAULT_RESOURCE)) inside getView method (transitional default picture is better than wrong one).
Set unique tag to your image, for example, position or url of image to load (contentView.image.setTag(url)).
When AsyncTask finishes, you can use some checks like
String url=(String)imageView.getTag();
if (url.equals(mUrl)) { //mUrl can be transmitted to AsyncTask instance separately
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
imageView.setImageDrawable(bmp);
});
});
}
It's needed because Adapter does not allocate memory for all N ImageView for N items. It stores just required amount for visible items and some reserved ImageViews. So there is no warranty that ImageView reference that you store will be actual within several seconds cause it can became invisible and be reused by visible ones.
Don't load images yourself. Use Universal Image Loader . It can cache, images and loaded bitmaps. Making your problem go away. It also supports showing stub images and automatic pause on scrolling and so on.
It solves your specic problems by only loading an image for the last instance of an imageView if you reuse it.
imageLoader.displayImage(imageUri, imageView);
Is all that is needed to asynchronously load an image.
I need to create ViewPager in Android with 5 slides, each consists of image and text. I have an array with resources for images:
private static final int[] images = {R.drawable.tutorial_step_01, R.drawable.tutorial_step_02, R.drawable.tutorial_step_03, R.drawable.tutorial_step_04, R.drawable.tutorial_step_05, R.drawable.tutorial_step_06};
then I create adapter:
#Override
public Object instantiateItem(ViewGroup container, int position) {
LinearLayout tv = (LinearLayout) inflater.inflate(R.layout.tut_slide, null);
TextView title = (TextView) tv.findViewById(R.id.tut_title);
title.setText(getResources().getText(titles[position]));
TextView content = (TextView) tv.findViewById(R.id.tut_content);
ImageView image = (ImageView) tv.findViewById(R.id.tut_image);
slide_image = BitmapFactory.decodeResource(getResources(), images[position]);
image.setImageBitmap(slide_image);
content.setText(getResources().getText(contents[position]));
((ViewPager) container).addView(tv, 0);
return tv;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((LinearLayout) object);
//
}
trouble is that fact android don't want to collect image after I choose another page. So, after 10-15 changes it goes out with OutOfMemory exception. Then I added to initializung rows
if (slide_image!= null) {
slide_image.recycle();
System.gc();
}
And it's work good! But except one thing: I have black screen instead of first image, whcih is replaced by real one after few flips. So I don't know what to do with such memory leaking
Well, I solved the problem finally. I faced it with a very similar case and as I've seen so many questions related to the same problem, I chose this question as it's yet not answered.
The PagerAdapter should call the destroyItem method not only when it surpasses the offLimitScreenPageLimit but also when a screen rotation occurs, but it doesn't, so it has to be forced to do so... to achieve it, you just have to set to null the adapter on the onStop or onDestroy method of the activity.
#Override protected void onDestroy(){
pager.setAdapter(null);
}
Cheers!
It's not clear what you are using but I encountered a similar problem.
I'm assuming you are using FragmentPagerAdapter.
When you scroll away using that adapter, it does not destroy the pages out of view and out of cache. If there is an ImageView in a fragment used by FragmentPageAdapter, OOM is inevitable
Just change the extend of the adapter to
FragmentStatePagerAdapter
This will destroy the fragments not in use and leave more memory free for new fragments.
It's still not perfect, I have found that sometimes I can scroll faster than the garbage collector picks up the destroyed bitmaps, but its pretty damn close.
If I was looking to improve it, I would override destroyItem, and then get the bitmap in use from the imageview and .recycle the bitmap.
Recycle ImageView's Bitmap
That behaviour shouldn't be related with a memory leak. It looks like it's related to when and how you recycle bitmaps within your viewpager updating lifecycle. Try calling onPageSelected() or notifyDatasetChanged() manually at some point on your initialization.
This solutions might not solve the problem completely, but give it a try. It's hard to tell with your explanation.
In My case I have 31 page in ViewPager. I use this :
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
if(dbCon!=null)
dbCon.close();
ViewPager viewPager = (ViewPager)container;
View view = (View) object;
viewPager.removeView(view);
}
and everthing works fine. Alhamdulillah.