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();

上面的信息非常的明确,是没有应用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(此群精品付费)

也可以关注我的微信公众号

时间: 2024-10-26 14:25:15

Android开发艺术探索——第八章:理解Window和WindowManager的相关文章

Android开发艺术探索——第一章:Activity的生命周期和启动模式

Android开发艺术探索--第一章:Activity的生命周期和启动模式 怀着无比崇敬的心情翻开了这本书,路漫漫其修远兮,程序人生,为自己加油! 一.序 作为这本书的第一章,主席还是把Activity搬上来了,也确实,和Activity打交道的次数基本上是最多的,而且他的内容和知识点也是很多的,非常值得我们优先把他掌握,Activity中文翻译过来就是"活动"的意思,但是主席觉得这样翻译有些生硬,直接翻译成"界面"可能更好,的确,Activity主要也是用于UI效

Android开发艺术探索——第七章:Android动画深入分析

Android开发艺术探索--第七章:Android动画深入分析 Android的动画可以分成三种,view动画,帧动画,还有属性动画,其实帧动画也是属于view动画的一种,,只不过他和传统的平移之类的动画不太一样的是表现形式上有点不一样,view动画是通过对场景的不断图像交换而产生的动画效果,而帧动画就是播放一大段图片,很显然,图片多了会OOM,属性动画通过动态的改变对象的属性达到动画效果,也是api11的新特性,在低版本无法使用属性动画,但是我们依旧有一些兼容库,OK,我们还是继续来看下详细

Android开发艺术探索——第二章:IPC机制(上)

Android开发艺术探索--第二章:IPC机制(上) 本章主要讲解Android的IPC机制,首先介绍Android中的多进程概念以及多进程开发模式中常见的注意事项,接着介绍Android中的序列化机制和Binder,然后详细的介绍Bundle,文件共享,AIDL,Messenger,ContentProvider和Socker等进程间通讯的方法,为了更好的使用AIDL进行进程间通讯,本章引入了Binder连接池的概念,最后,本章讲解各种进程间通信方式的优缺点和使用场景,通过本章,可以让读者对

【读书笔记】【Android 开发艺术探索】第4章 View 的工作原理

一.基础知识 1.ViewRoot 和 DecorView ViewRoot 对应 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的三大流程都是通过 ViewRoot 来完成的.在ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRoot 对象. DecorView 添加到窗口 Window 的过程. 图片来自https://yq.ali

Android开发艺术探索读书笔记——进程间通信

1. 多进程使用场景 1) 应用某些模块因为特殊需求需要运行在单独进程中.如消息推送,使消息推送进程与应用进程能单独存活,消息推送进程不会因为应用程序进程crash而受影响. 2) 为加大一个应用可使用的内存,需要多进程来获取多份内存空间. 2. 如何开启多进程 给四大组件(Activity.Service.Receiver.ContentProvider)在AndroidMainfest中指定android:process属性指定. 如果进程以":"开头的进程,代表应用的私有进程,其

Android开发艺术探索——第二章:IPC机制(中)

Android开发艺术探索--第二章:IPC机制(中) 好的,我们继续来了解IPC机制,在上篇我们可能就是把理论的知识写完了,然后现在基本上是可以实战了. 一.Android中的IPC方式 本节我们开始详细的分析各中跨进程的方式,具体方式有很多,比如可以通过在Intent中附加extras来传递消息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外,ContentProvider天生就是支持扩进程访问的,所以通过Socket也可以实现IPC,上述的各种方法都能实现I

Android 开发艺术探索——第十章 Android的消息机制

Android 开发艺术探索--第十章 Android的消息机制读书笔记 Handler并不是专门用于更新UI的,只是常被用来更新UI 概述 Android的消息机制主要值得就是Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑. MessageQueue即为消息队列,顾名思义,它的内部存储了一组消息,以队列的的形式对外提供插入和删除的工作.虽然叫队列,但内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表. Looper意思为循

Android开发艺术探索

Android开发艺术探索1 Activity的生命周期和启动模式 典型情况下生命周期异常情况下生命周期 启动模式标准模式栈顶复用模式栈内复用模式单实例模式 Activity的FlagsIntentFilter的匹配规则 2 IPC机制 进程间通信 SerializanleParcelableBinder 3 View的事件体系 事件分发机制 事件分发机制点击事件就是MotionEvent 事件分发其实就是对MotionEvent事件的分发三大方法dispatchTouchEvent 分发onI

Android开发艺术探索(研读笔记)——03-Android中的IPC机制(一)

Android开发艺术探索(研读笔记) 作者:Dimon 微博:@Dimon-喰 GitHub:@Dimon94 LOFTER:@Dimon. 03-Android中的IPC机制(一) 1.Android IPC 简介 IPC(Inter-Process-Communication):含义为进程间通信,指两个进程之间进行数据交换的过程. 什么是进程:一般指一个执行单元,在PC和移动设备上的一个程序或者一个应用. 什么是线程:线程是CPU调度的最小单元,是一种有限的系统资源. 而一个进程可以包含多