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

hum*_*kie 36 multithreading android

我明白两者都是一样的.但我最近(派对有点晚)遇到了android支持注释.同一个注释中的注释

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

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

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

Vas*_*liy 70

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

事实证明,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
Run Code Online (Sandbox Code Playgroud)

评论中不包含与问题相关的任何信息,因为我没有与Tor Norbye(叹气)的沟通渠道,所以这里没有运气.

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

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

上述命令会发现的任何使用@MainThread@UiThread在任何AOSP java文件(后面没有附加Test字符串).它一无所获.这里也没有运气.

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

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

这里没什么特别有趣 让我们看看如何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 ...
}
Run Code Online (Sandbox Code Playgroud)

大奖!最后两行(其他因为它们不相关而省略)是"主"和"ui"线程可能确实是不同线程的第一个指示.

"ui"线程的概念在这一行中很清楚mUiThread = Thread.currentThread();- "ui"线程是Activity#attach(<params>)调用方法的线程.所以我们需要找出什么是"主"线程并比较两者.

看起来下一个提示可以在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");
}
Run Code Online (Sandbox Code Playgroud)

和:

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;
}
Run Code Online (Sandbox Code Playgroud)

请注意这些方法传递给的不同值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) {
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

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

我认为发生了以下情况:

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

这适用于所有应用程序,但有一个例外.

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

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