Android 主线程一定是 UI 线程吗?

2,381 阅读8分钟

事情的起因是这样的,在看官方文档的时候,遇到了这么一句话:

As such, the main thread is also sometimes called the UI thread. However, under special circumstances, an app’s main thread might not be its UI thread; for more information, see Thread annotations . from 进程和线程概览  |  Android 开发者  |  Android Developers

在线程注解的章节中,也有一句话:

注意:构建工具会将 @MainThread 和 @UiThread 注解视为可互换,因此您可以从 @MainThread 方法调用 @UiThread 方法,反之亦然。不过,如果不同线程上具有多个视图的系统应用程序的界面线程可能会与主线程不同。因此,您应使用 @UiThread 为与应用的视图层次结构关联的方法添加注解,并使用 @MainThread 仅为与应用生命周期关联的方法添加注解。

Activity#runOnUiThread(Runnable) 入手,查看系统源码:

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

这里判断了当前线程不是 mUiThread ,就用 Handler 发送消息,否则直接调用 run 方法。把焦点放在 mUiThread 上,看看它的初始化:

// in Activity.java
@UnsupportedAppUsage
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,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
    attachBaseContext(context);
    // ...
    mUiThread = Thread.currentThread();

    mMainThread = aThread;
    // ...
}

这里的 mUiThread 直接被赋值为当前线程,而这个方法中又多了一个可疑对象 mMainThread ,它的值来自于 attach 的参数,而且还是个 ActivityThread 类型。 这两个属性从名字上看就可以理解为什么会有这种说法了,因为它俩是两个对象。

/*package*/ ActivityThread mMainThread;

private Thread mUiThread;

attach 方法的调用栈是:

- frameworks/base/core/java/android/app/ActivityThread.java#performLaunchActivity
  - frameworks/base/core/java/android/app/ActivityThread.java#startActivityNow
  // 或
- frameworks/base/core/java/android/app/ActivityThread.java#performLaunchActivity
  - frameworks/base/core/java/android/app/ActivityThread.java#handleLaunchActivity

performLaunchActivity 中调用 attach 方法:

// in ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    //...
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        // ...
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException("Unable to instantiate activity " + component + ": " + e.toString(), e);
        }
    }

    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        if (activity != null) {
            // ... 
            // 【attach调用位置,这里的 aThread 参数是 this】
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback,
                    r.assistToken, r.shareableActivityToken);
            // ... 
        }
        r.setState(ON_CREATE); // 生命周期进入 ON_CREATE
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException("Unable to start activity " + component + ": " + e.toString(), e);
        }
    }
    return activity;
}

从这个调用链看, attach 传入的是 this 对象,即 ActivityThread 自身,所以需要查看 ActivityThread 的初始化位置。它的对象创建有两个位置:

BEC29696-710E-47AE-AAFF-E524541ACF2A.png

systemMain()main(String[] args) 两个方法。

main

public static void main(String[] args) {
    AndroidOs.install();
    CloseGuard.setEnabled(false);
    Environment.initForCurrentUser();

    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    initializeMainlineModules(); // Call per-process mainline module initialization.

    Process.setArgV0("<pre-initialized>");
    // 启动 Looper
    Looper.prepareMainLooper();

    long startSeq = 0;
    if (args != null) {
        for (int i = args.length - 1; i >= 0; --i) {
            if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                startSeq = Long.parseLong(
                        args[i].substring(PROC_START_SEQ_IDENT.length()));
            }
        }
    }
    // 初始化位置
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

这个 main 方法中,有我们知道的 Handler 机制中的 Looper ,在 Looper.loop() 方法后一行是抛出主线程 loop 异常退出的 RuntimeException。 这些正是证明了我们的应用程序中的主线程 Looper 退出后会导致的异常。

main 方法的调用,在应用程序进程启动流程中的 RuntimeInit.applicationInit(...) 中最后的 findStaticMain 方法中,通过反射调用的:

    protected static Runnable findStaticMain(String className, String[] argv,
            ClassLoader classLoader) {
        Class<?> cl;

        try {
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    "Missing class when invoking static main " + className, ex);
        }

        Method m;
        try {
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(
                    "Missing static main on " + className, ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(
                    "Problem getting static main on " + className, ex);
        }

        int modifiers = m.getModifiers();
        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
            throw new RuntimeException(
                    "Main method is not public and static on " + className);
        }
        return new MethodAndArgsCaller(m, argv);
    }

