且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

主线程和UI线程之间的区别

更新时间:2021-08-01 09:11:55

感谢一个非常有趣的问题.

Thanks for an exceptionally interesting question.

结果是,UI和主线程不一定相同.但是,如您引用的文档中所述,仅在某些系统应用程序(作为OS的一部分运行的应用程序)的上下文中,区别才是重要的.因此,只要您不构建定制的ROM或为手机制造商定制Android,我都不会费心做任何区分.

Turns out, UI and Main threads are not necessarily the same. However, as stated in the documentation you quoted, the distinction is important only in context of some system applications (applications that run as part of OS). Therefore, as long as you don't build a custom ROM or work on customizing Android for phone manufacturers, I wouldn't bother to make any distinction at all.

详细答案:

首先,我发现了将@MainThread@UiThread批注引入支持库的提交:

First of all I found the commit that introduced @MainThread and @UiThread annotations into support library:

commit 774c065affaddf66d4bec1126183435f7c663ab0
Author: Tor Norbye <tnorbye@google.com>
Date:   Tue Mar 10 19:12:04 2015 -0700

    Add threading annotations

    These describe threading requirements for a given method,
    or threading promises made to a callback.

    Change-Id: I802d2415c5fa60bc687419bc2564762376a5b3ef

该评论不包含与该问题有关的任何信息,并且由于我没有与Tor Norbye的交流渠道(叹气),所以在这里没有运气.

The comment doesn't contain any information related to the question, and since I don't have a communication channel to Tor Norbye (sigh), no luck here.

也许这些注释正在AOSP的源代码中使用,我们可以从中获得一些见解吗?让我们搜索AOSP中任一注释的用法:

Maybe these annotations are being used in source code of AOSP and we could derive some insights from there? Let's search for usages of either of the annotations in AOSP:

aosp  $ find ./ -name *.java | xargs perl -nle 'print "in file: ".$ARGV."; match: ".$& if m{(\@MainThread|\@UiThread)(?!Test).*}'
aosp  $

上面的命令将在AOSP中的任何.java文件中找到@MainThread@UiThread的任何用法(不带附加的Test字符串).它什么也没找到.这里也没有运气.

the above command would find any usage of @MainThread or @UiThread in any .java file in AOSP (not followed by additional Test string). It found nothing. No luck here either.

因此,我们需要去寻找AOSP来源中的提示.我猜想我可以从Activity#runOnUiThread(Runnable)方法开始:

So we need to go and look for hints in the source of AOSP. I guessed that I could start from Activity#runOnUiThread(Runnable) method:

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

这里没有什么特别有趣的.让我们看看mUiThread成员是如何初始化的:

nothing particularly interesting here. Let's see how mUiThread member is being initialized:

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
    attachBaseContext(context);

    mFragments.attachActivity(this, mContainer, null);

    mWindow = PolicyManager.makeNewWindow(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    mUiThread = Thread.currentThread();

    mMainThread = aThread;

    // ... more stuff here ...
}

头奖!最后两行(其他行由于不相关而被省略)是第一个指示主"线程和"ui"线程确实可能是不同的线程.

Jackpot! The last two lines (others omitted because they are irrelevant) are the very first indication that "main" and "ui" threads might indeed be distinct threads.

从这行mUiThread = Thread.currentThread();可以清楚地看到"ui"线程的概念-"ui"线程是在其上调用Activity#attach(<params>)方法的线程.因此,我们需要找出主"线程是什么,然后将两者进行比较.

The notion of "ui" thread is clear from this line mUiThread = Thread.currentThread(); - "ui" thread is the thread on which Activity#attach(<params>) method is being called. So we need to find out what "main" thread is and compare the two.

下一个提示似乎可以在ActivityThread类中找到.此类确实是意大利面条,但是我认为有趣的部分是实例化ActivityThread对象的位置.

It looks like the next hint could be found in ActivityThread class. This class is quite a spaghetti, but I think that the interesting parts are where ActivityThread objects are being instantiated.

只有两个地方:public static void main(String[])public static ActivityThread systemMain().

这些方法的来源:

