带有ArrayList.addAll()的java.lang.OutOfMemoryError

时间:2016-12-07 15:10:15

标签: java android arraylist android-recyclerview out-of-memory

我有一个线程列表,我已分页使用无限滚动我遇到的问题(我的用户)是OutOfMemoryError: Failed to allocate a [x] byte allocation with [y] free bytes and [z] until OOM. x,y和z属性因用户而异但原因不同该错误总是在同一个地方,当我刷新帖子时。我完全不在我的深度,因为我不知道如何优化我的代码或使它成为现实,所以这不会发生。因为这是我的应用程序目前最大的崩溃。我已在下方发布了PostFragment,请参阅refreshPosts(ArrayList<Posts> newObjects)方法,因为这是发生崩溃的地方。

public class PostFragment extends Fragment implements View.OnClickListener {

private View mRootView;
private GridLayoutManager mLayoutManager;
private ThreadItem mThreads;
private PostItem mPost;
private PostAdapter mAdapter;
private PostResponse mData;
private EmoticonResponse mEmoticon;
private PostFeedDataFactory mDataFactory;
private EmoticonFeedDataFactory mEmoticonDataFactory;
private static PostFragment mCurrentFragment;
private int REQUEST_CODE;

//Flip
private boolean isFlipped = false;
private Animation flipAnimation;

@BindView(R.id.postsRecyclerView)
RecyclerView mRecyclerView;

@BindView(R.id.toolbarForPosts)
Toolbar mToolbar;

@BindView(R.id.threadText)
TextView mThreadText;
@BindView(R.id.flipText)
TextView mFlipTextView;
@BindView(R.id.shareText)
TextView mShareTextView;
@BindView(R.id.replyText)
TextView mReplyTextView;

@BindView(R.id.scrimColorView)
View mBackgroundView;

@BindView(R.id.fabMenu)
FloatingActionButton mFabMenu;
@BindView(R.id.flipFab)
FloatingActionButton mFlipFab;
@BindView(R.id.shareFab)
FloatingActionButton mShareFab;
@BindView(R.id.replyFab)
FloatingActionButton mReplyFab;

//Collapsing Toolbar
@BindView(R.id.postParentAppBarLayout)
AppBarLayout postAppBarLayout;
@BindView(R.id.postCollapseToolbar)
CollapsingToolbarLayout postCollapseToolbarLayout;
@BindView(R.id.mainImageContainer)
ViewGroup mainContainer;

//Back to top
@BindView(R.id.backToTopButton)
Button mBackToTop;

public static boolean isFromReply;

//FAB
private boolean mIsFabOpen = false;
private Animation fab_open, fab_close, rotate_forward, rotate_backward;

//Pagination
private int mCurrentPage = 1;
private ArrayList<Posts> postList = new ArrayList<>();
private boolean mIsLoading = false;
private boolean mIsLastPage = false;


public static PostFragment newInstance(@NonNull ThreadItem threadItem) {

    Bundle args = new Bundle();
    args.putParcelable("ThreadItem", Parcels.wrap(threadItem));

    mCurrentFragment = new PostFragment();

    mCurrentFragment.setArguments(args);

    isFromReply = false;

    return mCurrentFragment;
}

public static PostFragment newPostInstance(@NonNull PostItem postItem) {

    Bundle args = new Bundle();
    args.putParcelable("PostItemFromCompose", Parcels.wrap(postItem));

    mCurrentFragment = new PostFragment();

    mCurrentFragment.setArguments(args);

    isFromReply = true;

    return  mCurrentFragment;
}

public PostFragment() {

}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    mRootView = inflater.inflate(R.layout.fragment_post, container, false);

    if (savedInstanceState == null) {
        ButterKnife.bind(this, mRootView);
        initUI();
    }
    return mRootView;

}

