尋找android中的設計模式(一)

                                       尋找android中的設計模式(一)
  1. 概述

    維護android系統中的聯繫人模塊有一段時間了,最近開始學習java設計模式,對書(HEAD_FIRST設計模式)中精彩設計非常崇拜和對OO原則有了更深刻的理解。作爲對設計模式的初學者,我將記錄對它的追求之路。該系列文章將結合聯繫人應用,尋找google原生代碼中設計模式。

  2. 尋找單例模式

    定義:確保一個類只有一個實例,並提供一個全局訪問點。

    對於初學單例模式,我往往會這樣寫:

public class Singleton {

     privatestatic SingletonmSingleton;

     private Singleton() {

     }

     public Singleton getInstance() {

          if (mSingleton == null) {

                mSingleton =new Singleton();

          }

          returnmSingleton;

     }

}

後來才發現在多線程的應用中就會有問題。對於多線程問題的解決,書中給了三中方案:1、對getInstance方法同步;2、急切實例化、3、雙重檢測加鎖。在android應用中都有用到。如下:

方案1

    public static synchronized ContactEditorUtils getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new ContactEditorUtils(context.getApplicationContext());
        }
        return sInstance;
    }

 

方案2

private static TouchPointManager sInstance = new TouchPointManager();

public static TouchPointManager getInstance() {
        return sInstance;
    }

方案3

    public static AccountTypeManager getInstance(Context context) {
        synchronized (mInitializationLock) {
            if (mAccountTypeManager == null) {
                context = context.getApplicationContext();
                mAccountTypeManager = new AccountTypeManagerImpl(context);
            }
        }
        return mAccountTypeManager;
    }

      下面看下下面代碼中的單例,分析下如何改善。

 public static final ContactsSimUtils getInstance(int whichCard) {

  ContactsSimUtils ret = null;
  try {
   ret = mListCard.get(whichCard);
   if (null == ret) {
    throw new NullPointerException();
   }
  } catch (NullPointerException e) {
   ret = mListCard.get(CARD_1);
  } finally {
  }
  return ret;
 }

不考慮多線程的話,上面代碼或許沒啥大問題,但我搜索了下該方法還是有其他線程訪問的。這樣的話還是有必要優化一下。方案一不適用,因爲同步方法會影響程序性能且這裏調用的比較頻繁。方案二也不推薦使用,因爲創建該實例會比較繁重,會影響啓動效率。看來方案三比較適用,它只有第一次的時候會同步並初始化。優化代碼如下:

 public static final ContactsSimUtils getInstance(int whichCard) {
  if(mListCard == null){
   synchronized (ContactsSimUtils.class) {
    if(mListCard == null){
     init();
    }
   }
  }
  return mListCard.get(whichCard);
 }

  1. 尋找觀察者模式

    定義:定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴都會收到通知並自動更新。

    學習了之後,自己也試着尋找生活當中的例子,就以旅遊爲例。

  1. 定義主題和觀察者的接口

    public interface Subject {
     public void registerObserver(Observer observer);
     public void removeObserver(Observer observer);
     public void notifyObservers();
    }

  public interface Observer {
 public void update(String msg);
}

  1. 實現主題和幾個客戶

    public class Teacher implements Subject {
    
    	private ArrayList<Observer> observers = new ArrayList<>();
    	private String msg;
    
    	@Override
    	public void registerObserver(Observer observer) {
    		observers.add(observer);
    	}
    
    	@Override
    	public void removeObserver(Observer observer) {
    		int index = observers.indexOf(observer);
    		if (index >= 0) {
    			observers.remove(index);
    		}
    
    	}
    
    	@Override
    	public void notifyObservers() {
    		for (Observer observer : observers) {
    			observer.update(msg);
    		}
    	}
    
    	public void sendMsg(String msg) {
    		this.msg = msg;
    		notifyObservers();
    	}
    }
    public class Student1 implements Observer {
    
    	public Student1(Subject subject) {
    		subject.registerObserver(this);
    	}
    
    	@Override
    	public void update(String msg) {
    		System.out.println("Student1 接收到通知:" + msg);
    	}
    
    }

    public class Student2 implements Observer {
    	public Student2(Subject subject) {
    		subject.registerObserver(this);
    	}
    
    	@Override
    	public void update(String msg) {
    		System.out.println("Student2 接收到通知:" + msg);
    	}
    
    }

    這是一個老師帶領學生旅遊的例子,同學們要在老師那裏報名要去旅遊,老師會通知報名的同學旅遊的時間。

  2. 測試下同學們是否收到旅遊的通知

    	public static void main(String[] args) {
    		Teacher subject = new Teacher();
    		Observer observer1 = new Student1(subject);
    		Observer observer2 = new Student2(subject);
    		subject.sendMsg("出發~~~~~");
    	}

    打印結果:

    Student1 接收到通知:出發~~~~~
    Student2 接收到通知:出發~~~~~

    看來同學們都收到了旅遊通知。

既然學會了如何使用觀察者模式,接下來我們來查找下自己維護的應用模塊當中是如何使用該模式的。

  1. 很快找到一處:

查看一個抽象的ContactsActivity,它是很多界面的父類,子類都會調用該父類的onCreate方法,在該方法中會往主題裏面註冊自己。如下:

    protected void onCreate(Bundle savedInstanceState) {
        ContactSaveService.registerListener(this);
        super.onCreate(savedInstanceState);
    }

