1. 程式人生 > >觀察者模式在android 上的最佳實踐

觀察者模式在android 上的最佳實踐

  在上一篇文章中介紹了介紹了觀察者模式的定義和一些基本概念,觀察者模式在 android開發中應用還是非常廣泛的,例如android按鈕事件的監聽、廣播等等,在任何類似於新聞-訂閱的模式下面都可以使用。從某種意義上面來說android有點像JAVA EE的WEB頁面,在都需要提供View層用於進行操作,在多個頁面之間傳遞資料傳送通知都是一件很麻煩的事情。

  在android中從A頁面跳轉到B頁面,然後B頁面進行某些操作後需要通知A頁面去重新整理資料,我們可以通過startActivityForResult來喚起B頁面,然後再B頁面結束後在A頁面重寫onActivityResult來接收返回結果從而來重新整理頁面。但是如果跳轉路徑是這樣的A->B->C->.....,C或者C以後的頁面來重新整理A,這個時候如果還是使用這種方法就會非常的棘手。使用這種方法可能會存在以下幾個弊端:

  1.   1.多個路徑或者多個事件的傳遞處理起來會非常困難。
  2.   2.資料更新不及時,往往需要使用者去等待,降低系統性能和使用者體驗。
  3.   3.程式碼結構混亂,不易編碼和擴充套件。

因此考慮使用觀察者模式去處理這個問題。

一.需求確定

  在APP中我們有一些設定專案,我們希望在設定完了以後,在主頁面能夠立即響應,例如QQ的清空聊天記錄,我們希望設定了以後回到主頁面後會自動清理,有些人可能會認為這是一件很簡單的事情,認為回到主頁面直接讀快取就好了,快取裡面是什麼就是什麼,課時這種方案存在2個問題:

  •   什麼時候讀取快取,是每次回到主頁面都去重新整理嗎,這樣會太消耗資源,使用者體驗也不好。
  • 不能區域性重新整理資料。

  因此行功能和程式碼結構上面來看我的需求主要有以下幾點:

  1.     1.能夠在某些頁面設定完了後直接通知其他監聽了這個事件的頁面立即重新整理,而不需要使用者回到某些頁面的時候再重新整理。
  2.    2.能夠區分是哪些事件通知的,從而針對不同的事件進行不同的處理。
  3.    3.能夠動態的擴充套件事件型別,可以讓呼叫者很快的註冊和監聽事件。

二.系統設計

 從上一篇文章中我們知道一個完整的觀察者模式需要這些物件:

  1.   Subject 抽象主題角色:也就是抽象的被觀察者物件,裡面儲存了所有的觀察者物件引用列表,提供了註冊和反註冊的功能。
  2.   ConcreteSubject具體的主題角色:將有關狀態存入各ConcreteObserver物件
        當它的狀態傳送改變時,向它的各個觀察者發出通知 。
  3.   Observer 抽象觀察者 :為所有的具體觀察者定義一個介面,在得到通知時更新自己。
  4.   ConcreteObserver 具體觀察者:維護一個指向ConcreteObserver物件的引用 ,儲存有關狀態,這些狀態應與目標的狀態保持一致,實現Observer的更新介面是自身狀態與目標的狀態保持一致

 針對在android我們需要設計一個一個抽象的BaseObserverActivity,讓所有的Activity頁面都去繼承它,從本質上來看繼承這個類的所有的Activity都是一個觀察者,然後再觀察者物件中去定義需要監聽是什麼型別的事件和根據對應的事件的處理。

三.具體實現方案

(1)EventSubjectInterface:抽象的主題角色實現

/**
 * 抽象的主題角色
 * @author zhiwu_yan
 * @since 2015年04月06日
 * @version 1.0
 */
public interface EventSubjectInterface {
    /**
     * 註冊觀察者
     * @param observer
     */
    public void registerObserver(EventObserver observer);

    /**
     * 反註冊觀察者
     * @param observer
     */
    public void removeObserver(EventObserver observer);

    /**
     * 通知註冊的觀察者進行資料或者UI的更新
     */
    public void notifyObserver(String eventType);
}

主要包括了觀察者的註冊方法和反註冊方法以及通知觀察者去更新UI的方法,我們來看看具體的實現。

(2)EventSubject:具體的主題角色的實現

/**
 * 具體的主題角色的實現,這裡用來監聽事件的發生,採用單例模式來實現
 * @author zhiwu_yan
 * @since 2015年04月06日
 * @version 1.0
 */
public class EventSubject implements EventSubjectInterface{

