Android开发艺术探索——第八章(理解Window和WindowManager)

亦余心之所善兮,虽九死其犹未悔。这篇文章主要讲述Android开发艺术探索——第八章:理解Window和WindowManager相关的知识,希望能为你提供帮助。
理解Window和WindowManager Window表示的是一个窗口的概念, 在日常生活中使用的并不是很多, 但是某些特殊的需求还是需要的, 比如悬浮窗之类的, 他的具体实现是PhoneWindow,创建一个Window很简单, 只需要WindowManager去实现, WindowManager是外界访问Window的入口, Window的具体实现是在WindowManagerService中, 他们两个的交互是一个IPC的过程, android中的所有视图都是通过Windowl来实现的, 无论是Activity,Dialog还是Toast,他们的视图都是直接附加在Window上的, 因此Window是View的直接管理者, 在之前的事件分发中我们说到, View的事件是通过WIndow传递给DecorView, 然后DecorView传递给我们的View, 就连Activity的setContentView,都是由Window传递的。
一.Window和WindowManager 为了了解Window的工作机制, 我们首先来看下如何通过WindowManager来创建一个Window

Button btn = new Button(this); btn.setText(" 我是窗口" ); WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); WindowManager.LayoutParams layout = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT , WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSLUCENT); layout.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; layout.gravity = Gravity.CENTER; layout.type = WindowManager.LayoutParams.TYPE_PHONE; layout.x = 300; layout.y = 100; wm.addView(btn, layout);

上述的代码, 其中type和flag是比较重要的, 我们来看下
Flag参数表示window的属性, 他有很多选项, 我们挑几个重点的
  • FLAG_NOT_FOCUSABLE
表示窗口不需要获取焦点, 也不需要接收各种事件, 这属性会同时启动FLAG_NOT_TOUCH_MODAL, 最终的事件会传递给下层的具体焦点的window
  • FLAG_NOT_TOUCH_MODAL,
在此模式下, 系统会将当前window区域以外的单击事件传递给底层的Window, 此前的Window区域以内的单机事件自己处理, 这个标记很重要, 一般来说都需要开启, 否则其他window将无法获取单击事件
  • FLAG_SHOW_WHEN_LOCKED
开启这个属性可以让window显示在锁屏上
Type参数表示window的类型, window有三种类型, 分别是应用, 子, 系统, 应用window对应一个Activity,子Window不能单独存在, 需要依赖一个父Window, 比如常见的Dialog都是子Window,系统window需要声明权限, 比如系统的状态栏
Window是分层的, 每个Window对应着z-ordered,层级大的会覆盖在层级小的Window上面, 这和html中的z-index的概念是一致的, 在这三类中, 应用是层级范围是1-99, 子window的层级是1000-1999, 系统的层级是2000-2999。这些范围对应着type参数, 如果想要window在最顶层, 那么层级范围设置大一点就好了, 很显然系统的值要大一些, 系统的值很多, 我们一般会选择TYPE_SYSTEM_OVERLAY和TYPE_SYSTEM_ERROR, 记得要设置权限哦;
< uses-permission android:name= " android.permission.SYSTEM_ALERT_WINDOW" />

WindowManager所提供的功能很简单, 常用的有三个方法, 添加View,更新View,删除View,这三个方法定义在ViewManager中, 而WindowManager继承自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); }

虽然只有三个功能, 但是这三个功能足够我们使用了, 我们常见的可以推动的View, 其实也很好实现, 就是不断的更改他xy的位置
btn.setOnTouchListener(new View.OnTouchListener() { @ Override public boolean onTouch(View v, MotionEvent event) { int rawX = (int) event.getRawX(); int rawY = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: layout.x = rawX; layout.y = rawY; wm.updateViewLayout(btn,layout); break; } return false; } });

