WindowManager的分析

一、Window和WindowManager

Window:表示一个窗口,从下面Window的源码中可以看出它有且只有一个实现类PhoneWindow。


    The only existing implementation of this abstract class is
     * android.policy.PhoneWindow, which you should instantiate when needing a
     * Window. 

WindowManager:它是系统提供我们操作Window的一个接口,我们可以通过WindowManage提供的方法对window进行添加、修改、删除等操作。一般有View的地方都有window。

二、Window在事件分发中的应用

当一个点击事件产生时,它首先传递到Activity、在Activity的dispatchToucheEvent的方法中进行分发、然后传递到Window、最后才到顶级view。看源码:


    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

由于window只有一个实现类,所以查看PhoneWindow的源码。


    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

而这里的mDecor就是顶级View,就这样事件就从window传递到了顶级View了。

三、创建Window的快速入门

运行截图

由于Toast的源码也是通过添加window实现的,只不过使用了handler而已,因此这里我们参照Toast来简单实现

xml文件


        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity" >

        <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="创建Window" />

    </LinearLayout>

java代码


        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = (Button) findViewById(R.id.btn);
        button = new Button(this);
        button.setText("我是Window");
        button.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Toast.makeText(getApplicationContext(), "我是被Window按钮点击的", 0).show();
            }
        }) ;
        btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                mWM = (WindowManager)getSystemService(Context.WINDOW_SERVICE);

                 mParams = new WindowManager.LayoutParams();

                // XXX This should be changed to use a Dialog, with a Theme.Toast
                // defined that sets up the layout params appropriately.
                final WindowManager.LayoutParams params = mParams;
                params.height = WindowManager.LayoutParams.WRAP_CONTENT;
                params.width = WindowManager.LayoutParams.WRAP_CONTENT;
                params.flags =
                        WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
//                      | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                        |  WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ;
                params.format = PixelFormat.TRANSLUCENT;
                //设置这个flag可以将被添加按钮有点击事件
                params.type = WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING;
                params.setTitle("Toast");
                mWM.addView(button, mParams) ;
            }
        });

简单说明下:上面的代码大部分是我从Toast的源码中赋值下来的,不过为了让显示的Toast有点击效果,做了小小的修改,上面已经做了注释。

四、Toast创建的源码分析

首先查看Toast的makeText()方法


    public static Toast makeText(Context context, CharSequence text, int duration) {
        Toast result = new Toast(context);

        LayoutInflater inflate = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);

        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }

从源码中看,并没有做什么特别的操作,只是创建了Toast变量、获取了TextView并将赋值给了Toast变量。

接着,我们看Toast显示的重要代码show()方法:


     public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

虽然看上去代码很少,但这却是实现了Toast的显示,首先,通过getService()获取一个INotificationManager的aidl对象,而实现它的Binder是NotificationManagerService,这一点我们可以从NotificationManagerService的源码中可以看出


    /** {@hide} */
    public class NotificationManagerService extends INotificationManager.Stub

接着,通过上下文获取应用的包名,然后就最重要的一个类TN了,看源码:


        private static class TN extends ITransientNotification.Stub {
        final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                handleShow();
            }
        };

        final Runnable mHide = new Runnable() {
            @Override
            public void run() {
                handleHide();
                // Don‘t do this in handleHide() because it is also invoked by handleShow()
                mNextView = null;
            }
        };

        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        final Handler mHandler = new Handler();    

        int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
        int mX, mY;
        float mHorizontalMargin;
        float mVerticalMargin;

        View mView;
        View mNextView;

        WindowManager mWM;

        TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
        }

        /**
         * schedule handleShow into the right thread
         */
        @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }

        /**
         * schedule handleHide into the right thread
         */
        @Override
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.post(mHide);
        }

        public void handleShow() {
            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();
                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;
                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();
            }
        }

        private void trySendAccessibilityEvent() {
            AccessibilityManager accessibilityManager =
                    AccessibilityManager.getInstance(mView.getContext());
            if (!accessibilityManager.isEnabled()) {
                return;
            }
            // treat toasts as notifications since they are used to
            // announce a transient piece of information to the user
            AccessibilityEvent event = AccessibilityEvent.obtain(
                    AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
            event.setClassName(getClass().getName());
            event.setPackageName(mView.getContext().getPackageName());
            mView.dispatchPopulateAccessibilityEvent(event);
            accessibilityManager.sendAccessibilityEvent(event);
        }        

        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.removeView(mView);
                }

                mView = null;
            }
        }
    }

