android开发艺术探索(八)

理解Window和WindowManager

Window和WindowManager

Window
Window是一个抽象类,它的具体实现是PhoneWindow。我们可以通过WindowManager创建一个Window。WidowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。
Android中的所有视图都是通过Window来呈现的,不管是Activity、Dialog还会Toast,他们的视图实际上都是附加在Window上的,因此Window实际上是View的直接管理者。

我们来使用WindowManger在向布局中添加一个按钮:具体实现如下

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        WindowManager manager=getWindowManager();
        Button bt=new Button(this);
        bt.setText("button");
        WindowManager.LayoutParams params=
        new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT,0,0,
                PixelFormat.TRANSPARENT
        );
        params.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                |WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        params.gravity= Gravity.LEFT|Gravity.TOP;
        params.x=100;
        params.y=300;
        manager.addView(bt,params);
    }

其中flags和type这两个参数比较重要。
flags

Flags表示Window的属性,它有多选项,通过这些选项可以控制Window的显示特性。

FLAG_NOT_FOCUSABLE

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

FLAG_NOT_TOUCH_MODAL

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

FLAG_SHOW_WHEN_LOCKED

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

type

type参数表示Window的类型,Window有三种类型,分别是应用Window、子Window和系统Window。
应用类Window对应着一个Activity。
子类不能单独存在,他需要负数在特定的父Window中,比如常见的一些Dialog就是一个子Window。
系统Window是需要声明权限才能创建的Widow,比如Toast和系统状态栏都是系统Window。

此外,系统Window使用需要在AndroidMainfest中使用相应的权限。

WindowManager
WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager;

public interface ViewManager {
    void addView(View var1, LayoutParams var2);
    void updateViewLayout(View var1, LayoutParams var2);
    void removeView(View var1);
}

我们还使用上面那个例子:我们来实现一个可以拖动的Window,我们来给按钮加一个监听:

  bt.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent ev) {
                int rawX= (int) ev.getRawX();
                int rawY= (int) ev.getRawY();

                switch (ev.getAction()){
                    case MotionEvent.ACTION_MOVE:
                        params.x=rawX;
                        params.y=rawY;
                        /**在这里不断更新按钮的位置*/
                        manager.updateViewLayout(bt,params);
                        break;
                }
                return false;
            }
        });

Window的内部机制

Window是一个抽象的概念,是以View的形式存在。通过WindowManager来操作View。

Window的添加过程
Window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口。它的实现类是WindowManagerImpl类。WindowManagerImpl并没有直接实现Window的三大操作,而是通过WindowManagerGloba来处理。

a. WindowManagerGlobal中的addView
b. 检查参数是否合法;
c. 如果子Window还需要调节布局参数;
d. 创建ViewRootImpl并将View添加到列表中;
e. 通过ViewRootImpl的setView来更新界面并完成Window的添加过程:
requestLayout中的scheduleTraversals是View绘制的入口,最终通过WindowSession来完成Window的添加过程,注意其实这里是个IPC过程,最终会通过WindowManagerService的addWindow方法来实现Window的添加。

Window的删除过程
Window的删除过程和添加过程一样,都是先通过WindowManagerImpl后,再进一步通过WindowManagerGloba来实现的。

a. WinodwManagerGlobal中的removeView;
b. findViewLocked来查找待删除待View的索引,再调用removeViewLocked来做进一步删除;
c. removeViewLocked通过ViewRootImpl的die方法来完成删除操作,包括同步和异步两种方式,同步方式可能会导致意外的错误,不推荐,一般使用异步的方式,其实就是通过handler发送了一个删除请求,将View添加到mDyingViews中;
d. die方法本质调用了doDie方法,真正删除View的逻辑在该方法的dispatchDetachedFromWindow方法中,主要做了四件事:垃圾回收,通过Session的remove方法删除Window,调用View的dispatchDetachedFromWindow方法同时会回调View的onDetachedFromWindow以及onDetachedFromWindowInternal,调用WindowManagerGlobal的doRemoveView刷新数据。

具体需要研究源码。

Window的更新过程
Window的更新还是通过WindowManagerGloba的updateViewLayout方法。

