簡體   English   中英

主線程和UI線程的區別

[英]Difference between the main thread and UI thread

我明白兩者是一樣的。 但是我最近(參加聚會有點晚)遇到了android support annotations 相同的注釋

但是,在系統應用程序在不同線程上具有多個視圖的情況下,UI 線程可能與主線程不同

我無法理解這里的場景。 有人可以解釋一下嗎?

編輯:我已經閱讀了開發人員文檔,這與此問題中鏈接的支持文檔相矛盾。 請停止發布兩者是相同的。

感謝您提出一個非常有趣的問題。

事實證明,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 ...
}

大獎! 最后兩行(其他被省略,因為它們不相關)是“主”和“ui”線程可能確實是不同線程的第一個跡象。

從這一行mUiThread = Thread.currentThread();可以清楚地看出“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 (它會成為應用程序的“主”線程)?

我認為會發生以下情況:

每當一個新應用程序啟動時,都會執行ActivityThread public static void main(String[])方法。 “主”線程正在那里初始化,所有對Activity生命周期方法的調用都是從該線程進行的。 Activity#attach()方法(其來源如上所示)中,系統將“ui”線程初始化為“this”線程,該線程也恰好是“main”線程。 因此,對於所有實際情況,“主”線程和“ui”線程是相同的。

這適用於所有應用程序,只有一個例外。

Android 框架在第一次啟動時,也是作為一個應用程序運行的,但是這個應用程序比較特殊(例如:有特權訪問)。 這個“專業”的一部分是它需要一個專門配置的“主”線程。 由於它已經運行了public static void main(String[])方法(就像任何其他應用程序一樣),它的“main”和“ui”線程被設置為同一個線程。 為了獲得具有特殊特性的“主”線程,系統應用程序對public static ActivityThread systemMain()執行靜態調用並存儲獲得的引用。 但是它的“ui”線程沒有被覆蓋,因此“main”和“ui”線程最終是不一樣的。

簡單的答案也是 UI 線程中的主線程。

因此,主線程有時也稱為UI 線程 如 Android 文檔的Processes 和 Threads線程部分所述。 安卓文檔

此外,UI 工具包不是線程安全的,不得處理工作線程。 我再次引用Android 文檔,因為它是 Android 的參考指南:

因此,Android 的單線程模型只有兩條規則:

1.不要阻塞UI線程

2.不要從UI線程之外訪問Android UI工具包

希望我能回答你的要求。

最簡單的例子是:一個 Android 服務運行在主線程上,但該服務沒有用戶界面。 您不能在此處將 Main 線程稱為 UI-Thread

歸功於 Squunk

在 Android 中,“主”應用程序線程有時稱為 UI 線程。

引用來自官方 API 關於主線程:

[...] 應用程序與 Android UI 工具包中的組件(來自 android.widget 和 android.view 包的組件)交互的線程。 因此,主線程有時也稱為 UI 線程。

官方 API 在這里找到

當應用程序啟動時,系統會為該應用程序創建一個執行線程,稱為“main”。 這個線程非常重要,因為它負責將事件分派到適當的用戶界面小部件,包括繪圖事件。 它也是您的應用程序與來自 Android UI 工具包的組件(來自 android.widget 和 android.view 包的組件)交互的線程。 因此,主線程有時也稱為 UI 線程。

閱讀本教程文檔。 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