    private List<EventObserver> mEventObservers=new ArrayList<EventObserver>();
    private static volatile EventSubject mEventSubject;
    private EventSubject(){

    }

    public synchronized static EventSubject getInstance(){
        if(mEventSubject ==null){
            mEventSubject =new EventSubject();
        }
        return mEventSubject;
    }

    @Override
    public void registerObserver(EventObserver observer) {
        synchronized (mEventObservers){
            if(observer!=null){
                if(mEventObservers.contains(observer)){
                    return;
                }
                mEventObservers.add(observer);
            }
        }

    }

    @Override
    public void removeObserver(EventObserver observer) {
        synchronized (mEventObservers){
            int index = mEventObservers.indexOf(observer);
            if (index >= 0) {
                mEventObservers.remove(observer);
            }
        }
    }

    @Override
    public void notifyObserver(String eventType) {
        if(mEventObservers!=null && mEventObservers.size()>0 && eventType!=null){
            for(EventObserver observer:mEventObservers){
                observer.dispatchChange(eventType);
            }
        }

    }
}

裡面要注意的地方是:使用單例模式來控制只有一個主題角色,裡面儲存了所有的觀察者物件(EventObserver)列表,也就是護士手中的名單(見上一章),值得一提的是使用synchronized去控制多執行緒操作的問題。

(3)EventObserverInterface:抽象觀察者物件

/**
 * 觀察者介面
 * @author zhiwu_yan
 * @since 2015年04月06日
 * @version 1.0
 */
public interface EventObserverInterface {
    /**
     * 根據事件進行資料或者UI的更新
     * @param eventType
     */
    public void dispatchChange(String eventType);
}

裡面只有一個根據事件型別來跟新UI的方法,我們看看具體的抽象觀察者。

(4)EventObserver:具體的抽線觀察者

/**
 * 用於更新UI,這裡執行更新UI的onChange方法
 * @author  zhiwu_yan
 * @since   2015年04月06
 * @version 1.0
 */
public abstract class EventObserver implements EventObserverInterface {

    private Handler mHandler;

    public EventObserver(){
        mHandler=new Handler(Looper.getMainLooper());
    }


    public abstract void onChange(String eventType);

    @Override
    public void dispatchChange(String eventType){
        mHandler.post(new NotificationRunnable(eventType));
    }

    private final class NotificationRunnable implements Runnable{
        private String mEventType;
        public NotificationRunnable(String eventType){
            this.mEventType=eventType;
        }
        @Override
        public void run() {
            EventObserver.this.onChange(mEventType);
        }
    }
}

我們定義了一個抽象的onChange方法交給子類去實現,這個方法就是用來處理對應的事件型別,比如需要重新整理資料等等。因為mHandler.post來跟新UI執行緒的,所以如果是耗時的操作需要另外開執行緒去處理。

(5)前面已經說過了,Android裡面我們需要定義一個帶觀察者模式的BaseActivity用來給某些需要監聽的業務的Activity使用,這樣只要繼承了該Activity的都是一個具體的觀察者物件。

/**
 * 帶有觀察者模式的Activity,本質上就是觀察者
 * @author  zhiwu_yan
 * @since  2015年04月6日 20:41
 * @version 1.0
 */
public abstract class BaseObserverActivity extends ActionBarActivity {

    private ActivityEventObserver mActivityEventObserver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mActivityEventObserver=new ActivityEventObserver(this);
        registerObserver(mActivityEventObserver);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        removeObserver(mActivityEventObserver);
    }


    public void registerObserver(EventObserver observer) {
        final String[] observerEventTypes=getObserverEventType();//獲取所有需要監聽的業務型別
        if(observerEventTypes!=null && observerEventTypes.length>0){
            final EventSubject eventSubject=EventSubject.getInstance();
            eventSubject.registerObserver(observer);

        }

    }

    public void removeObserver(EventObserver observer) {
        final String[] observerEventTypes=getObserverEventType();//獲取所有需要監聽的業務型別
        if(observerEventTypes!=null && observerEventTypes.length>0){
            final EventSubject eventSubject=EventSubject.getInstance();
            eventSubject.removeObserver(observer);
        }
    }

    /**
     * 該方法會在具體的觀察者物件中呼叫,可以根據事件的型別來更新對應的UI,這個方法在UI執行緒中被呼叫,
     * 所以在該方法中不能進行耗時操作,可以另外開執行緒
     * @param eventType 事件型別
     */
    protected abstract void onChange(String eventType);

    /**
     * 通過這個方法來告訴具體的觀察者需要監聽的業務型別
     * @return
     */
    protected abstract String[] getObserverEventType();

    private static class ActivityEventObserver extends EventObserver {
        //新增弱引用,防止物件不能被回收
        private final WeakReference<BaseObserverActivity> mActivity;
        public ActivityEventObserver(BaseObserverActivity activity){
            super();
            mActivity=new WeakReference<BaseObserverActivity>(activity);
        }

        @Override
        public void onChange(String eventType) {
            BaseObserverActivity activity=mActivity.get();
            if(activity!=null){
                activity.onChange(eventType);
            }
        }
    }


}

