【问题标题】:Difference between the main thread and UI thread主线程和UI线程的区别
【发布时间】:2017-04-08 15:36:26
【问题描述】:

我明白两者是相同的。但我最近(参加聚会有点晚了)遇到了 android support annotations。同一条注释为

但是,UI 线程可能与主线程不同 在系统应用程序具有不同视图的情况下线程 线程

我无法理解这里的场景。有人可以解释一下吗?

编辑:我已经阅读了开发人员文档,这与此问题中链接的支持文档相矛盾。请停止发布两者都是相同的。

【问题讨论】:

标签: android multithreading


【解决方案1】:

感谢您提出一个非常有趣的问题。

事实证明,UI 和主线程不一定相同。但是,正如您引用的文档中所述,这种区别仅在某些系统应用程序(作为操作系统的一部分运行的应用程序)的上下文中很重要。因此,只要您不为手机制造商构建定制ROM或定制Android,我根本不会费心去区分。

长答案

首先我找到了将@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

评论不包含与问题相关的任何信息,并且由于我没有与 Tor Norbye 的沟通渠道(叹气),所以这里没有运气。

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

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

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

所以我们需要去AOSP的源码中寻找提示。我猜我可以从Activity#runOnUiThread(Runnable)方法开始:

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

这里没有什么特别有趣的。让我们看看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 ...
}

大奖!最后两行(由于不相关而省略了其他行)是“main”和“ui”线程可能确实是不同线程的第一个迹象。

“ui”线程的概念从mUiThread = Thread.currentThread(); 这一行中可以清楚地看出——“ui”线程是调用Activity#attach(&lt;params&gt;) 方法的线程。所以我们需要找出什么是“主”线程并比较两者。

看起来下一个提示可以在ActivityThread 类中找到。这个类是一个意大利面条,但我认为有趣的部分是 ActivityThread 对象被实例化的地方。

只有两个地方: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) 的不同值。为了完整起见,我还将发布其来源:

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(将成为应用程序的“主”线程)的方法?

我认为发生了以下情况:

每当一个新的应用程序启动时,ActivityThreadpublic static void main(String[]) 方法就会被执行。 “主”线程正在那里初始化,并且对Activity 生命周期方法的所有调用都是从那个确切的线程进行的。在Activity#attach() 方法中(其源代码如上所示)系统将“ui”线程初始化为“this”线程,该线程也恰好是“main”线程。因此,对于所有实际情况,“main”线程和“ui”线程都是相同的。

这适用于所有应用程序,只有一个例外。

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

【讨论】:

  • 首先非常感谢您的回答。有时我很惊讶人们如何拥有如此惊人的挖掘技能并推广同样的技能。我知道这两个线程在系统应用程序方面明显不同,但是在不同的 ui 线程上有多个视图也是我想要理解的。有什么方法可以让我们深入了解这一点,还是我错过了一些重要的东西。为了清楚线程模型,给你满分。我会将其标记为对问题主要意图的回答,但对我的问题的另一部分有何想法?
  • @humblerookie,感谢您的热情话语。并非所有系统应用程序都使用特殊的“主”线程——它们中的大多数只是可以访问 systemOrSignature 权限组的常规应用程序。它是系统应用程序(呈现整个手机的根View 的应用程序)需要特殊配置的“主”线程。
  • @humblerookie,对于多线程上的 UI,这不是您可以通过常规应用程序(甚至系统应用程序)实现的 - 每个应用程序都有一个 UI 线程。但是,这种行为可以通过多个应用程序实现:由于每个应用程序都有自己的 UI 线程,因此可以从应用程序 Y 绑定应用程序 X 中定义的 Service,然后应用程序 Y 可以向应用程序 X 发送命令,应用程序 X 会渲染 UI。虽然有这种方案的用例(例如自定义键盘保护),但它并不普遍。
  • 有道理。谢谢:)
  • 是否有可能看到那个“特殊”应用程序对“主”线程做了什么?以及它如何使用 'ui' 和 'main' 线程之间的分离?
【解决方案2】:

简单的答案是 您的主线程也在 UI 线程中。

因此,主线程有时也称为UI线程。如 Processes and Threads 的 Android 文档的线程部分所述。 Android Documentation

此外,UI 工具包不是线程安全的,不得处理工作线程。我再次引用 Android Documentation,因为它是 Android 的参考指南:

因此,Android 的单线程模型只有两条规则:

1.不要阻塞UI线程

2.不要从UI线程外访问Android UI工具包

希望我回答你的要求。

【讨论】:

    【解决方案3】:

    最简单的例子是:一个 Android 服务在主线程上运行,但该服务没有用户界面。 此处不能将主线程称为 UI 线程

    感谢Sqounk

    【讨论】:

      【解决方案4】:

      在 Android 中,“主”应用程序线程有时称为 UI 线程。

      引用官方 API 关于主线程:

      [...] 应用程序与 Android UI 工具包中的组件(来自 android.widget 和 android.view 包的组件)交互的线程。因此,主线程有时也称为 UI 线程。

      官方API找到here.

      【讨论】:

        【解决方案5】:

        当启动应用程序时,系统会为应用程序创建一个执行线程,称为“main”。这个线程非常重要,因为它负责将事件分派给适当的用户界面小部件,包括绘图事件。它也是您的应用程序与 Android UI 工具包中的组件(来自 android.widget 和 android.view 包的组件)交互的线程。因此,主线程有时也称为 UI 线程。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-23
          • 2011-09-29
          • 2021-05-18
          • 1970-01-01
          • 2012-12-18
          • 2012-09-01
          相关资源
          最近更新 更多