Picasso продолжает перезагружать изображения, прокручивая вверх в списке, медленно загружается

Я искал SO темы для ответов, но не смог понять мою проблему из предыдущего обсуждения. У меня есть просмотр списка, который загружает около 50 изображений (раньше было около 100, но это вообще не загружало никаких изображений). После получения моего содержимого JSON (включая URL-адрес изображения) из конечной точки API через адаптер мой код помещает его в представление списка.

В настоящее время, из 50 изображений, Пикассо будет загружать по одному изображению за раз, когда я прокручиваю канал вниз. Я чувствую, как будто фиксация прокрутки на одном элементе в списке сделает загрузку этого изображения быстрее. Однако, когда я прокручиваю вверх, он помещает заполнитель обратно и снова загружает изображение. Есть ли способ решить эту проблему?

public class MainActivity extends Activity {
    private List<Post> myPosts = new ArrayList<Post>();
    protected String[] mBlogPostTitles;
    public static final String TAG = MainActivity.class.getSimpleName();//prints name of class without package name

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(isNetworkAvailable()) {
            GetBlogPostsTask getBlogPostsTask = new GetBlogPostsTask(); // new thread
            getBlogPostsTask.execute();// don't call do in background directly
        }else{
            Toast.makeText(this, "Network is unavailable", Toast.LENGTH_LONG).show();
        }
    }
    public boolean isNetworkAvailable() {
        ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = manager.getActiveNetworkInfo();

        boolean isAvailable = false;

        if(networkInfo != null && networkInfo.isConnected()){
            isAvailable = true;
        }

        return isAvailable;
    }
    private void populateListView() {
        ArrayAdapter<Post> adapter = new MyListAdapter();
        ListView list = (ListView) findViewById(R.id.postsListView);
        list.setAdapter(adapter);
    }

    private class MyListAdapter extends ArrayAdapter<Post>{
        public MyListAdapter() {
            super(MainActivity.this, R.layout.item_view, myPosts);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            // make sure we have a view to work with
            View itemView = convertView;
            if (itemView == null) {
                itemView = getLayoutInflater().inflate(R.layout.item_view, parent,false);
            }
            //find the post to work with
            Post currentPost = myPosts.get(position);
            Context context = itemView.getContext();

            String imageURL = currentPost.getImage();
            if(imageURL == null || imageURL.isEmpty()){
                ImageView imageView = (ImageView) itemView.findViewById(R.id.item_image);
                imageView.setVisibility(View.GONE);
            }else{
                ImageView imageView = (ImageView) itemView.findViewById(R.id.item_image);
                Picasso.with(context)
                        .load(imageURL)
                        .tag(context)
                        .placeholder(R.drawable.kanye8080s)
                        .error(R.drawable.stadiumarcadium)
                        .into(imageView);
                imageView.setVisibility(View.VISIBLE);
            }

            //Username
            TextView userText = (TextView) itemView.findViewById(R.id.item_txtUser);
            userText.setText(currentPost.getUser());

            //Time of post
            TextView timeText = (TextView) itemView.findViewById(R.id.item_txtTime);
            timeText.setText("" + currentPost.getTime());

            //The actual post
            TextView postText = (TextView) itemView.findViewById(R.id.item_txtPost);
            postText.setText("" + currentPost.getPost());

            //The actual post
            TextView likesText = (TextView) itemView.findViewById(R.id.item_txtLikes);
            likesText.setText("" + currentPost.getLikes());

            return itemView;
        }
    }

    private class GetBlogPostsTask extends AsyncTask<Object, Void, List> {

        @Override
        protected List doInBackground(Object[] params) {

            int responseCode = -1;//need to have this variable outside scope of try/catch block
            JSONObject jsonResponse = null;
            StringBuilder builder = new StringBuilder();
            HttpClient client = new DefaultHttpClient();
            HttpGet httpget = new HttpGet(""); /// api endpoint redacted

            try {

                HttpResponse response = client.execute(httpget);
                StatusLine statusLine = response.getStatusLine();
                responseCode = statusLine.getStatusCode();

                if(responseCode == HttpURLConnection.HTTP_OK){ //could have used just 200 value
                    HttpEntity entity = response.getEntity();
                    InputStream content = entity.getContent();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(content));
                    String line;
                    while((line = reader.readLine()) != null){
                        builder.append(line);
                    }

                    jsonResponse = new JSONObject(builder.toString());

                    JSONArray jsonPosts = jsonResponse.getJSONArray("posts");
                    for(int i=0; i < jsonPosts.length(); i++ ){
                        JSONObject jsonPost = jsonPosts.getJSONObject(i);

                        int post_id = Integer.parseInt(jsonPost.getString("id"));
                        String post_user = jsonPost.getString("user");
                        String post_account = jsonPost.getString("account");
                        int post_time = Integer.parseInt(jsonPost.getString("time"));
                        String post_post = jsonPost.getString("post");
                        String post_image = jsonPost.getString("image");
                        int post_likes = Integer.parseInt(jsonPost.getString("likes"));

                        myPosts.add(new Post(post_id, post_user, post_account, post_time, post_post, post_image, "profile picture here", post_likes));
                    }
                }else{
                    Log.i(TAG, "Unsuccessful HTTP Response Code: " + responseCode);
                }
            }
            catch (MalformedURLException e){
                Log.e(TAG, "Exception caught");
            }
            catch (IOException e){
                Log.e(TAG, "Exception caught");
            }
            catch (Exception e){//must be in this order, this is the last, general catch
                Log.e(TAG, "Exception caught", e);
            }

            return null;
        }
        @Override
        protected void onPostExecute(List result) {
            // call populateListView method here
            populateListView();
            super.onPostExecute(result);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

EDIT:

Я обновил свой код в виде шаблона держателя вида, создал два отдельных вида (один для сообщения с изображением, другой для сообщения только с текстом), а также включил новые возможности обнаружения прокрутки в Picasso.

Я видел улучшение в загрузке некоторых изображений быстрее, по крайней мере, когда вид сфокусирован во время прокрутки, изображение теперь с большей вероятностью загружается. Однако при прокрутке вверх те самые изображения, которые были когда-то загружены, исчезают. Такое ощущение, что Пикассо загружает только 4-5 изображений за раз и заменяет уже загруженные, чтобы освободить место. Мой обновленный код ниже:

public class MainActivity extends Activity {
    private List<Post> myPosts = new ArrayList<Post>();
    protected String[] mBlogPostTitles;
    public static final String TAG = MainActivity.class.getSimpleName();//prints name of class without package name

    ...

    private void populateListView() {
        Activity activity = MainActivity.this;

        ArrayAdapter<Post> adapter = new MyListAdapter();
        ListView list = (ListView) findViewById(R.id.postsListView);
        list.setAdapter(adapter);
        list.setOnScrollListener(new SampleScrollListener(activity));
    }

    private class MyListAdapter extends ArrayAdapter<Post>{
        public MyListAdapter() {
            super(MainActivity.this, R.layout.item_view, myPosts);
        }

        @Override
        public int getViewTypeCount() {
            return 2;
        }

        @Override
        public int getItemViewType(int position) {
            String imageURL = myPosts.get(position).getImage();
            if(imageURL == null || imageURL.isEmpty()){
                return 1; // text based
            }else{
                return 0; // image based
            }
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            PostViewHolder holder;

            int type = getItemViewType(position);

            View itemView = convertView;

            // make sure we have a view to work with
            if (itemView == null) {
                holder = new PostViewHolder();
                if(type == 1) {
                    itemView = getLayoutInflater().inflate(R.layout.item_view, parent, false);
                } else {
                    itemView = getLayoutInflater().inflate(R.layout.image_post_view, parent, false);
                    holder.image = (ImageView) itemView.findViewById(R.id.item_image);
                }
                holder.user = (TextView) itemView.findViewById(R.id.item_txtUser);
                holder.time = (TextView) itemView.findViewById(R.id.item_txtTime);
                holder.post = (TextView) itemView.findViewById(R.id.item_txtPost);
                holder.likes = (TextView) itemView.findViewById(R.id.item_txtLikes);

                itemView.setTag(holder);
            } else {
                holder = (PostViewHolder) itemView.getTag();
            }

            //find the post to work with
            Post currentPost = myPosts.get(position);

            if(type != 1) {
                Context context = itemView.getContext();
                String imageURL = currentPost.getImage();

                Picasso.with(context).setIndicatorsEnabled(true);
                //Picasso.with(context).setLoggingEnabled(true);
                Picasso.with(context)
                        .load(imageURL)
                        .tag(context)
                        .placeholder(R.drawable.kanye8080s)
                        //.skipMemoryCache()
                        .error(R.drawable.stadiumarcadium)
                        .fit()
                        .into(holder.image);
            }
            //Username
            holder.user.setText(currentPost.getUser());

            //Time of post
            holder.time.setText("" + currentPost.getTime());

            //The actual post
            holder.post.setText(currentPost.getPost());

            //Likes for the post
            holder.likes.setText("" + currentPost.getLikes());

            return itemView;
        }
    }
    public class SampleScrollListener implements AbsListView.OnScrollListener {
        private final Context context;

        public SampleScrollListener(Context context) {
            this.context = context;
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            final Picasso picasso = Picasso.with(context);
            if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_TOUCH_SCROLL) {
                picasso.resumeTag(context);
            } else {
                picasso.pauseTag(context);
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                             int totalItemCount) {
            // Do nothing.
        }
    }
    ...
}

Откуда возникла проблема? Стоит ли как-то предварительно загружать эти изображения в кеш? Хотя я уже рассмотрел новую приоритетную функцию Picasso, должен ли я как-то сказать Picasso загружать изображения в том порядке, в котором они появляются в моем списке просмотра? Есть идеи? Как я могу "сохранить" изображения, которые уже были загружены при прокрутке вверх?

Ответ 1

изменить размер с помощью picasso

 Picasso.with(context)
.load(imageURL)
.tag(context)
.placeholder(R.drawable.kanye8080s)
.error(R.drawable.stadiumarcadium)
.into(imageView)
.resize(x,y);

//Это определенно поможет

Ответ 2

Размер кеша памяти Picasso ограничен, поэтому при прокрутке длинных списков он не генерирует ошибок в памяти. Как только изображения выходят из кеша памяти, заполнитель будет отображаться, когда изображение будет перезагружено либо из кеша диска, либо из сети.

Кэш диска включен по умолчанию, поэтому время перезагрузки должно быть очень быстрым. Вы можете использовать setIndicatorsEnabled(true), чтобы узнать, откуда загружаются изображения.

Если вы обнаружите, что Picasso перезагружает изображения из сети, это, вероятно, проблема с отправкой HTTP-заголовков с сервера. Я не считаю, что Picasso фактически кэширует изображения на самом диске, вместо этого полагаясь на уровень HTTP, который будет подчиняться заголовку без кэша и будет перезагружаться из сети, если истекает время истечения срока.

Ответ 3

Я бы посмотрел на две вещи.

Номер один - это размеры загружаемых изображений. Я не знаю, каков максимальный размер кеша по умолчанию в Picasso, но похоже, что вы можете превысить его с помощью всего лишь нескольких изображений, в результате чего остальные будут выведены из кеша.

Номер два, вероятно, не является основной проблемой, но также способствует повышению производительности. Вы делаете много вызовов findViewById(), которые довольно дороги. Посмотрите на шаблон "ViewHolder" для "кэширования" этих запросов.

Изменить - см. Джейк Уортон ответить на аналогичный вопрос для более подробной информации

Ответ 4

Я предлагаю вам использовать GLIDE для загрузки изображений. Поскольку GLIDE работает быстро и с функцией загрузки кеша, вы можете получить супербыструю загрузку изображений. С GLIDE вы получаете множество функций.

Загрузить здесь https://github.com/bumptech/glide

Ответ 5

Использование:

recyclerview.getRecycledViewPool().setMaxRecycledViews(0, 0);

Это решило мою проблему