这里也证实了 ActivityThread 在创建过程中是通过反射创建的,是我们正常理解的主线程。

systemMain

public static ActivityThread systemMain() {
    // 低内存设备上的系统进程不能使用硬件加速绘图,因为这会给进程增加太多开销
    if (!ActivityManager.isHighEndGfx()) {
        ThreadedRenderer.disable(true);
    } else {
        ThreadedRenderer.enableForegroundTrimming();
    }
    ActivityThread thread = new ActivityThread();
    thread.attach(true, 0);
    return thread;
}

从备注看,就可以知道这个方法和系统进程有关系。systemMain 方法的调用是在 SystemServer 中:

    private void createSystemContext() {
        ActivityThread activityThread = ActivityThread.systemMain();
        mSystemContext = activityThread.getSystemContext();
        mSystemContext.setTheme(DEFAULT_SYSTEM_THEME);

        final Context systemUiContext = activityThread.getSystemUiContext();
        systemUiContext.setTheme(DEFAULT_SYSTEM_THEME);
    }

它的调用栈是:

- frameworks/base/services/java/com/android/server/SystemServer.java#run
- frameworks/base/services/java/com/android/server/SystemServer.java#main

根源来自于 SystemServer 的 main 方法,这个方法是 SystemServer 进程启动的最主要的方法,也可以从这里推理出,这个 ActivityThread 是 SystemServer 中的主线程。

ActivityThread#attach

从代码中,两者都是创建了一个 ActivityThread 对象,然后调用了 ActivityThread 的 attach 方法,两者参数不同:

    // main
    thread.attach(false, startSeq);
    // systemMain
    thread.attach(true, 0);

去 ActivityThread 查看 attach 方法:

@UnsupportedAppUsage
private void attach(boolean system, long startSeq) {
    sCurrentActivityThread = this;
    mSystemThread = system;
    if (!system) {
        android.ddm.DdmHandleAppName.setAppName("<pre-initialized>", UserHandle.myUserId());
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        final IActivityManager mgr = ActivityManager.getService();
        try {
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        // 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)) {
                    mSomeActivitiesChanged = false;
                    try {
                        ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
            }
        });
    } 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();
            mInstrumentation.basicInit(this);
            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);
        }
    }

    ViewRootImpl.ConfigChangedCallback configChangedCallback
            = (Configuration globalConfig) -> {
        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(globalConfig,
                    null /* compat */)) {
                updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
                        mResourcesManager.getConfiguration().getLocales());

                // This actually changed the resources! Tell everyone about it.
                if (mPendingConfiguration == null
                        || mPendingConfiguration.isOtherSeqNewer(globalConfig)) {
                    mPendingConfiguration = globalConfig;
                    sendMessage(H.CONFIGURATION_CHANGED, globalConfig);
                }
            }
        }
    };
    ViewRootImpl.addConfigCallback(configChangedCallback);
}

在这个方法中,第一个参数 system 标志着当前主线程是不是系统主线程,第二个参数也只在非系统应用程序的创建过程中用到。 所以,从这里可以看出系统应用程序和普通应用程序是有区别的,两者对主线程绑定操作是不一样的。

如果是系统主线程,会创建一个 Instrumentation 对象,并调用它的 basicInit(ActivityThread) 。并且创建一个 context , 根据这个 context 创建 mInitialApplication 对象,并调用 onCreate 方法。

    /**
     * Only sets the ActivityThread up, keeps everything else null because app is not being
     * instrumented.
     */
    final void basicInit(ActivityThread thread) {
        mThread = thread;
    }

mInitialApplication 的类型是 Application 。

mInitialApplication = context.mPackageInfo.makeApplication(true, null);

mInitialApplication 初始化通过 makeApplication 方法创建,它的第一个参数 forceDefaultAppClass 为 true 时,限制了 Application 类型仅能为 android.app.Application 。 推测这个 Application 应该是系统的 Application 。

而如果 system = false 时,不是系统的 ActivityThread 时,会通过 ActivityManager 绑定 Application :

final IActivityManager mgr = ActivityManager.getService();
try {
    mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
    throw ex.rethrowFromSystemServer();
}

这个 mAppThread 是一个 ApplicationThread 对象:

final ApplicationThread mAppThread = new ApplicationThread();

两者的不同之处

从 main 和 systemMain 的调用来源可以分析出,两者的不同之处:

  • main 方法用于在应用程序创建过程中绑定 ActivityThread 。
  • systemMain 方法用于 SystemServer 中,绑定 ActivityThread 。

