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.
         * 

Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. *

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,这篇就到这了。。。

源码下载

你可能感兴趣的:(Android进阶)