Merci pour exceptionnellement intéressante question.
S'avère, de l'INTERFACE et des threads Principaux ne sont pas nécessairement les mêmes. Toutefois, comme indiqué dans la documentation que vous avez cité, la distinction est importante seulement dans le contexte de certaines applications du système (les applications qui s'exécutent dans le cadre de l'OS). Par conséquent, tant que vous ne créez pas une ROM custom ou de travailler sur la personnalisation d'Android pour les fabricants de téléphones, je n'aurais pas pris la peine de faire une distinction à tous.
La réponse longue:
Tout d'abord j'ai trouvé le commit qui a introduit @MainThread
et @UiThread
des annotations dans la bibliothèque de prise en charge:
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
Le commentaire ne contient pas toutes les informations relatives à la question, et depuis je n'ai pas de canal de communication à Tor Norbye (soupir), pas de chance ici.
Peut-être que ces annotations sont utilisées dans le code source de PSBA et nous avons pu tirer quelques idées à partir de là? Nous allons chercher des usages de l'une des annotations dans PSBA:
aosp $ find ./ -name *.java | xargs perl -nle 'print "in file: ".$ARGV."; match: ".$& if m{(\@MainThread|\@UiThread)(?!Test).*}'
aosp $
la commande ci-dessus serait de trouver toute utilisation de la @MainThread
ou @UiThread
en tout .fichier java dans PSBA (pas suivie par d'autres Test
chaîne de caractères). Il n'a rien trouvé. Pas de chance ici.
Nous avons donc besoin d'aller chercher des informations dans la source de PSBA. J'ai deviné que je pouvais commencer à partir de Activity#runOnUiThread(Runnable)
méthode:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
rien de particulièrement intéressant ici. Nous allons voir comment mUiThread
membre est en cours d'initialisation:
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 ...
}
Jackpot! Les deux dernières lignes (les autres omis parce qu'ils ne sont pas pertinents) sont la première indication que "principal" et "interface utilisateur" threads peut en effet être distincts de fils.
La notion de "ui" thread est clair à partir de cette ligne, mUiThread = Thread.currentThread();
- "l'interface utilisateur" fil est le fil sur lequel Activity#attach(<params>)
méthode est appelée. Nous avons donc besoin de savoir ce que "principal" thread et de comparer les deux.
Il ressemble à la prochaine astuce pourrait être trouvée dans l' ActivityThread
classe. Cette classe est tout à fait un spaghetti, mais je pense que les parties intéressantes sont où ActivityThread
des objets sont instanciés.
Il existe seulement deux lieux: public static void main(String[])
et public static ActivityThread systemMain()
.
Les sources de ces méthodes:
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");
}
et:
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;
}
Remarque la valeur différente de ces méthodes passent attach(boolean)
. Pour être complet, je vais poster sa source ainsi:
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) {
}
});
}
Pourquoi il y a deux moyens pour initialiser ActivityThread
(qui va devenir le "principal" au fil de l'application)?
Je pense que les événements suivants se produisent:
Chaque fois qu'un nouveau démarrage de l'application, public static void main(Strin[])
méthode de ActivityThread
est en cours d'exécution. Le "principal" thread est en cours d'initialisation là, et tous les appels à l' Activity
du cycle de vie des méthodes sont fabriqués à partir de la même thread. En Activity#attach()
méthode (de sa source a été montré ci-dessus), le système s'initialise "ui" thread "ce" thread, qui est aussi le "principal" thread. Par conséquent, pour tous les cas pratiques "principal" thread et "ui" thread sont les mêmes.
Cela est vrai pour toutes les applications, avec une exception.
Quand Android cadre est démarré pour la première fois, elle s'exécute comme une application, mais cette application est spécial (par exemple: dispose d'un accès privilégié). Une partie de cette "spécialité", c'est qu'il a besoin d'un spécialement configuré "principale" de fil. Depuis qu'il a déjà couru dans l' public static void main(String[])
méthode (tout comme n'importe quelle autre application), son "principal" et "interface utilisateur" threads sont créés à la même thread. Afin d'obtenir des "principaux" thread avec des caractéristiques spéciales, le système app effectue un appel statique d' public static ActivityThread systemMain()
et les magasins de la référence. Mais son "interface utilisateur" thread n'est pas surchargé, donc "principal" et "interface utilisateur" fils finissent pas ne plus le même.