从app启动到actiivty创建显示,绘制流程等

App 启动流程(基于Android8.0):

1.点击桌面 App图标,Launcher进程向systemserver进程的AMS发起 startActivity请求(binder方式) ,system _server 进程收到请求后,向 Zygote 进程发送创建进程的请求(socket请求),Zygote 进程 fork 出App进程(socket请求);
2.App 进程初始化过程如下:

1).应用进程默认的java异常处理机制(可以实现监听、拦截应用进程所有的Java crash的逻辑);
2).JNI调用启动进程的binder线程池(注意应用进程的binder线程池资源是自己创建的并非从zygote父进程继承的);
3).通过反射创建ActivityThread对象并调用其“main”入口方法。

3.ActivityThread#main函数初始化的主要逻辑是:

①.创建并启动主线程的loop消息循环;
自此:主线程就有了完整的 Looper、MessageQueue、Handler
ActivityThread 的 Handler 就可以开始处理 Message,包括 Application、Activity、ContentProvider、Service、Broadcast 等组件的生命周期函数,都会以 Message 的形式,在主线程按照顺序处理。

主线程就进入阻塞状态,等待 Message,一旦有 Message 发过来,主线程就会被唤醒,处理 Message,处理完成之后,如果没有其他的 Message 需要处理,那么主线程就会进入休眠阻塞状态继续等待。
可以说Android系统的运行是受消息机制驱动的,而整个消息机制是由上面所说的四个关键角色相互配合实现的。

②.通过binder调用AMS的attachApplication接口将自己attach注册到AMS中。
AMS服务在执行应用的attachApplication注册请求过程中,发送ScheduleLaunActivity给ApplicationThread,会通过oneway类型的binder调用应用进程ApplicationThread的bindApplication接口(bindApplication接口函数实现:通过Hanlder往应用主线程消息队列post BIND_APPLICATION消息,触发在主线程执行handleBindApplication初始化函数)

4.ActivityThread.handleBindApplication()初始化过程中在应用主线程中,主要完成如下几件事件:

①根据框架传入的ApplicationInfo信息创建应用APK对应的LoadedApk对象;
②创建应用Application的Context对象;
③创建类加载器ClassLoader对象并触发Art虚拟机执行OpenDexFilesFromOat动作加载应用APK的Dex文件;
④通过LoadedApk加载应用APK的Resource资源;
⑤调用LoadedApk的makeApplication函数,创建应用的Application对象;
⑥执行应用Application#onCreate生命周期函数(APP应用开发者能控制的第一行代码)(应用全局状态的初始化点,例如初始化全局变量、加载共享资源等);

5.AMS调用client.handleLaunchActivity,然后执行ActivityThread的performLaunchActivity方法

此方法中做了哪些操作:
①.通过类加载器创建Activity
②.调用attach方法:

1.关联上下文
2.创建PhoneWindow对象
3.设置窗口回调接口

③.调用callActivityCreate

调用actiivty的oncreate方法
5.1.Activity的oncreate方法中做了什么?

setContentView后 -> phoneWindow.setContentView()
1.installDecor;创建decorView
2.无转场动画时候执行mLayoutInflate.inflate(layoutResId,mContentParent ,渲染布局,将xml转成java对象
3.phoneWindow的构造函数里mLayoutInflater = LayoutInflate.from(context))

installDecor方法:

1.generateDecor方法主要就是创建DecorView,拿到contentParent
2.generareLayout(mDecor)中属性设置,xml中的一些属性,然后把选出的布局文件add到DecorView上
3.decorView.setWindow(this)

DecorView:FrameLayout类的子类:

通常认为DecorView是Android视图树的根节点,也就是最顶层的View.
DecorView内部包含一个LinearLayout,在这个LinearLayout里面有上下三个部分
1.上面是个ViewStub,
2.中间的是TitleView,根据Activity的主题设置的
3.最下面是个ContentViews,就是布局文件

xml转成java对象是通过LayoutInflate对象的inflate方法,LayoutInflater是一个抽象类,不能实例化,此对象获取方式有三种

