简体   繁体   English

主线程和UI线程的区别

[英]Difference between the main thread and UI thread

I'm given to understand that both are the same.我明白两者是一样的。 But I recently(a bit late to the party) came across android support annotations .但是我最近(参加聚会有点晚)遇到了android support annotations The note in the same reads相同的注释

However, it's possible for a UI thread to be different from the main thread in the case of system apps with multiple views on different threads但是,在系统应用程序在不同线程上具有多个视图的情况下,UI 线程可能与主线程不同

I'm unable to understand the scene here.我无法理解这里的场景。 Can someone explain the same?有人可以解释一下吗?

EDIT: I have gone through the developer documentation and the same is contradicting the support doc linked in this question.编辑:我已经阅读了开发人员文档,这与此问题中链接的支持文档相矛盾。 Kindly stop posting both are the same.请停止发布两者是相同的。

Thanks for an exceptionally interesting question.感谢您提出一个非常有趣的问题。

Turns out, UI and Main threads are not necessarily the same.事实证明,UI 和主线程不一定相同。 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.因此,只要您不构建自定义 ROM 或为手机制造商定制 Android,我根本不会费心做任何区分。

The long answer :长答案

First of all I found the commit that introduced @MainThread and @UiThread annotations into support library:首先,我找到了将@MainThread@UiThread注释引入支持库的提交:

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

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.该评论不包含与该问题相关的任何信息,而且由于我没有与 Tor Norbye 的沟通渠道(叹气),所以这里不走运。

Maybe these annotations are being used in source code of AOSP and we could derive some insights from there?也许这些注释正在 AOSP 的源代码中使用,我们可以从中获得一些见解? Let's search for usages of either of the annotations in AOSP:让我们搜索 AOSP 中任一注释的用法:

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

the above command would find any usage of @MainThread or @UiThread in any .java file in AOSP (not followed by additional Test string).上面的命令会在 AOSP 的任何 .java 文件中找到@MainThread@UiThread任何用法(后面没有额外的Test字符串)。 It found nothing.它什么也没找到。 No luck here either.这里也没有运气。

So we need to go and look for hints in the source of AOSP.所以我们需要去AOSP的源码中寻找提示。 I guessed that I could start from Activity#runOnUiThread(Runnable) method:我猜我可以从Activity#runOnUiThread(Runnable)方法开始:

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

nothing particularly interesting here.这里没有什么特别有趣的。 Let's see how mUiThread member is being initialized:让我们看看mUiThread成员是如何被初始化的:

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 ...
}

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.最后两行(其他被省略,因为它们不相关)是“主”和“ui”线程可能确实是不同线程的第一个迹象。

The notion of "ui" thread is clear from this line mUiThread = Thread.currentThread();从这一行mUiThread = Thread.currentThread();可以清楚地看出“ui”线程的概念mUiThread = Thread.currentThread(); - "ui" thread is the thread on which Activity#attach(<params>) method is being called. - “ui”线程是调用Activity#attach(<params>)方法的线程。 So we need to find out what "main" thread is and compare the two.所以我们需要找出什么是“主”线程并比较两者。

It looks like the next hint could be found in ActivityThread class.看起来下一个提示可以在ActivityThread类中找到。 This class is quite a spaghetti, but I think that the interesting parts are where ActivityThread objects are being instantiated.这个类很像意大利面,但我认为有趣的部分是实例化ActivityThread对象的地方。

There are only two places: public static void main(String[]) and public static ActivityThread systemMain() .只有两个地方: public static void main(String[])public static ActivityThread systemMain()

The sources of these methods:这些方法的来源:

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");
}

and:和:

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;
}

Note the different value these methods pass to attach(boolean) .请注意这些方法传递给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) {
        }
    });
}

Why there are two means for initializing ActivityThread (which will become the "main" thread of the application)?为什么有两种方式来初始化ActivityThread (它会成为应用程序的“主”线程)?

I think the following takes place:我认为会发生以下情况:

