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
Related
I am developing an app in android which do multiple async connections to spotify and to my own server. My problem is that when I load the tracks I have to wait for the images to show in a ImageView. This is the app flow:
On the App run I download a general Playlist to begin the player with the first track.
(First async call) Then I download the album image.
(Second async call) At the same time of the second step I connect to my server and download a set of tracks (the Spotify URI).
When the async connection to the server I create an ArrayList made of Track objects. Every Track object has its own properties and calls an async task (DownloadImageTask) which downloads the album image for the track.
Just before of creating the ArrayList (while the images are being downloaded), I create an adapter with 2 TextView and an ImageView and asign the ArrayList as data for the adapter of the ListView (MainActivity).
Just here comes my problem. I try to save as a property of the Tracks the ImageView, in order to, when I receive the DownloadImageTask, asign the Bitmap received to the setImageBitmap function of the ImageView. But right there the ImageView is null, so, I can't asign the Image.
I was thinking on a callback when the list is created for sending the pointer to the ImageView, but can't find the way. Otherwise I could test another way if you give me an idea.
I can post the code on demand (It is pretty much code, and I don't want to make a great question with a lot of unuseful code).
Thanks in advance.
Adapter code:
public class ContinueListAdapter extends ArrayAdapter<MyTrack> {
private int layoutResource;
public ContinueListAdapter(Context context, int layoutResource, List<MyTrack> tracksList){
super(context, layoutResource, tracksList);
this.layoutResource = layoutResource;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
view = layoutInflater.inflate(layoutResource, null);
}
MyTrack theTrack = getItem(position);
if (theTrack != null) {
TextView userData = (TextView) view.findViewById(R.id.userData);
TextView trackData = (TextView) view.findViewById(R.id.trackData);
ImageView albumImage = (ImageView) view.findViewById(R.id.albumImage);
userData.setText("Petición de: " + theTrack.getUserName());
trackData.setText(theTrack.getSongName()+" / "+theTrack.getAlbumName());
albumImage.setImageBitmap(theTrack.getBitmap()); //I did this first with the hope of getting the image when it was downloaded, no way.
theTrack.setImageView(albumImage); //So I passed the ImageView to a property of the Track object, but the ImageView reference is null
}
return view;
}
}
Instead of dealing with images use Glide or Picasso libraries. You will have background downloading, image caching, scaling and all the stuff handled by these libraries in usually 3-5 lines of code.
But right there the ImageView is null, so, I can't asign the Image
Even if your target image view at the moment download finishes is gone, you most likely will want to show the image again soon (i.e. user will get back to you activity or list row would be scrolled back to the screen) - since there's cache, it will be served from there next time you refer the same image.
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.
How do I update the text of a button to a listview through another class without modifying the text of the other buttons on the list?
Ex:
ITEM 1 [DOWNLOAD]
ITEM 2 [DOWNLOADING... 60%]
ITEM 3 [DOWNLOADING... 40%]
ITEM 4 [DOWNLOAD]
Actually, works but scrolling the listview, other buttons have your values changed too..
I need to create a list of mídia ready for download, but when I click in a download button, the download starts, the percent updates but other buttons have your text changed too...
I would like to update the text "downloadBt" buttons of the listView items through my class Downloader, making progress on them: Downloading ... 30%
What is the best form to make this?
Solution:
Create a new instance of List:
private static class ViewHolder {
protected ImageView cover;
protected TextView issueNumber;
protected TextView details;
protected Button downloadBt;
protected Button moreBt;
protected View convertView; <<<
protected ViewGroup parent; <<<
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
list.get(position).setListPosition(position);
if (convertView == null) {
LayoutInflater inflater = context.getLayoutInflater();
convertView = inflater.inflate(R.layout.list_item_list_issue, null);
viewHolder = new ViewHolder();
viewHolder.cover = (ImageView) convertView.findViewById(R.id.issue_list_item_cover);
viewHolder.issueNumber = (TextView) convertView.findViewById(R.id.issue_list_item_number);
viewHolder.details = (TextView) convertView.findViewById(R.id.issue_list_item_details);
viewHolder.downloadBt = (Button) convertView.findViewById(R.id.list_item_issue_download);
viewHolder.moreBt = (Button) convertView.findViewById(R.id.list_item_issue_more);
viewHolder.parent = parent;
viewHolder.convertView = convertView;
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.issueNumber.setText(list.get(position).getIssue());
viewHolder.downloadBt.setText(list.get(position).getDownloadStatus());
try{
vList.remove(position);
}catch(Exception e){
e.printStackTrace();
}
vList.add(position, viewHolder);
return convertView;
}
And in my Downloader class, send the position and the Adapter instance. I have been created a method refresh in my adapter class:
public void refresh(int position){
if((listView.getFirstVisiblePosition() <= position)&&(position <= listView.getLastVisiblePosition()))
getView(position, vList.get(position).convertView, vList.get(position).parent);
}
And this update only a selected item in the listview.
!!UPDATE!!
It is clear from the log that you are trying to update the button from a different thread other than UI thread. The UI objects/views should be updated only from the UI thread and UI thread should not be used for long running operations. These are basics you are supposed to know. I strongly recommend you go through the docs and/or videos for better understanding.
The solution is to use AsyncTask instead of Runnable and AsyncTask has a method progressUpdate() that runs on the UI thread. Alternatively, you can have your Runnable as a subclass in Activity and use the method runOnUiThread() for updating the view.
I also notice problem with your thread pools as you associate them with the objects in list view, which means, if there are 10 convertView objects, you'll have 10 thread pools with 50 threads. This will degrade the performance and you're essentially using only one thread from this pool. You need to get strong with your basics and revisit the solution.
Inside a list view, the item objects are reused, which is the convertView argument in the getview()method in the Adapter. So, when convertView is null, you create the item view object for the first time and as you scroll through, the same object is re-used instead of creating a new one as this saves CPU cycles( and hence battery) in terms of avoiding garbage collection and creating new objects. In other words, if you have a list of 100 items and only 10 items can be fit on your screen only 10 objects will be created even though you scroll through all the 100 items as the same 10 objects are re-used as the screen responds to scrolling. This is true for the child views as well(i.e., the buttons as well).
From your code, I see that you are using Button objects in your download threads. Button objects are also re-used. Check if this could be the problem.
Check this video if you haven't already.
The problem is that you are trying to update the button itself, you should update the items wrapped by adapter and call on the main thread adapter.notifyDataSetChanged(); in order to get your list view refreshed (trigger other calls to getView for visible positions). So your ComicDownloader should accept a comic object and a reference to the adapter itself.
Sundeep is right, convertView (toghether with the button in it) gets reused (and this is a normal and wanted behaviour) and you should'n rely on it.
This line: viewHolder.downloadBt.setText(list.get(position).getDownloadStatus()); should be called just before returning the convertView, and not only when convertView is null, doing so you make sure the returned view is up to date according to requested position, no matter if is reused or fresh created.
EDIT:
In your DownloadListener implementation, IssueListAdapter.this.notifyDataSetChanged(); should be called on the main thread. Currently, it's called directly from your run() call, wich is executed in background. Because this listener already knows which comic is updated, you can have a singleton for all your comics. Also, since your ComicDownloader is final, it will be related to first position that 'created' it, that means that wen you'll start a download, your button will update correct, but the comic downloaded behind may not be the one you think it is. To solve this, I recomend to create a ComicDownloader when button is clicked (you can also share one clickListener instance and retreive current comic for pressed button - setting a tag related to current position for button is a correct way to do it).
In your ComicDownloader class, executorService should be static (one pool shared across downloaders).
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.