java.lang.OutOfMemoryError 与 ArrayList.addAll()

标签 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();
}


}

再次提前致谢。

最佳答案

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

private void refreshPosts(ArrayList<Posts> newObjects) {

        postList.addAll(newObjects);
        populateUIWithData();

}

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

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

LRU 缓存本质上是一个映射。项目使用 key 存储,就像 ID 一样。使用 LRU 缓存,您可以放入新项目,但一旦达到预先确定的限制,旧项目就会开始被推出,以便为新项目腾出空间。

这会节省内存,但会为您生成更多管理代码。

您的适配器不会有帖子列表,而只会有帖子 ID 列表。这应该更容易内存。

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

当您绑定(bind)到列表项 View 时,您可以使用 LRU 缓存中帖子的 ID 来查找帖子。

  • 如果有的话,那就太好了。这称为缓存命中。将帖子绑定(bind)到 列表项 View 。

  • 如果没有,则缓存未命中。您还有一些工作要做。

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

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

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

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

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

<小时/>

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

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

<小时/>

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

关于java.lang.OutOfMemoryError 与 ArrayList.addAll(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41020954/

相关文章:

java - 在更改所选操作选项卡之前添加确认操作对话框?

Android - 将旋转位图绘制到 Canvas 上的特定位置

Android In App Billing v3 不适用于 Nexus 7

android - 如何获取某一列中具有相同值的行列表?

java - 在play框架中动态创建Date

java - 在 Observable 中超时时发出项目

java - 如何使用TestNG创建多用户测试场景?

android - 免费的模糊逻辑库,无法在 android 上解析文件

java - 如果该行包含字符串匹配的任何部分,则使用扫描仪显示整行(Java)

java - 对使用 Java 中的通用类创建的 Arraylist 进行排序