[转]Android限制只能在主线程中进行UI访问的实现原理

宁可枝头抱香死,何曾吹落北风中。这篇文章主要讲述[转]Android限制只能在主线程中进行UI访问的实现原理相关的知识,希望能为你提供帮助。
【[转]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











































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































    推荐阅读