private void initUI() {
    //UI Setup
    mLayoutManager = new GridLayoutManager(getActivity(), 1);
    mRecyclerView.setLayoutManager(mLayoutManager);
    mDataFactory = new PostFeedDataFactory(getActivity());
    mEmoticonDataFactory = new EmoticonFeedDataFactory(getActivity());
    TextView textThreadTopic = (TextView) mRootView.findViewById(R.id.threadTopic);
    TextView textNumPosts = (TextView) mRootView.findViewById(R.id.numPosts);

    //FAB onClick Set-Up
    mFabMenu.setOnClickListener(this);
    mShareFab.setOnClickListener(this);
    mReplyFab.setOnClickListener(this);
    mFlipFab.setOnClickListener(this);

    //FAB Animation Set up
    fab_open = AnimationUtils.loadAnimation(getActivity().getApplicationContext(),
            R.anim.fab_open);
    fab_close = AnimationUtils.loadAnimation(getActivity().getApplicationContext(),
            R.anim.fab_close);
    rotate_forward = AnimationUtils.loadAnimation(getActivity().getApplicationContext(),
            R.anim.rotate_forward);
    rotate_backward = AnimationUtils.loadAnimation(getActivity().getApplicationContext(),
            R.anim.rotate_backward);

    //Toolbar
    ((AppCompatActivity) getActivity()).setSupportActionBar(mToolbar);
    ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayShowTitleEnabled(false);
    mToolbar.setNavigationIcon(R.drawable.ic_back_white);
    mToolbar.invalidate();

    mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            getActivity().finish();
        }
    });

    //Load Parcel
    Intent intent = getActivity().getIntent();
    mThreads = Parcels.unwrap(getArguments().getParcelable("ThreadItem"));

    mPost = Parcels.unwrap(getArguments().getParcelable("PostItemFromCompose"));

    if (mThreads != null) {

        if (mThreads.getName() != null) {
            mThreadText.setText(mThreads.getName());
        }

        if (mThreads.getTopic_name() != null) {
            textThreadTopic.setText(mThreads.getTopic_name());
        }

        if (mThreads.getNum_posts() != null) {
            int numPosts = Integer.parseInt(mThreads.getNum_posts());
            if (numPosts > 1000) {
                textNumPosts.setText("1K");
            } else {
                textNumPosts.setText(mThreads.getNum_posts());
            }
        }
    }

    postAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {

        boolean isShow = false;
        int scrollRange = -1;

        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            if (scrollRange == -1) {
                scrollRange = appBarLayout.getTotalScrollRange();
            }

            if (scrollRange + verticalOffset == 0) {
                postCollapseToolbarLayout.setTitle("Threads");
                mainContainer.setVisibility(View.INVISIBLE);
                isShow = true;
            } else if (isShow) {
                postCollapseToolbarLayout.setTitle("");
                isShow = false;
                mainContainer.setVisibility(View.VISIBLE);
            }

        }
    });

    flipAnimation =
            AnimationUtils.loadAnimation(getActivity().getApplicationContext(), R.anim.flip);


    loadData(true, 1);
}

private void loadData(final boolean firstLoad, int readDirection) {

    if (isFromReply) {

        if (mPost.getThread_id() != null) {

            mDataFactory.getPostFeed(mPost.getThread_id(), readDirection, mCurrentPage,
                    new PostFeedDataFactory.PostFeedDataFactoryCallback() {
                        @Override
                        public void onPostDataReceived(PostResponse response) {
                            mData = response;

                            if (mData.getItems() != null) {
                                for (int i = 0; i < mData.getItems().size(); i++) {
                                    Posts singlePost = response.getItems().get(i);
                                    postList.add(singlePost);
                                }
                                if (firstLoad) {
                                    mIsLoading = false;
                                    mData.getItems().clear();
                                    mData.getItems().addAll(postList);


                                    mEmoticonDataFactory.getEmoticonFeed(
                                            new EmoticonFeedDataFactory.EmoticonFeedDataFactoryCallback() {
                                                @Override
                                                public void onEmoticonDataReceived(EmoticonResponse response) {
                                                    mEmoticon = response;
                                                    populateUIWithData();
                                                }

                                                @Override
                                                public void onEmoticonDataFailed(Exception exception) {

                                                }
                                            });

                                } else {
                                    mIsLoading = false;
                                    refreshPosts(postList);
                                }

                                if (mData.getItems().size() > 0) {
                                    if (Integer.valueOf(mData.getTotalPosts()) >= response.getItems().size()) {
                                        mCurrentPage++;
                                    } else {
                                        mIsLastPage = true;
                                    }
                                }

                            }
                        }

                        @Override
                        public void onPostDataFailed(Exception exception) {

                            customToast("Error: " + exception.toString());
                        }
                    });

        }

    } else {

        if (mThreads.getId() != null)
            mDataFactory.getPostFeed(mThreads.getId(), readDirection, mCurrentPage,
                    new PostFeedDataFactory.PostFeedDataFactoryCallback() {
                        @Override
                        public void onPostDataReceived(PostResponse response) {
                            mData = response;

                            if (mData.getItems() != null) {
                                for (int i = 0; i < mData.getItems().size(); i++) {
                                    Posts singlePost = response.getItems().get(i);
                                    postList.add(singlePost);
                                }
                                if (firstLoad) {
                                    mIsLoading = false;
                                    mData.getItems().clear();
                                    mData.getItems().addAll(postList);


                                    mEmoticonDataFactory.getEmoticonFeed(
                                            new EmoticonFeedDataFactory.EmoticonFeedDataFactoryCallback() {
                                                @Override
                                                public void onEmoticonDataReceived(EmoticonResponse response) {
                                                    mEmoticon = response;
                                                    populateUIWithData();
                                                }

                                                @Override
                                                public void onEmoticonDataFailed(Exception exception) {

                                                }
                                            });

                                } else {
                                    mIsLoading = false;
                                    refreshPosts(postList);
                                }

                                if (mData.getItems().size() > 0) {
                                    if (Integer.valueOf(mData.getTotalPosts()) >= response.getItems().size()) {
                                        mCurrentPage++;
                                    } else {
                                        mIsLastPage = true;
                                    }
                                }

                            }
                        }

                        @Override
                        public void onPostDataFailed(Exception exception) {

                            customToast("Error: " + exception.toString());
                        }
                    });
    }


}