二.Window的内部机制 Window是一个抽象的概念, 每一个Window对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl建立关系, 因此Window并不是实际存在的, 这点从WindowManager定义的接口都是针对View, 这说明View才是window的实体, 在实际使用当中我们并不能直接访问
1.Window的添加过程
Window的添加过程是通过WindowManager的addView去实现的, 而真正实现的是一个接口, , 也就是WindowManagerImpl
@ Override public void addView(View view, ViewGroup.LayoutParams params) { mGlobal.addView(view, params, mDisplay, mParentWindow); }@ Override public void updateViewLayout(View view, ViewGroup.LayoutParams params) { mGlobal.updateViewLayout(view, params); }@ Override public void removeView(View view) { mGlobal.removeView(view, false); }

可以发现, WindowManagerImpl并没有直接去实现一个Window的三大操作, 而是全部交给了WindowManagerGlobal来处理, WindowManagerGlobal是一个工厂的性质提供自己的实现,
在WindowManagerGlobal中有一段如下的代码:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

WindowManagerImpl这种工作模式就是典型的桥接模式, 将所有的操作全部委托给WindowManagerGlobal去实现, WindowManagerGlobal的addView方法主要分如下几步:
  • 1.检查参数是否合法, 如果是子Window还需要调整一下参数
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有如下几个列表是比较重要的
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> ();

在上面的声明中, mViews存储所有window所对应的View, mRoots存储是所有window所对应的ViewRootImpl, mParams存储是所对应的布局参数
, 而mDyingViews则存储那些正在被删除的对象, 在addView中通过如下方式将Window的一系列对象添加到列表中
root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams);

  • 3.通过ViewRootImpl来更新界面并完成Window的添加
这个步骤由ViewRootImpl的setView完成, 他内部会通过requstLayout来万和城呢过异步刷新请求, 在下面代码中, scheduleTraversals实际上就是View绘制的入口
@ Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }

接下来会通过WindowSession最终来完成Window的添加过程, 在下面的代码中, WindowSession的类型IWindowSession, 他是一个Binder对象, 也是一次真正意义上的IPC操作
代码缺失
在Session内部会通过WindowManagerService来实现Window的添加, 代码如下:
代码缺失
如此一来,Window的添加请求就交给WindowManagerService去处理了, 在WindowManagerService内部为每一个应用添加了一个单独的Session,具体WIndow在WindowManagerService中怎么添加的, 我们就不去分析了, 读者可以自己去熟悉下
2.Window的删除过程
Window的删除过程和添加过程一样, 都是通过Impl再通过Global来实现了, 下载是Global的removeView
代码
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); } }

removeView的代码很清晰, 首先通过findViewLocked来查找待删除的索引, 这个超找过程就是建立的数组遍历, 然后调用removeViewLocked来进一步的删除
private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); if (view != null) { InputMethodManager imm = InputMethodManager.getInstance(); if (imm != null) { imm.windowDismissed(mViews.get(index).getWindowToken()); } } boolean deferred = root.die(immediate); if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view); } } }

removeViewLocked是通过ViewRootImpl来完成删除操作的, 在windowmanager中提供了两种接口removeView和removeViewImmediate, 他们分别表示异步删除和同步删除, 其中removeViewImmediate, 使用起来要格外注意, 一般来说不需要使用此方法来删除window以免发生意外的错误, 这里主要是异步删除的问题, 具体的删除操作是ViewImple的die方法来完成的, 在异步删除的情况下, die只是发生一个删除的请求后就返回了, 这个时候View并没有完成删除的操作, 所有最后会将其添加到mDyingViews中, mDyingViews表示待删除的View列表, ViewRootImpe的die方法如下:
boolean 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(); return false; }if (!mIsDrawing) { destroyHardwareRenderer(); } else { Log.e(TAG, " Attempting to destroy the window while drawing!\\n" + " window= " + this + " , title= " + mWindowAttributes.getTitle()); } mHandler.sendEmptyMessage(MSG_DIE); return true; }