1).在 Activity 中通过 getLayoutInflater()获取
2).LayoutInflater里静态方法from(context)获取
3).context.getSystemService(Context.LAYOUT INFLATER SERVICE)获取

inflate方法中:

首先通过 resource对象把 resld 指向的 xml 文件转换为XmlResourceParser

在这里插入图片描述
在infalte方法中

   public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
    	.......
        View result = root;
        //如果提供了root并且attachToRoot为true,则这个方法返回的就是root
		//否则它返回的是XML文件的根。
		
            if (TAG_MERGE.equals(name)) {
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }
                //如果 Xml根标签是 TAG_MERGE(即merge),则 root 不能为空,attachToRoot必须为 true,否则抛异常
                rInflate(parser, root, inflaterContext, attrs, false);	//重要方法1
            } else {
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                //重要方法2,createViewFromTag
                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);
                    }
                }

                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);重要方法3

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }
        }
        return result;
    }
}

1.rInflate方法中不能使用merge做子节点,createViewFromTag创建根View对象,递归遍历所有的子标签rInflateChildren,创建view对象并添加到根View上

    void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
            	//不能使用merge作为根标签
                throw new InflateException("<merge /> must be the root element");
            } else {
            	//创建View不能
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
}

2.如果没有merge根节点,通过createViewFromTag创建temp根节点View对象,如果传进来的root不为null,第三个参数为true,root的LayoutParams赋值给temp根节点,然后rInflateChildren,遍历所有的标签添加到temp,temp被添加到root,inflate方法返回的是root,

如果root==null,此方法返回的是temp根节点

createViewFromTag方法中先tryCreateView(通过factory创View),如果那几个factory没有创建出view的话,去判断自定义View与否,

==如果有.view = onCreateView(context, parent, name, attrs);

==否则view = createView(context, name, null, attrs);

==最终会反射调用两参的构造方法,且构造器会缓存到layoutInfater中sConstructorMap:HashMap<viewName, constructor>)

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
	。。。。。。
    try {
        View view = tryCreateView(parent, name, context, attrs);

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(context, parent, name, attrs);
                } else {
                    view = createView(context, name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        
        return view;
    }
}
tryCreateView方法
public final View tryCreateView(XXX)
    View view;
    if (mFactory2 != null) {
        view = mFactory2.onCreateView(parent, name, context, attrs);
    } else if (mFactory != null) {
        view = mFactory.onCreateView(name, context, attrs);
    } else {
        view = null;
    }

    if (view == null && mPrivateFactory != null) {
        view = mPrivateFactory.onCreateView(parent, name, context, attrs);
    }

    return view;
}
最终调用到createView
public final View createView(@NonNull Context viewContext, @NonNull String name,
        @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {

    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);

            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        } else {
            // If we have a filter, apply it to cached constructor
            if (mFilter != null) {
                // Have we seen this name before?
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    // New class -- remember whether it is allowed
                    clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                            mContext.getClassLoader()).asSubclass(View.class);
                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                    mFilterMap.put(name, allowed);
  
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
        }
            return view;
        } 
    }
}
6.AMS调用client.handleResumeActivity,执行ActivityThread的performResumeActiivty方法

调用到resume->onResume->makeVisible->wm.addview(DecorView)
addview真正完成添加和显示过程,自此开始绘制流程

wm.addview实际是调用的windowManagerGlobal的addView 方法
该方法中创建ViewRootImpl对象,并调用了ViewRootImpl.setView方法
绘制流程的起点viewRootImpl.setView

Activity对View的管理委托给Window,外界访问Window是通过WindowManager,windowManager实现了viewManager接口(具有addView,updateView,removeView能力),因此WindowManager具有对view的管理能力,WindowManagerImpl继承了WindowManager,但是并没有实现WindowManager的三大操作,而是将所有的操作全部委托给WindowManagerGlobal来实现。

WindowManagerGlobal以工厂的形式向外提供自己的实例,存储Window对应的所有View:Window对应的所有ViewRootImpl,Window对应的所有的布局参数,存储正在被删除的View对象,或者说是那些已经调用removeView方法但是删除操作还未完成的Window对象

