[轉]Android限制只能在主線程中進行UI訪問的實現原理
目錄
Android限制只能在主線程中進行UI訪問
Thread的實現
Android Thread 的構造方法
Android Thread 的start()方法
如何在我們自己的代碼中去檢測當前Thread是不是UI線程呢?
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.");
}
}
1
2
3
4
5
6
我們來看下這個方法,其中mThread是一個final類型,賦值是在ViewRootImpl的構造方法中,指向mThread = Thread.currentThread();
final Thread mThread;
public ViewRootImpl(Context context, Display display) {
...
mThread = Thread.currentThread();
...
}
1
2
3
4
5
6
7
即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 {
...
}
1
2
3
4
而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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
1
2
3
4
5
6
7
8
9
10
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);
}
1
2
3
4
下面來詳細分析 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); ?
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
首先看下[create]?部分的代碼,通過靜態函數 currentThread 獲取創建線程所在的當前線程,然後將當前線程的一些屬性傳遞給即將創建的新線程。這是通過 VMThread 轉調 dalvik 中的代碼實現的。
// android/libcore/luni/src/main/java/java/lang/Thread.java
public static Thread currentThread() {
return VMThread.currentThread();
}
1
2
3
4
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);
}
1
2
3
4
5
6
7
8
來看下 dvmThreadSelf() 方法,每一個 dalvik 線程都會將自身存放在key 為 pthreadKeySelf 的線程本地存儲中,獲取當前線程時,只需要根據這個 key 查詢獲取即可
// dalvik/vm/Thread.cpp 中:
Thread* dvmThreadSelf()
{
return (Thread*) pthread_getspecific(gDvm.pthreadKeySelf);
}
1
2
3
4
5
dalvik Thread 有一個名為 threadObj 的成員變量:
// dalvik/vm/Thread.h
/* the java/lang/Thread that we are associated with */
Object* threadObj;
1
2
3
在之後的分析中我們可以看到,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));
}
}
1
2
3
4
5
6
7
8
9
10
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);
}
1
2
3
4
5
6
7
8
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();
}
1
2
3
4
5
6
7
8
9
10
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
首先,通過調用 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);
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
在新線程的 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();
}
}
1
2
3
4
5
6
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
在 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.
}
1
2
3
或者
if(Looper.getMainLooper().getThread() == Thread.currentThread()) {
// Current Thread is Main Thread.
}
1
2
[轉]Android限制只能在主線程中進行UI訪問的實現原理