觀察者模式——RecyclerView中的應用

Fxymine4ever發表於2019-02-18

觀察者模式是軟體設計模式的一種。在此種模式中,一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實時事件處理系統。 摘自維基百科

首先,我在這裡先提出本文的幾個問題

  1. 什麼是觀察者模式?
  2. RecyclerViewAdpter.notifyDataSetChanged為什麼能重新整理介面?
  3. RecyclerView的觀察者,在哪裡被註冊和登出的呢?

什麼是觀察者模式?

可能對於部分初學者來說,還不是很明白觀察者模式是什麼,那麼接下來我會用簡單的例子來描述一下觀察者模式.

被觀察者(目標物件)

負責管理所有在這裡註冊過的觀察者,常常可以通過遍歷觀察者的集合來呼叫所有的觀察者.

// 被觀察者
public interface Observable<T>{
    void register(Observer<T> observer);

    void unregister(Observer<T> observer);

    void notifyObserver();
}
複製程式碼

這裡我們可以將學生系統作為它的具體實現類,學生將在這裡被註冊

public class StudentSystem implements Observable<String> {
    private List<Observer<String>> observers = new ArrayList<>();

    //學生(觀察者)註冊學籍的方法
    @Override
    public void register(Observer<String> observer) {
        System.out.println(observer+"註冊了學籍");
        observers.add(observer);
    }

    //學生(觀察者)登出學籍的方法
    @Override
    public void unregister(Observer<String> observer) {
        if(observers.contains(observer)){
            System.out.println(observer+"登出了學籍");
            observers.remove(observer);
        }
    }

    //學生系統的全員廣播,通知學生(觀察者)
    @Override
    public void notifyObserver() {
        for (Observer<String> observer : observers) {
            observer.update("系統提示:"+observer+"馬上開學了!");
        }
    }
}

複製程式碼

觀察者

觀察者會將聯絡方式(引用)留給被觀察者以便於能很方便通知到它.

public interface Observer<T> {
    void update(T msg);//每個觀察者被通知的入口
}
複製程式碼

同樣,在這裡我們將學生作為觀察者,則update方法是學生系統向學生通知的途徑.

public class Student implements Observer<String> {
    @Override
    public void update(String msg) {
        System.out.println(msg);
    }
}
複製程式碼

這樣我們就構成了最簡單的觀察者

public class Main {
    public static void main(String[] args) {
        Student stu1 = new Student();
        Student stu2 = new Student();
        Student stu3 = new Student();

        StudentSystem system = new StudentSystem();

        system.register(stu1);
        system.register(stu2);
        system.register(stu3);

        system.notifyObserver();
        
        system.unregister(stu1);
        system.unregister(stu2);
        system.unregister(stu3);
    }
}
複製程式碼
輸出:
Student@2503dbd3 註冊了學籍
Student@4b67cf4d 註冊了學籍
Student@7ea987ac 註冊了學籍
系統提示: Student@2503dbd3 馬上開學了!
系統提示: Student@4b67cf4d 馬上開學了!
系統提示: Student@7ea987ac 馬上開學了!
Student@2503dbd3 登出了學籍
Student@4b67cf4d 登出了學籍
Student@7ea987ac 登出了學籍
複製程式碼

小小總結一下

  • 觀察者把自己的引用留給了被觀察者,便於被觀察者使用觀察者的方法.
  • JDK中自帶了觀察者模式,不過被觀察者(Obeservable)是一個類,不方便使用
  • 觀察者模式在Android中使用得特別多,著名的RecyclerViewBroadcastReceiver等均使用了觀察者模式

回到正題——RecyclerView中的應用

只要是一個Android開發者,我想每一個人都用過RecyclerView吧!這是Google提供的十分優秀的大量資料展示的新控制元件,其中的設計十分地精妙,運用了大量的設計模式與程式設計思想.

在我去年剛開始學習Andorid的時候,我就一直在想RecyclerViewAdapter為什麼呼叫notifyDataSetChange就能重新整理所有的Item呢?由於當時未能養成一種閱讀原始碼的習慣,只能將這個疑惑放下.

不過接下來,我將帶領大家探索一下這個問題的謎底

RecyclerViewAdpter.notifyDataSetChanged為什麼能重新整理介面?

先看看RecyclerViewAdapter的關係

由於RecyclerView的類實在是太龐大了,有足足7000多行,所以說這裡擷取小部分.可以看到adapter是RecyclerView中的一個靜態內部類

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
    //省略無數程式碼...
        public abstract static class Adapter<VH extends RecyclerView.ViewHolder> {
            //這個物件很關鍵,後文會講
            private final RecyclerView.AdapterDataObservable mObservable = new RecyclerView.AdapterDataObservable();
            //省略無數程式碼...
        }
}
複製程式碼

直擊重點:Adpter.notifyDataSetChanged

        public final void notifyDataSetChanged() {
            this.mObservable.notifyChanged();
        }
複製程式碼

這裡呼叫了mObservablenotifyChanged方法,而mObservable在上面已經給出來了,是RecyclerView.AdapterDataObservable的物件,所以說我們繼續深究.

AdapterDataObservable

static class AdapterDataObservable extends Observable<RecyclerView.AdapterDataObserver> {
        AdapterDataObservable() {
        }
        //...省略部分程式碼

