在 GitHub 查看
背景
Android 开发中,会有很多情况下用到观察者模式,如果你自定义了一个 Listener,在需要调用他们来通知观察者时,或许会遇到以下几点头疼的问题:
这个 Listener 是 Nullable 的,那么每次调用前都需要作判空处理
1 2 3
| if (listener != null) { listener.foo(); }
|
事件是在其他线程中发出,而观察者需要在另一个线程中(通常是 UI 线程)处理,所以需要在每次调用的时候做 Handler#post
操作
1 2 3 4 5 6 7 8 9 10
| if(Looper.myLooper() != Looper.getMainLooper()) { handler.post(new Runnable() { @Override public void run() { listener.foo(); } }); } else { listener.foo(); }
|
如果观察者可以注册多个,那每次在调用的时候的时候都需要遍历一下所有 Listener
1 2 3
| for(final FooListener listener : listener) { listener.foo(); }
|
这三种情况可能会同时出现,比如在做与 Service 通信时是很常见的,如果回调方法更多时,代码写起来那是相当痛苦😂
造轮子
基于上面的原因,于是我撸一个轮子,就叫「Wrapper」。它利用 AnnotationProcessor 在构建过程中自动生成指定 Listener 的 Wrapper 类,在需要时只需简简单单的调用一下方法即可:
Wrapper 会根据需要生成判空处理、post、遍历等代码,从编写繁琐无趣的代码中解放出来。
1 2 3 4 5 6 7 8
| @WrapperClass @WrapperMultiple public interface SomeListener { @UiThread void onFoo(View view);
boolean onUserLeave(); }
|
经过「Wrapper」的处理,会生成如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package com.linroid.wrapper;
import android.os.Handler; import android.os.Looper; import android.view.View;
import java.util.HashSet;
public class SomeListenerMultiWrapper implements SomeListener { private HashSet<SomeListener> _delegates = new HashSet<>();
private Handler _handler;
public SomeListenerMultiWrapper() { this._handler = new Handler(Looper.getMainLooper()); }
public SomeListenerMultiWrapper(Handler _handler) { this._handler = _handler; }
public synchronized boolean addWrapper(SomeListener _delegate) { if (_delegate != null) { return this._delegates.add(_delegate); } return false; }
public synchronized boolean removeWrapper(SomeListener _delegate) { return this._delegates.remove(_delegate); }
@Override public boolean onUserLeave() { for (final SomeListener _delegate : _delegates) { if (_delegate != null) { _delegate.onUserLeave(); } } return false; }
@Override public void onFoo(final View view) { if (Looper.myLooper() == null || Looper.myLooper() != _handler.getLooper()) { _handler.post(new Runnable() { @Override public void run() { for (final SomeListener _delegate : _delegates) { if (_delegate != null) { _delegate.onFoo(view); } } } }); } else { for (final SomeListener _delegate : _delegates) { if (_delegate != null) { _delegate.onFoo(view); } } } } }
|
使用「Wrapper」
引入
在你的 build.gradle
:
1 2 3 4
| dependencies { annotationProcessor 'com.linroid.wrapper:compiler:0.1.0' compile 'com.linroid.wrapper:library:0.1.0' }
|
可以使用的注解:
@WrapperClass
对单个接口 / 类进行处理,默认只会进行判空处理
1 2 3 4
| @WrapperClass public interface SomeListener { void onFoo(View view); }
|
@UiThread
需要进行 Handler#post
处理,可以用在方法或者类 / 接口上,如果用在类 / 接口,会对所有方法进行处理
1 2 3 4 5 6 7 8 9
|
@WrapperClass public interface SomeListener { @UiThread void onFoo(View view); boolean onUserLeave(); }
|
@WrapperMultiple
支持多个 Listener
1 2 3 4 5
| @WrapperClass @WrapperMultiple public interface SomeListener { void onFoo(View view); }
|
@WrapperGenerator
与@WrapperClass
不同,你可以创建一个空的 Class,将所有需要处理的接口 / 类添加进来(这样就可以处理你无法修改的一些 Listener 了,比如 Android SDK 中的)。
1 2 3 4 5 6 7 8 9 10 11 12
| @WrapperGenerator( values = { View.OnClickListener.class, View.OnLongClickListener.class, MenuItem.OnMenuItemClickListener.class, View.OnScrollChangeListener.class } ) @UiThread @WrapperMultiple public class SomeGenerator { }
|
调用
经过 Wrapper 的处理,会生成一个包名相同的 XXXWrapper
的类,如果添加了 @ WrapperMultiple
注解,会额外生成一个 XXXMultiWrapper
类。
需要注意的是,如果处理的是一个接口,那么生成的 Wrapper 会实现这个接口;而如果处理的是一个类,那么生成的 Wrapper 不会继承这个类。
添加完注解在执行一次 build 后,Wrapper 就会生成好相应的 XXXWrapper
类,使用它们非常简单:
1 2 3 4 5 6 7 8 9
| SomeListenerWrapper wrapper = new SomeListenerWrapper(listener);
wrapper.setWrapper(listener); wrapper.onFoo(view); SomeListenerMultiWrapper multiWrapper = new SomeListenerMultiWrapper();
multiWrapper.addWrapper(listener); multiWrapper.onFoo(view);
|
最后
如果你有什么好的建议可以在评论留言,也可以提 PR :)
(嗯,我知道 kotlin 大法🐒
Author:
linroid
Permalink:
http://linroid.com/2014/12/21/2014-12-21-wrapper-pattern/
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
Do you believe in DESTINY?