private void populateUIWithData() {


    ImageButton moreOptionsButton = (ImageButton) mRootView.findViewById(R.id.moreOptions);

    moreOptionsButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            PopupMenu popupMenu = new PopupMenu(v.getContext(), v);
            popupMenu.inflate(R.menu.thread_options);
            popupMenu.getMenu();
            popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    switch (item.getItemId()) {

                        case R.id.watch:
                            WatchedThreadsRequestData watchedThreadsRequestData = new WatchedThreadsRequestData(getActivity());
                            watchedThreadsRequestData.setWatchedThread(mThreads.getId(), new WatchedThreadsRequestData.WatchedThreadsFeedback() {
                                @Override
                                public void onWatchedRequestReceived(ThreadResponse response) {

                                    customToast("Thread watched");

                                }

                                @Override
                                public void onWatchedRequestFailed(Exception exception) {

                                    customToast("Thread wasn't watched: " + exception.toString());

                                }
                            });
                            return true;
                        case R.id.shareThread:
                            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
                            sharingIntent.putExtra(Intent.EXTRA_TEXT, mThreads.getName() + " - " + Constants.LIVE_URL +
                                    "talk/" + mThreads.getTopic_url() + '/' + mThreads.getThread_url());
                            sharingIntent.setType("text/plain");
                            getActivity().startActivity(Intent.createChooser(sharingIntent, "Share via"));
                            return true;
                        case R.id.hideThread:
                            customToast("Hide: coming soon");
                            return true;
                        default:
                            customToast("Somethings Wrong");
                            return true;
                    }
                }
            });
            setForceShowIcon(popupMenu);
            popupMenu.show();

        }
    });


    if (mAdapter == null) {
        mAdapter = new PostAdapter(getActivity(), mData, mEmoticon);
        mRecyclerView.setAdapter(mAdapter);
    } else {
        mAdapter.setData(mData.getItems());
        mAdapter.notifyDataSetChanged();
    }

    mRecyclerView.addOnScrollListener(paginationListener);

}

public static void setForceShowIcon(PopupMenu popupMenu) {
    try {
        Field[] fields = popupMenu.getClass().getDeclaredFields();
        for (Field field : fields) {
            if ("mPopup".equals(field.getName())) {
                field.setAccessible(true);
                Object menuPopupHelper = field.get(popupMenu);
                Class<?> classPopupHelper = Class.forName(menuPopupHelper
                        .getClass().getName());
                Method setForceIcons = classPopupHelper.getMethod(
                        "setForceShowIcon", boolean.class);
                setForceIcons.invoke(menuPopupHelper, true);
                break;
            }
        }
    } catch (Throwable e) {
        e.printStackTrace();
    }
}