        public void notifyChanged() {
            for(int i = this.mObservers.size() - 1; i >= 0; --i) {
                ((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onChanged();
            }

        }

        //...省略部分程式碼
    }
複製程式碼

顯而易見,這是一個JDK提供的Observable的子類,而這個類的內部維護著一個ArrayList組成的集合,用於儲存觀察者物件.
所以說,notifyChanged方法裡,就是遍歷這個被觀察者持有的觀察者物件,並呼叫它的onChanged方法

onChanged

我們按著ctrl鍵點進這個方法,發現這是一個抽象靜態類

    public abstract static class AdapterDataObserver {
        public AdapterDataObserver() {
        }

        public void onChanged() {
        }

        public void onItemRangeChanged(int positionStart, int itemCount) {
        }

        public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
            this.onItemRangeChanged(positionStart, itemCount);
        }

        public void onItemRangeInserted(int positionStart, int itemCount) {
        }

        public void onItemRangeRemoved(int positionStart, int itemCount) {
        }

        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        }
    }
複製程式碼

我們順藤摸瓜,於是找到了它的實現類RecyclerViewDataObserver

private class RecyclerViewDataObserver extends RecyclerView.AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        public void onChanged() {
            RecyclerView.this.assertNotInLayoutOrScroll((String)null);
            RecyclerView.this.mState.mStructureChanged = true;
            RecyclerView.this.processDataSetCompletelyChanged(true);
            if (!RecyclerView.this.mAdapterHelper.hasPendingUpdates()) {
                    RecyclerView.this.requestLayout();
            }

        }
        //...省略部分程式碼
    }
複製程式碼

真相大白:requestLayout()

在這個實現類中,onChanged的內容就是那麼的美麗~終於解除了我學習Android一年以來的心病,就是這個方法就是更新佈局的關鍵

RecyclerView.this.requestLayout();//請求重新繪製介面
複製程式碼

最後,我們發現這裡呼叫了View的requestLayout方法,確實是請求重新繪製介面.不過這已經是View相關的內容,與本文無關了.有興趣的朋友可以繼續深入瞭解

public void requestLayout() {
        if (this.mInterceptRequestLayoutDepth == 0 && !this.mLayoutFrozen) {
            super.requestLayout();
        } else {
            this.mLayoutWasDefered = true;
        }

    }
複製程式碼

總結一下RecyclerView更新介面

這裡運用了觀察者模式,通過被觀察者AdapterDataObservable遍歷每一個觀察者RecyclerViewDataObserver,然後找到它的onChanged()方法,利用requestLayout()更新整個RecyclerView.

RecyclerView的觀察者模式,在哪裡被註冊的?

又是這熟悉的一段程式碼,既然它在Recyclerview的Adapter中,那麼觀察者模式肯定和Adapter的初始化有著千絲萬縷的聯絡.

private final RecyclerView.AdapterDataObservable mObservable = new RecyclerView.AdapterDataObservable();
複製程式碼

setAdapter

於是,我們從最開始的setAdapter方法開始看,畢竟這是Adapter的入口嘛

    public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
        this.setLayoutFrozen(false);
        this.setAdapterInternal(adapter, false, true);
        this.processDataSetCompletelyChanged(false);
        this.requestLayout();
    }
複製程式碼

在這裡,與Adapter相關的只有this.setAdapterInternal(adapter, false, true);
這一行程式碼,我敢肯定,祕密就在這一行程式碼中!

setAdapterInternal

    private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
        if (this.mAdapter != null) {//如果recyclerView之前設定過Adapter了
            //登出觀察者
            this.mAdapter.unregisterAdapterDataObserver(this.mObserver);
            this.mAdapter.onDetachedFromRecyclerView(this);
        }

        if (!compatibleWithPrevious || removeAndRecycleViews) {
            this.removeAndRecycleViews();
        }

        this.mAdapterHelper.reset();
        RecyclerView.Adapter oldAdapter = this.mAdapter;
        this.mAdapter = adapter;
        if (adapter != null) {
            //幸福來得這麼突然?
            //註冊觀察者
            adapter.registerAdapterDataObserver(this.mObserver);
            adapter.onAttachedToRecyclerView(this);
        }

        if (this.mLayout != null) {
            this.mLayout.onAdapterChanged(oldAdapter, this.mAdapter);
        }

        this.mRecycler.onAdapterChanged(oldAdapter, this.mAdapter, compatibleWithPrevious);
        this.mState.mStructureChanged = true;
    }
複製程式碼

幸福來得這麼突然?才進入兩個方法就找到了關鍵?

對了,我們看看adapter的方法幹了啥!

registerAdapterDataObserver

        public void registerAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
            this.mObservable.registerObserver(observer);
        }
複製程式碼
        public void unregisterAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
            this.mObservable.unregisterObserver(observer);
        }
複製程式碼

原來如此,這兩個方法果然登出和註冊了觀察者!

仍然總結一下下

RecyclerViewsetAdapter方法中,呼叫了setAdapterInternal.然後在setAdapterInternal中利用adapter.registerAdapterDataObserver方法註冊觀察者,利用adapter.unregisterAdapterDataObserver方法登出觀察者.

最後

通過以上的學習,我們已經解答了開頭提出的三個問題了,相信大家也學到了不少. 如果有錯誤的地方歡迎指正.


雖然我只是一名大二的本科生,但是歡迎大家和我私信交流!

相關文章