目录
Android限制只能在主线程中进行UI访问
我们知道,Android中规定了访问UI只能在主线程中进行,如果在子线程中访问UI的话,程序就会抛出异常Only the original thread that created a view hierarchy can touch its views.
查看源码后可以发现,这个验证工作是由ViewRootImpl
的checkThread()
方法来完成的
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
我们来看下这个方法,其中mThread
是一个final
类型,赋值是在ViewRootImpl
的构造方法中,指向mThread = Thread.currentThread();
final Thread mThread;
public ViewRootImpl(Context context, Display display) {
...
mThread = Thread.currentThread();
...
}
即mThread
和当前调用的Thread.currentThread()
不是一个Thread的话,即可判定当前不是UI线程中执行。
这里再多说一点,系统为什么不允许在子线程中访问UI呢?这是因为Android的UI空间不是线程安全的,如果在多线程中并发访问可能会导致UI空间处于不可预期的状态。
Thread的实现
Android Thread 的构造方法
涉及到的 Android 源码路径:
libcore/luni/src/main/java/java/lang/Runnable.java
libcore/luni/src/main/java/java/lang/Thread.java
libcore/luni/src/main/java/java/lang/ThreadGroup.java
libcore/luni/src/main/java/java/lang/VMThread.java
dalvik/vm/native/java_lang_VMThread.cpp
dalvik/vm/Thread.cpp
首先来分析 Android Thread,它实现了 Runnable
接口
// libcore/luni/src/main/java/java/lang/Thread.java
public class Thread implements Runnable {
...
}
而Runnable
只有一个无参无返回值的 run()
接口:
// libcore/luni/src/main/java/java/lang/Runnable.java
/**
* Represents a command that can be executed. Often used to run code in a
* different {@link Thread}.
*/
public interface Runnable {
/**
* Starts executing the active part of the class' code. This method is
* called when a thread is started that has been created with a class which
* implements {@code Runnable}.
*/
public void run();
}
Android Thread存在六种状态,这些状态定义在枚举 State
中,源码注释写的很清晰
// libcore/luni/src/main/java/java/lang/Thread.java
/**
* A representation of a thread's state. A given thread may only be in one
* state at a time.
*/
public enum State {
/**
* The thread has been created, but has never been started.
*/
NEW,
/**
* The thread may be run.
*/
RUNNABLE,
/**
* The thread is blocked and waiting for a lock.
*/
BLOCKED,
/**
* The thread is waiting.
*/
WAITING,
/**
* The thread is waiting for a specified amount of time.
*/
TIMED_WAITING,
/**
* The thread has been terminated.
*/
TERMINATED
}
Android Thread 类中一些关键成员变量如下:
// libcore/luni/src/main/java/java/lang/Thread.java
volatile VMThread vmThread;
volatile ThreadGroup group;
volatile String name;
volatile int priority;
volatile long stackSize;
Runnable target;
private static int count = 0;
private long id;
ThreadLocal.Values localValues;
vmThread
:可视为对 dalvik thread 的简单封装,Thread
类通过 VMThread 里面的 JNI 方法来调用 dalvik 中操作线程的方法,通过它的成员变量thread
和vmata
,我们可以将 Android Thread 和 dalvik Thread 的关联起来;group
:每一个线程都属于一个group
,当线程被创建时就会加入一个特定的group
,当线程运行结束,会从这个group
中移除;priority
:线程优先级;stackSize
:线程栈大小;target
:一个Runnable
对象,Thread 的run()
方法中会转调该target
的run()
方法,这是线程真正处理事务的地方;id
:线程id
,通过递增 count 得到该id,如果没有显式给线程设置名字,那么就会使用 Thread+id
当作线程的名字。注意这不是真正意义上的线程 id,即在 logcat 中打印的 tid 并不是这个 id,那 tid 是指 dalvik 线程的 id;localValues
:线程本地存储(TLS)数据,而TLS的作用是能将数据和执行的特定的线程联系起来。
接下来,我们来看Android Thread 的构造函数,大部分构造函数都是通过转调静态函数 create 实现的
// libcore/luni/src/main/java/java/lang/Thread.java
public Thread() {
create(null, null, null, 0);
}
下面来详细分析 create
这个关键函数:
// libcore/luni/src/main/java/java/lang/Thread.java
private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
Thread currentThread = Thread.currentThread(); ❶
if (group == null) {
group = currentThread.getThreadGroup();
}
...
this.group = group;
synchronized (Thread.class) {
id = ++Thread.count;
}
if (threadName == null) {
this.name = "Thread-" + id;
} else {
this.name = threadName;
}
this.target = runnable;
this.stackSize = stackSize;
this.priority = currentThread.getPriority();
this.contextClassLoader = currentThread.contextClassLoader;
// Transfer over InheritableThreadLocals.
if (currentThread.inheritableValues != null) {
inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);
}
// add ourselves to our ThreadGroup of choice
this.group.addThread(this); ❷
}
首先看下[create]❶部分的代码,通过静态函数 currentThread
获取创建线程所在的当前线程,然后将当前线程的一些属性传递给即将创建的新线程。这是通过 VMThread 转调 dalvik 中的代码实现的。
// android/libcore/luni/src/main/java/java/lang/Thread.java
public static Thread currentThread() {
return VMThread.currentThread();
}
VMThread 的 currentThread
是一个 native 方法,其 JNI 实现为
// dalvik/vm/native/java_lang_VMThread.cpp
static void Dalvik_java_lang_VMThread_currentThread(const u4* args,
JValue* pResult)
{
...
RETURN_PTR(dvmThreadSelf()->threadObj);
}
来看下 dvmThreadSelf()
方法,每一个 dalvik 线程都会将自身存放在key 为 pthreadKeySelf
的线程本地存储中,获取当前线程时,只需要根据这个 key 查询获取即可
// dalvik/vm/Thread.cpp 中:
Thread* dvmThreadSelf()
{
return (Thread*) pthread_getspecific(gDvm.pthreadKeySelf);
}
dalvik Thread 有一个名为 threadObj
的成员变量:
// dalvik/vm/Thread.h
/* the java/lang/Thread that we are associated with */
Object* threadObj;
在之后的分析中我们可以看到,dalvik Thread 这个成员变量 threadObj
关联的就是对应的 Android Thread 对象,所以通过 native 方法 VMThread.currentThread()
返回的是存储在 TLS 中的当前 dalvik 线程对应的 Android Thread。
接着分析上面[create]❷部分的代码,如果没有给新线程指定 group
,那么就会指定 group
为当前线程所在的 group
中,然后给新线程设置 name
,priority
等。最后通过调用 ThreadGroup
的 addThread
方法将新线程添加到 group 中:
// libcore/libart/src/main/java/java/lang/ThreadGroup.java
/**
* Called by the Thread constructor.
*/
final void addThread(Thread thread) throws IllegalThreadStateException {
synchronized (threadRefs) {
...
threadRefs.add(new WeakReference<Thread>(thread));
}
}
ThreadGroup
的代码相对简单,它有一个名为 threadRefs
的列表,持有属于同一组的 thread
引用,可以对一组 thread
进行一些线程操作。
上面分析的是 Android Thread 的构造过程,从上面的分析可以看出,Android Thread 的构造方法仅仅是设置了一些线程属性,并没有真正去创建一个新的 dalvik Thread,dalvik Thread 创建过程要等到客户代码调用 Android Thread 的 start() 方法才会进行。
Android Thread 的start()方法
下面我们来分析 Java Thread 的 start()
方法:
// libcore/luni/src/main/java/java/lang/Thread.java
public synchronized void start() {
checkNotStarted();
hasBeenStarted = true;
VMThread.create(this, stackSize);
}
Android Thread 的 start
方法很简单,仅仅是转调 VMThread 的 native 方法
// dalvik/vm/native/java_lang_VMThread.cpp
static void Dalvik_java_lang_VMThread_create(const u4* args, JValue* pResult)
{
Object* threadObj = (Object*) args[0];
s8 stackSize = GET_ARG_LONG(args, 1);
/* copying collector will pin threadObj for us since it was an argument */
dvmCreateInterpThread(threadObj, (int) stackSize);
RETURN_VOID();
}
dvmCreateInterpThread
的实现
// dalvik/vm/Thread.cpp
bool dvmCreateInterpThread(Object* threadObj, int reqStackSize)
{
Thread* self = dvmThreadSelf();
...
Thread* newThread = allocThread(stackSize);
newThread->threadObj = threadObj;
...
Object* vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT);
dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)newThread);
dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, vmThreadObj);
...
pthread_t threadHandle;
int cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart, newThread);
/*
* Tell the new thread to start.
*
* We must hold the thread list lock before messing with another thread.
* In the general case we would also need to verify that newThread was
* still in the thread list, but in our case the thread has not started
* executing user code and therefore has not had a chance to exit.
*
* We move it to VMWAIT, and it then shifts itself to RUNNING, which
* comes with a suspend-pending check.
*/
dvmLockThreadList(self);
assert(newThread->status == THREAD_STARTING);
newThread->status = THREAD_VMWAIT;
pthread_cond_broadcast(&gDvm.threadStartCond);
dvmUnlockThreadList();
...
}
/*
* Alloc and initialize a Thread struct.
*
* Does not create any objects, just stuff on the system (malloc) heap.
*/
static Thread* allocThread(int interpStackSize)
{
Thread* thread;
thread = (Thread*) calloc(1, sizeof(Thread));
...
thread->status = THREAD_INITIALIZING;
}
首先,通过调用 allocThread
创建一个名为 newThread
的 dalvik Thread 并设置一些属性,将设置其成员变量 threadObj
为传入的 Android Thread,这样 dalvik Thread 就与Android Thread 关联起来了;
然后创建一个名为 vmThreadObj
的 VMThread
对象,设置其成员变量 vmData
为 newThread
,设置 Android Thread threadObj 的成员变量 vmThread
为这个 vmThreadObj
,这样 Android Thread 通过 VMThread 的成员变量 vmData
就和 dalvik Thread 关联起来了。
最后,通过 pthread_create
创建 pthread
线程,并让这个线程 start
,这样就会进入该线程的 thread entry 运行,下来我们来看新线程的 thread entry 方法 interpThreadStart
,同样只列出关键的地方:
// dalvik/vm/Thread.cpp
/*
* pthread entry function for threads started from interpreted code.
*/
static void* interpThreadStart(void* arg)
{
Thread* self = (Thread*) arg;
...
/*
* Finish initializing the Thread struct.
*/
dvmLockThreadList(self);
prepareThread(self);
...
/*
* Change our state so the GC will wait for us from now on. If a GC is
* in progress this call will suspend us.
*/
dvmChangeStatus(self, THREAD_RUNNING);
/*
* Execute the "run" method.
*
* At this point our stack is empty, so somebody who comes looking for
* stack traces right now won't have much to look at. This is normal.
*/
Method* run = self->threadObj->clazz->vtable[gDvm.voffJavaLangThread_run];
JValue unused;
ALOGV("threadid=%d: calling run()", self->threadId);
assert(strcmp(run->name, "run") == 0);
dvmCallMethod(self, run, self->threadObj, &unused);
ALOGV("threadid=%d: exiting", self->threadId);
/*
* Remove the thread from various lists, report its death, and free
* its resources.
*/
dvmDetachCurrentThread();
return NULL;
}
/*
* Finish initialization of a Thread struct.
*
* This must be called while executing in the new thread, but before the
* thread is added to the thread list.
*
* NOTE: The threadListLock must be held by the caller (needed for
* assignThreadId()).
*/
static bool prepareThread(Thread* thread)
{
assignThreadId(thread);
thread->handle = pthread_self();
thread->systemTid = dvmGetSysThreadId();
setThreadSelf(thread);
...
return true;
}
/*
* Explore our sense of self. Stuffs the thread pointer into TLS.
*/
static void setThreadSelf(Thread* thread)
{
int cc;
cc = pthread_setspecific(gDvm.pthreadKeySelf, thread);
...
}
在新线程的 thread entry 方法 interpThreadStart
中,首先设置线程的名字,然后通过调用 prepareThread
设置线程 id
以及其它一些属性,并调用 setThreadSelf
将新 dalvik Thread 自身保存在 TLS 中,这样之后就能通过 dvmThreadSelf
方法从 TLS 中获取它。然后修改状态为 THREAD_RUNNING
,并调用对应 Android Thread 的 run
方法,运行客户代码:
// libcore/luni/src/main/java/java/lang/Thread.java
public void run() {
if (target != null) {
target.run();
}
}
target
在前面已经做了介绍,它是线程真正处理逻辑事务的地方。一旦逻辑事务处理完毕从 run
中返回,线程就会回到 interpThreadStart
方法中,继续执行 dvmDetachCurrentThread
方法:
// dalvik/vm/Thread.cpp
/*
* Detach the thread from the various data structures, notify other threads
* that are waiting to "join" it, and free up all heap-allocated storage.
* /
void dvmDetachCurrentThread()
{
Thread* self = dvmThreadSelf();
Object* vmThread;
Object* group;
...
group = dvmGetFieldObject(self->threadObj, gDvm.offJavaLangThread_group);
/*
* Remove the thread from the thread group.
*/
if (group != NULL) {
Method* removeThread =
group->clazz->vtable[gDvm.voffJavaLangThreadGroup_removeThread];
JValue unused;
dvmCallMethod(self, removeThread, group, &unused, self->threadObj);
}
/*
* Clear the vmThread reference in the Thread object. Interpreted code
* will now see that this Thread is not running. As this may be the
* only reference to the VMThread object that the VM knows about, we
* have to create an internal reference to it first.
*/
vmThread = dvmGetFieldObject(self->threadObj,
gDvm.offJavaLangThread_vmThread);
dvmAddTrackedAlloc(vmThread, self);
dvmSetFieldObject(self->threadObj, gDvm.offJavaLangThread_vmThread, NULL);
/* clear out our struct Thread pointer, since it's going away */
dvmSetFieldObject(vmThread, gDvm.offJavaLangVMThread_vmData, NULL);
...
/*
* Thread.join() is implemented as an Object.wait() on the VMThread
* object. Signal anyone who is waiting.
*/
dvmLockObject(self, vmThread);
dvmObjectNotifyAll(self, vmThread);
dvmUnlockObject(self, vmThread);
dvmReleaseTrackedAlloc(vmThread, self);
vmThread = NULL;
...
dvmLockThreadList(self);
/*
* Lose the JNI context.
*/
dvmDestroyJNIEnv(self->jniEnv);
self->jniEnv = NULL;
self->status = THREAD_ZOMBIE;
/*
* Remove ourselves from the internal thread list.
*/
unlinkThread(self);
...
releaseThreadId(self);
dvmUnlockThreadList();
setThreadSelf(NULL);
freeThread(self);
}
/*
* Free a Thread struct, and all the stuff allocated within.
*/
static void freeThread(Thread* thread)
{
...
free(thread);
}
在 dvmDetachCurrentThread
函数里,首先获取当前线程 self
,这里获得的就是当前执行 thread entry 的新线程,然后通过其对应的 Android Thread 对象 threadObj
获取该对象所在 group
,然后将 threadObj
这个 Android Thread 对象从 group
中移除;
接着清除 Android 与 dalvik 线程之间的关联关系,并通知 join 该线程的其它线程;
最后,设置线程状态为 THREAD_ZOMBIE,清除 TLS 中存储的线程值,并通过调用 freeThread
释放内存,至此线程就终结了。
如何在我们自己的代码中去检测当前Thread是不是UI线程呢?
最后来说下在app中检测当前Thread是不是UI线程的方法:
if(Looper.myLooper() == Looper.getMainLooper()) {
// Current Thread is Main Thread.
}
或者
if(Looper.getMainLooper().getThread() == Thread.currentThread()) {
// Current Thread is Main Thread.
}
参考文章:
https://blog.csdn.net/kesalin/article/details/37659547
https://stackoverflow.com/questions/11411022/how-to-check-if-current-thread-is-not-main-thread