这个类的结构还是比较清晰的,首先是两个runnable对象,分别用来做显示和隐藏,接着就是TN的构造函数创建mPamas,接着是show、和hide两个方法通过handler来post上面的两个runanable,接着又是两个方法具体实现显示和隐藏。这里需要主要的是TN它是继承ITransientNotification.Stub的,所以它也是一个Binder。

再回到上面的show()方法,通过调用NotificationManagerService的enqueueToast()方法,将包名,TN,duration传递过去。


    record = new ToastRecord(callingPid, pkg, callback, duration);
    mToastQueue.add(record);

在enqueueToast()方法中,将我们传递过来的参数分装成一个ToastRecord,然后将其添加到mToastQueue中。mToastQueue是一个ArrayList。注意:这里的callback就是我们之前传递过来的TN。

接着:


     if (index == 0) {
        showNextToastLocked();
    }

    private void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
                record.callback.show();
                scheduleTimeoutLocked(record, false);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Object died trying to show notification " + record.callback
                        + " in package " + record.pkg);
                // remove it from the list and let the process die
                int index = mToastQueue.indexOf(record);
                if (index >= 0) {
                    mToastQueue.remove(index);
                }
                keepProcessAliveLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
                    record = null;
                }
            }
        }
    }

这个方法首先取出ArrayList中的record,然后调用record中的callback的show()方法,我们知道这里的callback就是TN,也就是说这里调用的是TN的show()方法。即如下:


    /**
     * schedule handleShow into the right thread
     */
    @Override
    public void show() {
        if (localLOGV) Log.v(TAG, "SHOW: " + this);
        mHandler.post(mShow);
    }


     final Runnable mShow = new Runnable() {
        @Override
        public void run() {
            handleShow();
        }
    };

    public void handleShow() {
            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();
                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;
                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();
            }
        }

上面显示的核心代码其实就是获取WindowManager,然就是通过wm调用addView()方法进行添加,这里就完成了添加工作,关于WindowManager的addView()实现原理将会在下面分析。再回到 record.callback.show();下面就是调用scheduleTimeoutLocked()方法:


     private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
        {
            Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
            long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
            mHandler.removeCallbacksAndMessages(r);
            mHandler.sendMessageDelayed(m, delay);
        }

这里比较简单,无非就是timeout了,移除record。这样,关于Toast的show方法就分析完了。

五、addView()的底层实现原理

再谈之前,我们再看看windowmanager的结构,它继承ViewManager


    public interface WindowManager extends ViewManager {

再看看ViewPager:


    public interface ViewManager
    {
        /**
         * Assign the passed LayoutParams to the passed View and add the view to the window.
         * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
         * errors, such as adding a second view to a window without removing the first view.
         * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
         * secondary {@link Display} and the specified display can‘t be found
         * (see {@link android.app.Presentation}).
         * @param view The view to be added to this window.
         * @param params The LayoutParams to assign to view.
         */
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }

可以看出,ViewPager只提供了三个方法,增加、修改、删除。而这也是windowManager主要功能。

从上面WindowManager的结构可以看出它是一个接口,主要是通过它的实现类完成的:


    public final class WindowManagerImpl implements WindowManager {

查看addView()方法



    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }


     private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

在WindowManagerGlobal中比较几个重要的代码如下


      ...
      root = new ViewRootImpl(view.getContext(), display);
      ...
      mViews[index] = view;
      mRoots[index] = root;
      mParams[index] = wparams;
      ...
       // do this last because it fires off messages to start doing things
    try {
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        synchronized (mLock) {
            final int index = findViewLocked(view, false);
            if (index >= 0) {
                removeViewLocked(index, true);
            }
        }
        throw e;
    }

从源码可以看出,最后调用ViewRootImp的setView()方法来实现view的添加。


    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
    getHostVisibility(), mDisplay.getDisplayId(),
    mAttachInfo.mContentInsets, mInputChannel);

    private final IWindowSession mWindowSession;

   final class Session extends IWindowSession.Stub

session的addToDisplay方法如下:


    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets,
            InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outInputChannel);
    }

这个mService又是WindowManagerservice,就这样window的添加就交给了WindowManagerservice,由于篇幅,关于windowmanagerservice如何添加的就不详细分析了。到这里,而且已经知道了添加大致流程了

OK,这篇就到这了。。。

源码下载

时间: 2024-10-11 16:35:04

WindowManager的分析的相关文章

Android应用Activity、Dialog、PopWindow窗口显示机制及源码分析

[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重劳动成果] 1 背景 之所以写这一篇博客的原因是因为之前有写过一篇<Android应用setContentView与LayoutInflater加载解析机制源码分析>,然后有人在文章下面评论和微博私信中问我关于Android应用Dialog.PopWindow.Toast加载显示机制是咋回事,所以我就写一篇文章来分析分析吧(本文以Android5.1.1 (API 22)源码为基础分析),以便大家在应