而从 ActivityThread#attach 方法中,可以分析出:

  • 普通的应用程序,将当前 ActivityThread 的 ApplicationThread 和参数 startSeq 通过 IActivityManager.attachApplication(mAppThread, startSeq) 绑定到了应用程序的 Application,这里是一个跨进程通信,调用到了 AMS 所在的进程。并且,还添加了一个关于 GC 的 Binder 监听。所以普通应用程序需要 Binder 绑定到另一个进程中。

  • 而 SystemServer ,首先创建了一个 Instrumentation ,它的作用是监控系统与应用程序的交互,在这里它只是把 ActivityThread 对象保存了起来,然后创建了一个 ContextImpl ,然后通过 ContextImpl.mPackageInfo.makeApplication() 创建了一个 Application ,并调用了 onCreate 方法。整个流程没有 Binder 通信,说明就是在系统进程中进行的。

这里我一直有个疑问,SystemServer 算是一个系统应用程序吗? 对于传统的 Android 教程中,一直把 SystemServer 和进程的概念绑定在一起,一般提到 SystemServer ,就会特指 SystemServer 进程。

首先进程和应用程序是两个概念我们,SystemServer 进程的创建是在 Zygote 进程启动过程的 ZygoteInit.startSystemServer 方法 ,在这个方法中,Zygote 执行了 Zygote.forkSystemServer,内部会调用到 nativeForkSystemServer 方法,就是在这里 fork 出了进程。 Fork 出 SystemServer 进程后,会对 SystemServer 进行进行一些初始化的处理,最终调用的是 handleSystemServerProcess 方法。在这个方法中实际调用了 Zygote.zygoteInit ,而在 zygoteInit 方法中,有以下操作:

// ...
// 启动 binder 线程池
ZygoteInit.nativeZygoteInit(); 
// 进入 SystemServer 的 main 方法
RuntimeInit.applicationInit(targetVersion, argv, classLoader);

RuntimeInit.applicationInit 内部调用了 SystemServer 类的 main 函数,也就是说,SystemServer 类 和 SystemServer 进程,是两个东西。 SystemServer 类的代码作为应用程序运行在 SystemServer 进程中。

总结

每当一个新的应用程序启动时,ActivityThread 的 main 方法 就会被执行。主线程正在那里初始化,并且启动 Activity 时,会通过ActivityThread#performLaunchActivity方法,内部调用到 Activity#attach 方法:

    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,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
        // ...
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        // ...
    }

这也是我们最开始分析 mUiThread 的来源时提到的位置。在这里,Activity 将 UI 线程初始化为当前线程,而这个方法的调用就是在 ActivityThread 自身中;并且 mMainThread 的值来自参数 aThread ,这里传入的参数也是 ActivityThread 自身,所以,这里的 mMainThread 一定是与 mUiThread 相等的。

但 SystemServer 例外,它也创建并持有了一个 ActivityThread 对象。SystemServer 作为 Android 系统的一部分,也是作为一个应用程序运行的。但是这个应用程序是特殊的存在,它也需要配置一个 ActivityThread。

    private void createSystemContext() {
        ActivityThread activityThread = ActivityThread.systemMain();
        mSystemContext = activityThread.getSystemContext();
        mSystemContext.setTheme(DEFAULT_SYSTEM_THEME);

        final Context systemUiContext = activityThread.getSystemUiContext();
        systemUiContext.setTheme(DEFAULT_SYSTEM_THEME);
    }

在 SystemServer 中,并没有把 ActivityThread 保存起来,也没有类似 mUiThread 的属性,所以,它只是有一个主线程,没有 UI 线程。 另一个角度来看,SystemServer 所代表的应用程序并没有 UI ,所以它也不会有 UI 线程的概念。

还有令人在意的一点是这里 ActivityThread 有两个方法获取 Context: getSystemContext()getSystemUiContext() 。 SystemContext 后续会用在各种 ManagerService 中使用。 SystemUiContext 由 SystemContext 衍生出来,拓展了关于 UI 的一些能力。

SystemUiContext System Context to be used for UI. This Context has resources that can be themed. that the created system UI context shares the same LoadedApk as the system context.

主线程是不是 UI 线程的答案也就明确了,在应用程序中是,在 SystemServer 中,也有一个主线程,但是没有 UI 线程的概念。 所以在应用程序开发中,主线程一定是 UI 线程没什么问题。