Whenever a new application started, public static void main(String[]) method of ActivityThread is being executed.每当一个新应用程序启动时,都会执行ActivityThread public static void main(String[])方法。 The "main" thread is being initialized there, and all calls to Activity lifecycle methods are being made from that exact thread. “主”线程正在那里初始化,所有对Activity生命周期方法的调用都是从该线程进行的。 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.Activity#attach()方法(其来源如上所示)中,系统将“ui”线程初始化为“this”线程,该线程也恰好是“main”线程。 Therefore, for all practical cases "main" thread and "ui" thread are the same.因此,对于所有实际情况,“主”线程和“ui”线程是相同的。

This is true for all applications, with one exception.这适用于所有应用程序,只有一个例外。

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). Android 框架在第一次启动时,也是作为一个应用程序运行的,但是这个应用程序比较特殊(例如:有特权访问)。 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.由于它已经运行了public static void main(String[])方法(就像任何其他应用程序一样),它的“main”和“ui”线程被设置为同一个线程。 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.为了获得具有特殊特性的“主”线程,系统应用程序对public static ActivityThread systemMain()执行静态调用并存储获得的引用。 But its "ui" thread is not overridden, therefore "main" and "ui" threads end up being not the same.但是它的“ui”线程没有被覆盖,因此“main”和“ui”线程最终是不一样的。

Simple Answer is Your main thread in also the UI thread.简单的答案也是 UI 线程中的主线程。

As such, the main thread is also sometimes called the UI thread .因此,主线程有时也称为UI 线程 As stated in Android documentation's thread portion of Processes and Threads .如 Android 文档的Processes 和 Threads线程部分所述。 Android Documentation 安卓文档

Moreover, UI toolkit is not thread safe and must not be dealt with worker threads.此外,UI 工具包不是线程安全的,不得处理工作线程。 I am again quoting Android Documentation as it is the reference guide for Android that:我再次引用Android 文档,因为它是 Android 的参考指南:

Thus, there are simply two rules to Android's single thread model:因此,Android 的单线程模型只有两条规则:

1.Do not block the UI thread 1.不要阻塞UI线程

2.Do not access the Android UI toolkit from outside the UI thread 2.不要从UI线程之外访问Android UI工具包

Hope I answer what you ask for.希望我能回答你的要求。

The simplest example is: An Android Service runs on the Main thread but the Service does not have a User Interface.最简单的例子是:一个 Android 服务运行在主线程上,但该服务没有用户界面。 You cannot call the Main thread here as UI-Thread .您不能在此处将 Main 线程称为 UI-Thread

Credits to Sqounk 归功于 Squunk

In Android, the "main" application thread is sometimes called the UI thread.在 Android 中,“主”应用程序线程有时称为 UI 线程。

Quote from official API about the Main Thread:引用来自官方 API 关于主线程:

[...] the thread in which your application interacts with components from the Android UI toolkit (components from the android.widget and android.view packages). [...] 应用程序与 Android UI 工具包中的组件(来自 android.widget 和 android.view 包的组件)交互的线程。 As such, the main thread is also sometimes called the UI thread.因此,主线程有时也称为 UI 线程。

Official API found here.官方 API 在这里找到

When an application is launched, the system creates a thread of execution for the application, called "main."当应用程序启动时,系统会为该应用程序创建一个执行线程,称为“main”。 This thread is very important because it is in charge of dispatching events to the appropriate user interface widgets, including drawing events.这个线程非常重要,因为它负责将事件分派到适当的用户界面小部件,包括绘图事件。 It is also the thread in which your application interacts with components from the Android UI toolkit (components from the android.widget and android.view packages).它也是您的应用程序与来自 Android UI 工具包的组件(来自 android.widget 和 android.view 包的组件)交互的线程。 As such, the main thread is also sometimes called the UI thread.因此,主线程有时也称为 UI 线程。

Read this tutorial Documents.阅读本教程文档。 https://developer.android.com/guide/components/processes-and-threads.html#Threads https://developer.android.com/guide/components/processes-and-threads.html#Threads

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM