【Android开发艺术探索】理解Window和WindowManager

2024-03-11 05:50

本文主要是介绍【Android开发艺术探索】理解Window和WindowManager,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

个人博客:
http://www.milovetingting.cn

理解Window和WindowManager

window_windowmanager_mind.png

Window表示一个窗口的概念,是一个抽象类,具体实现是PhoneWindow,可以通过WindowManager创建一个Window。WindowManager是外界访问Window的入口,Window具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。

Window和WindowManager

WindowManager.LayoutParams

关注flags和type两个参数:

Flags参数表示Window的属性,可以控制Window的显示特性。

** FLAG_NOT_FOCUSABLE **

表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window。

** FLAG_NOT_TOUCH_MODAL **

系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件自己处理。

** FLAG_SHOW_WHEN_LOCKED **

让Window显示在锁屏的界面上。

Type参数表示Window类型

Window有三种类型:应用Window、子Window、系统Window。应用Window对应一个Activity。子Window不能单独存在,需要附属在特定的父Window中,如Dialog就是子Window。系统Window需要声明权限才能创建,如Toast和系统状态栏就是系统Window。

Window是分层的,每个Window都有对应的z-ordered,层级大的覆盖在层级小的Window上。应用Window的层级范围是1-99,子Window的层级范围是1000-1999,系统Window层级范围是2000-2999。

WindowManager常用的三个方法:添加View、更新View和删除View。这是从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);
}

Window的内部机制

Window是一个抽象概念,每一个Window都对应一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系。

Window的添加过程

Window的添加过程需要通过WindowManager的addView来实现。WindowManager是一个接口,它的实现类是WindowManagerImpl。

public void addView(View view, LayoutParams params) {this.applyDefaultToken(params);this.mGlobal.addView(view, params, this.mContext.getDisplay(), this.mParentWindow);}

WindowManagerImpl并没有直接实现addView,而是通过内部的WindowManagerGlobal实现的。

