The most reliable solution in Android to detect whether the current primary thread is present

  • 2020-06-12 10:35:39
  • OfStack

How do You determine if a thread is the main thread in Android? For this problem, you might say based on the name of the thread, of course this will solve the problem, but is this the most reliable? One day Google1 changes the name of the thread to something else.

Methods revealed

The following approach is the most reliable solution.


public static boolean isInMainThread() {
      return Looper.myLooper() == Looper.getMainLooper();
}

In fact, the title of the article is basically resolved here, but the research here is too superficial, not deep enough, so we need to continue, I hope you can continue to read.

warren

Experiment 1

Ok, now, let's do a few tests on this stable method. First, the following method adds a little debugging print information.


private boolean isInMainThread() {
    Looper myLooper = Looper.myLooper();
  Looper mainLooper = Looper.getMainLooper();
  Log.i(LOGTAG, "isInMainThread myLooper=" + myLooper
      + ";mainLooper=" + mainLooper);
  return myLooper == mainLooper;
}

Ok, then we run a test in the main thread and call the above method. So let's call it this way.


Log.i(LOGTAG, "testInMainThread inMainThread=" + isInMainThread());

OK, let's take a look at the output log. Verify OK.


I/TestInMainThread(32028): isInMainThread myLooper=Looper{40d35ef8};mainLooper=Looper{40d35ef8}
I/TestInMainThread(32028): testInMainThread inMainThread=true

Experiment 2

Now let's continue validation on a non-main thread that has no message loop.


new Thread() {
    @Override
    public void run() {
      Log.i(LOGTAG, "testIn NOT in MainThread isMainThread="
          + isInMainThread());
      super.run();
  }
}.start();

As we can see from the log results below, the Looper for the main thread (translated as a circulating pump, not very nice) has been initialized. But looper for our newly created thread is still null. This is because the thread in Android does not have a message loop bound to it by default (Threads by default do a message loop associated course, the method works).


I/TestInMainThread(32028): isInMainThread myLooper=null;mainLooper=Looper{40d35ef8}
I/TestInMainThread(32028): testIn NOT in MainThread isMainThread=false

Experiment 3

Going forward, we create a thread bound to the message loop. Here is a typical example of creating a thread bound to the message loop, using the separate prepare () and loop () methods to create an Handler bound to Looper, according to the Android developer documentation.


new Thread() {
  private Handler mHandler;
  @Override
  public void run() {
      Looper.prepare();
      mHandler = new Handler() {
            public void handleMessage(Message msg) {
              // process incoming messages here
          }
      };
      Log.i(LOGTAG, "testInNonMainLooperThread isMainThread="
            + isInMainThread());
      Looper.loop();
  }
     
}.start();

OK, now check the following log again,


I/TestInMainThread(32028): isInMainThread myLooper=Looper{40d72c58};mainLooper=Looper{40d35ef8}
I/TestInMainThread(32028): testInNonMainLooperThread isMainThread=false

Both Looper are initialized and assigned, but they are different objects.

Principle of excavation

But why? What's the mystery? Ok, let's look at Looper.class


// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class /**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
} private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
} /**
 * Return the Looper object associated with the current thread. 
 * Returns null if the calling thread is not associated with a Looper.
 */
public static Looper myLooper() {
    return sThreadLocal.get();
}  /** Returns the application's main looper, which lives in the main thread of the application.
 */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

For the main thread, the method prepareMainLooper is called by the Android runtime instead of being explicitly called by the program. With this method, looper for the main thread is created and object references are passed to sMainLooper. Therefore, it is guaranteed that the reference obtained by main thread myLooper() and getMainLooper() are the same reference.

For non-main threads without message loops, the default looper for the current thread is null, which is not the same as looper for the main thread because you never call prepare() manually.

For non-main threads that are bound to the message loop, when the Looper.prepare method is called, the Looper for the main thread has been created by the Android runtime environment, and when the prepare method is called, the looper bound to the non-main thread is created, which, of course, cannot be the same as Looper1 for the main thread.

To sum up, this method is reliable.


Related articles: