【问题标题】:Picasso keeps reloading images while scrolling upwards in listview, loads slowly毕加索在列表视图中向上滚动时不断重新加载图像,加载缓慢
【发布时间】:2015-03-02 12:36:42
【问题描述】:

我一直在搜索 SO 主题以寻找答案,但无法从之前的讨论中找出我的问题。我有一个列表视图,它加载了大约 50 张图像(它曾经是大约 100 张,但这几乎没有加载任何图像)。在通过适配器从 api 端点获取我的 JSON 内容(包括图像 URL)后,我的代码将其放入列表视图中。

目前,毕加索有 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);
    }
}

编辑:

我已将我的代码更新为视图持有者模式,创建了两个单独的视图(一个用于带有图像的帖子,一个用于仅带有文本的帖子),还包括毕加索的新 scroll detection capabilities

我已经看到一些图像加载速度更快的改进,至少当视图在滚动时获得焦点时,图像现在更有可能加载。但是,在向上滚动那些曾经加载过的相同图像时,它们就会消失。感觉好像毕加索一次只加载 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.
        }
    }
    ...
}

问题出在哪里?我应该以某种方式在缓存中预加载这些图像吗?虽然我已经研究了毕加索的新优先功能,但我应该告诉毕加索以某种方式按照它们在我的列表视图中出现的顺序加载图像吗?有任何想法吗?如何在向上滚动时“保留”已加载的图像?

【问题讨论】:

  • 默认情况下,毕加索已经按照开发者文档here 的建议进行缓存。有关使用的缓存的一些讨论,请参阅this answer。如果您想检查缓存是否正常工作,请通过在您的 Picasso 实例上调用 setIndicatorsEnabled(true) 来启用 Picasso 的调试模式。指标描述为here
  • 我打开了指标,但无法精确定位。即使在包含 10 个项目的列表视图中减少了图像数量 (7),它仍然表现异常。图片在您向上滚动时会重新加载,并且看起来加载缓慢,除非它们位于列表视图上。
  • 为什么要在 imageView 上调用 setVisibility()。试试没有那个。为此,请使用 Picasso 中的占位符和错误图像,这可能是问题的一部分。我相信你可以用毕加索.load(null)。
  • 当列表视图项只是基于文本的帖子(无图像)时,我将隐藏图像视图,并在列表视图中的项目附加图像时将其设置为可见。如果我摆脱设置可见性,它不会显示一些图像。我已经在毕加索中使用了占位符。

标签: java android picasso


【解决方案1】:

我建议您使用 GLIDE 进行图像加载。由于 GLIDE 速度很快,并且具有缓存加载功能,您可以获得超快速的图像加载,使用 GLIDE,您可以获得很多功能..

在这里下载 https://github.com/bumptech/glide

【讨论】:

    【解决方案2】:

    使用:

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

    这解决了我的问题

    【讨论】:

      【解决方案3】:

      对毕加索使用调整大小

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

      //这肯定有帮助

      【讨论】:

        【解决方案4】:

        我会看两件事。

        第一是正在加载的图像的大小。我不知道 Picasso 中默认的最大缓存大小是多少,但听起来您可能仅使用几张图像就超过了它,导致其他图像被从缓存中逐出。

        第二个可能不是核心问题,但也有助于性能。 你做了很多 findViewById() 调用,这些调用相当昂贵。 查看“缓存”这些查找的“ViewHolder”模式。

        编辑 - 请参阅Jake Wharton's answer to a similar question 了解更多详情

        【讨论】:

        • 作为注释,我调整了我的图像大小并修复了我在@24x7 看到的问题。
        • 你能告诉我你如何在可变高度和固定宽度上调整图像大小的代码吗?另外, fit() 不应该完成同样的事情吗?我已经在 picasso 中加入了对它的调用
        【解决方案5】:

        Picasso 的内存缓存大小受到限制,因此在滚动长列表时不会产生内存不足错误。图像从内存缓存中取出后,将在从磁盘缓存或网络重新加载图像时显示占位符。

        默认情况下启用磁盘缓存,因此重新加载时间应该非常快。您可以使用setIndicatorsEnabled(true) 来查看图片是从哪里加载的。

        如果您发现 Picasso 正在从网络重新加载图像,这可能是从服务器发送的 HTTP 标头的问题。我不相信 Picasso 实际上将图像缓存在磁盘本身上,而是依赖于 HTTP 层,该层将遵循 no-cache 标头,并在过期时间过后从网络重新加载。

        【讨论】:

        • 图片URL的来源主要来自Parse,也有一些来自Facebook和imgur。所有图像都是直接 URL。我还将列表视图中的项目数减少到 10 - 7 个,其中有图像。同样的错误仍在发生。图像加载缓慢。如果您然后向下滚动并再次备份,它们会重新加载。
        • 它们是从网络重新加载还是从磁盘缓存重新加载?
        • 我不太确定。这是我的 logcat 的输出:pastebin.com/usNa77Hq,我向下和向上滚动了两次。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-31
        • 2014-08-29
        • 1970-01-01
        • 2014-09-06
        • 1970-01-01
        相关资源
        最近更新 更多