public void addView(View view, android.view.ViewGroup.LayoutParams params, Display display, Window parentWindow) {if (view == null) {//检查viewthrow new IllegalArgumentException("view must not be null");} else if (display == null) {//检查displaythrow new IllegalArgumentException("display must not be null");} else if (!(params instanceof LayoutParams)) {//检查paramsthrow new IllegalArgumentException("Params must be WindowManager.LayoutParams");} else {LayoutParams wparams = (LayoutParams)params;if (parentWindow != null) {//调整子窗口的布局参数parentWindow.adjustLayoutParamsForSubWindow(wparams);} else {Context context = view.getContext();if (context != null && (context.getApplicationInfo().flags & 536870912) != 0) {wparams.flags |= 16777216;}}View panelParentView = null;int index;ViewRootImpl root;synchronized(this.mLock) {if (this.mSystemPropertyUpdater == null) {this.mSystemPropertyUpdater = new Runnable() {public void run() {synchronized(WindowManagerGlobal.this.mLock) {for(int i = WindowManagerGlobal.this.mRoots.size() - 1; i >= 0; --i) {((ViewRootImpl)WindowManagerGlobal.this.mRoots.get(i)).loadSystemProperties();}}}};SystemProperties.addChangeCallback(this.mSystemPropertyUpdater);}int index = this.findViewLocked(view, false);if (index >= 0) {if (!this.mDyingViews.contains(view)) {//不允许重复添加窗口throw new IllegalStateException("View " + view + " has already been added to the window manager.");}((ViewRootImpl)this.mRoots.get(index)).doDie();}if (wparams.type >= 1000 && wparams.type <= 1999) {index = this.mViews.size();for(int i = 0; i < index; ++i) {if (((ViewRootImpl)this.mRoots.get(i)).mWindow.asBinder() == wparams.token) {panelParentView = (View)this.mViews.get(i);}}}//创建ViewRootImplroot = new ViewRootImpl(view.getContext(), display);//设置LayoutParamsview.setLayoutParams(wparams);this.mViews.add(view);this.mRoots.add(root);this.mParams.add(wparams);}try {//ViewRootImpl添加viewroot.setView(view, wparams, panelParentView);} catch (RuntimeException var15) {synchronized(this.mLock) {index = this.findViewLocked(view, false);if (index >= 0) {this.removeViewLocked(index, true);}}throw var15;}}}

ViewRootImpl调用setView方法

public void setView(View view, LayoutParams attrs, View panelParentView) {synchronized(this) {if (this.mView == null) {//...this.mAdded = true;//1、调用requestLayout方法this.requestLayout();//...int res;try {this.mOrigWindowType = this.mWindowAttributes.type;this.mAttachInfo.mRecomputeGlobalAttributes = true;this.collectViewAttributes();//2、通过Session添加Windowres = this.mWindowSession.addToDisplay(this.mWindow, this.mSeq, this.mWindowAttributes, this.getHostVisibility(), this.mDisplay.getDisplayId(), this.mAttachInfo.mContentInsets, this.mAttachInfo.mStableInsets, this.mAttachInfo.mOutsets, this.mInputChannel);} catch (RemoteException var20) {this.mAdded = false;this.mView = null;this.mAttachInfo.mRootView = null;this.mInputChannel = null;this.mFallbackEventHandler.setView((View)null);this.unscheduleTraversals();this.setAccessibilityFocus((View)null, (AccessibilityNodeInfo)null);throw new RuntimeException("Adding window failed", var20);} finally {if (restore) {attrs.restore();}}//...if (res < 0) {//添加Window失败this.mAttachInfo.mRootView = null;this.mAdded = false;this.mFallbackEventHandler.setView((View)null);this.unscheduleTraversals();this.setAccessibilityFocus((View)null, (AccessibilityNodeInfo)null);switch(res) {case -10:throw new InvalidDisplayException("Unable to add window " + this.mWindow + " -- the specified window type " + this.mWindowAttributes.type + " is not valid");case -9:throw new InvalidDisplayException("Unable to add window " + this.mWindow + " -- the specified display can not be found");case -8:throw new BadTokenException("Unable to add window " + this.mWindow + " -- permission denied for window type " + this.mWindowAttributes.type);case -7:throw new BadTokenException("Unable to add window " + this.mWindow + " -- another window of type " + this.mWindowAttributes.type + " already exists");case -6:return;case -5:throw new BadTokenException("Unable to add window -- window " + this.mWindow + " has already been added");case -4:throw new BadTokenException("Unable to add window -- app for token " + attrs.token + " is exiting");case -3:throw new BadTokenException("Unable to add window -- token " + attrs.token + " is not for an application");case -2:case -1:if (view.getContext().getPackageName().startsWith("com.google.android.gms")) {try {if (AppGlobals.getPackageManager().isFirstBoot()) {Log.d(this.mTag, "firstboot crash return");return;}} catch (RemoteException var22) {var22.printStackTrace();return;}}throw new BadTokenException("Unable to add window -- token " + attrs.token + " is not valid; is your activity running?");default:throw new RuntimeException("Unable to add window -- unknown error code " + res);}}//...//将view和ViewRootImpl关联起来view.assignParent(this);//...}}}

1、requestLayout方法

public void requestLayout() {if (!this.mHandlingLayoutInLayoutRequest) {//A检测线程this.checkThread();this.mLayoutRequested = true;//B开始View的绘制this.scheduleTraversals();}}

A:检测线程,如果不是主线程,则报错。

 void checkThread() {if (this.mThread != Thread.currentThread()) {throw new ViewRootImpl.CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");}}

B:开始View的绘制

void scheduleTraversals() {if (!this.mTraversalScheduled) {this.mTraversalScheduled = true;this.mTraversalBarrier = this.mHandler.getLooper().getQueue().postSyncBarrier();//回调mTraversalRunnablethis.mChoreographer.postCallback(2, this.mTraversalRunnable, (Object)null);if (!this.mUnbufferedInputDispatch) {this.scheduleConsumeBatchedInput();}this.notifyRendererOfFramePending();this.pokeDrawLockIfNeeded();}}

回调mTraversalRunnable

final class TraversalRunnable implements Runnable {TraversalRunnable() {}public void run() {//调用doTraversalViewRootImpl.this.doTraversal();}}

调用doTraversal

void doTraversal() {if (this.mTraversalScheduled) {this.mTraversalScheduled = false;this.mHandler.getLooper().getQueue().removeSyncBarrier(this.mTraversalBarrier);if (this.mProfile) {Debug.startMethodTracing("ViewAncestor");}//调用performTraversalsthis.performTraversals();if (this.mProfile) {Debug.stopMethodTracing();this.mProfile = false;}}}

调用performTraversals方法,在performTraversals方法内会调用performMeasure()、PerformLayout()、PerformDraw(),并最终会调用view的measure()、layout()、draw()方法

回到ViewRootImpl的setView方法,在2处通过WindowSession调用addToDisplay()方法,WindowSession是一个Binder对象,最终的实现为Session类。Session中的addToDisplay方法:

@Overridepublic 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);}

addToDisplay方法通过调用WindowManagerService的addWindow方法。具体的addWindow方法,此处不再分析。

Window的删除过程

Window的删除过程和添加过程基本一样,都是先通过WindowManagerImpl,再通过WindowManagerGlobal来实现的。

public void removeView(View view, boolean immediate) {if (view == null) {throw new IllegalArgumentException("view must not be null");} else {synchronized(this.mLock) {int index = this.findViewLocked(view, true);View curView = ((ViewRootImpl)this.mRoots.get(index)).getView();this.removeViewLocked(index, immediate);if (curView != view) {throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView);}}}}

removeView方法中又调用removeViewLocked方法

private void removeViewLocked(int index, boolean immediate) {ViewRootImpl root = (ViewRootImpl)this.mRoots.get(index);View view = root.getView();if (view != null) {InputMethodManager imm = InputMethodManager.getInstance();if (imm != null) {imm.windowDismissed(((View)this.mViews.get(index)).getWindowToken());}}boolean deferred = root.die(immediate);if (view != null) {view.assignParent((ViewParent)null);if (deferred) {this.mDyingViews.add(view);}}}

在removeViewLocked方法里调用ViewRootImpl的die方法

boolean die(boolean immediate) {if (immediate && !this.mIsInTraversal) {this.doDie();return false;} else {if (!this.mIsDrawing) {this.destroyHardwareRenderer();} else {Log.e(this.mTag, "Attempting to destroy the window while drawing!\n  window=" + this + ", title=" + this.mWindowAttributes.getTitle());}this.mHandler.sendEmptyMessage(3);return true;}}

die方法中,如果不是立即移除,则通过Handler发送一个移除消息,如果是立即移除,则调用doDie方法

void doDie() {//检查线程this.checkThread();synchronized(this) {if (this.mRemoved) {return;}this.mRemoved = true;if (this.mAdded) {this.dispatchDetachedFromWindow();}if (this.mAdded && !this.mFirst) {this.destroyHardwareRenderer();if (this.mView != null) {int viewVisibility = this.mView.getVisibility();boolean viewVisibilityChanged = this.mViewVisibility != viewVisibility;if (this.mWindowAttributesChanged || viewVisibilityChanged) {try {if ((this.relayoutWindow(this.mWindowAttributes, viewVisibility, false) & 2) != 0) {this.mWindowSession.finishDrawing(this.mWindow);}} catch (RemoteException var6) {}}this.mSurface.release();}}this.mAdded = false;}WindowManagerGlobal.getInstance().doRemoveView(this);}

真正移除Window是在dispatchDetachedFromWindow方法中实现的

void dispatchDetachedFromWindow() {if (this.mView != null && this.mView.mAttachInfo != null) {this.mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);this.mView.dispatchDetachedFromWindow();}this.mAccessibilityInteractionConnectionManager.ensureNoConnection();this.mAccessibilityManager.removeAccessibilityStateChangeListener(this.mAccessibilityInteractionConnectionManager);this.mAccessibilityManager.removeHighTextContrastStateChangeListener(this.mHighContrastTextManager);this.removeSendWindowContentChangedCallback();this.destroyHardwareRenderer();this.setAccessibilityFocus((View)null, (AccessibilityNodeInfo)null);this.mView.assignParent((ViewParent)null);this.mView = null;this.mAttachInfo.mRootView = null;this.mSurface.release();if (this.mInputQueueCallback != null && this.mInputQueue != null) {this.mInputQueueCallback.onInputQueueDestroyed(this.mInputQueue);this.mInputQueue.dispose();this.mInputQueueCallback = null;this.mInputQueue = null;}if (this.mInputEventReceiver != null) {this.mInputEventReceiver.dispose();this.mInputEventReceiver = null;}try {this.mWindowSession.remove(this.mWindow);} catch (RemoteException var2) {}if (this.mInputChannel != null) {this.mInputChannel.dispose();this.mInputChannel = null;}this.mDisplayManager.unregisterDisplayListener(this.mDisplayListener);this.unscheduleTraversals();}

在dispatchDetachedFromWindow中,最终通过Session来移除Window。

移除Window后,调用WindowManagerGlobal的doRemoveView方法将之前列表中的记录清除

void doRemoveView(ViewRootImpl root) {synchronized(this.mLock) {int index = this.mRoots.indexOf(root);if (index >= 0) {this.mRoots.remove(index);this.mParams.remove(index);View view = (View)this.mViews.remove(index);this.mDyingViews.remove(view);}}if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {this.doTrimForeground();}}

Window的更新过程

Window的更新是通过WindowManagerGlobal的updateViewLayout方法来实现的

public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) {if (view == null) {throw new IllegalArgumentException("view must not be null");} else if (!(params instanceof LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");} else {LayoutParams wparams = (LayoutParams)params;view.setLayoutParams(wparams);synchronized(this.mLock) {int index = this.findViewLocked(view, true);ViewRootImpl root = (ViewRootImpl)this.mRoots.get(index);this.mParams.remove(index);this.mParams.add(index, wparams);root.setLayoutParams(wparams, false);}}}

Window创建过程

Activity的Window创建过程

先说明Activity的创建过程。Activity是在ActivityThread的performLaunchActivity方法中创建的。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {//...ContextImpl appContext = createBaseContextForActivity(r);Activity activity = null;try {java.lang.ClassLoader cl = appContext.getClassLoader();//创建activityactivity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);StrictMode.incrementExpectedActivityCount(activity.getClass());r.intent.setExtrasClassLoader(cl);r.intent.prepareToEnterProcess();if (r.state != null) {r.state.setClassLoader(cl);}} 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 (localLOGV) Slog.v(TAG, "Performing launch of " + r);if (localLOGV) Slog.v(TAG, r + ": app=" + app+ ", appName=" + app.getPackageName()+ ", pkg=" + r.packageInfo.getPackageName()+ ", comp=" + r.intent.getComponent().toShortString()+ ", dir=" + r.packageInfo.getAppDir());if (activity != null) {CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());Configuration config = new Configuration(mCompatConfiguration);if (r.overrideConfig != null) {config.updateFrom(r.overrideConfig);}if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "+ r.activityInfo.name + " with config " + config);Window window = null;//创建windowif (r.mPendingRemoveWindow != null && r.mPreserveWindow) {window = r.mPendingRemoveWindow;r.mPendingRemoveWindow = null;r.mPendingRemoveWindowManager = null;}appContext.setOuterContext(activity);//将activity和window关联起来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);//...} catch (SuperNotCalledException e) {throw e;} catch (Exception e) {if (!mInstrumentation.onException(activity, e)) {throw new RuntimeException("Unable to start activity " + component+ ": " + e.toString(), e);}}return activity;}

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) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);//创建windowmWindow = new PhoneWindow(this, window, activityConfigCallback);mWindow.setWindowControllerCallback(this);mWindow.setCallback(this);mWindow.setOnWindowDismissedCallback(this);mWindow.getLayoutInflater().setPrivateFactory(this);//...//设置WindowManagermWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);if (mParent != null) {mWindow.setContainer(mParent.getWindow());}mWindowManager = mWindow.getWindowManager();mCurrentConfig = config;mWindow.setColorMode(info.colorMode);setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());enableAutofillCompatibilityIfNeeded();}

window创建完成后,在Activity的setContentView方法中,将View附加到Window中的DecorView上。

public void setContentView(@LayoutRes int layoutResID) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();}

window的唯一实现类为PhoneWindow,因此查看PhoneWindow的setContentView方法

public void setContentView(int layoutResID) {if (this.mContentParent == null) {//如果没有DecorView,则创建this.installDecor();} else if (!this.hasFeature(12)) {this.mContentParent.removeAllViews();}if (this.hasFeature(12)) {Scene newScene = Scene.getSceneForLayout(this.mContentParent, layoutResID, this.getContext());this.transitionTo(newScene);} else {this.mLayoutInflater.inflate(layoutResID, this.mContentParent);}this.mContentParent.requestApplyInsets();android.view.Window.Callback cb = this.getCallback();if (cb != null && !this.isDestroyed()) {cb.onContentChanged();}this.mContentParentExplicitlySet = true;}

View被添加到Window中的DecorView后,Window并没有马上被添加。在Activity的onResume方法中,通过调用makeVisible方法才被添加。

void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);}

Dialog的Window创建过程

1、创建Window

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {if (createContextThemeWrapper) {if (themeResId == ResourceId.ID_NULL) {final TypedValue outValue = new TypedValue();context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);themeResId = outValue.resourceId;}mContext = new ContextThemeWrapper(context, themeResId);} else {mContext = context;}mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//创建windowfinal Window w = new PhoneWindow(mContext);mWindow = w;w.setCallback(this);w.setOnWindowDismissedCallback(this);w.setOnWindowSwipeDismissedCallback(() -> {if (mCancelable) {cancel();}});w.setWindowManager(mWindowManager, null, null);w.setGravity(Gravity.CENTER);mListenersHandler = new ListenersHandler(this);}

2、初始化DecorView并将Dialog的视图添加到DecorView中

public void setContentView(@LayoutRes int layoutResID) {mWindow.setContentView(layoutResID);}

3、将DecorView添加到Window

public void show() {//...mWindowManager.addView(mDecor, l);mShowing = true;sendShowMessage();}

普通Dialog必须采用Activity的Context。

Toast的Window创建过程

Toast也是基于Window来实现,但由于Toast具有定时取消功能,所以系统采用了Handler。

Toast的显示

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}}

在show方法中,通过IPC调用NotificationManager的enqueueToast方法

        @Overridepublic void enqueueToast(String pkg, ITransientNotification callback, int duration){if (DBG) {Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback+ " duration=" + duration);}if (pkg == null || callback == null) {Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);return ;}final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));final boolean isPackageSuspended =isPackageSuspendedForUser(pkg, Binder.getCallingUid());if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&(!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())|| isPackageSuspended)) {Slog.e(TAG, "Suppressing toast from package " + pkg+ (isPackageSuspended? " due to package suspended by administrator.": " by user request."));return;}synchronized (mToastQueue) {int callingPid = Binder.getCallingPid();long callingId = Binder.clearCallingIdentity();try {ToastRecord record;int index;// All packages aside from the android package can enqueue one toast at a timeif (!isSystemToast) {index = indexOfToastPackageLocked(pkg);} else {index = indexOfToastLocked(pkg, callback);}// If the package already has a toast, we update its toast// in the queue, we don't move it to the end of the queue.if (index >= 0) {record = mToastQueue.get(index);record.update(duration);record.update(callback);} else {Binder token = new Binder();mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);record = new ToastRecord(callingPid, pkg, callback, duration, token);mToastQueue.add(record);index = mToastQueue.size() - 1;}keepProcessAliveIfNeededLocked(callingPid);// If it's at index 0, it's the current toast.  It doesn't matter if it's// new or just been updated.  Call back and tell it to show itself.// If the callback fails, this will remove it from the list, so don't// assume that it's valid after this.if (index == 0) {showNextToastLocked();}} finally {Binder.restoreCallingIdentity(callingId);}}}

showNextToastLocked方法

    @GuardedBy("mToastQueue")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(record.token);scheduleTimeoutLocked(record);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 dieint index = mToastQueue.indexOf(record);if (index >= 0) {mToastQueue.remove(index);}keepProcessAliveIfNeededLocked(record.pid);if (mToastQueue.size() > 0) {record = mToastQueue.get(0);} else {record = null;}}}}

通过record.callback,即Toast中的TN对象,调用show方法

        @Overridepublic void show(IBinder windowToken) {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.obtainMessage(SHOW, windowToken).sendToTarget();}

通过Handler发送消息,然后在处理消息的逻辑中调用了handleShow

public void handleShow(IBinder windowToken) {if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView+ " mNextView=" + mNextView);// If a cancel/hide is pending - no need to show - at this point// the window token is already invalid and no need to do any work.if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {return;}if (mView != mNextView) {// remove the old view if necessaryhandleHide();mView = mNextView;Context context = mView.getContext().getApplicationContext();String packageName = mView.getContext().getOpPackageName();if (context == null) {context = mView.getContext();}mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);// We can resolve the Gravity here by using the Locale for getting// the layout directionfinal Configuration config = mView.getContext().getResources().getConfiguration();final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());mParams.gravity = gravity;if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {mParams.horizontalWeight = 1.0f;}if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {mParams.verticalWeight = 1.0f;}mParams.x = mX;mParams.y = mY;mParams.verticalMargin = mVerticalMargin;mParams.horizontalMargin = mHorizontalMargin;mParams.packageName = packageName;mParams.hideTimeoutMilliseconds = mDuration ==Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;mParams.token = windowToken;if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);mWM.removeView(mView);}if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);// Since the notification manager service cancels the token right// after it notifies us to cancel the toast there is an inherent// race and we may attempt to add a window after the token has been// invalidated. Let us hedge against that.try {mWM.addView(mView, mParams);trySendAccessibilityEvent();} catch (WindowManager.BadTokenException e) {/* ignore */}}}

在handleShow方法中,将View添加到了window。

这篇关于【Android开发艺术探索】理解Window和WindowManager的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/796857

相关文章

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

使用Python开发一个现代化屏幕取色器

《使用Python开发一个现代化屏幕取色器》在UI设计、网页开发等场景中,颜色拾取是高频需求,:本文主要介绍如何使用Python开发一个现代化屏幕取色器,有需要的小伙伴可以参考一下... 目录一、项目概述二、核心功能解析2.1 实时颜色追踪2.2 智能颜色显示三、效果展示四、实现步骤详解4.1 环境配置4.

Python使用smtplib库开发一个邮件自动发送工具

《Python使用smtplib库开发一个邮件自动发送工具》在现代软件开发中,自动化邮件发送是一个非常实用的功能,无论是系统通知、营销邮件、还是日常工作报告,Python的smtplib库都能帮助我们... 目录代码实现与知识点解析1. 导入必要的库2. 配置邮件服务器参数3. 创建邮件发送类4. 实现邮件

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

基于Python开发一个有趣的工作时长计算器

《基于Python开发一个有趣的工作时长计算器》随着远程办公和弹性工作制的兴起,个人及团队对于工作时长的准确统计需求日益增长,本文将使用Python和PyQt5打造一个工作时长计算器,感兴趣的小伙伴可... 目录概述功能介绍界面展示php软件使用步骤说明代码详解1.窗口初始化与布局2.工作时长计算核心逻辑3

python web 开发之Flask中间件与请求处理钩子的最佳实践

《pythonweb开发之Flask中间件与请求处理钩子的最佳实践》Flask作为轻量级Web框架,提供了灵活的请求处理机制,中间件和请求钩子允许开发者在请求处理的不同阶段插入自定义逻辑,实现诸如... 目录Flask中间件与请求处理钩子完全指南1. 引言2. 请求处理生命周期概述3. 请求钩子详解3.1

如何基于Python开发一个微信自动化工具

《如何基于Python开发一个微信自动化工具》在当今数字化办公场景中,自动化工具已成为提升工作效率的利器,本文将深入剖析一个基于Python的微信自动化工具开发全过程,有需要的小伙伴可以了解下... 目录概述功能全景1. 核心功能模块2. 特色功能效果展示1. 主界面概览2. 定时任务配置3. 操作日志演示

JavaScript实战:智能密码生成器开发指南

本文通过JavaScript实战开发智能密码生成器,详解如何运用crypto.getRandomValues实现加密级随机密码生成,包含多字符组合、安全强度可视化、易混淆字符排除等企业级功能。学习密码强度检测算法与信息熵计算原理,获取可直接嵌入项目的完整代码,提升Web应用的安全开发能力 目录

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

一文教你如何解决Python开发总是import出错的问题

《一文教你如何解决Python开发总是import出错的问题》经常朋友碰到Python开发的过程中import包报错的问题,所以本文将和大家介绍一下可编辑安装(EditableInstall)模式,可... 目录摘要1. 可编辑安装(Editable Install)模式到底在解决什么问题?2. 原理3.