Window和WindowManager概述
Window是一个抽象类,它的具体实现是PhoneWindow,创建一个Window通过WindowManager 就可以完成。WindowManager是外界访问Window的入口,它的具体实现在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC的过程。Android中所有的视图都是通过Window来呈现的,无论是Activity,Dialog还是Toast,Window实际上是View的直接管理者。单击时间由Window传递给DecorView,然后DecorView再传递给我们的View,就连Activity设置视图的方法setContentView底层也是通过Window来完成的。
WindowManager继承于ViewManager:
代码语言:javascript复制public interface WindowManager extends ViewManager
ViewManager中有3个抽象的方法:
代码语言:javascript复制public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
* <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
* errors, such as adding a second view to a window without removing the first view.
* <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
* secondary {@link Display} and the specified display can't be found
* (see {@link android.app.Presentation}).
* @param view The view to be added to this window.
* @param params The LayoutParams to assign to view.
*/
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
从这里我们可以得出,WindowManager操作Window的过程实际是在操作Window中的View。
Window的内部机制
Window的添加过程 从Window的源码中可以看出,他本身是一个接口,所以它的逻辑肯定都是由实现类去做的,而这个类就是WindowManagerImpl
代码语言:javascript复制public class WindowManagerImpl implements WindowManager {
private final DisplayMetrics mMetrics;
private final Display mDisplay;
public WindowManagerImpl(DisplayMetrics metrics) {
mMetrics = metrics;
DisplayInfo info = new DisplayInfo();
info.logicalHeight = mMetrics.heightPixels;
info.logicalWidth = mMetrics.widthPixels;
mDisplay = new Display(null, Display.DEFAULT_DISPLAY, info,
DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
}
@Override
public Display getDefaultDisplay() {
return mDisplay;
}
@Override
public void addView(View arg0, android.view.ViewGroup.LayoutParams arg1) {
// pass
}
@Override
public void removeView(View arg0) {
// pass
}
@Override
public void updateViewLayout(View arg0, android.view.ViewGroup.LayoutParams arg1) {
// pass
}
@Override
public void removeViewImmediate(View arg0) {
// pass
}
}
从WindowManagerImpl的源码中,我们会发现它并未实现3大方法,而是交给WindowManagerGlobal来实现,WindowManagerGlobal以工厂的型式对外提供自己的实例
代码语言:javascript复制public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
WindowManagerImpl的工作模式是典型的桥接模式,所有操作全部委托WindowManagerGlobal来实现 分析WindowManagerGlobal的源码,发现它的addView方法主要做了以下几步: 1.检查参数是否合法,如果是子Window那么还需要调整一些布局参数
代码语言:javascript复制1.parentWindow.adjustLayoutParamsForSubWindow(wparams);
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
2.创建ViewRootImpl并将View添加到列表中 WindowManagerGlobal内部有如下几个列表比较重要,从名字也可以猜到,他们分别用于存储Window对应的View,ViewRootImpl,WindowManager.LayoutParams
代码语言:javascript复制private View[] mViews;
private ViewRootImpl[] mRoots;
private WindowManager.LayoutParams[] mParams;
addView方法中会有如下的一段,进行添加的操作
代码语言:javascript复制index--;
mViews[index] = view;
mRoots[index] = root;
mParams[index] = wparams;
3.通过ViewRootImpl来更新界面并完成Window的添加过程,这个步骤是由ViewRootImpl的setView方法完成的,从下面这段代码可以看出来这个结论
代码语言:javascript复制try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
View的绘制是通过ViewRootImpl来完成的,这里也不例外,在setView内部,会通过requestLayout()方法来完成异步刷新的请求
代码语言:javascript复制// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
requestLayout()方法内部的逻辑:
代码语言:javascript复制@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
scheduleTraversals()是View 的绘制入口
代码语言:javascript复制void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
scheduleConsumeBatchedInput();
}
}
setView方法内最后有这么一段代码,说明了Window的添加过程最终是由WindowSession来完成的,mWindowSession的类型是IWindowSession,说明这是一个Binder对象,真正的实现类是Session(这个你又知道?),也就是说Window的添加过程是一次IPC调用
代码语言:javascript复制try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
查看Session内的addToDisplay方法
代码语言:javascript复制 @Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
发现最后是通过mService来添加Window的,而这个mService就是WindowManagerService,所以最后是通过WindowManagerService来完成的,WindowManagerService内部会为每一个应用保留一个Session
Window的删除过程 WindowManagerImpl的工作模式是典型的桥接模式,所有操作全部委托WindowManagerGlobal来实现,所以还是看WindowManagerGlobal的源码中removeView方法
代码语言:javascript复制public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " view
" but the ViewAncestor is attached to " curView);
}
}
里面的逻辑,就是通过findViewLocked找到待移除的View的索引,里面的就是一个数组遍历的逻辑
代码语言:javascript复制public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " view
" but the ViewAncestor is attached to " curView);
}
}
然后通过removeViewLocked方法,将View进行移除
代码语言:javascript复制private View removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots[index];
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews[index].getWindowToken());
}
}
root.die(immediate);
final int count = mViews.length;
// remove it from the list
View[] tmpViews = new View[count-1];
removeItem(tmpViews, mViews, index);
mViews = tmpViews;
ViewRootImpl[] tmpRoots = new ViewRootImpl[count-1];
removeItem(tmpRoots, mRoots, index);
mRoots = tmpRoots;
WindowManager.LayoutParams[] tmpParams
= new WindowManager.LayoutParams[count-1];
removeItem(tmpParams, mParams, index);
mParams = tmpParams;
if (view != null) {
view.assignParent(null);
// func doesn't allow null... does it matter if we clear them?
//view.setLayoutParams(null);
}
return view;
}
removeViewLocked方法中,其实是通过ViewRootImpl的die方法来进行删除的,WindowManager中提供了removeViewImmediate和removeView(该方法继承自ViewManager)两种接口方法,分别表示同步删除和异步删除removeViewImmediate一般都不使用这个方法来删除Window
ViewRootImpl的die方法代码:
代码语言:javascript复制public void die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
doDie();
} else {
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "Attempting to destroy the window while drawing!n"
" window=" this ", title=" mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
}
}
die方法中要一份为2,如果是同步操作,直接调用doDie()方法,如果是异步操作,只是使用mHandler.sendEmptyMessage(MSG_DIE),这个时候View并未完成删除的工作
代码语言:javascript复制 case MSG_DIE:
doDie();
break;
接下来是调用了doDie()方法
代码语言:javascript复制void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(TAG, "DIE in " this " of " mSurface);
synchronized (this) {
if (mAdded) {
dispatchDetachedFromWindow();
}
if (mAdded && !mFirst) {
invalidateDisplayLists();
destroyHardwareRenderer();
if (mView != null) {
int viewVisibility = mView.getVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
if (mWindowAttributesChanged || viewVisibilityChanged) {
// If layout params have been changed, first give them
// to the window manager to make sure it has the correct
// animation info.
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
}
mSurface.release();
}
}
mAdded = false;
}
}
doDie()方法中会调用dispatchDetachedFromWindow()方法,在这个方法内实现View的移除工作,主要做了四件事:垃圾回收,通过Session的remove方法删除Window,调用View的dispatchDetachedFromWindow方法同时会回调View的onDetachedFromWindow以及onDetachedFromWindowInternal,调用WindowManagerGlobal的doRemoveView刷新数据。
Window的更新过程
Window的更新过程是通过WindowManagerGlobal的updateViewLayout来实现的
代码语言:javascript复制public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots[index];
mParams[index] = wparams;
root.setLayoutParams(wparams, false);
}
}
内部逻辑是:首先更新view的LayoutParams,替换掉老的LayoutParams,然后是更新ViewRootImpl的LayoutParams,这里是通过root.setLayoutParams(wparams, false);方法实现
代码语言:javascript复制void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
int oldSoftInputMode = mWindowAttributes.softInputMode;
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
// preserve compatible window flag if exists.
int compatibleWindowFlag =
mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
// transfer over system UI visibility values as they carry current state.
attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
mWindowAttributes.flags |= compatibleWindowFlag;
applyKeepScreenOnFlag(mWindowAttributes);
if (newView) {
mSoftInputMode = attrs.softInputMode;
requestLayout();
}
// Don't lose the mode we last auto-computed.
if ((attrs.softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
== WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
& ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
| (oldSoftInputMode
& WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
}
mWindowAttributesChanged = true;
scheduleTraversals();
}
}
里面最后调用scheduleTraversals()
Window的创建过程
Activity的Window创建过程 在Activity的attach方法里,系统会创建Activity所属的Window,并为其设置回调,下面是attach方法中部分代码
代码语言:javascript复制mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(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();
Activity所属的Window的具体实现是PhoneWindow,Activity视图是通过setContentView方法附属在Window上面的
代码语言:javascript复制public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
这里是通过getWindow()来处理,getWindow()返回的是Window对象
代码语言:javascript复制public Window getWindow() {
return mWindow;
}
Window的具体实现是PhoneWindow,所以只需要查看PhoneWindow的setContentView方法,该方法主要做了以下内容: 1)如果没有DecorView,就通过installDecor()方法创建DecorView,installDecor()部分代码
代码语言:javascript复制 private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
了解到DecorView是通过generateDecor()方法产生的,这时候的DecorView只是一个空白的FrameLayout
代码语言:javascript复制protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
所以要查询DecorView在哪里进行初始化的,在PhoneWindow的generateLayout方法中发现DecorView的足迹,然后在generateLayout方法中发现如下一段代码
代码语言:javascript复制mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
这里mLayoutInflater生成的布局就被添加到DecorView中了
2)将View 添加到DecorView的mContentParent中 回到PhoneWindow的setContentView方法中的mContentParent.addView(view, params);可以验证我们的推断
3)回到Activity的onContentChanged方法通知Activity视图已经发生了改变
代码语言:javascript复制final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
这里是调用了Callback的onContentChanged,由于Callback就是Activity本身,所以就是Activity的onContentChanged方法,但里面是个空实现,所以应该是在子类中进行完成
代码语言:javascript复制 public void onContentChanged() {
}
经过上面的3步,DecorView初始化完毕,Activity的布局文件也被添加到DecorView的mContentParent中,但DecorView还没有被WindowManager添加到Window中,在ActivityThread的handleResumeActivity方法中,会调用Activity的onResume方法,然后调用Activity的makeVisible方法,在这个方法中DecorView才真正完成了添加和显示
代码语言:javascript复制void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
Dialog的Window创建过程 1)创建Window Dialog的构造函数中的部分内容如下:
代码语言:javascript复制mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
2)初始化DecorView并将Dialog的视图添加到DecorView中 这里同样调用的是Window的setContentView
代码语言:javascript复制 public void setContentView(View view) {
mWindow.setContentView(view);
}
3)将DecorView添加到Window中并显示 在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中
代码语言:javascript复制public void show() {
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
if (!mCreated) {
dispatchOnCreate(null);
}
onStart();
mDecor = mWindow.getDecorView();
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new WindowDecorActionBar(this);
}
WindowManager.LayoutParams l = mWindow.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
try {
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
} finally {
}
}
Dialog的Window创建过程和Activity的Window创建差不多,但是在Dialog被关闭时,它会通过WindowManager移除DecorView,这个逻辑在Dialog的dismissDialog()方法中
代码语言:javascript复制void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}
if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}
try {
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
注意:普通的Dialog有一个特殊之处,那就是必须采用Activity的Context,如果采用Application的Context,那么会报错
报错是没有应用的token导致的,而应用的token一般只有Activity才有,但是,系统Window比较特殊,它可以不需要token
Toast的Window创建过程 1.Toast是基于Window来实现的,系统中继承了handler做定时处理 2.内部实现IPC过程,第一类访NotificationManagerService(NMS) 3.通过回调TN接口,实现Toast的显示隐藏 4.Toast属于Window,它内部的视图由两种方式指定,一种是系统默认的样式,另一种是通过setView方法 来指定一个自定义View
代码语言:javascript复制/**
* 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.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
/**
* Close the view if it's showing, or don't show it if it isn't showing yet.
* You do not normally have to call this. Normally view will disappear on its own
* after the appropriate duration.
*/
public void cancel() {
mTN.hide();
try {
getService().cancelToast(mContext.getPackageName(), mTN);
} catch (RemoteException e) {
// Empty
}
}
无论是显示还是隐藏都是通过NotificationManagerService来实现的,查看它的源码中的enqueueToast方法,其中有如下一段
代码语言:javascript复制else {
// Limit the number of toasts that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i ) {
final ToastRecord r = mToastQueue.get(i);
if (r.pkg.equals(pkg)) {
count ;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " count
" toasts. Not showing more. Package=" pkg);
return;
}
}
}
}
当ToastRecord被添加到mToastQueue中后,NMS会通过showNextToastLocked()方法来显示当前的Toast
代码语言:javascript复制private void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" record.pkg " callback=" record.callback);
try {
record.callback.show();
scheduleTimeoutLocked(record, false);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to show notification " record.callback
" in package " record.pkg);
// remove it from the list and let the process die
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
callback实际上就是Toast中的TN对象的远程Binder, 最终会运行在发起Toast请求的应用的Binder线程池中
Toast显示之后,NMS会通过scheduleTimeoutLocked发送延迟消息,时长取决于Toast的时长
代码语言:javascript复制 private void scheduleTimeoutLocked(ToastRecord r)
{
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
//默认3.5s和2s
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
//发送延迟消息
mHandler.sendMessageDelayed(m, delay);
}
同样,Toast显示之后消失,也是通过callback来完成的
代码语言:javascript复制void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
//同样使用回调隐藏
record.callback.hide();
} catch (RemoteException e) {
//打印
}
//移除已处理过的弹框
mToastQueue.remove(index);
keepProcessAliveLocked(record.pid);
if (mToastQueue.size() > 0) {
//如果还有未完成的弹框,继续调用
showNextToastLocked();
}
}
Toast的显示和影响过程实际上是通过TN这个类实现的,两个主要方法show和hide,对应显示和隐藏, 这两个方法被NMS跨进程调用,因此它们运行在Binder线程池中,为了将执行环境切换到Toast的请求线程,在内部使用了handler
代码语言:javascript复制rivate static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
//处理显示的逻辑
handleShow();
}
};
final Runnable mHide = new Runnable() {
@Override
public void run() {
//处理隐藏的逻辑
handleHide();
mNextView = null;
}
};
//构建windManger对象,设置相应params参数
TN() {
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " this);
//通过handler传递消息
mHandler.post(mShow);
}
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " this);
mHandler.post(mHide);
}
}
通过handleShow和handleHide才能真正显示和隐藏Toast的地方
代码语言:javascript复制 mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
//添加
mWM.addView(mView, mParams);
//删除
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " mView " in " this);
mWM.removeView(mView);
}
两个重要的AIDL文件
代码语言:javascript复制//TN实现的接口
oneway interface ITransientNotification {
void show();
void hide();
}
代码语言:javascript复制interface INotificationManager
{
/** @deprecated use {@link #enqueueNotificationWithTag} instead */
void enqueueNotification(String pkg, int id, in Notification notification, inout int[] idReceived);
/** @deprecated use {@link #cancelNotificationWithTag} instead */
void cancelNotification(String pkg, int id);
void cancelAllNotifications(String pkg);
//管理Toast显示和隐藏
void enqueueToast(String pkg, ITransientNotification callback, int duration);
void cancelToast(String pkg, ITransientNotification callback);
void enqueueNotificationWithTag(String pkg, String tag, int id, in Notification notification, inout int[] idReceived);
void cancelNotificationWithTag(String pkg, String tag, int id);
}
参考书籍: 《Android开发艺术探索》