另外我們需要定義一個可以動態擴充套件的事件型別:EventType

/**
 * 所有的業務型別,在這裡寫,方便管理
 * @author zhiwu_yan
 * @since 2015年04月06日
 * @version 1.0
 */
public class EventType {

    private static volatile EventType mEventType;
    private final static Set<String> eventsTypes = new HashSet<String>();

    public final static String UPDATE_MAIN="com.updateMain";
    public final static String UPDATE_Text="com.updateText";
    private EventType(){
        eventsTypes.add(UPDATE_MAIN);
        eventsTypes.add(UPDATE_Text);
    }

    public static EventType getInstance(){
       if(mEventType==null){
           mEventType=new EventType();
       }
        return mEventType;
    }

    public boolean contains(String eventType){
        return eventsTypes.contains(eventType);
    }

}

我這裡主要定義個2個事件型別,如果需要你可以定義N個事件型別,只要把你需要定義的事件新增到事件類表裡面去就可以了。

我們在通知某個頁面需要更新的時候只需呀呼叫如下方法:

EventSubject eventSubject=EventSubject.getInstance();
        EventType eventTypes=EventType.getInstance();
        if(eventTypes.contains(eventType)){
            eventSubject.notifyObserver(eventType);
        }

為了便於管理我們也新建一個工具類:

/**
 * 通知中心,用來通知更新資料或者UI,採用單例模式
 * @author zhiwu_yan
 * @since 2015年04月6日
 * @version 1.0
 */
public class Notify {

    private static volatile Notify mNotify;
    private Notify(){

    }

    public static Notify getInstance(){
        if(mNotify==null){
            mNotify=new Notify();
        }
        return mNotify;
    }

    public void NotifyActivity(String eventType){
        EventSubject eventSubject=EventSubject.getInstance();
        EventType eventTypes=EventType.getInstance();
        if(eventTypes.contains(eventType)){
            eventSubject.notifyObserver(eventType);
        }
    }
}

到這裡基本的框架就完成,我們看看怎麼使用。

四.使用方法

定義一個A頁面:MainActivity。這個頁面是一個觀察者,需要監聽來自其他頁面的一些通知,一旦有修改就根據對應的的事件來做出不同的處理:

public class MainActivity extends BaseObserverActivity {

    private TextView mLableTv;
    private ImageView mPicIv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mLableTv=(TextView) findViewById(R.id.label_tv);
        mPicIv=(ImageView) findViewById(R.id.pic_iv);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        switch (id){
            case R.id.go_other_activity:
                goActivity(OtherActivity.class);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void goActivity(Class<?> activity){
        Intent intent=new Intent(this,activity);
        startActivity(intent);
    }

    @Override
    protected void onChange(String eventType) {
        if(EventType.UPDATE_MAIN==eventType){
            mPicIv.setImageResource(R.mipmap.pic_two);
        }else if(EventType.UPDATE_Text==eventType){
            mLableTv.setText("圖片被更新");
        }
    }

    @Override
    protected String[] getObserverEventType() {
        return new String[]{
                EventType.UPDATE_MAIN,
                EventType.UPDATE_Text
        };
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        startActivityForResult();
    }
}

主要看一下:onChange 方法:根據事件型別來更新不同的圖片,而在getObserverEventType()中我們定義了該觀察者需要觀察的業務型別,其它業務型別則會被忽略。

我們的B頁面:也就是發出通知的頁面,APP上面的設定頁面,唯一的作用就是通知觀察者:

public class OtherActivity extends ActionBarActivity {
    private Button mUpdateBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.other_activity);
        mUpdateBtn=(Button) findViewById(R.id.update_edit_btn);
        mUpdateBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Notify.getInstance().NotifyActivity(EventType.UPDATE_Text);
                Notify.getInstance().NotifyActivity(EventType.UPDATE_MAIN);
            }
        });
    }


}

好,大功告成!

部落格已經遷移至點選開啟連結 部落格有原始碼下載,歡迎訪問!!