1.windowManagerGlobla全局单例被WindowManagerImpl持有,被转移处理
2.WindowManagerImpl implements WindowManager extends ViewManager
内部维护如下几个列表:
mViews:存储所有Window所对应的View
mRoots:存储的是所有Window所对应的ViewRootImpl
mParams:存储的是所有Window所对应的布局参数
mDyingView:存储了那些正在被删除的View对象,或者说是那些已经调用了removeView方法但是删除操作还未完成的Window对象

ViewRootImpl:是window和view之间的桥梁,可以处理两边的对象,联结起来。

持有mWindowSession,类型是IWindowSession,它是一个Binder对象,真正的实现类是Session
Window的添加过程是一次IPC调用
requestLayout之后 IWindowSession

View树的树根,并管理view树
触发view的测量、布局和绘制
输入响应的中转站: ViewRootImpl --> DecorView --> Activity --> PhoneWindow --> DecorView --> ViewGroup --> View 为什么DecorView不直接把事件分发给子view呢?因为窗口要有一些处理,比如打电话脸部靠近屏幕的时候不会响应点击;在比如dialog点击外部区域收起弹窗
负责与wms进行进程间通信: (ViewRootImpl-->IWindowSession)本地进程 --------Binder通信-------- SystemService进程(WMS的Session)

WindowManagerService
WindowManagerService去处理Window的添加请求
WindowManagerService内部会为每一个应用保留一个单独的Session

7.绘制流程的起点viewRootImpl.setView

调用requestLayout

1.校验主线程
2.调用scheduleTraversals()

scheduleTraversals中:

1.往主线程的Handler对应的MessageQueue发送一个同步屏障消息
2.监听vsync信号

同步屏障作用:

在调用requestLayout后到vsync信号到达的这段时间内,不能在主线程执行其他任何任务,保证ui显示是第一位的;

当vsync信号到来,把doTraversal这个函数包裹成一个异步消息加入到messageQueue。

doTraversal中:

1屏障解除
2.performTraversals()开始绘制三大流程
8.performTraversals()中调用:
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();

绘制流程是由最外层的View开始,一步一步向内传递执行。
而整个过程又是递归等待的,最外层的View需要等内层所有的View执行完绘制流程才结束

8.1 performMeasure

measure流程开始执行之前
1.会先计算出DecorView的MeasureSpec。如果是match_parent就是屏幕的宽高
2.然后执行DecorView的measure()方法开始整个View树的测量

measure()方法是被final修饰了的,派生类都不能重写,所有View都会执行到View类的measure()方法。

ViewGroup:

1.onMeasure()方法中遍历所有子View,通过执行measureChildWithMargins()方法,先计算出子View的MeasureSpec再调用子View的measure()方法传递执行measure流程。
2.ViewGroup在所有子View的measure流程都执行结束后,再调用setMeasuredDimension()方法给自己的mMeasureWidth/Height赋值。

单一View直接测量自己的值并赋值

MeasureSpec32未,前两位是mode,后三十位是size.
每一个view都有自己的MeasureSpec,子view在测量自己的MeasureSpec时,有两个重要参数,mode和size,子view的大小根据父view的模式和size来限定。

8.2 performLayout:
通过4个点来确定子View在父View中的位置。同时也可以通过点的距离来计算出View的大小。

1.执行DecorView的layout()方法来开始整个View树的layout流程。而DecorView包括其他的ViewGroup都没有另外实现layout()方法,都会执行到View的layout()方法。                  
2.layout()方法中会先执行setFrme()方法确定View自己在父View中的位置,接着再执行onLayout()方法来遍历所有的子View,计算出子View在自己心中的位置(4个点)后,再执行子View的layout流程。    

不同的ViewGroup有着不同的方式来安排子View在自己心中的位置。 所以View类中的onLayout()是一个空方法,等着View们自己去实现。

自定义ViewGroup的时候如果不在onLayout方法中安排子View的位置,将看不见子View。

ViewGroup:

