5.3.1 一个变量引发的思考

最后更新于:2022-04-02 05:49:56

Thread类虽说挺简单,但它构造函数中的那个canCallJava却一度使我感到费解。因为我一直使用的是自己封装的Pthread类。当发现Thread构造函数中竟然存在这样一个东西时,很担心自己封装的Pthread类会不会有什么重大问题,因为当时我还从来没考虑过Java方面的问题。 ~~~ // canCallJava表示这个线程是否会使用JNI函数。为什么需要一个这样的参数呢? Thread(bool canCallJava = true)。 ~~~ 我们必须得了解它实际创建的线程函数是什么。Thread类真实的线程是创建在run函数中的。 1. 一个变量,两种处理 先来看一段代码: **Thread.cpp** ~~~ status_t Thread::run(const char* name, int32_tpriority, size_t stack) { Mutex::Autolock_l(mLock); .... //如果mCanCallJava为真,则调用createThreadEtc函数,线程函数是_threadLoop。 //_threadLoop是Thread.cpp中定义的一个函数。 if(mCanCallJava) { res = createThreadEtc(_threadLoop,this, name, priority,stack,&mThread); } else{ res = androidCreateRawThreadEtc(_threadLoop, this, name, priority, stack,&mThread); } ~~~ 上面的mCanCallJava将线程创建函数的逻辑分为两个分支,虽传入的参数都有_threadLoop,但调用的函数却不同。先直接看mCanCallJava为true的这个分支,代码如下所示: **Thread.h::createThreadEtc()函数** ~~~ inline bool createThreadEtc(thread_func_tentryFunction, void *userData, const char*threadName = "android:unnamed_thread", int32_tthreadPriority = PRIORITY_DEFAULT, size_tthreadStackSize = 0, thread_id_t*threadId = 0) { returnandroidCreateThreadEtc(entryFunction, userData, threadName, threadPriority, threadStackSize,threadId) ? true : false; } ~~~ 它调用的是androidCreateThreadEtc函数,相关代码如下所示: ~~~ // gCreateThreadFn是函数指针,初始化时和mCanCallJava为false时使用的是同一个 //线程创建函数。那么有地方会修改它吗? static android_create_thread_fn gCreateThreadFn= androidCreateRawThreadEtc; int androidCreateThreadEtc(android_thread_func_tentryFunction, void*userData,const char* threadName, int32_tthreadPriority,size_t threadStackSize, android_thread_id_t*threadId) { returngCreateThreadFn(entryFunction, userData, threadName, threadPriority,threadStackSize, threadId); } ~~~ 如果没有人修改这个函数指针,那么mCanCallJava就是虚晃一枪,并无什么作用,很可惜,代码中有的地方是会修改这个函数指针的指向的,请看: 2. zygote偷梁换柱 在第四章4.2.1的第2小节AndroidRuntime调用startReg的地方,就有可能修改这个函数指针,其代码如下所示: **AndroidRuntime.cpp** ~~~ /*static*/ int AndroidRuntime::startReg(JNIEnv*env) { //这里会修改函数指针为javaCreateThreadEtc androidSetCreateThreadFunc((android_create_thread_fn)javaCreateThreadEtc); return0; } ~~~ 所以,如果mCanCallJava为true,则将调用javaCreateThreadEtc。那么,这个函数有什么特殊之处呢?来看其代码,如下所示: **AndroidRuntime.cpp** ~~~ int AndroidRuntime::javaCreateThreadEtc( android_thread_func_tentryFunction, void* userData, const char*threadName, int32_tthreadPriority, size_t threadStackSize, android_thread_id_t* threadId) { void**args = (void**) malloc(3 * sizeof(void*)); intresult; args[0] = (void*) entryFunction; args[1] = userData; args[2] = (void*) strdup(threadName); //调用的还是androidCreateRawThreadEtc,但线程函数却换成了javaThreadShell。 result= androidCreateRawThreadEtc(AndroidRuntime::javaThreadShell, args, threadName, threadPriority,threadStackSize, threadId); returnresult; } ~~~ **AndroidRuntime.cpp** ~~~ int AndroidRuntime::javaThreadShell(void* args){ ...... intresult; //把这个线程attach到JNI环境中,这样这个线程就可以调用JNI的函数了 if(javaAttachThread(name, &env) != JNI_OK) return -1; //调用实际的线程函数干活 result = (*(android_thread_func_t)start)(userData); //从JNI环境中detach出来。 javaDetachThread(); free(name); returnresult; } ~~~ 3. 费力而讨好 你明白mCanCallJava为true的目的了吗?它创建的新线程将: - 在调用你的线程函数之前会attach到 JNI环境中,这样,你的线程函数就可以无忧无虑地使用JNI函数了。 - 线程函数退出后,它会从JNI环境中detach,释放一些资源。 第二点尤其重要,因为进程退出前,dalvik虚拟机会检查是否有attach了,但是最后未detach的线程如果有,则会直接abort(这不是一件好事)。如果你关闭JNI check选项,就不会做这个检查,但我觉得,这个检查和资源释放有关系。建议还是重视JNIcheck。如果直接使用POSIX的线程创建函数,那么凡是使用过attach的,最后就都需要detach! Android为了dalvik的健康真是费尽心机呀。 4. 线程函数_threadLoop介绍 不论一分为二是如何处理的,最终的线程函数_threadLoop都会被调用,为什么不直接调用用户传入的线程函数呢?莫非_threadLoop会有什么暗箱操作吗?下面,我们来看: **Thread.cpp** ~~~ int Thread::_threadLoop(void* user) { Thread* const self = static_cast(user); sp strong(self->mHoldSelf); wp weak(strong); self->mHoldSelf.clear(); #if HAVE_ANDROID_OS self->mTid = gettid(); #endif boolfirst = true; do { bool result; if(first) { first = false; //self代表继承Thread类的对象,第一次进来将调用readyToRun,看看是否准备好 self->mStatus = self->readyToRun(); result = (self->mStatus == NO_ERROR); if (result && !self->mExitPending) { result = self->threadLoop(); } }else { /* 调用子类实现的threadLoop函数,注意这段代码运行在一个do-while循环中。 这表示即使我们的threadLoop返回了,线程也不一定会退出。 */ result = self->threadLoop(); } /* 线程退出的条件: 1)result 为false。这表明,如果子类在threadLoop中返回false,线程就可以 退出。这属于主动退出的情况,是threadLoop自己不想继续干活了,所以返回false。 读者在自己的代码中千万别写错threadLoop的返回值。 2)mExitPending为true,这个变量可由Thread类的requestExit函数设置,这种 情况属于被动退出,因为由外界强制设置了退出条件。 */ if(result == false || self->mExitPending) { self->mExitPending = true; self->mLock.lock(); self->mRunning = false; self->mThreadExitedCondition.broadcast(); self->mLock.unlock(); break; } strong.clear(); strong = weak.promote(); }while(strong != 0); return0; } ~~~ 关于_threadLoop,我们就介绍到这里。请读者务必注意下面一点: - threadLoop运行在一个循环中,它的返回值可以决定是否退出线程。
';