android Window和WindowManager

          前言:Window表示一个窗口的概念,在日常的开发过程中我们很少用到它,但是在某些特定的场景下我们会使用到。查看源码我们知道Window是一个抽象类,而它的实现类是PhoneWindow。而PhoneWindow又是在哪里实例化的呢?不明白的同学可以去我这篇文章看看https://blog.csdn.net/qq_27970997/article/details/83658203。那么Window到底是如何工作的呢?这里面就涉及到另外的两个类:WindowManager和WindowManagerService。那么它们之间的关系又是什么样子的呢?

WindowManager是外界访问Window的入口,WindowManagerService是Window的具体实现,WindowManager和WindowManagerService的交互是一个IPC的过程

 

先来看一段代码,这是ActivityThread$handleResumeActivity()方法里面的一段代码

 // 这是ActivityThread$handleResumeActivity()方法里面的一段代码
 // 我们一行一行读
 if (r.window == null && !a.mFinished && willBeVisible) {
    // r代表的是ActivityClientRecord 
    // ActivityClientRecord 用来保存Activity的信息
    // 获取ActivityClientRecord的Window
    r.window = r.activity.getWindow();
    // 获取ActivityClientRecord的DecorView
    View decor = r.window.getDecorView();
    // 设置DecorView为不可见
    decor.setVisibility(View.INVISIBLE);
    // 拿到当前Activity的WindowManager(WindowManager实现了ViewManager接口)
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams l = r.window.getAttributes();
    // 设置当前Activity的DecorView
    a.mDecor = decor;
    l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
    l.softInputMode |= forwardBit;
    if (r.mPreserveWindow) {
        a.mWindowAdded = true;
        r.mPreserveWindow = false;
        ViewRootImpl impl = decor.getViewRootImpl();
        if (impl != null) {
            impl.notifyChildRebuilt();
        }
    }
    
    if (a.mVisibleFromClient) {
        if (!a.mWindowAdded) {
            a.mWindowAdded = true;
	    // 把DecorView添加到Window(通过WindowManager进行添加)中
            wm.addView(decor, l);
        } else {
            a.onWindowAttributesChanged(l);
        }
    }
}

重点看13,14和32行,这里我就不啰嗦了,看我的注释应该就能看懂了。这个代码很好的解释了Window的外界访问入口WindowManager是如何添加控件的。

当然,可以给Window设置Flags属性:举几个常见的属性

1》FLAG_NOT_FOCUSABLE

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

2》FLAG_NOT_TOUCH_MODAL

      在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。一般来说此标记是开启状态,否则其他Window将无法收到单击事件

3》FLAG_SHOW_WHEN_LOCKED

      开启此模式可以让Window显示在锁屏的界面上

 

 

WindowManager

       因为WindowManager是实现了ViewManager接口,所以我们先来看看ViewManager接口里面有些什么东西

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

ViewManager接口提供了3个方法,一个添加视图,一个更新视图,一个移除视图,由此可知,WindowManager也具有这3个方法。需要注意的是,我们操作的对象是View。由此看来,WindowManager操作Window的过程其实是操作Window里面的View的过程。

 

Window

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

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

   在WindowManagerImpl类中我们找到对应的addView(),updateViewLayout(),removeView()方法,但是我们发现WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal来处理,那么我们接着往下走,看看WindowManagerGlobal里面是怎么进行处理的(以addView()为例)

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 如果添加进来的View为空,则直接抛出异常 	
        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");
        }

	// 如果Window不为空,调整一些布局参数
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

	// ViewRootImpl:实现View的绘制
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            
	    // 省略部分代码  
            // 实例化ViewRootImpl 
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            // mViews存储的是所有Window所对应的View
            mViews.add(view);
	    // mRoots存储的是所有Window所对应的ViewRootImpl
            mRoots.add(root);
	    // mParams存储的是所有Window所对应的布局参数
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
	        // 开始进行View的绘制
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                // 省略代码
                requestLayout();
                // 省略代码
            }
    }
}