1.确定自己在父View中的位置
2.遍历所有子View,计算出在自己心中的位置(4个点)后,再执行子View的layout流程

单一View直接布局自己

8.3 PerformDraw:

通过层层调用会执行到View的draw()方法

draw()方法中:

drawBackground(canvas);//绘制自己的背景  
onDraw(canvas)//空实现,绘制自己的内容,自定义时重写该方法
dispatchDraw(canvas);//绘制子View,ViewGroup来说dispatchDraw就是去绘制子 View
onDrawForeground(canvas);//绘制滚动条
9.自定义View流程
9.1 自定义单一View Demo:圆形进度条(CircleProgressBar)
1. 定义自定义View类

首先,我们需要创建一个继承自View的类,比如CircleProgressBar。

public class CircleProgressBar extends View {  
    // 构造函数  
    public CircleProgressBar(Context context) {  
        super(context);  
        // 初始化  
    }  
  
    public CircleProgressBar(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        // 从XML属性中获取值(如果有的话)  
    }  
  
    public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {  
        super(context, attrs, defStyleAttr);  
        // 使用默认样式  
    }  
}
  1. 重写onMeasure方法
    为了确定View的大小,我们需要重写onMeasure方法。在这个例子中,我们可能希望进度条有一个固定的大小。

    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    	//其中,DEFAULT_WIDTH和DEFAULT_HEIGHT是定义的默认宽度和高度。
        int width = resolveSize(DEFAULT_WIDTH, widthMeasureSpec);  
        int height = resolveSize(DEFAULT_HEIGHT, heightMeasureSpec);  
        // 设置最小宽度和高度  
        setMeasuredDimension(Math.max(width, height), Math.max(width, height));  
    }
    
  2. 重写onDraw方法
    在onDraw方法中,我们使用Canvas对象来绘制自定义View的内容。

     private int progress = 0; // 进度值  
     private Paint paint; // 绘制工具  
       
     // ... 其他初始化代码 ...  
       
     @Override  
     protected void onDraw(Canvas canvas) {  
         super.onDraw(canvas);  
    
         // 绘制背景圆  
         paint.setColor(Color.LTGRAY);  
         canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - 10, paint);  
    
         // 根据进度绘制前景圆  
         if (progress > 0) {  
             paint.setColor(Color.BLUE);  
             double sweepAngle = 360 * progress / 100.0; // 将进度转换为角度  
             canvas.drawArc(new RectF(10, 10, getWidth() - 10, getHeight() - 10), -90, (float) sweepAngle, true, paint);  
         }  
    
     // 可以添加其他绘制代码...  
     }
    
  3. 提供设置进度的方法
    了从外部设置进度值,我们需要提供一个公共方法。

     public void setProgress(int progress) {  
         if (progress < 0) progress = 0;  
         if (progress > 100) progress = 100;  
         this.progress = progress;  
         invalidate(); // 请求重绘View  
     }
    
  4. 在Activity或Fragment中使用自定义View
    在XML布局文件中添加自定义View:

     <com.example.myapp.CircleProgressBar  
         android:id="@+id/circleProgressBar"  
         android:layout_width="wrap_content"  
         android:layout_height="wrap_content"  
         app:layout_constraintBottom_toBottomOf="parent"  
         app:layout_constraintEnd_toEndOf="parent"  
         app:layout_constraintStart_toStartOf="parent"  
         app:layout_constraintTop_toTopOf="parent"/>
    

在Activity或Fragment中设置进度:

CircleProgressBar circleProgressBar = findViewById(R.id.circleProgressBar);  
circleProgressBar.setProgress(50); // 设置进度为50%
9.2 自定义ViewGroup