private RecyclerView.OnScrollListener paginationListener = new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

        boolean hasEnded = newState == SCROLL_STATE_IDLE;

        if (hasEnded) {
            mFabMenu.show();
            mFabMenu.setClickable(true);
        } else {
            if (mIsFabOpen)
                closeMenu();
            mFabMenu.hide();
            mFabMenu.setClickable(false);
        }

    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        int visibleItemCount = mLayoutManager.getChildCount();
        int totalItemCount = mLayoutManager.getItemCount();
        int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();

        if (!mIsLoading && !mIsLastPage) {
            if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount) {
                loadMoreItems();
            }
        }

        //Back to top
        if (mLayoutManager.findLastVisibleItemPosition() == totalItemCount - 1) {
            mBackToTop.setVisibility(View.VISIBLE);
            mBackToTop.setClickable(true);

            mBackToTop.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mLayoutManager.scrollToPositionWithOffset(0,0);
                }
            });

        } else {
            mBackToTop.setVisibility(View.GONE);
            mBackToTop.setClickable(false);
        }

    }



};

private void loadMoreItems() {
    if (!isFlipped) {
        mIsLoading = true;
        loadData(false, 1);
    } else {
        mIsLoading = true;
        loadData(false, -1);
    }

}

private void refreshPosts(ArrayList<Posts> newObjects) {

        postList.addAll(newObjects);
        populateUIWithData();

}

@Override
public void onClick(View v) {
    int id = v.getId();

    switch (id) {
        case R.id.fabMenu:
            animateFAB();
            break;
        case R.id.shareFab:
            share();
            break;
        case R.id.replyFab:
            reply();
            break;
        case R.id.flipFab:
            flip();
            break;
    }

}

public void animateFAB() {

    if (mIsFabOpen) {
        closeMenu();
    } else {
        mFabMenu.startAnimation(rotate_forward);
        mReplyFab.startAnimation(fab_open);
        mShareFab.startAnimation(fab_open);
        mFlipFab.startAnimation(fab_open);

        mReplyFab.setClickable(true);
        mShareFab.setClickable(true);
        mFlipFab.setClickable(true);

        mFlipTextView.setVisibility(View.VISIBLE);
        mShareTextView.setVisibility(View.VISIBLE);
        mReplyTextView.setVisibility(View.VISIBLE);

        mBackgroundView.setVisibility(View.VISIBLE);

        mIsFabOpen = true;

    }
}

private void closeMenu() {
    mFabMenu.startAnimation(rotate_backward);
    mReplyFab.startAnimation(fab_close);
    mShareFab.startAnimation(fab_close);
    mFlipFab.startAnimation(fab_close);

    mReplyFab.setClickable(false);
    mShareFab.setClickable(false);
    mFlipFab.setClickable(false);

    mFlipTextView.setVisibility(View.INVISIBLE);
    mShareTextView.setVisibility(View.INVISIBLE);
    mReplyTextView.setVisibility(View.INVISIBLE);

    mBackgroundView.setVisibility(View.INVISIBLE);

    mIsFabOpen = false;
}

private void reply() {

    PreferenceConnector.writeString(getActivity().getApplicationContext(), "threadID", mThreads.getId());
    PreferenceConnector.writeString(getActivity().getApplicationContext(), "threadTitle", mThreads.getName());

    if (PreferenceConnector.readString(getActivity(), "authToken") == null ||
            PreferenceConnector.readString(getActivity(), "authToken").equalsIgnoreCase("skip")) {

        final AlertDialog.Builder loginDialog = new AlertDialog.Builder(getActivity());

        loginDialog.setTitle("Please log in");
        loginDialog.setMessage("You need to be logged in to reply");
        loginDialog.setPositiveButton("Log in", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent intent = new Intent(getActivity().getApplicationContext(), LoginActivity.class);
                startActivity(intent);

            }
        });

        loginDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });

        loginDialog.show();


    } else {
        closeMenu();
        Intent intent = new Intent(getActivity().getApplicationContext(), NewPostActivity.class);
        intent.putExtra("Threads", Parcels.wrap(mThreads));
        getActivity().finish();
        startActivityForResult(intent, REQUEST_CODE);
    }

}

private void share() {
    Intent sharingIntent = new Intent(Intent.ACTION_SEND);
    sharingIntent.putExtra(Intent.EXTRA_TEXT, mThreads.getName() + " - " + Constants.LIVE_URL +
            "talk/" + mThreads.getTopic_url() + '/' + mThreads.getThread_url());
    sharingIntent.setType("text/plain");
    startActivity(Intent.createChooser(sharingIntent, "Share via"));
}