public static void main(String[] args) {
    SamplingProfilerIntegration.start();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    // Set the reporter for event logging in libcore
    EventLogger.setReporter(new EventLoggingReporter());

    Security.addProvider(new AndroidKeyStoreProvider());

    // Make sure TrustedCertificateStore looks in the right place for CA certificates
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

和:

public static ActivityThread systemMain() {
    // The system process on low-memory devices do not get to use hardware
    // accelerated drawing, since this can add too much overhead to the
    // process.
    if (!ActivityManager.isHighEndGfx()) {
        HardwareRenderer.disable(true);
    } else {
        HardwareRenderer.enableForegroundTrimming();
    }
    ActivityThread thread = new ActivityThread();
    thread.attach(true);
    return thread;
}

请注意这些方法传递给attach(boolean)的不同值.为了完整起见,我还将发布其来源:

Note the different value these methods pass to attach(boolean). For completeness I will post its source as well:

private void attach(boolean system) {
    sCurrentActivityThread = this;
    mSystemThread = system;
    if (!system) {
        ViewRootImpl.addFirstDrawHandler(new Runnable() {
            @Override
            public void run() {
                ensureJitEnabled();
            }
        });
        android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                UserHandle.myUserId());
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        final IActivityManager mgr = ActivityManagerNative.getDefault();
        try {
            mgr.attachApplication(mAppThread);
        } catch (RemoteException ex) {
            // Ignore
        }
        // Watch for getting close to heap limit.
        BinderInternal.addGcWatcher(new Runnable() {
            @Override public void run() {
                if (!mSomeActivitiesChanged) {
                    return;
                }
                Runtime runtime = Runtime.getRuntime();
                long dalvikMax = runtime.maxMemory();
                long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                if (dalvikUsed > ((3*dalvikMax)/4)) {
                    if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                            + " total=" + (runtime.totalMemory()/1024)
                            + " used=" + (dalvikUsed/1024));
                    mSomeActivitiesChanged = false;
                    try {
                        mgr.releaseSomeActivities(mAppThread);
                    } catch (RemoteException e) {
                    }
                }
            }
        });
    } else {
        // Don't set application object here -- if the system crashes,
        // we can't display an alert, we just want to die die die.
        android.ddm.DdmHandleAppName.setAppName("system_process",
                UserHandle.myUserId());
        try {
            mInstrumentation = new Instrumentation();
            ContextImpl context = ContextImpl.createAppContext(
                    this, getSystemContext().mPackageInfo);
            mInitialApplication = context.mPackageInfo.makeApplication(true, null);
            mInitialApplication.onCreate();
        } catch (Exception e) {
            throw new RuntimeException(
                    "Unable to instantiate Application():" + e.toString(), e);
        }
    }

    // add dropbox logging to libcore
    DropBox.setReporter(new DropBoxReporter());

    ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
        @Override
        public void onConfigurationChanged(Configuration newConfig) {
            synchronized (mResourcesManager) {
                // We need to apply this change to the resources
                // immediately, because upon returning the view
                // hierarchy will be informed about it.
                if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) {
                    // This actually changed the resources!  Tell
                    // everyone about it.
                    if (mPendingConfiguration == null ||
                            mPendingConfiguration.isOtherSeqNewer(newConfig)) {
                        mPendingConfiguration = newConfig;

                        sendMessage(H.CONFIGURATION_CHANGED, newConfig);
                    }
                }
            }
        }
        @Override
        public void onLowMemory() {
        }
        @Override
        public void onTrimMemory(int level) {
        }
    });
}

为什么有两种初始化ActivityThread的方法(它将成为应用程序的主"线程)?

Why there are two means for initializing ActivityThread (which will become the "main" thread of the application)?

我认为会发生以下情况:

无论何时启动新应用程序,都会执行ActivityThreadpublic static void main(String[])方法.在此初始化主"线程,并从该确切线程进行对Activity生命周期方法的所有调用.在Activity#attach()方法(其来源如上所示)中,系统将"ui"线程初始化为"this"线程,该线程也恰好是"main"线程.因此,在所有实际情况下,主"线程和"ui"线程都是相同的.

Whenever a new application started, public static void main(String[]) method of ActivityThread is being executed. The "main" thread is being initialized there, and all calls to Activity lifecycle methods are being made from that exact thread. In Activity#attach() method (its source was shown above) the system initializes "ui" thread to "this" thread, which is also happens to be the "main" thread. Therefore, for all practical cases "main" thread and "ui" thread are the same.

对于所有应用程序都是如此,只有一个例外.

This is true for all applications, with one exception.

首次启动Android框架时,它也可以作为应用程序运行,但是此应用程序很特殊(例如:具有特权访问权限).这种特殊性"的一部分是它需要一个经过特殊配置的主"线程.由于它已经通过public static void main(String[])方法运行(就像其他任何应用程序一样),因此将其主"线程和"ui"线程设置为同一线程.为了获得具有特殊特征的主"线程,系统应用程序对public static ActivityThread systemMain()进行了静态调用并存储了获得的引用.但是它的"ui"线程没有被覆盖,因此"main"和"ui"线程最终是不一样的.

When Android framework is being started for the first time, it too runs as an application, but this application is special (for example: has privileged access). Part of this "specialty" is that it needs a specially configured "main" thread. Since it has already ran through public static void main(String[]) method (just like any other app), its "main" and "ui" threads are being set to the same thread. In order to get "main" thread with special characteristics, system app performs a static call to public static ActivityThread systemMain() and stores the obtained reference. But its "ui" thread is not overridden, therefore "main" and "ui" threads end up being not the same.