当涉及到自定义ViewGroup时,我们通常会创建一个继承自ViewGroup或其子类(如LinearLayout、RelativeLayout等)的类,并在其中管理子View的布局和交互。下面是一个简单的自定义ViewGroup的例子,我们将创建一个名为CustomLayout的类,它水平地排列其子View,并允许设置每个子View之间的间距。

  1. 定义自定义ViewGroup类
    首先,我们需要创建一个继承自ViewGroup的类。假设我们想要创建一个简单的水平布局,我们可以命名为CustomHorizontalLayout。

     public class CustomHorizontalLayout extends ViewGroup {  
     	    // 构造函数  
     	    public CustomHorizontalLayout(Context context) {  
     	        super(context);  
     	    }  
       
         public CustomHorizontalLayout(Context context, AttributeSet attrs) {  
             super(context, attrs);  
         }  
       
         public CustomHorizontalLayout(Context context, AttributeSet attrs, int defStyleAttr) {  
             super(context, attrs, defStyleAttr);  
         }  
    
     // ... 其他代码 ...  
     }
    
  2. 重写关键方法
    onMeasure:用于测量View的大小。在自定义ViewGroup中,我们需要遍历子View并测量它们的大小,然后根据需要设置整个ViewGroup的大小。
    onLayout:用于确定子View在ViewGroup中的位置。在自定义ViewGroup中,我们需要根据测量的结果和自定义的布局逻辑来放置子View。

     @Override  
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
         // 遍历子View并测量它们的大小  
         measureChildren(widthMeasureSpec, heightMeasureSpec);  
       
         // 设置整个ViewGroup的大小(这里简单起见,假设和父容器一样大)  
         setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                 resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
     }  
    
     @Override  
     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
         // 根据需要确定子View的位置  
         final int count = getChildCount();  
         int left = getPaddingLeft();  
         int top = getPaddingTop();  
         for (int i = 0; i < count; i++) {  
             final View child = getChildAt(i);  
             if (child.getVisibility() != View.GONE) {  
                 final int childWidth = child.getMeasuredWidth();  
                 child.layout(left, top, left + childWidth, top + child.getMeasuredHeight());  
                 left += childWidth;  
             }  
         }  
     }
    
10.绘制相关问题:
10.1 RequestLayout,onLayout,onDraw,onDrawChild的区别

RequestLayout:调用onMeasure和onlayout的过程,根据标记位判断是否调用onDraw;

onLayout:针对ViewGroup,让子View分布好位置

onDraw:绘制控件本身(背景,canvas图层,内容,子View,fading边缘和装饰)

onDrawChild:回调每个子控件的onDraw方法

10.2怎么优化自定义View

①:减少冗余代码,尤其onDraw的代码,尽量减少调用刷新方法的频率
②:避免在onDraw里面处理内存分配逻辑
③:每次执行RequestLayout非常耗时,使用自定义ViewGroup,onlayout可以直接对子控件布局
④:减少View的层级,避免冗余复杂

10.3 怎么测量view的大小
10.3.1. OnGlobalLayoutListener获取

ViewTreeObserver.addOnGlobalLayoutListener() 监听View的onLayout()绘制过程,一旦layout触发变化,立即回调onLayoutChange方法。
在布局发生改变或者某个视图的可视状态发生改变时调用该事件,会被多次调用,
注意,使用完也要主要调用removeOnGlobalListener()方法移除监听事件。避免后续每一次发生全局View变化均触发该事件,影响性能。

    ViewTreeObserver vto = view.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            Log.i(TAG, "width: " + view.getWidth());
            Log.i(TAG, "height: " + view.getHeight());
        }
    });
10.3.2. OnPreDrawListener获取

在视图将要绘制时调用该监听事件,会被调用多次,因此获取到视图的宽度和高度后要移除该监听事件。这同样是 ViewTreeObserver 的接口。
OnPreDrawListener是在draw之前的回调,此时已经 layout 过,可以获取到 View 的宽高值。OnPreDrawListener还可以控制绘制流程,返回false的时候就取消当前绘制流程,View会再schedule下一次绘制:

   getWindow().getDecorView().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
       @Override
       public boolean onPreDraw() {
           getTextWidthHeight();
           getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
           return false;
       }
   });
10.3.3. OnLayoutChangeListener获取

