Разница между основной нитью и потоком пользовательского интерфейса

Мне дано понять, что оба они одинаковы. Но я недавно (немного опоздавший на вечеринку) наткнулся на аннотации поддержки Android. Примечание в том же чтении

Однако возможно, что поток пользовательского интерфейса отличается от основного поток в случае системных приложений с несколькими видами на разных темы

Я не могу понять сцену здесь. Может ли кто-то объяснить то же самое?

РЕДАКТИРОВАТЬ: Я прошел через документацию разработчика, и то же самое противоречит документу поддержки, связанному в этом вопросе. Пожалуйста, прекратите публикацию обоих вариантов.

Ответ 1

Спасибо за исключительно интересный вопрос.

Оказывается, пользовательский интерфейс и основные потоки не обязательно одинаковы. Однако, как указано в цитированной вами документации, различие важно только в контексте некоторых системных приложений (приложений, выполняемых как часть ОС). Поэтому, пока вы не создаете пользовательский ПЗУ или не работаете с настройкой Android для производителей телефонов, я не стал бы вообще делать какие-либо различия.

Длинный ответ:

Прежде всего, я нашел коммит, который вносил аннотации @MainThread и @UiThread в библиотеку поддержки:

commit 774c065affaddf66d4bec1126183435f7c663ab0
Author: Tor Norbye <[email protected]>
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  $

приведенная выше команда найдет любое использование @MainThread или @UiThread в любом .java файле в AOSP (за которым не следует дополнительная строка 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(<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");
}

и

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 (который станет "основным" потоком приложения)?

Я думаю, что происходит следующее:

Всякий раз, когда запускается новое приложение, выполняется 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" потоки оказываются не одинаковыми.

Ответ 2

Простой ответ Ваш основной поток также в потоке пользовательского интерфейса.

Таким образом, основной поток также иногда называют потоком пользовательского интерфейса. Как указано в разделе потока документации для Android Процессы и потоки. Документация для Android

Кроме того, инструментарий UI не является потокобезопасным и не должен обрабатываться рабочими потоками. Я снова цитирую Документацию для Android, поскольку это справочное руководство для Android, которое:

Таким образом, для модели с одним потоком в Android существует всего два правила:

1. Не блокируйте поток пользовательского интерфейса

2. Не открывайте инструментарий Android UI из-за пределов пользовательского интерфейса.

Надеюсь, я отвечу на то, о чем вы просите.

Ответ 3

В Android, "основной" поток приложений иногда называют потоком пользовательского интерфейса.

Цитата из официального API о главной теме:

[...] поток, в котором ваше приложение взаимодействует с компонентами из набора инструментов Android UI (компоненты из пакетов android.widget и android.view). Таким образом, основной поток также иногда называют потоком пользовательского интерфейса.

Официальный API нашел здесь.

Ответ 4

Самый простой пример: Служба Android работает в основном потоке, но Служба не имеет пользовательского интерфейса. Вы не можете назвать главный поток здесь как UI-Thread.

Кредиты Sqounk

Ответ 5

Когда приложение запускается, система создает поток выполнения для приложения, называемый "main". Этот поток очень важен, поскольку он отвечает за отправку событий соответствующим виджетам пользовательского интерфейса, включая события рисования. Это также поток, в котором ваше приложение взаимодействует с компонентами из набора инструментов Android UI (компоненты из пакетов android.widget и android.view). Таким образом, основной поток также иногда называют потоком пользовательского интерфейса.

Прочтите этот учебник. https://developer.android.com/guide/components/processes-and-threads.html#Threads