如理解有误或者模棱两可的地方,欢迎指出。共同进步,一起成长!
前言:为什么要了解观察者模式?
- 最近准备入手retrofit就遇到了RxJava(由于入坑晚+公司框架太老以至于到现在才开始自学rxJava);RxJava的异步让人佩服,切换线程只需一行代码,序列化事件链式操作......各种Obsever、Observable于是引出了观察者模式。
- 熟悉接口回调的能很快看懂观察者模式。
PS:个人认为学以致用,研究茴香豆有几种写法没有意义,但是我们要知其所以然,只会调api就显得很Low了哇。当我们用api出现问题,或者不能满足需求的时候,我们就得往底层看,去看源码,弄懂实现原理。懂了,才敢放心的用各种操作拼凑起来满足自己的需求。
本篇主要分为:
- 通用接口回调套路
- view click回调事件简单分析
- 接口回调对象的传递(主要是单例模式)
正文:观察者模式先来看看接口回调
接口回调异步是怎么实现的?
接口注册、实现和接口调用分离
总的来说就分三步走:
1.定义一个接口(待实现的抽象方法)
2.实现接口并注册接口
3.调用接口方法(这里一般是一个类,拥有刚才定义的接口,并对外提供注册接口的方法)然后回调到实现接口的地方执行
下面以我们常用的点击事件回调看一下:
- 实现接口并注册接口
//at mainActivity
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.v("tag","ui主线程发送msg "+"当前线程: "+Thread.currentThread().getId());
testThread.mhandler.sendEmptyMessage(0);
}
});
跟进去看一下View.OnClickListener()接口,其实从这儿可以看到接口是在View内部的
- 定义一个接口
public interface OnClickListener {
void onClick(View v);
}
就一个很简单的抽象方法,接口方法默认是public abstract,可以省略
- 调用接口方法(简单分析下点击事件,不感兴趣的可以直接跳过performclick)
都是在view.class里面,方便查看省略了很多代码
可以看到点击事件经过事件分发机制,经过事件分发拦截处理,最后来到view的dispatchTouchEvent(不了解事件分发机制的同学可以自行google,后面有空也会分享一点心得)
public boolean dispatchTouchEvent(MotionEvent event) {
//....................
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true; //决定是否调用onTouchEvent
}
if (!result && onTouchEvent(event)) { //result如果为flase这里调用onTouchEvent
result = true;
}
}
//............
可以看到如果上面li.mOnTouchListener.onTouch(this, event)返回为true,result为true; resulet如果为true,往下就不会执行ontouchEvent(进而执行click)了。
这也是为什么在重写ontouch返回为true,对应view的click事件不会响应。
onTouchEvent里面会调用performClick。注意是action_up的时候才会调用哦,也就是手指头按下不会,松开才会回调click事件。不知道平时大家有没有注意,不知道的还不赶紧试试= =
public boolean onTouchEvent(MotionEvent event) {
//...........
case MotionEvent.ACTION_UP:
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick(); //====这里调用performClick
}
}
}
//...........
下面代码可以看到如果mlickListener不为null就可以执行onclick方法,回调到注册接口那里执行。当然对对象判空是一种意识,不然如果忘了注册接口,就不仅仅是不能触发回调,而是直接空指针异常闪退了。
//at View.class
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this); //=====这里调用接口的onclick
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
虽然看起来代码很多,那是因为android为了实现各种view事件复杂逻辑的代码。我们自己的逻辑如果比较简单,直接获取到subject,然后通过它的接口调用一下方法就行啦。
贴一个自己写的简单接口回调demo
//接口CallBackInterface
public interface CallBackInterface {
void onCallBack();
}
//Subject.class
public class Subject {
private CallBackInterface callBackInterface;
public void setCallBackInterface(CallBackInterface callBackInterface){
this.callBackInterface = callBackInterface;
}
public void notifyCallBack(){
if(callBackInterface != null) {
callBackInterface.onCallBack();
}
}
//一般情况是在这个类调用notifyCallBack();比如突然断网、数据库更新等。如果在其他地方调用,需要获取到subject这个注册了接口的对象。
}
//mainActivity
private Subject subject = new Subject(); //获取subjuect对象是重点
subject.setCallBackInterface(new CallBackInterface() {
@Override
public void onCallBack() {
Log.v("tag","at mainActivity 11111");
}
});
//secondeActivity
private Subject subject; //这个subject要是mainActivity 注册的那个对象,比如用构造函数,set等方法传过来。
subject.notifyCallBack();
其实我觉得接口回调3步走没什么毛病。主要是subject.setCallBackInerface()和subject.notifyCallBack()的subject要是同一个对象。
ps:不要觉得没对象就随便new一个........FFFFFF
如果callBackInterface.onCallBack()在Subject类里面调用,那没什么毛病。如果是在其他地方调用,就要考虑下这个subject注册和调用的地方传递。
- 上面的demo可以用intent传,不过subject要可序列化,所有Subject要实现Parcelable接口。
- 普通的类也可以用构造函数传过去。
- 如果确定Subject对象只有一个(即单例),那么用单例模式是最方便的了。
-
其他方法(骚操作)
Subject换成单例就变成下面这样,注意看注释
/**
* Created by Dynamic on 2017/11/2.
*/
public class Subject {
private static volatile Subject singleSubject; //volatile是由于jdk1.5之前的漏洞
private Subject(){ //构造函数私有,确保单例的关键
}
public static Subject getInstance(){
if(singleSubject == null){ //懒汉式,只有需要用到它的时候才去new,不是一开始就new出来
synchronized (Subject.class){ //线程同步,避免多线程同时执行,new很多个出来(单例)
if(singleSubject == null){
// 双重检查;因为同步块里面第一个null去new,执行完后,已经不为Nulll了
//如果singleSubject有了,后面的线程就没必要再去new了
singleSubject = new Subject();
}
}
}
return singleSubject;
}
private CallBackInterface callBackInterface;
public void setCallBackInterface(CallBackInterface callBackInterface){
this.callBackInterface = callBackInterface;
}
public void notifyCallBack(){
if(callBackInterface != null) {
callBackInterface.onCallBack();
}
}
}
上面使用的是双重检测的单例模式了,不管在什么地方哪个类里面,只需要Subject.getInstance,拿到的就一定是同一个subject,非常方便。
关于单例模式
- 上面的双重检测方法,在jdk1.5之后才能完全有效。原因是volatile在1.5之前有问题。关于volatile的内存可见性以及指令排序都应该去了解一下。
- 另外还有一直静态内部类的实现方式。类似于懒汉加载,不过使用静态内部类访问static变量,把同步交给jvm类加载处理,保证线程安全。
可能有人会问接口回调能干什么呢?
- 当然是异步啦。像上面的demo,mainActivity注册完接口后,就去干其他事情了,不用等着secondActiviy调用callBack。接口调用的回调通知回来的时候,才去处理回调。
- 类A(如网络管理类)的事件(wifi断开)回调到类B(数据库管理类)处理(入库断开的事件),就可以访问B的私有成员变量和方法了。(以前不知道接口回调,还是用的handler通信 == )
下一篇我会以自己的看法来讲述一下观察者模式:以前刚听到这个模式的时候,觉得被观察者好可怜,被围观。后面渐渐发现被观察者才是老大啊,所有信号都是它发的发出来的,然后一群观察者去响应。怎么说呢,就好像是大家(可以规划到一个类集合里面)都在干活。老板突然说了句发工资了,然后大家全部去响应领工资,而且每个员工各自领的工资不一样(每个观察者实现回调不一样)。