在die方法内部只是做了简单的判断, 那么就发送了一个MSG_DIE的消息, ViewRootImpl中的mHandler会处理此消息并且并调用doDie方法, 如果是同步删除就会直接调用doDie方法, 在doDie方法内部会操作dispatchDetachedFromWindow, 真正删除window就是在这里面实现的, 他主要做了四件事
  • 1.垃圾回收相关的工作, 比如清除数据和消息, 移除回调
  • 2.通过Session的remove方法来删除window,这同样是一个IPC的过程, 最终会调用wms的removeWindow方法
  • 3.调用view的dispatchDetachedFromWindow
    方法, 在内不会调用onDetachedFromWindow,他做了一些回收资源或者停止动画的一些操作
  • 4.调用WindowManagerGlobal的doRemoveView方法刷新数据
3.Window的更新过程
到这里, window的删除就接收完了, 在说下更新, 需要看WindowManagerGlobal的updateViewLayout方法
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); } }

updateViewLayout做的方法比较简单, 首先他更新View的LayoutParams替换老的, 接着再更新下ViewRootimpl中的LayoutParams, 这一步是通过viewrootimpl的setLayoutParams来做的, 在ViewRootImpl中会通过scheduleTraversals方法来对View, 测量, 布局, 重绘等等, 除了view本身的重绘之外, ViewRootImpl还会通过WindowSession来更新Window的视图, 这个过程最终是WindowManagerService的relayoutWindow来实现的, 具体也是一个IPC的过程
三.Window的创建过程 通过上面的分析, 我们知道, view是Android中视图的呈现方式, 但是view不能单独存在, 他必须依附在window这个抽象类中, 因此有视图的地方就有window, activity, toast都是, 我们继续来分析window的创建过程
1.Activity的Window创建过程
要分析Activity的Window创建过程就需要去了解activity的启动过程, 详细的会在后面说, 这里简单概括, activity的启动过程很复杂, 最终会由ActivityThread中的perfromLaunchActivity()来完成整个启动过程
if (activity != null) { Context appContext = createBaseContextForActivity(r, activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (DEBUG_CONFIGURATION) Slog.v(TAG, " Launching activity " + r.activityInfo.name + " with config " + config); activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.voiceInteractor);

在Activity的attach方法中, 系统会创建activity所属的window对象并为其设置回调接口, window对象的创建过程是由PolicyManager的akeNewWindow方法实现的, 由于activity实现了window的callback方法接口, 因此当window接受到外界的状态改变的时候就会去调用activity的方法, callback接口中的方法很多, 但是有几个确实我们非常熟悉的, 你如onAttachedToWindow等
代码缺失
从上面的分析来看, activity会通过window是通过PolicyManager的一个工厂方法来创建的, 但是从PolicyManager的类名来看, 他不是一个普通的类, 他是一个chelv类, PolicyManager中实现的几个工厂方法全部在接口IPolicy中声明, 我们来看下这个接口:
public interface IPolicy { public Window makeNewWindow(Context context); public LayoutInflater makeNewLayoutInflater(Context context); public WindowManagerPolicy makeNewWindowManager(); public FallbackEventHandler makeNewFallbackEventHandler(Context context); }

在实际的调用中, PolicyManager是如何关联到Policy类中的mackNewWindow方法来实现如下, 由此可以发现, window的具体实现的确就是phoneWindow
public static Window makeNewWindow(Context context) { return new PhoneWindow(context); }

关于PolicyManager是如何关联到IPolicy中, 这个无法从源码中调用关系得到, 这里的猜测可能是编译环节动态控制的, 到这里window已经创建完成了, 下面分析activity的视图是怎么依附在window上的由于activity的视图是由setContentView开始的, 所有我们先看下这个方法:
public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }

这里可大致的看出,
  • 1.如果没有DecorView就去创建他
DecorView是一个FrameLayout,在之前就已经说过了, 这里说一下。DecorView是activity中的顶级View,一般来说他的内部包含标题栏和内部栏, 但是这个会随着主题的变化而发生改变的, 不管怎么样, 内容是一定要存在的, 并且内容有固定的id, 那就是content,完整的就是android.R.id.content, DecorView的创建是由installDecor方法来完成的, 在方法内部会通过generateDecor方法来完成创建DecorView,这个时候他就是一个空白的FrameLayout
protected DecorView generateDecor(){ return new DecorView(getContext(),-1); }

