如何利用觀察者模式在不同Activity通知

如何利用觀察者模式在不同Activity通知

在PTT上面看到
http://www.ptt.cc/bbs/AndroidDev/M.1415782724.A.961.html

其實很簡單, 只要寫一個簡單的Listener就可以達到這樣的目的。

“我是想從activityB裡面的button點擊後跳到activity中執行移動畫面到該地點,
我的activityA是一個google map v2的畫面”

根據他敘述的內容, 我可以假設他有兩個Activity, 想要在A做了某些動作之後, 通知B做某些改變, 基於這樣的需求我模擬成這樣:
假設有一個A activity上面有一個TextView,
當我轉跳到B activity的時候, 做出送訊息的要求,
之後回到A, TextView的內容已經修改成B activity所傳送的內容。
這代表什麼意思?
代表著我們可以即時通知某一個註冊的物件,
在某些事情改變以後, 馬上也跟著改變。
這就是觀察者模式

一開始宣告出A畫面有一個TextView跟一個傳送Button。


當按下Button的時候, 就會利用startActivityForResult傳送到B Activity,


為什麼不直接用startActivity就好了呢?

因為我們需要回來看結果, 不希望A actvity被關閉。
接著傳送到B Activity的畫面


這個畫面只有一個Button, 他會傳送一個字串,”msg from B”
代表這是從B Activity傳送出去的。
傳送完這個字串以後, 就會關掉這個Activity, 因此會回到A activity。

這樣就可以看到TextView被B所改變了。
現在來看一下程式碼

首先是MainActivity的程式碼

public class MainActivity extends AppCompatActivity {
    private TextView msgTextView;
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        msgTextView = (TextView) findViewById(R.id.msg);
        button = (Button) findViewById(R.id.to_b_act);
        button.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClass(MainActivity.this, BActivity.class);
                startActivityForResult(intent, 0);

            }
        });
        NoticeCenter.getInstance().addOnDataChangedListener(new NoticeCenter.OnDataChangedListener() {

            @Override
            public void onDataChanged(String msg) {
                msgTextView.setText(msg);
            }
        });
    }
}

可以看到上面加了兩個事件, 一個是Button的事件, 也就是按下Button會傳送到B activity。
MainActivity的xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.givemepass.sigletonandobservedemo.MainActivity">
    <TextView
        android:id="@+id/msg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:layout_below="@id/msg"
        android:id="@+id/to_b_act"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Go to BActivity" />
</RelativeLayout>

另外一個就是我們今天的主角,觀察者模式,來看一下程式碼

public class NoticeCenter {

    private static NoticeCenter mNoticeCenter;

    private ArrayList<OnDataChangedListener> mOnDataChangedListener;

    //singleton 確保只有一個實體
    private NoticeCenter(){}

    public static NoticeCenter getInstance(){
        if(null == mNoticeCenter){
            mNoticeCenter = new NoticeCenter();
            mNoticeCenter.init();
        }
        return mNoticeCenter;
    }

    private void init(){
        mOnDataChangedListener = new ArrayList<OnDataChangedListener>();
    }
    //observe pattern
    public interface OnDataChangedListener{
        public void onDataChanged(String msg);
    }

    public void addOnDataChangedListener(OnDataChangedListener listener){
        mOnDataChangedListener.add(listener);
    }

    public void removeOnDataChangedListener(OnDataChangedListener listener){
        mOnDataChangedListener.remove(listener);
    }

    public void notifyDataChanged(String msg){
        for(OnDataChangedListener listener : mOnDataChangedListener){
            if(listener != null){
                listener.onDataChanged(msg);
            }
        }
    }
}

好長一串, 首先我用獨體模式讓整個物件只產生一份,
接著宣告一個陣列, 用來裝我們的Listener, 這個Listener是用來通知的,
當有人註冊以後, 只要被呼叫notifyDataChanged就會跑迴圈來通知。

接著來看BActivity程式碼

public class BActivity extends AppCompatActivity {
    private Button sendBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        sendBtn = (Button) findViewById(R.id.send_btn);
        sendBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {
                NoticeCenter.getInstance().notifyDataChanged("msg from b");
                finish();
            }
        });
    }
}

BActivity的xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.givemepass.sigletonandobservedemo.BActivity">
    <Button
        android:text="send msg to MainActivity"
        android:id="@+id/send_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

只是做了一個事件的宣告, 當傳送完字串, 就將這個Activity關閉。
以上就是簡易的觀察者模式通知的範例。
那有人就會說, startActivityForResult就可以做到這樣的事情,等它回來再把值塞TextView內。

沒錯!
但是它做不到同時多個畫面或物件進行註冊時, 達到同時通知的效果,
而且這個方法可以傳任何物件, 不用實做序列化,是不是很方便呢?