明顯這裏的主題是ContactSaveService這個服務類,觀察者是繼承ContactsActivity的子類。然而主題ContactSaveService並沒有實現一個類似上面Subject的接口,而觀察者實現的接口在主題裏面。確實在android開發裏面都習慣這樣用。主題裏面的註冊和解註冊代碼如下:

    public static void registerListener(Listener listener) {
        if (!(listener instanceof Activity)) {
            throw new ClassCastException("Only activities can be registered to"
                    + " receive callback from " + ContactSaveService.class.getName());
        }
        sListeners.add(0, listener);
    }

    public static void unregisterListener(Listener listener) {
        sListeners.remove(listener);
    }

這樣做的好處是,確保在保存聯繫人的時候方便將保存過程的狀態通知到activity(觀察者)。

  1. 接着找下一處

    這是監聽數據庫變化的一個例子,從下面的方法可以明顯的看出,是在往主題裏面註冊觀察者。這裏的觀察者都繼承於抽象類ContentObserver並非接口。因爲抽象類裏面需要一些公共的模塊(handler對象和獲取binder方法),這樣是合理的。就類似主題可以繼承java.util.Observable而不用自己定義接口一樣。

            getActivity().getContentResolver().registerContentObserver(CallLog.CONTENT_URI, true,
                    mCallLogObserver);
            getActivity().getContentResolver().registerContentObserver(
                    ContactsContract.Contacts.CONTENT_URI, true, mContactsObserver);
            getActivity().getContentResolver().registerContentObserver(
                    Status.CONTENT_URI, true, mVoicemailStatusObserver);

    跟蹤代碼發現,這裏註冊的是ContentObserver裏的binder對象,而且是通過binder機制遠程註冊到服務裏面。看來主題是這個服務了。查找代碼可以發現,註冊過去的binder對象會保存到服務端的一個列表裏面。ObserverEntry是實現Ibinder的一個類。

    那麼如何通知觀察者呢?像聯繫人的數據庫,任何應用都可以註冊的,就是說服務端會保存很多個binder對象。下面去尋找下操作數據庫之後做了什麼。如下:

        protected void notifyChange(boolean syncToNetwork) {
            getContext().getContentResolver().notifyChange(ContactsContract.AUTHORITY_URI, null,
                    syncToNetwork);
        }

    增刪更新數據庫都會調用上面方法來通知各個觀察者。如何通知呢?繼續查找。最終找到ContentService裏的notifyChange方法,截取了主要代碼如下:

                ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
                synchronized (mRootNode) {
                    mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
                            userHandle, calls);
                }
                final int numCalls = calls.size();
                for (int i=0; i<numCalls; i++) {
                    ObserverCall oc = calls.get(i);
                    try {
                        oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
                            Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
                        }
                    } catch (RemoteException ex) {
                        synchronized (mRootNode) {
                            Log.w(TAG, "Found dead observer, removing");
                            IBinder binder = oc.mObserver.asBinder();
                            final ArrayList<ObserverNode.ObserverEntry> list
                                    = oc.mNode.mObservers;
                            int numList = list.size();
                            for (int j=0; j<numList; j++) {
                                ObserverNode.ObserverEntry oe = list.get(j);
                                if (oe.observer.asBinder() == binder) {
                                    list.remove(j);
                                    j--;
                                    numList--;
                                }
                            }
                        }
                    }
                

    先是收集所有註冊該uri的觀察者(binder對象),最後for循環遠程通知(onChange)到所有的觀察者。

  2. 最後再找一處

    代碼裏面看到這個方法

    getAdapter().notifyDataSetChanged();

    非常類似主題當中的notifyObservers方法。難道也是一種觀察者模式?跟蹤源碼。。。在BaseAdapter看到如下:

        public void notifyDataSetChanged() {
            mDataSetObservable.notifyChanged();
        }

    mDataSetObservableDataSetObservable類型。

    public class DataSetObservable extends Observable<DataSetObserver> {
        /**
         * Invokes {@link DataSetObserver#onChanged} on each observer.
         * Called when the contents of the data set have changed.  The recipient
         * will obtain the new contents the next time it queries the data set.
         */
        public void notifyChanged() {
            synchronized(mObservers) {
                // since onChanged() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just march thru the list in the reverse order.
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onChanged();
                }
            }
        }

    看來DataSetObservable是繼承主題抽象模版類的一個具體目標,T爲抽象觀察者類。在主題抽象模版類裏面實現了註冊和解註冊。下面看下觀察者是在哪裏註冊的。我們知道列表綁定數據到適配器一般都要用setAdapter方法,舉例listviewsetAdapter方法看到如下:

                mDataSetObserver = new AdapterDataSetObserver();
                mAdapter.registerDataSetObserver(mDataSetObserver);

    AdapterDataSetObserver的實現如下:

        class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
            @Override
            public void onChanged() {
                super.onChanged();
                if (mFastScroll != null) {
                    mFastScroll.onSectionsChanged();
                }
            }

    它繼承抽象觀察者類,看來在列表綁定數據的時候將該觀察者註冊到了適配器裏的一個主題對象裏面。如果多個列表綁定同一個適配器就類似多個觀察者註冊到了主題裏面。適配器一旦調用notifyDataSetChanged方法,會通知所有觀察者中的數據跟着變化。

  1. 總結

    以上記錄了尋找單例模式和觀察者模式的路程,後面還會繼續其他設計模式的尋找。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章