Android native thread
本文使用android版本为:Android pie_9.0.0_r3
在Android的Framework层,使用线程时都是Thread
类,究其根本还是POSIX标准的pthread
类实现,只不过加入了一些android中特有的特性,比如:继承RefBase
,将其纳入只能指针体系。不过因为对pthread进行了二次包装,其具体的线程相关API已和pthread
大相径庭。在深入Framework代码时,难免会遇到Thread
对象,了解它,对相关代码的理解将会更加顺利。
Thread定义
但只推荐理解,不推荐使用。在开发自己程序时,依然只建议使用pthread
,因为在Thread
类定义的地方,开发者已经做了相关不推荐使用的注释(如下一段代码所示)。至于深层次的原因,暂不得而知。
// /system/core/include/utils/Thread.h
namespace android {
// DO NOT USE: please use std::thread
class Thread : virtual public RefBase {
public:
// 创建线程对象,但并不创建或启动关联线程。
explicit Thread(bool canCallJava = true);
virtual ~Thread();
// 在threadLoop函数中启动线程,threadLoop函数必须被实现
virtual status_t run(const char* name, int32_t priority = PRIORITY_DEFAULT, size_t stack = 0);
// 请求对象的线程退出。 这个函数是异步的,当该函数结束后,线程可能还在运行。
virtual void requestExit();
// 线程运行前执行,可以用于执行一些初始化函数
virtual status_t readyToRun();
// 调用requestExit函数,并且等待对象的线程退出。可能造成死锁
status_t requestExitAndWait();
// 等待直到线程退出。如果线程退出,则该函数立刻return,注意别再对象线程中调用该函数,这样容易导致死锁。
status_t join();
// 用来查看线程是否正在运行
bool isRunning() const;
#if defined(__ANDROID__)
// 和线程调用gettid()函数的调用是一致的,将会返回线程在内核中的ID。如果返回-1则表示线程并未运行。
pid_t getTid() const;
#endif
protected:
// 如果requestExit()函数被调用,则该函数将返回true
bool exitPending() const;
private:
// 子类必须实现的函数。是线程生命周期的开始,在Thread对象中,该函数有两种运行模式
// 1、loop:如果threadLoop() return true, 该函数还将继续被执行,直到requestExit函数被调用
// 2、once:如果threadLoop() return false, 线程将在该函数return时退出。
virtual bool threadLoop() = 0;
private:
Thread& operator=(const Thread&);
static int _threadLoop(void* user);
const bool mCanCallJava;
// 读或写时总是持有mLock
thread_id_t mThread; // 由创建线程时,pthread_t值的指针类型转换而来
mutable Mutex mLock;
Condition mThreadExitedCondition;
status_t mStatus;
// 请注意,所有对mExitPending和running的访问都需要持有mLock
volatile bool mExitPending;
volatile bool mRunning;
sp<Thread> mHoldSelf;
#if defined(__ANDROID__)
// legacy for debugging, not used by getTid() as it is set by the child thread
// and so is not initialized until the child reaches that point
pid_t mTid;
#endif
};
}; // namespace android
上述是Thread类头文件中对于Thread的定义,我们重点关注构造函数和虚函数实现,即如下函数:
- `Thread()与~Thread() :构造函数与析构函数
onFirstRef()
:从RefBase
处即成的智能指针第一次创建的函数。run
:创建线程的函数,一般在threadLoop()
函数中被调用readyToRun()
:线程准备函数_threadLoop()
:线程循环函数requestExit()
:请求退出函数
接下来也会以此为序,对各个重要函数实现作详细解读。
构造函数与析构函数
Thread::Thread(bool canCallJava) :
mCanCallJava(canCallJava), mThread(thread_id_t(-1)), mLock("Thread::mLock"),
mStatus(NO_ERROR), mExitPending(false), mRunning(false)
#if defined(__ANDROID__)
, mTid(-1)
#endif
{
}
Thread::~Thread()
{
}
可以看到,构造函数非常简单,都只是初始化一些私有变量,简单了解一下这些变量的作用:
- mCanCallJava :
- mStatus :标志着线程运行时的状态
- mExitPending:标志着线程是否调用过
requestExit()
,调用过则返回true - mRunning:线程是否正在运行
析构函数更简单,直接啥也没做。相信一些释放资源的工作都分配到其它函数中了。
onFirstRef()
当Thread对象创建,并被智能指针(如sp<Thread>)第一次引用时,就会调用该函数。该函数没有默认实现,但在FrameWork的很多代码中,都会通过重写该函数来初始化一些数据,阅读代码时应该引起足够的重视。
readyToRun():线程执行准备
该函数通常是用于使用者在线程运行之前,做一些准备工作的。所以,在默认实现中,直接返回了NO_ERROR,如果子线程有需要可以直接重写,以达到对应目的。
// /system/core/libutils/Threads.cpp
status_t Thread::readyToRun()
{
return NO_ERROR;
}
run():创建线程
真正创建线程的函数,线程也是从这个函数调用之后开始正式运行的,通常子类会将该函数包装到一个类似start()
的函数中,以示线程启动。
status_t Thread::run(const char* name, int32_t priority, size_t stack) {
// 当线程发生异常,需要重新尝试时,需要将线程相关状态初始化到最开始的状态。
mStatus = NO_ERROR;
mExitPending = false;
mThread = thread_id_t(-1);
// 让Thread对象拥有自己的强指针,防止被销毁
mHoldSelf = this;
mRunning = true; // 标志线程已经运行,不一定正在运行。自己体会
bool res;
if (mCanCallJava) {
res = createThreadEtc(_threadLoop, this, name, priority, stack, &mThread);
} else { // 将_threadLoop作为线程函数,创建线程
res = androidCreateRawThreadEtc(_threadLoop, this, name, priority, stack, &mThread);
}
if (res == false) { // 如果线程创建失败,设置相关的失败状态
mStatus = UNKNOWN_ERROR; // something happened!
mRunning = false;
mThread = thread_id_t(-1);
mHoldSelf.clear(); // "this" 引用减一
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
可以创建一个新线程的入口有两个:createThreadEtc()
和 androidCreateRawThreadEtc()
。决定变量在于mCanCallJava
,从变量的字面意思来说,就是如果变量为true,创建的线程可以被java代码调用。如果为false则在本地使用。
其实createThreadEtc()
函数创建线程,最终也会使用 androidCreateRawThreadEtc()
函数,只不过createThreadEtc()
会在调用 androidCreateRawThreadEtc()
函数之前,会去初始化JNI部分,使得java代码可以直接调用该线程。至于如何初始化,这主要是JNI部分的内容,就不在本文中展开了。
androidCreateRawThreadEtc
int androidCreateRawThreadEtc(android_thread_func_t entryFunction, // 线程函数
void *userData, // 线程函数对应的参数
const char* threadName __android_unused, // 线程名称
int32_t threadPriority,
size_t threadStackSize, // 线程栈大小
android_thread_id_t *threadId)
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 意味着子线程结束后自行释放资源,不需要主线程等待
#if defined(__ANDROID__)
if (threadPriority != PRIORITY_DEFAULT || threadName != NULL) {
thread_data_t* t = new thread_data_t;
t->priority = threadPriority;
t->threadName = threadName ? strdup(threadName) : NULL;
t->entryFunction = entryFunction;
t->userData = userData;
entryFunction = (android_thread_func_t)&thread_data_t::trampoline;
userData = t;
}
#endif
if (threadStackSize) { // 如果有指定栈大小,将其设置为线程栈大小
pthread_attr_setstacksize(&attr, threadStackSize);
}
errno = 0;
pthread_t thread;
int result = pthread_create(&thread, &attr, (android_pthread_entry)entryFunction, userData);
pthread_attr_destroy(&attr);
// ...
if (threadId != NULL) {
*threadId = (android_thread_id_t)thread; // XXX: this is not portable
}
return 1;
}
总结一下:
androidCreateRawThreadEtc()
函数的作用是:封装了创建线程的操作。
run()
函数通常调用androidCreateRawThreadEtc()
,并为其指定_threadLoop
函数指针作为线程函数,最终创建出了一个真正的线程,并通过参数将线程ID存储在mThread
变量中。此时,如果一切顺利,mRunning = true
。
_threadLoop() : 线程函数
该函数是真正的线程函数,通过在run()
函数中调用androidCreateRawThreadEtc()
将该函数设置给pthread线程。
int Thread::_threadLoop(void* user) { // 线程函数创建时,传递的线程参数就是Thread对象的this指针
Thread* const self = static_cast<Thread*>(user);
// ...
bool first = true;
do {
bool result;
if (first) { // 第一次运行
first = false; // 只执行一次
self->mStatus = self->readyToRun(); // 调用Thread对象的readyToRun()函数
result = (self->mStatus == NO_ERROR);
// 如果状态正常,并且没有调用过退出相关函数,则一直循环执行threadLoop()函数。
if (result && !self->exitPending()) {
result = self->threadLoop();
}
} else { // 后续运行,都全走这里
result = self->threadLoop();
}
{ // 建立mLock作用域
Mutex::Autolock _l(self->mLock);
if (result == false || self->mExitPending) { // 1. 条件满足线程退出
self->mExitPending = true;
self->mRunning = false;
self->mThread = thread_id_t(-1);
self->mThreadExitedCondition.broadcast();
break;
}
}
// Release our strong reference, to let a chance to the thread
// to die a peaceful death.
strong.clear();
// And immediately, re-acquire a strong reference for the next loop
strong = weak.promote();
} while(strong != 0);
return 0;
}
代码中,标记为1处,是线程退出的两种情况:
- 主动退出:当
threadLoop()
函数的返回值为false时,线程主动结束。 - 被动退出:当
mExitPending
为true时,由外界影响被动退出。mExitPending
可以通过requestExit()
函数设置为true。
这其中,threadLoop()
函数是一个未被默认实现的函数,必须由子线程实现。通常子线程重写该函数后,会在其中实现主要的线程逻辑,当子线程想要结束线程时,只要该函数返回false即可,如果该函数返回true,并且外界没有调用requteExit()
和requestExitAndWait()
函数,那么threadLoop()
函数将会一直循环执行下去。
requestExit() 线程退出函数
该函数非常简单,直接看。
void Thread::requestExit()
{
Mutex::Autolock _l(mLock);
mExitPending = true;
}
Framework线程示例:StartPropertySetThread
该类为Thread
子类,主要用于修改属性,具体作用就不展开,主要查看一下一个最简单的Thread
线程,如何实现。
头文件:
// \frameworks\native\services\surfaceflinger\StartPropertySetThread.h
#include <stddef.h>
#include <utils/Mutex.h>
#include <utils/Thread.h>
namespace android {
class StartPropertySetThread : public Thread { // 继承自Thread
public:
explicit StartPropertySetThread(bool timestampPropertyValue);
status_t Start();
private:
virtual bool threadLoop();
static constexpr const char* kTimestampProperty = "service.sf.present_timestamp";
const bool mTimestampPropertyValue;
};
}
具体实现:
#include <cutils/properties.h>
#include "StartPropertySetThread.h"
namespace android {
StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue):
Thread(false), mTimestampPropertyValue(timestampPropertyValue) {}
status_t StartPropertySetThread::Start() { // 在start函数中,封装了run函数,以创建和运行真正的线程
return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
}
bool StartPropertySetThread::threadLoop() { //实现主要逻辑
property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
property_set("service.bootanim.exit", "0");
property_set("service.bootanim.progress", "0");
property_set("ctl.start", "bootanim");
return false; // 返回fanlse表示 执行一次完后,线程就会等待退出。
}
} // namespace android
本文相对简单,强调了几个重要函数及其实现。总结一下
总结一下
- Android framework层大量使用的线程类为Thread。但是不知是何原因,作者不推荐使用。
- Thread类其实是对POSIX中pthread线程的一个包装,增强了其功能。
- 在FrameWork中,Thread类,并不被直接使用。通常的做法是通过继承的方式,子类应该重写必要的函数。重要的函数有:
onFirstRef()
:继承子RefBase
,通常在这里初始化一些变量,并不限于线程。readyToRun()
:线程第一次运行时会调用的函数,通常在这里初始化一些线程相关的变量或数据。threadLoop()
:线程函数,在其中实现主要的线程逻辑,函数返回true时,还会重新运行,直到函数返回false,或者requestExit()
类型的函数被调用requestExit()
:线程被动退出函数,由其它线程调用,调用后线程可能不会立刻退出。
- Android Framework中,继承
Thread
类,实现threadLoop()
函数(其中包含主要线程逻辑)。需要创建和启动线程时,调用父类的run()
函数即可实现最简单的线程。