addView方法的不同实现

106 阅读5分钟

1、addView的起源

关于addView方法,我说下我的理解,首先说明下这个方法的起源,addView方法的声明在ViewManager.java中。

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类,分别是ViewGroup和WindowManager。根据子类的实现分别看下他们继承了addView之后是怎么实现该方法的。
在这里插入图片描述

2、ViewGroup#addView

首先看ViewGroup如何继承addView方法的,在ViewGroup中发现其实addView方法还有好多个,根据参数列表的不同分为以下几个:当然这里调用这个方法的都是ViewGroup的实例了,所以就假设一个ViewGroup的实例为ViewGA,我们需要通过ViewGA调用addView方法,而这个被add的View就假设为ViewChild,所以调用的方式应该是:ViewGA.addView(ViewChild, LP);先说明下这个功能是什么: 将ViewChild挂载在ViewGA下,作为ViewGA的子view。

public void addView(View child);
public void addView(View child, int index);
public void addView(View child, int width, int height);
@Override)
public void addView(View child, LayoutParams params);
public void addView(View child, int index, LayoutParams params);

从上面几个当中一眼就能看出来,继承自ViewManager的是哪个了,其实看这么多代码,最后会发现,这种参数列表不同的函数,最后极有可能都是调到了同一个函数。 是的不出所料,果然最后都是调用了上面列出来的最后一个三参数方法。其他方法有兴趣的自己看下,也不多,主要看从ViewManager继承过来的这个方法。

@Override
public void addView(View child, LayoutParams params) {
    addView(child, -1, params);
}

一看就很清晰,就是将index赋值为-1,并调用最后一个方法。然后看下这个方法具体实现的内容。

public void addView(View child, int index, LayoutParams params) {
    if (DBG) {
        System.out.println(this + " addView");
    }

    if (child == null) {    //child为null即,我们要添加的这个View为null

        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }

    // addViewInner() will call child.requestLayout() when setting the new LayoutParams
    // therefore, we call requestLayout() on ourselves before, so that the child's request
    // will be blocked at our level
    requestLayout();  //这里会出发应用绘帧,具体实现后面会详细说一节
    invalidate(true);  //该view是否需要标记为脏区,这里就显而易见了
    addViewInner(child, index, params, false);//核心区域
}

好的接下来就是看addViewInner是干了什么。

private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {
    if (child.getParent() != null) {  //很简单,若被添加的child还有//parentView那就会报错
        throw new IllegalStateException("The specified child already has a parent. " +
                "You must call removeView() on the child's parent first.");
    }
    if (!checkLayoutParams(params)) { //检查参数转型后是否为空
        params = generateLayoutParams(params);
    }

    if (preventRequestLayout) {
        child.mLayoutParams = params;
    } else {
        child.setLayoutParams(params); //设置参数
    }

    if (index < 0) {
        index = mChildrenCount; //index<0,默认作为最后一个view
    }

    addInArray(child, index); //这里有个View数组和ViewCount维护,新加View得按序放入数组,并数量+1;

    // tell our children
    if (preventRequestLayout) {//不知道有什么区别,感觉功能差不多,无非是检查了下this是不是null,但是也没啥区别啊,都是把this赋值给child的mParent
        child.assignParent(this);
    } else {
        child.mParent = this;
    }
    
    dispatchViewAdded(child);//执行回调函数

   }

省略了一些暂时无关的函数,可以发现,其实就是将调用这个方法的ViewGA作为方法参数ViewChild ParentView而已。所以我们就可以知道这个ViewGroup中的addView其实就是将参数中的view作为子view挂载在调用的实例下而已。

3、WindowManager#addView:

正如这里所说,WindowManager会把活都外包出去,所以直接找到位于Session.java中的:

@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
        InputChannel outInputChannel, InsetsState outInsetsState,
        InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
        float[] outSizeCompatScale) {
    return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
            requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
            outAttachedFrame, outSizeCompatScale);
}

这里可以看到调用了WindowManagerService的addWindow方法,都到这里了相比不说你也知道了,是添加了一个window,并且根据这个里面说的流程,我们还可以在WindowManagerGlobal中看到新建了一个viewRootImpl的实例,并且将ViewRootImpl实例作为addView方法的参数的 ParentView,这里也讲了,ViewRootImpl是DecorView的ParentView,那就可以想象到了,其实这个view已经充当了一个DecorView的角色了。 所以综上,可以知道,WindowManager里调用的addView方法,其实是添加一个Window,并且这个Window也有自己的ViewRootImpl->decorView->contentView这一个View树,只是这个window不是我们在Activity类的mWindow罢了。

4、实践:

这里我写了一个例子,可以看到,在我点击按键后,会出现一个新的窗口。然后dumpsys以下window信息就可以看到这里的窗口信息,新添加的窗口和我们应用的窗口是同级的。(若需要完整的demo也可以私聊我)

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

    Button bt1 = findViewById(R.id.bt01);
    bt1.setOnClickListener(this);
}
@Override
public void onClick(View view) {
    LayoutInflater li = LayoutInflater.from(this);
    View view_added = li.inflate(R.layout.activity_main1, null);
    WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
    lp.width = 550;
    lp.height = 950;
lp.setTitle("helloworld !");
    switch (view.getId()){

        case R.id.bt01:
            getWindowManager().addView(view_added, lp);
            break;
        default:
            break;
    }
}

在这里插入图片描述

在这里插入图片描述

5、小结:

总体而言,addView是继承自ViewManager接口的,在Window和View侧有不同的实现主体,而且也很好理解,view侧就是添加一个view,window侧就是添加一个window(废话文学了属于是)。整体还是需要了解window的整体架构,就是Activity->phoneWindow->Decorview->contentView这个模型,了解后其实会更方便理解一点。 目前我其实还有几点疑问:1、WindowManagerService中是如何添加Window的;2、通过WindowManager添加的窗口是如何存在的,他有和Activity关联吗?这两点疑问目前还没搞清楚,也希望有大佬能指点下,或者一起探讨下。