在视图的 layout 改变时调用该事件,会被多次调用,因此需要在获取到视图的宽度和高度后执行 remove 方法移除该监听事件。

    view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
       @Override
       public void onLayoutChange(View v, int left, int top, int right, int bottom,int oldLeft, int oldTop, int oldRight, int oldBottom) {
            view.removeOnLayoutChangeListener(this);
            int w = view.getWidth();
            int h = view.getHeight();
      }
   });
10.3.4. 重写View的onSizeChanged,该方法会在onCreate之后,onDraw之前调用

在视图的大小发生改变时调用该方法,会被多次调用,因此获取到宽度和高度后需要考虑禁用掉代码。该实现方法需要继承 View,且多次被调用,不建议使用。

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.i("TAG", "width = " + getWidth() + "height = " + getHeight());
    }
10.3.5. 使用View.post()方法

利用Handler通信机制,发送一个Runnable到MessageQueue中,当View布局处理完成时,自动发送消息,通知UI进程。借此机制,巧妙获取View的高宽属性,代码简洁,相比ViewTreeObserver监听处理,还不需要手动移除观察者监听事件。

    view.post(new Runnable() {
            @Override
            public void run() {
                int width = view.getWidth();
                int measuredWidth = view.getMeasuredWidth();
                Log.i(TAG, "width: " + width);
                Log.i(TAG, "measuredWidth: " + measuredWidth);
            }
        });

post实现:

   public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        // 添加到AttachInfo的Handler消息队列中
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
 
 
        // 加入到这个View的消息队列中
        getRunQueue().post(action);
        return true;
    }
   
   private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
}

post方法中,首先判断attachInfo成员变量是否为空,如果不为空,则直接加入到对应的Handler消息队列中。否则走getRunQueue().post(action); 从Attach字面意思来理解,其实就可以知道,当View执行attach时,才会拿到mAttachInfo, 因此我们在onResume或者onCreate中调用view.post(),其实走的是getRunQueue().post(action)。

mAttachInfo在什么时机才会赋值。 两部分:
一个是ViewRootImpl第一次performTraversals的时候会分发
二个是ViewGroup.addView的时候会把自己的attachInfo传递给子view

10.4. 判断第一帧渲染完成的方法
10.4.1 通过onWindowFocusChanged方法

当Activity的窗口获得焦点并且为第一次运行时(即focus为true),可以认为第一帧已经渲染完成。但这种方法有一个缺点,即Activity可能并没有真正完全渲染出来,只是时间点很接近。

缺点:应用的Activity需要继承重写该方法的基类, 无法全局监听;这时Activity并没有真正渲染出来, 只是时间点很接近了。

10.4.2 通过监听DecorView的绘制事件

对DecorView添加onDrawListener或onPreDrawListener。当DecorView执行了onDraw方法时,可以认为第一帧绘制完成。这种方法可以全局实现ActivityLifeCallBack,无需Activity继承特定的基类。

10.4.3 使用Choreographer类

Choreographer类用于协调动画、输入和绘制的定时任务。它接收来自显示子系统的垂直同步(vsync)信号,并安排作为下一帧渲染工作的一部分的工作。通过注册一个监听垂直同步信号的回调,并在其回调方法doFrame中处理,可以知道第一个垂直同步信号的接收点,从而判断第一帧的绘制时间点。

10.4.4 向DecorView添加1像素的View, 在onDraw函数里监听下一个vsync事件认为渲染完成;

PS: 该View是DecorView的最后一个子View, 因为安卓是深度优先递归measure、layout、draw, 所以该View是最后一下执行onDraw函数。

