繁体   English   中英

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

[英]java.lang.OutOfMemoryError with ArrayList.addAll()

我有一个线程列表,我已分页使用无限滚动功能,我(以及我的用户)遇到的问题是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();
}


}

再次预先感谢。

您的问题基本上可以归结为:

private void refreshPosts(ArrayList<Posts> newObjects) {

        postList.addAll(newObjects);
        populateUIWithData();

}

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

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

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

这将节省内存,但会为您带来更多管理代码。

您的适配器没有帖子列表,只会有帖子ID列表。 这应该更容易记忆。

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

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

  • 如果在那里,那就太好了。 这就是所谓的缓存命中 将帖子绑定到列表项视图。

  • 如果不是,则您有一个cache miss 您有工作要做。

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

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

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

一旦工作,就必须调整高速缓存的限制,以使其足够低以至于不会超出内存,但是又足够高以至于命中率/未命中率不会接近零。

顺便说一句,我注意到您每次创建一个帖子时都会犯一个错误,即创建一个新适配器并将其交给RecyclerView 您应该一次创建适配器,保留对其的引用并进行更新。 有一个添加一个帖子块然后调用notifyDataSetChanged()


节省内存的另一个想法是使用文本压缩。 如果问题更多是由于帖子的平均大小较大而不是帖子的数量过多导致的,那么除了LRU缓存之外,您还可以探索这种想法。

概念是您可以接收一定大小的帖子,使用ZipOutputStream将其写入缓冲区,然后将缓冲区保存在内存中。 当需要显示帖子时,可以使用ZipInputStream读取缓冲区以解压缩文本。 这里的问题是性能,因为压缩/解压缩占用大量CPU。 但是,如果问题确实很长,则可能需要考虑使用这种方法。


更好的方法:仅将帖子的第一部分另存为列表中的“概述”显示。 当用户单击列表项时,从服务器检索整个帖子并将其显示在另一个页面中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM