观察者模式
观察者模式定义了对象之间的一对多依赖,当一个对象状态发生改变时,其依赖者便会收到通知并做相应的更新。其原则是:为交互对象之间松耦合。以松耦合方式在一系列对象之间沟通状态,我们可以独立复用主题(Subject)/可观测者(Observable)和观测者(Observer),即只要遵守接口规则改变其中一方并不会影响到另一方。这也是其主要的设计原则。
举个例子:老师留高软作业,所有学生收到作业信息。老师对应多个学生,留作业是发生的变化,学生会收到作业信息。老师(或学生)通常步行去学校,今天决定以后都坐车去学校的变化,并不会影响学生(或老师)的去学校方式。
以面向对象语言为例:主题类负责实现观察者的注册、注销、通知观察者;观察者类负责更新信息。
SMSContentObserver
https://github.com/88ios/SMSContentObserver-master/tree/master/app/src/main/java/com/aikaifa/message
以短信验证码自动填充项目为例:
为实现短信验证码自动填充,就要求对信箱进行监听,当信箱收到特定地址发来的信件,观察者执行读取信息并解析,符合要求后,将执行信息填充的操作。
在该例子中,信箱即为主题,负责完成观察者的注册、注销和通知:
主题类(public class MainActivity extends AppCompatActivity),在类中持有一个观察者类MessageContentObserver的私有对象。
https://github.com/88ios/SMSContentObserver-master/blob/master/app/src/main/java/com/aikaifa/message/MainActivity.java
1.注册
1 protected void onCreate(Bundle savedInstanceState) { 2 super.onCreate(savedInstanceState); 3 setContentView(R.layout.activity_main); 4 codeEdt = (EditText) findViewById(R.id.smsCode); 5 findViewById(R.id.send_sms_btn).setOnClickListener(new View.OnClickListener() { 6 @Override 7 public void onClick(View v) { 8 senSMSCode(); 9 } 10 }); 11 messageContentObserver = new MessageContentObserver(MainActivity.this, handler); 12 getContentResolver().registerContentObserver(Uri.parse("content://sms/"), true, messageContentObserver); 13 }
方法完成了对观察者的注册。逻辑为先点击信息发送,同时注册观察者。
2.注销
1 protected void onDestroy() { 2 super.onDestroy(); 3 getContentResolver().unregisterContentObserver(messageContentObserver); 4 }
方法完成了对观察者的注销。由于没有测试代码,我推测调用该方法应该是每过周期性的一段时间进行调用。
3.通知
1 private void senSMSCode() { 2 BmobSMS.requestSMSCode("13433962858", "aikaifa", new QueryListener<Integer>() { 3 @Override 4 public void done(Integer smsId, BmobException ex) { 5 if (ex == null) {//验证码发送成功 6 Log.i("smile", "短信id:" + smsId);//用于后续的查询本次短信发送状态 7 } 8 } 9 }); 10 }
该方法完成信息发送,逻辑上只是点击发送所执行的业务。而下一个方法完成了所收到信息的获取。
1 @SuppressLint("HandlerLeak") 2 Handler handler = new Handler() { 3 @Overrid。 4 public void handleMessage(Message msg) { 5 if (msg.what == MSG_RECEIVE_CODE) { 6 codeEdt.setText(msg.obj.toString()); //设置读取到的内容 7 } 8 } 9 };
到现在为止也没有完成确切的通知,而这个通知实际上在注册观察者时已经隐性通知过了。
似乎有点矛盾,理解一下这个过程:我要实现信箱收到验证码信息,然后自动填充的过程。但是这个验证码必须是你先发送信息请求来的,这个发送信息实际上是最深层次的变化,信箱收到验证码才是表面变化。所以,你先发送信息(发生了变化),从信箱获取验证码(通知观察者的信息),注册观察者,并传递信息(通知观察者)。
观察者类
https://github.com/88ios/SMSContentObserver-master/blob/master/app/src/main/java/com/aikaifa/message/MessageContentObserver.java
更新
1 public void onChange(boolean selfChange, Uri uri) { 2 Log.e("tag", uri.toString()); 3 if (uri.toString().equals("content://sms/raw")) { // 第一次回调 4 return; 5 } 6 Uri inboxUri = Uri.parse("content://sms/inbox"); // 第二次回调 查询收件箱里的内容 7 Cursor c = mContext.getContentResolver().query(inboxUri, null, null, null, "date desc"); // 按时间顺序排序短信数据库 8 if (c != null) { 9 if (c.moveToFirst()) { 10 String address = c.getString(c.getColumnIndex("address"));//发送方号码 11 String body = c.getString(c.getColumnIndex("body")); // 短信内容 12 if (!address.equals("10086")) { 13 return; 14 } 15 Pattern pattern = Pattern.compile("(\\d{6})");//正则表达式匹配验证码 16 Matcher matcher = pattern.matcher(body); 17 if (matcher.find()) { 18 code = matcher.group(0); 19 Message msg = Message.obtain(); 20 msg.what = MainActivity.MSG_RECEIVE_CODE; 21 msg.obj = code; 22 mHandler.sendMessage(msg); 23 } 24 } 25 c.close(); 26 } 27 }
这个方法会两次调用,因为涉及查询信箱信件内容,而收到短信不会立即写入信箱,所以需要两次调用。这个方法完成发送地址信息匹配和自动写入操作。
总结
在这个项目中利用观察者设计模式完成了验证码自动写入的功能。仔细思考一下过程:发送信息、等待验证码、注册观察者、通知观察者和观察者处理。
由于在这个项目中是对特定验证码进行自动写入,逻辑上是以观察者模式进行的,而实际上实现了对收到信息进行验证,如果匹配则写入的一对一依赖关系。不用观察者模式也可以完成(一对一不用注册,直接处理通知信息)。
可以对其扩展,加入对不同发送地址的验证码写入功能(转化为一对多)。使其转变为将信箱为主题,新收到的验证码为变化,不同的发送地址为不同的观察者。这样,就必须使用观察者模式,好处是不用反复编写不同地址发来验证码的通知方法,代码复用性好,其次模块之间耦合度低,利于模块内功能拓展,比如发送短信功能的开发。
地址项目:https://github.com/88ios/SMSContentObserver-master/tree/master/app/src/main/java/com/aikaifa/message