注册ActivityLifecycleCallbacks, 在onCreate时向DecorView添加1像素的透明View,并在View执行onDraw时添加监听vsync, 以第一次执行vsync认为当前Activity渲染完成;

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                CustomerView customerView = new CustomerView(activity);
                ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(1, 1);
                customerView.setBackgroundColor(Color.TRANSPARENT);
                customerView.setLayoutParams(layoutParams);
                ((ViewGroup) activity.getWindow().getDecorView()).addView(customerView);
            }//....

            public class CustomerView extends View {
                public CustomerView(Context context) {
                    super(context);
                }

                public CustomerView(Context context, AttributeSet attrs) {
                    super(context, attrs);
                }

                public CustomerView(Context context, AttributeSet attrs, int defStyleAttr) {
                    super(context, attrs, defStyleAttr);
                }

                @Override
                public void onWindowFocusChanged(boolean hasWindowFocus) {
                    super.onWindowFocusChanged(hasWindowFocus);
                    Log.d("brycegao", "brycegao MainActivity CustomerView onWindowFocus: " + hasWindowFocus);
                }

                @Override
                protected void onDraw(Canvas canvas) {
                    super.onDraw(canvas);
                    Log.d("brycegao", "brycegao MainActivity CustomerView onDraw");//参考https://www.jianshu.com/p/e5f9fba7294aChoreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {@Override public void doFrame(long frameTimeNanos) {//监听vsync, 执行该回调时认为View已经渲染完成Log.d("brycegao", "brycegao MainActivity CustomerView vsync");//Choreographer.getInstance().postFrameCallback(this);  //计算帧率,预期每隔16ms执行一次}});}
                }
            }
        });
    }
}
10.4.5 View执行完成onDraw后post个Message, 当执行Message时认为第一帧结束; 在Activity的onCreate函数结束后发post个消息, 执行该Runnable时认为第一帧绘制完成。
  mHandler = new Handler();
  getWindow().getDecorView().post(new Runnable() {
     @Override 
     public void run() {
        Log.d("brycegao", "brycegao MainActivity decorview post");
        Thread.dumpStack();
        mHandler.post(new Runnable() {
           //认为第一帧绘制完成
           @Override 
           public void run() {
              Log.d("brycegao", "brycegao MainActivity process custom message: "+ tvContent.getWidth());
           }
        });
     }
  });

在onCreate函数执行setContentView后会执行2次performTrasvel, 从而在上面日志中看到2次onMeasure, 但onLayout、onDraw执行了1次。

注意: View.post方法和Handler的post方法是有区别的,
在onCreate函数里执行View.post方法并不会向MessageQueue里添加Message;
而是等待View执行了onAttachToWindow函数时向主线程MessageQueue里添加Message,
这时消息队列里已经有performTrasvel对应的Message。 所以也可以用这种方式判断。

  public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    //在dispatchOnAttachToWindow函数里赋值//onCreate函数里attachInfo为null
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }// Postpone the runnable until we know on which thread it needs to run.// Assume that the runnable will be successfully placed after
    attach.getRunQueue().post(action); //onCreate函数会走到这里,在dispatchOnAttach里运行
    return true;
}

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;//
    // 。。。// Transfer all pending runnables.
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }
    performCollectViewAttributes(mAttachInfo, visibility);
    onAttachedToWindow();
    //...
}
10.5 requestLayout、invalidate 和 postInvalidate方法

子view需要刷新时,就调用invalidate,找到自己的父view并通知他刷新自己
根据源码:
View首先通过成员变量mParent记录自己的父View,然后将attachInfo中保存的信息告诉父View来刷新自己

postInvalidate子线程中使用
invalidate主线程中调用

requestLayout() 和 invalidate区别以及原因:
requestLayout: requestLayout会直接递归调用父窗口的requestLayout,直到ViewRootImpl,然后触发peformTraversals,由于mLayoutRequested为true,会导致onMeasure和onLayout被调用。不一定会触发OnDraw。requestLayout触发onDraw可能是因为在在layout过程中发现l,t,r,b和以前不一样,那就会触发一次invalidate,所以触发了onDraw,也可能是因为别的原因导致mDirty非空(比如在跑动画)

invalidate: view的invalidate不会导致ViewRootImpl的invalidate被调用,而是递归调用父view的invalidateChildInParent,直到ViewRootImpl的invalidateChildInParent,然后触发peformTraversals,会导致当前view被重绘,由于mLayoutRequested为false,不会导致onMeasure和onLayout被调用,而OnDraw会被调用

postInvalidate: postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。

一般来说,只要刷新的时候就调用invalidate,需要重新measure就调用requestLayout,后面再跟个invalidate(为了保证重绘),这是我个人的理解

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值