这年头, 副业都成了常态了, 连looper也不例外, 我们这篇文章呢就来聊一下looper的副业。
首先呢, 我们回顾一下looper的主业是什么, 就是消息循环啊
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next();
msg.target.dispatchMessage(msg);
}
}
这不断从消息队列取消息, 分发消息。没有可以处理的消息的话, 就在这干等着, 在这个next函数里, 我们跟进去看一下,
Message next() {
Message msg;
for (;;) {
......
nativePollOnce(ptr, …);
......
return msg;
}
}
上面这个nativePollOnce可能会阻塞, 有三种情况会返回, 一个是出错了, 一个是超时了, 还一个是有事件了。出错没什么好说的, 超时是因为有消息的触发时间到了, 至于说有事件, 这个就是重点了。
我们来看下looper它是怎么处理事件的,
int Looper::pollInner(int timeoutMillis) {
......
epoll_wait(mEpollFd, eventItems, ..., timeoutMillis);
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
if (fd == mWakeEventFd) {
......
} else {
......
}
}
......
}
这里epoll_wait等待事件, 返回了之后, 除了出错和超时, 接下来就是处理事件了。在一个for循环里一个一个地处理, 每个事件都对应一个fd描述符。我们注意到, 这里分两种情况, 一种是描述符为mWakeEventFd, 另一种就是别的描述符。
mWakeEventFd就是用于线程消息队列的, 这个是looper的主业。副业呢就是监听别的fd描述符了啊, 想想也是, epoll_wait功能这么强大, 就只监听一个描述符也太浪费了, 不如充分利用一下, 搞点副业。
looper非常贴心地提供了一个接口, 可以让你随便添加fd, 带上你想让epoll监听的事件, 还有一个回调, 收到事件的时候就会调这个回调。
int
addFd(int fd, ..., int events, sp<LooperCallback>& callback,)
{
......
epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem);
}
这个函数在Java层也有开放的接口, 在MessageQueue这个类里
void addOnFileDescriptorEventListener(FileDescriptor fd, int events, OnFileDescriptorEventListener listener)
{
......
}
这个机制呢Framework里用到的还不少, 不过主要是native层用的, 比如说Choreographer里用到了
status_t NativeDisplayEventReceiver::initialize() {
mMessageQueue->getLooper()->addFd(mReceiver.getFd(), 0,
EVENT_INPUT, this, NULL);
}
这个是监听vsync信号的, 如果有vsync信号了, SurfaceFlinger就会往socket fd里面写, 然后应用这边就能收到事件了。
我们自己可以做个试验啊,
private void createPipe() {
try {
mFds = ParcelFileDescriptor.createPipe();
} catch (IOException e) {
e.printStackTrace();
}
}
创建一个管道, 有一对描述符, 一个读, 一个写。然后给读端的描述符传到另一个进程。
private void bindService() {
Intent intent = new Intent(this, MyService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(…, IBinder service) { IMyAidlInterface call = IMyAidlInterface.Stub.asInterface(service);
call.publish(mFds[0], mCallback);
}
}, BIND_AUTO_CREATE);
}
这先bindService, 然后通过binder调用给描述符传到service进程, 注意描述符不能通过intent传递, 会抛异常。
然后往写描述符里写一个字符串, 看读那端能不能收到
private void writePipe() {
try {
AutoCloseOutputStream output = new AutoCloseOutputStream(mFds[1]);
output.write("hello".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
service收到描述符之后, 马上监听这个描述符的读事件,
@Overridepublic IBinder onBind(Intent intent) {
return new IMyAidlInterface.Stub() {
@Override
public void publish(ParcelFileDescriptor pfd, ICallback callback) {
observe(pfd, callback);
}
};
}
这里要注意, 给描述符设置成非阻塞的, 然后监听这个描述符, 这传了一个回调。
private void observe(ParcelFileDescriptor pfd, ICallback callback) {
mPfd = pfd;
FileDescriptor fd = pfd.getFileDescriptor();
IoUtils.setBlocking(fd, false);
mCallback = callback;
MessageQueue queue = Looper.getMainLooper().getQueue(); queue.addOnFileDescriptorEventListener(fd, EVENT_INPUT, this);
}
在收到可读事件之后, 就给数据读出来。
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
AutoCloseInputStream reader = new
AutoCloseInputStream(mPfd);
StringBuilder sb = new StringBuilder();
final byte[] buffer = new byte[4];
do {
size = reader.read(buffer, 0, buffer.length);
if (size > 0) {
sb.append(new String(buffer, 0, size));
}
} while (size > 0);
mCallback.onRecv(sb.toString());
return EVENT_INPUT;
}
好了, 通过这个实验, 大家是否进一步掌握了looper的副业原理了呢
热门评论