Toast的经典使用方式:
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
但上面代码在工作线程中执行的话,是无法弹出Toast提示的。除非在其前后分别执行Looper.prepare()和Looper.loop()。
Looper.prepare();
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
Looper.loop();
至于原因,之前一直未想明白。今天看了下Toast的源码实现,
/**
* Show the view for the specified duration.
*/
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
//将该Toast的tn加入到NotificationManagerService的ToastQueue中,NotificationManagerService负责Toast的管理工作
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
当NotificationManagerService需要显示该Toast时,将会执行Toast.TN的show()和hide()回调。而这两个回调的实现为通过Handler来post一个Runnable
/**
* schedule handleShow into the right thread
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
而mHandler定义如下,即 使用当前线程的Looper作为Handler的mLooper。
final Handler mHandler = new Handler();
这样就可以一目了然,为何Toast无法在工作线程正常使用了。因为工作线程若没有初始化其Looper,也就没有MessageQueue来管理Handler消息,Handler的post()或sendMessage()方法将会返回失败(false),消息得不到处理。
记得以前一个项目,一直存在一个疑问:Activity的onCreate()中创建一个Handler,但使用Handler发送的消息,只会在onResume()之后才会被执行(多线程的缘故,工作线程执行的比onResume早)。当时只是发现这个现象,但一直没有细究原因。
首先,主线程的Looper初始化是在ActivityThread.main()方法中执行的
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());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
而启动一个Activity的方式就是在ActivityThread中就是发送和处理LAUNCH_ACTIVITY消息。
// we use token to identify this activity without having to send the
// activity itself back to the activity manager. (matters more with ipc)
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
ActivityClientRecord r = new ActivityClientRecord();
。。。。。。
queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
}
LAUNCH_ACTIVITY消息处理中就是加载该自定义Activity类,顺序初始化并回调Activity的onCreate、onStart、onResume回调。
也就是说Activity的onCreate、onStart、onResume三个生命周期回调都是LAUNCH_ACTIVITY消息处理,而在这三个生命周期期间发送的Handler消息自然只能排在LAUNCH_ACTIVITY消息之后执行了。