private void flip() {

    if (!isFlipped) {


        mAdapter.clearAll();
        isFlipped = true;
        mRecyclerView.startAnimation(flipAnimation);
        loadData(false, -1);
        closeMenu();

    } else {


        mAdapter.clearAll();
        isFlipped = false;
        mRecyclerView.startAnimation(flipAnimation);
        loadData(true, 1);
        closeMenu();
    }

}

private void customToast(String toastMessage) {

    LayoutInflater inflater = getActivity().getLayoutInflater();
    View layout = inflater.inflate(R.layout.custom_toast,
            (ViewGroup) getActivity().findViewById(R.id.toastContainer));
    TextView customToastText = (TextView) layout.findViewById(R.id.customToastText);
    customToastText.setText(toastMessage);

    Toast toast = new Toast(getActivity().getApplicationContext());
    toast.setGravity(Gravity.BOTTOM, 0, 25);
    toast.setDuration(Toast.LENGTH_LONG);
    toast.setView(layout);
    toast.show();

}

@Override
public void onResume() {
    super.onResume();
    if (mData != null && mAdapter != null) {

            mAdapter.notifyDataSetChanged();

    }
    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
                if (mIsFabOpen) {
                    closeMenu();
                } else {
                    getActivity().finish();
                }

                return true;
            }
            return false;
        }
    });
}

public void updateView() {
    mAdapter.notifyDataSetChanged();
}


}

再次提前感谢。

1 个答案:

答案 0 :(得分:1)

你的问题基本归结为:

private void refreshPosts(ArrayList<Posts> newObjects) {

        postList.addAll(newObjects);
        populateUIWithData();

}

列表只能变大,永远不会变小。如果服务器有很多帖子,那么OutOfMemory几乎是不可避免的。

解决此问题的一种方法是使用LRU(最近最少使用)缓存。您可以使用实用程序类:android.util.LruCache

LRU Cache本质上是一个Map。项目与密钥一起存储,如ID。使用LRU缓存,您可以放入新项目,但是一旦达到预定限制,旧项目就会被推出以便为新项目腾出空间。

这将节省内存,但为您制作更多管理代码。

您的适配器不会包含帖子列表,而只会包含帖子ID列表。这应该更容易记忆。

当用户滚动并收集更多帖子时,您会将帖子ID添加到列表中,并使用帖子ID将帖子映射到LRU缓存中。

当您绑定到列表项视图时,您使用LRU缓存中的帖子ID查找帖子。

  • 如果它在那里,很棒。这称为缓存命中。将帖子绑定到 列表项视图。

  • 如果没有,那么您有缓存未命中。你有一些工作要做。

    • 启动服务器请求以按ID检索帖子。我看到你当前的代码只是检索帖子的块,所以你在这里需要一些新的服务器代码。

    • 请求完成后,将帖子放入LRU缓存中,并使用adapter.notifyItemChanged()让适配器知道您的项目已更改。除非用户滚动超出它,否则RecyclerView应尝试再次与列表项视图绑定。这一次,您应该获得缓存命中。

这是基本的想法。我写了一些代码,但由于我无法看到你的模型类,数据工厂和适配器类,所以我仍然有很多问题。

一旦你有了工作,你必须调整缓存的限制,使其足够低,不要超出内存,但要足够高,你的命中/未命中率不会接近于零。

顺便说一句,我注意到你错误地创建了一个新的适配器,并在每次收到一个帖子时将其交给RecyclerView。您应该创建一次适配器,保留对它的引用并更新它。有一个方法可以添加一个帖子,然后调用notifyDataSetChanged()

节省内存的另一个想法是使用文本压缩。如果问题更多的是帖子的平均大小而不是大量帖子,那么除了LRU缓存之外,您可能会探索这个想法。

这个概念是你可以获取超过一定大小的帖子,使用ZipOutputStream将它们写入缓冲区,然后将缓冲区保存在内存中。在显示帖子的时候,您使用ZipInputStream读取缓冲区以解压缩文本。这里的问题是性能,因为压缩/解压缩是CPU密集型的。但如果问题确实很长,那么这种方法可能需要考虑。

更好的方法:只保存帖子的第一部分作为&#34;概述&#34;显示在列表中。当用户单击列表项时,从服务器检索整个帖子并在另一页中显示该帖子。