Android应用Activity、Dialog、PopWindow、Toast窗体加入机制及源代码分析

[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处.尊重劳动成果] 1 背景 之所以写这一篇博客的原因是由于之前有写过一篇<Android应用setContentView与LayoutInflater载入解析机制源代码分析>.然后有人在文章以下评论和微博私信中问我关于Android应用Activity.Dialog.PopWindow载入显示机制是咋回事,所以我就写一篇文章来分析分析吧(本文以Android5.1.1 (API 22)源代码为基础分析),以

Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析

1  背景 之所以写这一篇博客的原因是因为之前有写过一篇<Android应用setContentView与LayoutInflater加载解析机制源码分析>, 然后有人在文章下面评论和微博私信中问我关于Android应用Activity.Dialog.PopWindow加载显示机制是咋回事,所以我就写一 篇文章来分析分析吧(本文以Android5.1.1 (API 22)源码为基础分析),以便大家在应用层开发时不再迷糊. PS一句:不仅有人微博私信我这个问题,还有人问博客插图这些是用啥画的,这

浅析 Android 的窗口

一.窗口的概念 在开发过程中,我们经常会遇到,各种跟窗口相关的类,或者方法.但是,在 Android 的框架设计中,到底什么是窗口?窗口跟 Android Framework 中的 Window 类又是什么关系?以手机QQ 的主界面为例,如下图所示,上面的状态栏是一个窗口,手机QQ 的主界面自然是一个窗口,而弹出的 PopupWindow 也是一个窗口,我们经常使用的 Toast 也是一个窗口.像 Dialog,ContextMenu,以及 OptionMenu 等等这些都是窗口.这些窗口跟 W

浅析Android的窗口

一.窗口的概念 在开发过程中,我们经常会遇到,各种跟窗口相关的类,或者方法.但是,在 Android 的框架设计中,到底什么是窗口?窗口跟 Android Framework 中的 Window 类又是什么关系?以手Q 的主界面为例,如下图所示,上面的状态栏是一个窗口,手Q 的主界面自然是一个窗口,而弹出的 PopupWindow 也是一个窗口,我们经常使用的 Toast 也是一个窗口.像 Dialog,ContextMenu,以及 OptionMenu 等等这些都是窗口.这些窗口跟 Windo

Monkey源码分析番外篇之WindowManager注入事件如何跳出进程间安全限制

在分析monkey源码的时候有些背景知识没有搞清楚,比如在看到monkey是使用windowmanager的injectKeyEvent方法注入事件的时候,心里就打了个疙瘩,这种方式不是只能在当前应用中注入事件吗?Google了下发现了国外一个大牛有留下蛛丝马迹描述这个问题,特意摘录下来并做相应部分的翻译,其他部分大家喜欢就看下,我就不翻译了. How it works Behind the scenes, Monkey uses several private interfaces to co

android WindowManager解析与骗取QQ密码案例分析

最近在网上看见一个人在乌云上提了一个漏洞,应用可以开启一个后台Service,检测当前顶部应用,如果为QQ或相关应用,就弹出一个自定义window用来诱骗用户输入账号密码,挺感兴趣的,总结相关知识写了一个demo,界面如下(界面粗糙,应该没人会上当吧,意思到了就行哈=, =): demo地址:https://github.com/zhaozepeng/GrabQQPWD Window&&WindowManager介绍 分析demo之前,先要整理总结一下相关的知识.先看看Window类,Wi

Monkey源代码分析番外篇WindowManager如何出的喷射事件的进程间的安全限制

在分析monkey源代码时的一些背景知识不明确,例如看到monkey它是用windowmanager的injectKeyEvent的喷射事件时的方法.我发现自己陷入疙瘩,这种方法不仅能够在当前的应用程序,注入的事件它?Google在国外找到下一个大牛离开的问题的叙述性说明痕迹,特意摘录下来并做对应部分的翻译,其它部分大家喜欢就看下.我就不翻译了. How it works Behind the scenes, Monkey uses several private interfaces to c

【转】UIAutomator源码分析之启动和运行

我们可以看到UiAutomator其实就是使用了UiAutomation这个新框架,通过调用AccessibilitService APIs来获取窗口界面控件信息已经注入用户行为事件,那么今天开始我们就一起去看下UiAutomator是怎么运作的. 我们在编写了测试用例之后,我们需要通过以下几个步骤把测试脚本build起来并放到测试机器上面: android create uitest-project -n AutoRunner.jar -t 5 -p D:\\Projects\UiAutoma