Window和WindowManger
实现可拖动的悬浮框
如下添加一个Button到屏幕(100,300)的位置
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
- FLAG_NOT_TOUCH_MODAL 表示当前Window区域外的点击事件传递给底层的Window,区域内的事件则自己处理,一般都要开启
- TYPE_APPLICATION_OVERLAY 表示覆盖在所有应用上面,需要申请权限和手动授权
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Button mButton;
private WindowManager.LayoutParams mLayoutParams;
private WindowManager mWindowManager;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkAndRequestPermissions();
}
private void showButton() {
mButton = new Button(this);
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mLayoutParams = new WindowManager.LayoutParams();
mButton.setText("test");
mButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
float rawX = event.getRawX();
float rawY = event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mLayoutParams.x = (int) rawX;
mLayoutParams.y = (int) rawY;
mWindowManager.updateViewLayout(mButton, mLayoutParams);
break;
}
return false;
}
});
mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mWindowManager.addView(mButton, mLayoutParams);
}
private void checkAndRequestPermissions() {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 1);
} else {
showButton();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "权限授予失败,无法开启悬浮窗", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "权限授予成功,开启悬浮窗", Toast.LENGTH_SHORT).show();
showButton();
}
}
}
}
Window有三种,由type决定层级,大的会覆盖在小的上面:
- 应用Window:对应一个Activity,层级1-99
- 子Window:不能单独存在,需要依附在父Window,如Dialog,层级1000-1999
- 系统Window:申请权限才能创建,如Toast,层级2000-2999
Window内部机制
WindowManager继承ViewManager,但其本身是接口
public interface WindowManager extends ViewManager {
......
}
public interface ViewManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
实现ViewManager 3个方法的类是WindowManagerImpl,传递给WindowManagerGlobal
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
WindowManagerGlobal中的如下域存储Window所对应的View、ViewRootImpl、LayoutParams以及调用removeView但还未被删除的对象
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
添加过程
WindowManagerGlobal中的addView()方法中,将View、ViewRootImpl、LayoutParams对象添加到列表
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId){
.....
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView, userId);
.....
}
ViewRootImpl中的setView()方法会调用requestLayout()异步刷新View
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
ViewRootImpl中的scheduleTraversals()通过postCallback()运行TraversalRunnable线程
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
ViewRootImpl中的doTraversal()会调用performTraversals()完成measure、layout、draw
void doTraversal() {
if (mTraversalScheduled) {
......
performTraversals();
......
}
}
控件加载完后,接下来会通过mWindowSession的addToDisplayAsUser()添加Window
try {
......
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
setFrame(mTmpFrame);
} catch (RemoteException e) {
......
} finally {
......
}
mWindowSession类型为IWindowSession,其是Binder对象,添加过程是一次IPC调用,实现类是Session,内部通过WindowManagerService实现添加
@Override
public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
outInsetsState, outActiveControls, userId);
}
WindowManagerService的addWindow()
public int addWindow(Session session, IWindow client, int seq, LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,int requestUserId) {
......
//首先检查权限
int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName, appOp);
.....
synchronized (mGlobalLock) {
.....
//判断Window类型
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
parentWindow = windowForClientLocked(null, attrs.token, false);
......
}
.....
//创建WindowState和相关属性关联
final WindowState win = new WindowState(this, session, client, token, parentWindow, appOp[0], seq, attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow);
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs, callingPid, callingUid);
attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
......
//注册通道,使窗口可接收事件
final boolean openInputChannels = (outInputChannel != null && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
win.openInputChannel(outInputChannel);
}
......
win.attach();
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();
......
win.mToken.addWindow(win);
displayPolicy.addWindowLw(win, attrs);
.......
//设置动画
final WindowStateAnimator winAnimator = win.mWinAnimator;
winAnimator.mEnterAnimationPending = true;
winAnimator.mEnteringAnimation = true;
if (activity != null && activity.isVisible()&& !prepareWindowReplacementTransition(activity)) {
prepareNoneTransitionForRelaunching(activity);
}
}.......
return res;
}
删除过程
WindowManagerGlobal中的removeView()通过findViewLocked()找到待删除View的索引
@UnsupportedAppUsage
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 = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
private int findViewLocked(View view, boolean required) {
final int index = mViews.indexOf(view);
if (required && index < 0) {
throw new IllegalArgumentException("View=" + view + " not attached to window manager");
}
return index;
}
再调用WindowManagerGlobal中的removeViewLocked()
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (root != null) {
root.getImeFocusController().onWindowDismissed();
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
WindowManager提供了removeView() / removeViewImmediate() 异步 / 同步删除,具体实现在ViewRootImpl的die(),异步删除只发送消息,并将其添加到mDyingViews列表
boolean die(boolean immediate) {
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
ViewRootImpl的doDie()调用dispatchDetachedFromWindow(),主要做
- GC、清除数据、回调
- 调用Session的remove()删除Window(IPC),最后调到WindowManagerService的removeWindow()
- 调用View的dispatchDetachedFromWindow(),回调onDetachedFromWindow()
- 调用WindowManagerGlobal的doRemoveView()刷新数据(mRoot、mParams、mDyingViews)
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
......
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
更新过程
WindowManagerGlobal的updateViewLayout()
- 更新View的LayoutParams
- 调用ViewRootImpl中的setLayoutParams(),调用scheduleTraversals()重新measure、layout、draw
- 调用ViewRootImpl中的relayoutWindow(),调用WindowSession的relayout(),最后到WindowManagerService的relayoutWindow()
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.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
Window的创建过程
Activity的Window创建过程
ActivityThread中的performLaunchActivity()通过ClassLoader创建activity,调用attach()传入运行过程中所需要的上下文环境变量
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);
......
}......
try {
......
if (activity != null) {
......
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);
......
}
......
}......
return activity;
}
Activity的attach()创建Window的唯一实现类PhoneWindow,将自身作为回调
final void attach(......) {
......
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
......
}
Activity在setContentView()将布局ID传给了PhoneWindow
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow的setContentView()如下
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor(); // 1. 创建mDecor 、mContentParent
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent); // 2.将Activity的布局加载到DecorView中的mContentParent(即R.id.content)
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged(); // 3. 回调Activity的onContentChanged()
}
mContentParentExplicitlySet = true;
}
PhoneWindow的installDecor()通过generateDecor()、generateLayout()创建mDecor 、mContentParent
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
......
} ......
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
......
}
}
protected DecorView generateDecor(int featureId) {
......
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle(); //根据Style设置属性
......
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
}......
int layoutResource; //布局资源ID,接下来根据属性设置不同的布局
int features = getLocalFeatures();
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
......
}else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { //取消Title,故其需要在setContentView()之前设置
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
}......
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //将布局ID加载到mDecor
......
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); //获取content
......
if (getContainer() == null) { //设置背景和Title
mDecor.setWindowBackground(mBackgroundDrawable);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
......
return contentParent;
}
上面的ID_ANDROID_CONTENT为R.id.content
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
以R.layout.screen_title的布局为例,R.id.content将是main_activity.xml的父布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
在ActivityThread中的handleResumeActivity()
- 调用performResumeActivity()再调用Activity的performResume()回调onResume()
- 若window==null,则获取Window、DecorView、WindowManager、LayoutParams,将DecorView添加到WindowManager
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
......
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
......
final Activity a = r.activity;
......
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
......
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}......
}
}......
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
......
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
......
}
若window!=null,则调用Activity的makeVisible(),通过WindowManager将mDecor添加到Window
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
Dialog的Window创建过程
在构造函数中创建Window,通过setWindowManager()和WindowManagerImpl建立联系
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
......
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
......
}
在setContentView()中通过Window将布局添加到DecorView
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
在show()中通过addView()将DecorView添加到Window,在dismiss()中通过removeViewImmediate()移除
public void show() {
......
if (!mCreated) {
dispatchOnCreate(null);
} else {
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
onStart();
mDecor = mWindow.getDecorView();
......
mWindowManager.addView(mDecor, l);
......
}
Dialog必须采用Activity的Context,否则会报错,但可以通过设置type让其成为系统Window
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkAndRequestPermissions();
}
private void showDialog(){
Dialog dialog = new Dialog(getApplicationContext());
TextView textView = new TextView(this);
textView.setText("test");
dialog.setContentView(textView);
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
dialog.show();
}
private void checkAndRequestPermissions() {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 1);
} else {
showDialog();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "permission denied", Toast.LENGTH_SHORT).show();
} else {
showDialog();
}
}
}
}
Toast的Window创建过程
Toast基于Handler,且内部有两类IPC
- Toast访问NotificationManagerService
- NotificationManagerService回调Toast的TN接口
Toast的视图可采用系统默认,或通过setView()自定义View,其show()方法如下
public void show() {
......
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
final int displayId = mContext.getDisplayId();
try {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
if (mNextView != null) {
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
} else {
ITransientNotificationCallback callback =
new CallbackBinder(mCallbacks, mHandler);
service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
}
} else {
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
}
} catch (RemoteException e) {
}
}
NotificationManagerService的enqueueToast方法()会将Toast封装成ToastRecord再添加到mToastQueue,同一包名应用最多同时存在50个,防止一直弹Toast导致其他应用没有机会弹
private void enqueueToast(......) {
synchronized (mToastQueue) {
......
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, token);
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
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;
}
}
}
......
record = getToastRecord(callingUid, callingPid, pkg, token, text, callback,
duration, windowToken, displayId, textCallback);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
.....
}
if (index == 0) {
showNextToastLocked();
}
}.....
}
}
具体显示Toast的方法在showNextToastLocked(),调用ToastRecord的show()显示,并从mToastQueue移除
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (record.show()) {
scheduleDurationReachedLocked(record);
return;
}
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null;
}
}
scheduleDurationReachedLocked()通过Handler发送一个MESSAGE_DURATION_REACHED控制其显示时间,这里也可知LENGTH_LONG是3.5s,LENGTH_SHORT是2s
private void scheduleDurationReachedLocked(ToastRecord r) {
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
int delay = r.getDuration() == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
AccessibilityManager.FLAG_CONTENT_TEXT);
mHandler.sendMessageDelayed(m, delay);
}
收到MESSAGE_DURATION_REACHED后,通过cancelToastLocked()调用ToastRecord的hide(),再次调用showNextToastLocked()显示下一个Toast
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
record.hide();
ToastRecord lastToast = mToastQueue.remove(index);
......
if (mToastQueue.size() > 0) {
showNextToastLocked();
}
}
ToastRecord的show()会回调到Toast中TN的handleShow(),将View添加到Window
public void handleShow(IBinder windowToken) {
......
if (mView != mNextView) {
......
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
......
try {
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
}
}
}