RecyclerView#Adapter使用中的兩個陷阱

其實就是Adapter中可以被覆寫的兩個方法

1、onDetachedFromRecyclerView

看下方法說明

        /**
         * Called by RecyclerView when it stops observing this Adapter.
         *
         * @param recyclerView The RecyclerView instance which stopped observing this adapter.
         * @see #onAttachedToRecyclerView(RecyclerView)
         */
        public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
        }

在RecyclerView不再觀察這個Adapter時被調(diào)用。
與之對應(yīng)的是onAttachedToRecyclerView

        /**
         * Called by RecyclerView when it starts observing this Adapter.
         * <p>
         * Keep in mind that same adapter may be observed by multiple RecyclerViews.
         *
         * @param recyclerView The RecyclerView instance which started observing this adapter.
         * @see #onDetachedFromRecyclerView(RecyclerView)
         */
        public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        }

在RecyclerView開始觀察這個Adapter時,被調(diào)用。

通常我們的理解是這樣的:
頁面進(jìn)入時,顯示RecyclerView,調(diào)用onAttachedToRecyclerView,做一些注冊工作;
頁面退出時,銷毀RecyclerView,調(diào)用onDetachedFromRecyclerView,做一些解注冊和其他資源回收的操作。

而實際上,這兩個方法的調(diào)用時機是:

    public void setAdapter(Adapter adapter) {
        // bail out if layout is frozen
        ...
        setAdapterInternal(adapter, false, true);
        ...
    }

    private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);//這里onDetachedFromRecyclerView
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);//這里onAttachedToRecyclerView
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
    }

可以看到,在調(diào)用setAdapter方法時,新設(shè)置的Adapter會調(diào)用onAttachedToRecyclerView,原有的Adapter會調(diào)用onDetachedFromRecyclerView。
所以如果覆寫了onDetachedFromRecyclerView,為了確保被調(diào)用,需要在頁面退出時,手動調(diào)用setAdapter(null)。

2、onViewDetachedFromWindow

方法說明:

        /**
         * Called when a view created by this adapter has been detached from its window.
         *
         * <p>Becoming detached from the window is not necessarily a permanent condition;
         * the consumer of an Adapter's views may choose to cache views offscreen while they
         * are not visible, attaching and detaching them as appropriate.</p>
         *
         * @param holder Holder of the view being detached
         */
        public void onViewDetachedFromWindow(@NonNull VH holder) {
        }

簡言之,就是當(dāng)itemView被從window上detach時調(diào)用。看起來很美好,與之對應(yīng)的方法是:

        /**
         * Called when a view created by this adapter has been attached to a window.
         *
         * <p>This can be used as a reasonable signal that the view is about to be seen
         * by the user. If the adapter previously freed any resources in
         * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow}
         * those resources should be restored here.</p>
         *
         * @param holder Holder of the view being attached
         */
        public void onViewAttachedToWindow(@NonNull VH holder) {
        }

然后我們也如第一個方法般調(diào)用了:
onViewAttachedToWindow中做一些注冊工作;
onViewDetachedFromWindow中做一些解注冊和釋放資源的工作。

在RecyclerView正常滾動時,這兩個方法都會被調(diào)用。然而頁面退出時,onViewDetachedFromWindow并不會被調(diào)用!

追根溯源,會發(fā)現(xiàn)癥結(jié)在LinearLayoutManager中:

    @Override
    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
        super.onDetachedFromWindow(view, recycler);
        if (mRecycleChildrenOnDetach) {
            removeAndRecycleAllViews(recycler);
            recycler.clear();
        }
    }

默認(rèn)mRecycleChildrenOnDetach=false。我們需要調(diào)用setRecycleChildrenOnDetach(true)才能實現(xiàn)在頁面退出時,依然調(diào)用onViewDetachedFromWindow方法。

整合RecyclerView

可以設(shè)計一個RecyclerView的基類,在基類中做如下處理:

public class BaseRecyclerView extends RecyclerView implements LifecycleObserver {
    public BaseRecyclerView(@NonNull Context context) {
        super(context);
        init(context);
    }

    public BaseRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public BaseRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {
        if (context instanceof LifecycleOwner) {
            ((LifecycleOwner) context).getLifecycle().addObserver(this);
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void onDestory() {
        /*
         確保Adapter#onDetachedFromRecyclerView被調(diào)用
         */
        setAdapter(null);
    }

    @Override
    public void setLayoutManager(LayoutManager layoutManager) {
        super.setLayoutManager(layoutManager);
        if (layoutManager instanceof LinearLayoutManager) {
            /*
            確保Adapter#onViewDetachedFromWindow被調(diào)用
             */
            ((LinearLayoutManager) layoutManager).setRecycleChildrenOnDetach(true);
        }
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容