我们知道 requestLayout();其实就是刷新整个页面布局的方法,那么我们来看看 requestLayout();方法里面做了些什么

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
   void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

第一步:checkThread()即检查线程。有没有发现这个错误很熟悉,在开发过程中我们都知道不能在子线程中更新UI,现在知道

为什么了吧!

 

第二步:执行scheduleTraversals()方法

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

我们关注最后一行

void pokeDrawLockIfNeeded() {
        final int displayState = mAttachInfo.mDisplayState;
        if (mView != null && mAdded && mTraversalScheduled
                && (displayState == Display.STATE_DOZE
                        || displayState == Display.STATE_DOZE_SUSPEND)) {
            try {
                mWindowSession.pokeDrawLock(mWindow);
            } catch (RemoteException ex) {
                // System server died, oh well.
            }
        }
    }

最终会执行 mWindowSession.pokeDrawLock(mWindow),其中mWindowSession是IWindowSession,它是一个Binder对象,真正的实现类是Session,具体的IPC的调用过程有兴趣的同学可以自己去研究研究。最终会执行到WindowManagerService里面的pokeDrawLock去执行

    @Override
    public void pokeDrawLock(IBinder window) {
        final long identity = Binder.clearCallingIdentity();
        try {
            mService.pokeDrawLock(this, window);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }
  public void pokeDrawLock(Session session, IBinder token) {
        synchronized (mWindowMap) {
            WindowState window = windowForClientLocked(session, token, false);
            if (window != null) {
                window.pokeDrawLockLw(mDrawLockTimeoutMillis);
            }
        }
    }

那么Activity是如何创建Window的呢,这就涉及到Activity的启动流程了,具体的我就不再细讲了,不明白的同学可以去我的另外一篇博客去看看,里面有详解https://blog.csdn.net/qq_27970997/article/details/83658203

这里我稍微总结一下:

1.Activity的启动过程很复杂,但是最终会执行到handleLaunchActivity()方法,这个方法里面首先是执行

performLaunchActivity()方法,在performLaunchActivity()方法里面首先通过反射生成Activity的实例

,然后调用生命周期里面的attach()方法,create(),start()方法

2.在attach()方法里面会做以下几个事情:

   1》实例化PhoneWindow

   2》获取WindowManager并进行设置

3.在onCreate()方法里面调用setContentView()方法,实例化DecorView并将我们设置的布局文件添加到ID为com.android.internal.R.id.content的容器中

4.回到handleLaunchActivity()方法,执行handleResumeActivity()方法,里面有这几行代码

 if (!a.mWindowAdded) {
      a.mWindowAdded = true;
      wm.addView(decor, l);
} 

DecorView被添加到Window当中,这样我们的整个流程就走完了,界面就显示出来了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android中的WindowManagerAndroid系统中的一个关键类,它负责管理所有窗口的显示和布局。而悬浮球则是窗口中的一个小球,它可以随意拖动并悬浮在其他应用程序之上。 实现悬浮球的拖动可以通过以下步骤完成: 1. 创建一个悬浮球的View:首先需要创建一个自定义的View,用于显示悬浮球。这个View可以是一个小球的图片或者是一个自定义形状的View。 2. 添加悬浮球的权限:在AndroidManifest.xml文件中添加SYSTEM_ALERT_WINDOW权限,这是一项特殊权限,用于显示系统级别的悬浮窗口。 3. 创建悬浮球的WindowManager:通过WindowManager类的实例,可以获取到系统的WindowManager服务。 4. 将悬浮球的View添加到WindowManager中:使用WindowManager.LayoutParams参数来设置上述View的显示位置、大小、类型等属性,并通过WindowManager的addView方法将View添加到窗口中。 5. 监听悬浮球的触摸事件:为悬浮球的View设置触摸监听器,当用户按下悬浮球并移动手指时,通过更新悬浮球的LayoutParams参数的x和y属性来实现悬浮球的拖动操作。 6. 处理悬浮球的点击事件:为悬浮球的View设置点击监听器,当用户点击悬浮球时,可以执行相应的操作,如显示菜单、打开应用程序等。 7. 移除悬浮球的View:当不再需要显示悬浮球时,可以通过调用WindowManager的removeView方法将悬浮球的View从窗口中移除,并及时回收相关资源。 以上就是利用WindowManager实现Android悬浮球拖动的基本步骤。通过监听触摸事件和点击事件,可以实现悬浮球的拖动和响应用户操作的功能。同时,需要注意悬浮球的权限问题,以保证正确显示和操作悬浮球。 ### 回答2: Android WindowManager 悬浮球是一种常见的用户界面元素,常用于显示快捷方式、通知或其他常用功能。用户可以通过拖动悬浮球的方式来操作相关功能。 要实现悬浮球的拖动功能,首先需要创建一个悬浮球的视图,并将其添加到 WindowManager 中。可以通过创建一个继承自 View 的自定义悬浮球类来实现这一点。 在自定义悬浮球类中,需要重写 onTouchEvent() 方法以处理触摸事件。当用户按下悬浮球时,记录下手指按下的初始位置。当用户移动手指时,根据手指的移动距离来更新悬浮球的位置。可以使用 WindowManager.LayoutParams 的 x 和 y 属性来指定悬浮球的位置。 同时,还可以根据需要添加一些限制条件,如限制悬浮球的移动范围、限制悬浮球的吸附行为等。这些都可以通过对 MotionEvent 的处理来实现。 当用户松开手指时,悬浮球的拖动操作结束。此时,可以保存最后的位置信息以便下次使用。同时,还可以根据需要触发相应的功能操作,如打开一个快捷方式或显示一个通知等。 需要注意的是,要实现悬浮球的拖动功能,必须具有相应的权限。在 AndroidManifest.xml 文件中声明 SYSTEM_ALERT_WINDOW 权限,并在运行时请求该权限,以确保可以在其他应用程序上方显示悬浮球。 综上所述,通过自定义 View 类,重写 onTouchEvent() 方法,结合 WindowManager 的使用,可以实现 Android WindowManager 悬浮球的拖动功能。这样,用户就可以方便地通过拖动悬浮球来操作相关功能,并且可以根据实际需求对拖动行为进行定制。 ### 回答3: 在Android中,WindowManagerAndroid系统的一个重要组件,用于管理应用程序窗口的创建、显示和更新。悬浮球是一种常见的用户界面交互方式,它可以在屏幕上悬浮显示,并且可以通过拖动操作实现位置的改变。 实现悬浮球拖动的基本步骤如下: 1. 创建悬浮球视图:通过编程方式创建一个悬浮球视图,并设置其初始位置和样式。可以使用自定义的绘制工具或者使用现有的View类来实现悬浮球的外观。 2. 监听触摸事件:为悬浮球视图设置触摸事件的监听器,以便能够响应用户的触摸操作。可以通过重写onTouchEvent()方法来实现触摸事件的处理。 3. 处理触摸事件:在触摸事件的监听器中,根据不同的事件类型(如按下、移动、抬起),实现相应的逻辑。例如,当用户按下悬浮球时,记录下手指的初始位置;当用户移动手指时,根据手指的位置和初始位置计算悬浮球的新位置,并更新悬浮球的显示;当用户抬起手指时,完成悬浮球的拖动操作。 4. 更新悬浮球位置:在悬浮球拖动的过程中,需要不断更新悬浮球的位置。可以使用WindowManager的LayoutParams类来设置悬浮球的新位置,并通过WindowManager的updateViewLayout()方法来更新悬浮球的显示。 5. 处理边界情况:在处理悬浮球的拖动过程中,需要考虑到悬浮球是否超出屏幕边界的情况。可以通过判断悬浮球的位置,以及屏幕的大小来限制悬浮球的移动范围,避免悬浮球超出屏幕。 总之,通过使用WindowManager和触摸事件的监听器,可以实现Android中悬浮球的拖动功能。这个功能在许多应用中都很常见,例如一些工具类应用和游戏应用,可以增加用户的交互体验和便利性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值