1. Android Handler回顾
在Android中,UI线程是一个很重要的概念。我们在日常开发中对UI的更新和一些系统行为,都必须在UI线程(主线程)中进行调用。我们在子线程更新UI时最常用的手段就是Handler,Handler的主要原理:
主要是有一个Looper不停的从队列读消息,子线程通过持有Handler向队列写消息,以此来实现线程通信。但让Looper线程不一定是主线程,子线程也可以通过Looper.prepare();
来创建Looper,构建Handler时可以将Looper传入到Handler构造方法来和Looper绑定。
2. JNI中实现Looper
理论上我们日常开发中不会涉及JNI中更新UI的问题,就算需要也可以回调到Java层,在Java层切换。但是当我们遇到很多线程需要回调JNI,而JNI线程回调Java需要通过JavaVM来创建JNIEnv,每个线程都来AttachCurrentThread会带来性能上的开销,我们会想都通过一个线程回调Java来解决这个问题,这个时候是不是就开始怀念Java的Handler了?
我们可以手动实现一个队列来实现一个线程回调Java:
template <typename T>
class BlockingQueue
{
public:
BlockingQueue()
:m_mutex(),
m_condition(),
m_data()
{
}
// 禁止拷贝构造
BlockingQueue(BlockingQueue&) = delete;
~BlockingQueue()
{
}
void push(T&& value)
{
// 往队列中塞数据前要先加锁
std::unique_lock<std::mutex> lock(m_mutex);
m_data.push(value);
m_condition.notify_all();
}
void push(const T& value)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_data.push(value);
m_condition.notify_all();
}
T take()
{
std::unique_lock<std::mutex> lock(m_mutex);
while(m_data.empty())
{
m_condition.wait(lock);
}
assert(!m_data.empty());
T value(std::move(m_data.front()));
m_data.pop();
return value;
}
size_t size() const
{
std::unique_lock<std::mutex> lock(m_mutex);
return m_data.size();
}
private:
// 实际使用的数据结构队列
std::queue<T> m_data;
// 条件变量的锁
std::mutex m_mutex;
std::condition_variable m_condition;
};
在JNI线程中读队列:
void callbackWorkThread() {
JNIEnv *recJniEnv;
if (javaVM->AttachCurrentThread(&recJniEnv, NULL) != JNI_OK) {
LOGE("java VM AttachCurrentThread failed");
return;
}
while (isWorking) {
Event *value = blockingqueue->take();
//xxxxx
}
}
除了自己实现有没有其他办法?
3. ALooper
JNI中为我们提供了ALooper,在头文件looper.h
中,ALooper的创建过程:
mainlooper = ALooper_prepare(0);
int ret = ALooper_addFd(mainlooper, readpipe, 1, ALOOPER_EVENT_INPUT, handle_message, NULL);
下面我们看看这两个方法的具体说明:
/**
* Prepares a looper associated with the calling thread, and returns it.
* If the thread already has a looper, it is returned. Otherwise, a new
* one is created, associated with the thread, and returned.
*
* The opts may be ALOOPER_PREPARE_ALLOW_NON_CALLBACKS or 0.
*/
ALooper* ALooper_prepare(int opts);
通过注释,我们可以看到,ALooper_prepare
会准备一个looper并关联到被调用线程。如果当前线程已经有Looper则直接返回,如果没有则创建并返回。由于我们是在主线程对MainLooper进行的初始化,主线程默认会创建Looper,所以直接返回的主线程的looper。
接下来再来看一下ALooper_addFd
方法:
/**
* Adds a new file descriptor to be polled by the looper.
* If the same file descriptor was previously added, it is replaced.
*
* "fd" is the file descriptor to be added.
* "ident" is an identifier for this event, which is returned from ALooper_pollOnce().
* The identifier must be >= 0, or ALOOPER_POLL_CALLBACK if providing a non-NULL callback.
* "events" are the poll events to wake up on. Typically this is ALOOPER_EVENT_INPUT.
* "callback" is the function to call when there is an event on the file descriptor.
* "data" is a private data pointer to supply to the callback.
*
* There are two main uses of this function:
*
* (1) If "callback" is non-NULL, then this function will be called when there is
* data on the file descriptor. It should execute any events it has pending,
* appropriately reading from the file descriptor. The 'ident' is ignored in this case.
*
* (2) If "callback" is NULL, the 'ident' will be returned by ALooper_pollOnce
* when its file descriptor has data available, requiring the caller to take
* care of processing it.
*
* Returns 1 if the file descriptor was added or -1 if an error occurred.
*
* This method can be called on any thread.
* This method may block briefly if it needs to wake the poll.
*/
int ALooper_addFd(ALooper* looper, int fd, int ident, int events,
ALooper_callbackFunc callback, void* data);
这里面用到了文件描述符,我们出于效率考虑,不会直接使用对应SD卡的文件,而是使用管道,管道一端负责写入,管道另一端会在looper所在的线程中,当监测到fd变化时,调用callback方法。
通过初始中的这样两个方法,我们就构建了一条通往主线程的通道。
3. 具体示例
在初始化的方法中,我们构筑了一条消息通道。接下来,我们就需要将消息发送至主线程。
void MainLooper::init() {
int msgpipe[2];
pipe(msgpipe);//管道知识之前系列做过介绍
readpipe = msgpipe[0];
writepipe = msgpipe[1];
mainlooper = ALooper_prepare(0);
int ret = ALooper_addFd(mainlooper, readpipe, 1, ALOOPER_EVENT_INPUT, MainLooper::handle_message, NULL);
}
int MainLooper::handle_message(int fd, int events, void *data) {
char buffer[LOOPER_MSG_LENGTH];
memset(buffer, 0, LOOPER_MSG_LENGTH);
read(fd, buffer, sizeof(buffer));
LOGD("receive msg %s" , buffer);
Toast::GetInstance()->toast(buffer);
return 1;
}
void MainLooper::send(const char *msg) {
pthread_mutex_lock(&looper_mutex_);
LOGD("send msg %s" , msg);
write(writepipe, msg, strlen(msg));
pthread_mutex_unlock(&looper_mutex_);
}
上面这种写法读固定长度的buffer会有粘包问题,即多个线程写,looper中读到的内容可能是错乱的,这时候我们应该指定通信协议,比如头两个字节做Header存放长度。
4. 总结
本文回顾了Android 传统Handler机制,以及在JNI中实现Looper和JNI提供的ALooper的使用方式和技巧:使用管道来实现线程通信,并通过自定义通信协议来解决粘包问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。