Download asynchronous image, check if image is processed

This question came to me after reading this: Performance recommendations (in particular, the part with the name "Async loading"). Basically, he tries to save information about the line in order to check if it has been processed and install only the downloaded image if the line is still visible. Here's how he maintains his position:

holder.position = position; new ThumbnailTask(position, holder) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null); 

Where is the ThumbnailTask ​​constructor:

 public ThumbnailTask(int position, ViewHolder holder) { mPosition = position; mHolder = holder; } 

In onPostExecute (), it performs the above check:

  if (mHolder.position == mPosition) { mHolder.thumbnail.setImageBitmap(bitmap); } 

I just don’t see how this gives any result. The holder and position are set in the constructor simultaneously with the same value (the position in the holder coincides with the position in the mPosition position). They do not change during AsyncTask (it is true that the position can change in getView (), but those stored in AsyncTask as private members are never managed). What am I missing here?

Also, maintaining a position in the first place does not seem like a good option: I believe that it is not guaranteed to be unique, and if I remember correctly, it is reset to 0 after scrolling. Am I thinking in the right direction?

+7
android android-listview android-asynctask
source share
4 answers

Background (you probably know this, but just in case): the adapter contains a collection of objects and uses the information from these objects to populate the views (each view is a position in the list). The list view displays a display of these views. For performance reasons, ListView will recycle views that are no longer visible as they scroll at the top or bottom of the list. Here's how to do it:

When the ListView needs a new view to display, it calls the getView adapter with the integer argument "position" to indicate which object in the adapter collection it wants to see (the position is just a number from 1 to N -1), where N is the number of objects in the adapter.

If he has any views that are no longer visible, he will pass one of them to the Adapter too, like "convertView". It says "reusing this old view, not creating a new one." Big gain in performance.

The code in the article attaches a ViewHolder to each view created by it, which, among other things, contains the position of the object requested by the ListView. In the code of the article, this position is hidden inside the ViewHolder along with a pointer to the field inside the view that will contain the image. ViewHolder is attached to the view as a tag (separate topic).

If the view is redesigned to hold another object (in a different position), the ListView will call Adapter.getView (newPosition, oldView ...) The code in the article will save the new position in the ViewHolder attached to the oldView (guess so far?) And start loading it A new image to put it in the view.

Now in this article, he launches AsyncTask to retrieve the data that must go into the view). This task has a position (from a call to getView) and a holder (from an oldView). The position indicates what data was requested. The owner tells you what data is currently to be posted in this view, and where it can be placed as soon as it appears.

If the view is redesigned again while AsyncTask is still running, the position in the holder will be changed so that these numbers do not match, and AsyncTask knows that the data is no longer needed.

Is this clearer?

+8
source share

When an AsyncTask is passed with a ViewHolder and position , it is assigned the value of position (say 5 ) and the value of the link (rather than a copy) for the ViewHolder. It also puts the current position in the ViewHolder (said 5 ), but the trick here is that for redesigned views, the old ViewHolder is also reused (in a related article):

 } else { holder = convertView.getTag(); } 

therefore, any code refers to this particular ViewHolder object, in fact, checks its position member value at the time the check is performed , and not at the time the object is created. Thus, checking onPostExecute makes sense because the position value passed to the task designer remains unchanged (in our case, it has a value of 5 ), because it is primitive, but the ViewHolder object can change its properties if the view is reused before reaching onPostExecute .

Note that we DO NOT copy the ViewHolder object in the task constructor, even if it looks like this. This is not how Java works :) See this article for clarification .

Also, maintaining a position does not seem like a good option: I believe that it is not guaranteed to be unique, and it is reset to zero after scrolling. It's true?

Not. A position here means an index in the * original dataset that is not displayed on the screen. Therefore, if you have 10 elements to display, but your screen only fits 3 at a time, your position will be in the range of 0-9, and the visibility of the lines does not matter.

+3
source share

As I understand it, you are trying to cancel the async-load-task of the image when viewing the view and no longer on the screen.

To do this, you can configure the RecyclerListener to list. It will be called when the listview does not need this view (when it is not on the screen), just before passing it as a redesigned adapter view.

inside this listener you can cancel the download task:

 theListView.setRecyclerListener(new RecyclerListener() { @Override public void onMovedToScrapHeap(View view) { for( ThumbnailTask task : listOfAllTasks ) task.viewRecycled(task); } }); 

and inside ThumbnailTask :

 public void viewRecycled(View v){ if(mHolder.theWholeView == v) v.cancel(); } 

Not for cancel.


Please note that this is not the best approach, as you should keep track of all your asynctask tasks. note that you can also cancel the task inside the adapter, where you will also get

 public View getDropDownView (int position, View recycledView, ViewGroup parent){ //.. your logic } 

but note that this may require you to highlight the ThumbnailTask inside the adapter, but this is not a good practice.


note that you can also use image download libraries that do everything for you, from asynchronous loading to caching. e.g.: https://github.com/nostra13/Android-Universal-Image-Loader

0
source share

Marcin’s accepted answer and message already perfectly describes what should happen. However, the linked web page does not work, and the google site in this section is also very vague and is just a link for people who already know about the “trick." "So, here is the missing part, for future links, that shows the necessary additions to getView ( )

 // The adapter getView method public View getView(int position, View convertView, ViewGroup parent) { // Define View that is going to be returned by Adapter View newViewItem; ViewHolder holder; // Recycle View if possible if (convertView == null) { // No view recycled, create a new one LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); newViewItem = inflater.inflate(R.layout.image_grid_view_item, null); // Attach a new viewholder holder = new ViewHolder(); holder.thumbnail = (ImageView) newViewItem.findViewById(R.id.imageGridViewItemThumbnail); holder.position = position; newViewItem.setTag(holder); } else { // Modify "recycled" viewHolder holder = (ViewHolder) convertView.getTag(); holder.thumbnail = (ImageView) convertView.findViewById(R.id.imageGridViewItemThumbnail); holder.position = position; // Re-use convertView newViewItem = convertView; } // Execute AsyncTask for image operation (load, decode, whatever) new LoadThumbnailTask(position, holder).execute(); // Return the ImageView return newViewItem; } // ViewHolder class, can be implemented inside adapter class static class ViewHolder { ImageView thumbnail; int position; } 
0
source share

All Articles