继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

looper竟然有副业?

2019-05-02 12:29:262983浏览

风语

1实战 · 2手记
TA的实战

这年头, 副业都成了常态了, 连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的副业原理了呢

打开App,阅读手记
3人推荐
发表评论
随时随地看视频慕课网APP

热门评论

大神,请教一下:你的代码是基于framework编译的吗?
基于SDK开发,似乎没有办法调用
IoUtils.setBlocking(fd, false); 
谢谢


查看全部评论