为了初始化DecorView的结构, PhoneWindow还需要通过generateLayout方法来加载具体的布局文件到DecorView中, 这个跟主题有关:
View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

其中ID_ANDROID_CONTENT的定义如下, 这个id对应的就是ViewGroup的mContentParent
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

  • 2.将View添加到DecorView的mContentParent中
这个过程比较简单, 由于在第一步的时候已经初始化了DecorView, 因此这一部就直接将activity的视图添加到DecorView的mContentParent中既可, mLayoutInflater.inflate(layoutResID, mContentParent); 到此为止, 由此可以理解activity的setcontentview的来历了, 也许有读者会怀疑, 为什么不叫setview来, 他明明是给activity设置视图啊, 从这里来看, 他的确不适合叫做setview,因为activity的布局文件只是添加到了DecorView的mContentParent中, 因此交setContentView更加准确。
  • 3.回调Activity的onCreateChanged方法来通知Activity视图已经发生改变
这个过程很简单, 由于window实现了Callback接口, 这里表示布局已经被添加到DecorView的mContentParent中了, 于是通知activity。使其可以做相应的处理Activity的onCreateChanged是一个空实现, 我们可以在子activity处理这个回调
final callback cb = getCallback(); if(cb != null & & !isDestroyed()){ cb.onContentChanged(); }

经过了上面的三个步骤, 到这里为止DecorView已经被创建并且初始化完毕了activity的布局文件也已经添加到了DecorView的内容中, 但是这个时候DecorView还没有被windowmanager添加到window中, 这里需要正确的理解window的概念, window更多的是表示一种抽象的功能集合, 虽然说早在activity的attch中window就已经被创建了, 但是这个时候由于DecorView还没有被windowmanager识别, 所有还不能提供具体的功能, 因为他还无法接收外界的输入, 在activityThread的makeVisible中, 才能被视图看到:
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }

到这里, window的创建过程就已经分析完了
2.Dialog的Window创建过程
有如下的几个步骤
  • 1.创建Window