a. WindowManagerGlobal的updateViewLayout;
b. 更新View的LayoutParams;
c. 更新ViewImple的LayoutParams,实现对View的重新测量,布局,重绘;
d. 通过WindowSession更新Window的视图,WindowManagerService.relayoutWindow()。

Window的创建过程

Activity的Window创建过程
当我们启动一个新的Activity时,相当于重新创建一个Activity。我们来具体分析下:

1、在Activity的attach中,系统会创建Activity所属的Window并为其设置回调(CallBack)。

2、Window是通过PolicyManager工厂类中的makeNewWindw()方法创建

3、makeNewWindw()方法中,通过new PhoneWindow()返回Window对象,Window的具体实现类是PhoneWindow。

4、Window创建好之后,通过PhoneWindow的setContentView将Activity与Window进行关联,这个方法大致步骤:

(1)创建DecorView(是Activity中的顶级View),由installDecor方法来完成,在方法内部会通过generateDecor方法直接创建DecorView。它的id是android.R.id.content。就是一个空白的FrameLayout

(2)将View添加到DecorView的mContentParent中,通过LayoutInflate.inflate()来添加。

(3)回调Activity的onContentChange方法通知Activity视图已经发生变化

(4) Activity onResume的时候会调用Activity的makeVisible方法真正完成DecorView的添加和显示。

Dialog的Window创建过程
Dialog的Window的创建过程和Activity类似,有以下步骤:
1.创建Window

通过PolicyManager的makeNewWindow方法创建Window;
2、初始化DecorView并将Dialog的视图添加到DecorView中
通过setContentView添加视图
3、将DecorView添加到Window中并显示
在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中。

当Dialog关闭时,会通过WindowManager来移除DecorView

mWindowManager.removeViewImmediate(mDecor);
这里需要注意的是:普通的Dialog必须采用Activity的Context,如果采用Application的Context,会报错。(没有应用token的错误,token一般只有Activity拥有)

Toast的Window创建过程

Toast和Dialog不同,但也是基于Window来实现的,但是Toast有定时取消功能,所以系统采用了Handler。
在Toast的内部有两类IPC过程,

第一类是Toast访问NotifycationManagerService(NMS)
第二类是NofitycationManager回调Toast里的TN接口。

Toast属于系统Window,它内部的视图由两种方式指定:

1、系统默认样式,内部视图mNextView一种为系统默认样式
2、通过setView方法指定一个自定义View

Toast的显示和隐藏:

Toast的显示和隐藏都需要通过NMS来实现,由于NMS运行在系统的进程中,所以只能通过远程调用的方式来显示和隐藏Toast。

TN是一个Binder类,当NMS处理Toast的显示和隐藏请求时会跨进程回调TN中的方法,由于NMS运行在Binder线程池中,所以需要通过Handler将其切换到当前线程中。注意::由于这里使用了Handler,所以这意味着Toast无法再没有Looper的线程中弹出,这是因为Handler需要使用Looper才能完成切换线程的功能。

Toast显示时,调用了NMS中的enqueueToast方法。它内部首先将Toast请求封装为ToastRecord对象并将其添加到一个名为mToastQueue的队列中。(非系统应用最多塞50个)

当ToastRecord被添加到mToastQueue中后,NMS就会通过showNextToastLocked的callback来完成的,Toast显示由ToastRecord的callback方法中的show方法完成。这个callback实际上就是Toast中的TN对象的远程Binder所以最终调用的是TN中的方法,并运行在发起Toast请求应用的Binder线程池中。

Toast显示以后,NMS还会通过scheduleTimeoutLocked方法来发送一个延时消息(具体的延时取决于Toast的时长),延时后NMS会通过cancelToastLocked方法来隐藏Toast并将其从mToastQueue中移除。隐藏是通过ToastRecord的callback中的hide方法来实现的。

callback回调TN的show和hide方法后,会通过handler发送两个Runnable,里面的handleShow和handleHide方法是真正完成显示和隐藏Toast的地方。handleShow方法中将Toast的视图添加到Window中,handleHide方法将Toast视图从Window中移除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值