zoukankan      html  css  js  c++  java
  • [转]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

  • 相关阅读:
    HDU 1002 A + B Problem II
    HDU 2602 Bone Collector WA谁来帮忙找找错
    爬楼梯问题-最大迈两步
    2106 Problem F Shuffling Along 中石油-未提交-->已提交
    2101 Problem A Snake Filled
    2078 Problem H Secret Message 中石油-未提交-->已提交
    有关 时间 空间 以及 数据类型 的总结
    hdu 2510
    hdu 1133 卡特兰 高精度
    hdu 2067
  • 原文地址:https://www.cnblogs.com/hj558558/p/10082344.html
Copyright © 2011-2022 走看看