dialog的window创建过程和activity的类似, 我们来看下
Dialog(Context context, int theme, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (theme = = 0) { TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme, outValue, true); theme = outValue.resourceId; } mContext = new ContextThemeWrapper(context, theme); } else { mContext = context; }mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); Window w = PolicyManager.makeNewWindow(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
这个过程也和activity的类似, 都是通过window去指定加载的布局文件
public void setContentView(int layoutResID) { mWindow.setContentView(layoutResID); }

  • 3.将DecorView添加到window并且显示
在Dialog的show方法中, 会通过windowmanager将DecorView添加到window中
mWindowManager.addView(mDecor, l); mShowing = true;

从上述的三个步骤可以发现, dialog的创建过程和activity很类似
普通的dialog有一个特殊的地方, 那就是必须用activity的context, 否则会报错
Dialog dialog = new Dialog(this); dialog.setTitle(" Hello" ); dialog.show();

Android开发艺术探索——第八章(理解Window和WindowManager)

文章图片

上面的信息非常的明确, 是没有应用token导致的, 而应用token一般只有activity持有, 所有这里需要activity的context,另外系统比较特殊, 他可以不需要token
3.Toast的Window创建过程
Toast和dialog不同, 他的工作过稍微复杂一点, 首先Toast也是基于Window来实现的, 但是由于Toast具有定时取消的功能, 所以系统采用了handler, 在toast内部有两类IPC的过程, 第一类Toast访问NotificationManagerService, 第二类是NotificationManagerService回调toast的TN接口
Toast具有系统的window,他内部试下的师徒有两种方式制定, 一个是系统设定, 还可以setview指定
/** * 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 } }

从上面的代码可以看出, 显示和隐藏是通过NMS来实现的, 由于NMS运行在系统, 所以只能通过远程调用, 这里就跨进程实现了IPC, 在这个时候NMS是运行在binder线程池中, 所以需要handler切换到主线程中, 所以这就意味着Toast无法再没有Lopper的线程中弹出
我们首先来看下Toast的显示过程
INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { // Empty }

NMS的enqueueToast方法的第一个参数就是当前应用的包名, 第二个参数表示远程回调, 第三个是时长, enqueueToast首先将Toast请求封装为一个ToastRecord对象将其添加到一个队列中, 其实这本书就是一个arraylist, 对于非系统应用来说, 他只能存50个, 这样是为了防止dos,如果不这样做, 试想一下, 其他应用还能弹出东西来吗?
代码丢失
正常情况下, 一个应用不可能达到上限, 当ToastRecord被添加到mToastQueue中后, NMS就会通过showNextToastLocked方法来显示Toast, 下面的代码很好理解, 需要注意的是, Toast的显示是由ToastRecord的callback来完成的, 这个callback实际上就是TN对象的远程Binder, 通过callback来访问TN中的方法是需要跨进程来完成的, 最终被调用的对象是TN中方法发起在Binder线程池中。
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 die int index = mToastQueue.indexOf(record); if (index > = 0) { mToastQueue.remove(index); } keepProcessAliveIfNeededLocked(record.pid); if (mToastQueue.size() > 0) { record = mToastQueue.get(0); } else { record = null; } } } }

Toast显示出来之后, NMS会通过scheduleTimeoutLocked来发送一个延时消息
private void scheduleTimeoutLocked(ToastRecord r) { mHandler.removeCallbacksAndMessages(r); Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); long delay = r.duration = = Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; mHandler.sendMessageDelayed(m, delay); }

上面的代码, LONG_DELAY是3.5s, SHORT_DELAY是2s,NMS会通过cancelToastLocked来隐藏toast并且清楚队列
ToastRecord record = mToastQueue.get(index); try { record.callback.hide(); } catch (RemoteException e) { Slog.w(TAG, " Object died trying to hide notification " + record.callback + " in package " + record.pkg); // don' t worry about this, we' re about to remove it from // the list anyway }

通过上面的分析, 大家知道toast的显示隐藏实际上是toast的TN这个类来实现的, 分别对应的show/hide, 由于这两个方法都是NMS以跨进程的方式调用的, 因此他运行在Binder池中, 为了切换, 在他们内部使用了handler
/** * schedule handleShow into the right thread */ @ Override public void show(IBinder windowToken) { if (localLOGV) Log.v(TAG, " SHOW: " + this); mHandler.obtainMessage(0, windowToken).sendToTarget(); }/** * schedule handleHide into the right thread */ @ Override public void hide() { if (localLOGV) Log.v(TAG, " HIDE: " + this); mHandler.post(mHide); }

上述的代码, mShow和mHide是两个Runnable, 他们的内部实现分别调用了具体方法, 由此可见, handlershow才是真正的方法,
public void handleShow(IBinder windowToken) { if (localLOGV) Log.v(TAG, " HANDLE SHOW: " + this + " mView= " + mView + " mNextView= " + mNextView); if (mView != mNextView) { // remove the old view if necessary handleHide(); 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 direction final 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); mWM.addView(mView, mParams); trySendAccessibilityEvent(); } }

而移除
public void handleHide() { if (localLOGV) Log.v(TAG, " HANDLE HIDE: " + this + " mView= " + mView); if (mView != null) { // note: checking parent() just to make sure the view has // been added...i have seen cases where we get here when // the view isn' t yet added, so let' s try not to crash. if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, " REMOVE! " + mView + " in " + this); mWM.removeViewImmediate(mView); }mView = null; } } }

到这里, 我们的window就全部分析完成了, 相信大家对window有了一些新的见解了
好的, 本章没有什么例子, 代码全在上面
来我的群玩玩吧
  • 1.通往Android的神奇之旅 555974449 (快满了)
  • 2.Android旅行的路途 484167109
  • 3.Android进阶深度学习群 515171658(此群精品付费)
也可以关注我的微信公众号
【Android开发艺术探索——第八章(理解Window和WindowManager)】
Android开发艺术探索——第八章(理解Window和WindowManager)

文章图片


    推荐阅读