当前位置:网站首页>framework源码读后感

framework源码读后感

2022-08-09 22:39:00 ximen502_

View部分

1.ViewParent

  今天查看了ViewGroup,ViewRootImpl和ViewParent的部分源代码,前面的两个类都实现了ViewParent接口。ViewGroup是一个抽象类,所以它无需实现ViewParent接口里面的方法,既然这样那么ViewGroup的子类应该会实现ViewParent里面的方法,就以requestLayout()方法为例找一找好了,于是我去找了FrameLayout类,搜索,竟然没有
requestLayout()方法的实现,这个可是具体类啊,竟然没有实现这个方法,不科学啊,不科学,然而这个类里面有一处对requestLayout()方法的调用,既然都没实现,那怎么可以调用呢?在ide里面鼠标点击过去一看,你猜怎么着?竟然跳转到了View类里面的requestLayout()方法,这个方法不是实现了某接口的方法,然而它却是ViewParent中方法
的一个同名方法,还能有这样的操作???
在这里插入图片描述

  (1)关于ViewParent的理解
这是一个接口,ViewGroup和ViewRootImpl类都实现了这个接口,ViewRootImpl类直接实现了requestLayout()方法,会对整个view树进行遍历执行measure,layout,draw操作。

  sdk里面的DecorView类的ViewParent变量指向的正是ViewRootImpl,requestLayout()方法层层向上(往view树根的方向)调用,当到达DecorView的requestLayout()方法调用的时候就会触发ViewRootImpl的requestLayout()方法,从而触发measure,layout,draw的三大流程。
  (2)WindowManager.AddView
在ActivityThread类,进行到activity生命周期的onResume()方法的时候,会有addView的
操作,这个调用流程大致是WindowManager=>WindowManagerImpl=>WindowManagerGlobal=>ViewRootImpl。
在ViewRootImpl中的setView方法里面,DecorView的ViewParent会被赋值为ViewRootImpl。

ViewRootImpl.java
// setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
// int userId){
    
// ...
// view.assignParent(this);
// ...
// }

  (3)这里面有个疑问就是,每一层普通View的ViewParent是在何时赋值的呢?
  activity#setContentView(int)=>PhoneWindow#setContentView
LayoutInflater#inflate=>LayoutInflater#rInflate,此方法中有个while循环,在调用viewGroup.addView()方法的时候,在addView方法的内部继续找ViewGroup#addViewInner找到了蛛丝马迹,ViewGroup在调用addViewInner方法的时候,在方法的内部对子view的ViewParent属性进行了赋值。

private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
    
    ...
    if (child.getParent() != null) {
    
        throw new IllegalStateException("The specified child already has a parent. " +
                "You must call removeView() on the child's parent first.");
    }
    ...
    // tell our children
    if (preventRequestLayout) {
    
        child.assignParent(this);
    } else {
    
        child.mParent = this;
    }
    ...
}

这段源码的追溯实在坎坷,如果能调试一下就更有把握确定这个调用顺序了。

2.Window

  Window是一个抽象类,它唯一的实现类是PhoneWindow类,它有一个WindowManager类型的属性,何时赋值的呢?
  这个属性的赋值是在ActivityThread中的handleLaunchActivity(…)方法=>performLaunchActivity(…)方法里面在反射实例化完成Activity和Application实例后,调用Activity的attach(…)方法的时候,在attach方法中创建了PhoneWindow对象,并调用了Window的setWindowManager方法对WindowManager属性进行了赋值。

3.View.invalidate

  invalidate()方法只会触发三大流程中的draw流程,measure和layout并不会触发,那么invalidate()调用后框架层是如何执行的呢?看了一篇博客,说类似requestLayout()方法层层向viewParent方向调用,最终会调用到ViewRootImpl.java中的方法里面去,这个时候就会执行一个scheduleTraversal()方法,进而触发draw流程。捋了一遍源码还没看太明白。还得再捋一遍。
  View调用自己的ViewParent属性的如下方法ViewParent invalidateChildInParent(int[] location, Rect r)继续返回ViewParent(子类)的ViewParent属性,这个方法在ViewGroup.java和ViewRootImpl.java中进行了实现,这样就会一直向view树的树根DecorView的ViewParent(ViewRootImpl),进而调用其ViewParent invalidateChildInParent(int[] location, Rect r)方法,从而触发三大流程的draw流程,就实现了view重新绘制的操作,基本捋明了。

4.activity

  四大组件activity, service, broadcast receiver, content provider,其中activity是负责页面展示的,但是其实activity本身是无法显示内容的,之所以能显示内容是因为activity里面有一个window属性,真正的显示内容是window实现的,这…为什么不直接用window作为显示内容的一大组件呢?还要多搞一个activity呢?不知道鲁宾是怎么想的?
  Activity的本质是什么呢?在源码中可以看到Activity也不过是个普通的java类,继承了ContextThemeWrapper类,实现了若干接口,嗯,应该是framework框架层赋予了它四大组件之一的地位。

x.sdk源码中的位运算

//00100000
//|
//00010000
//00110000
//0x30

取反和与运算
//~
//00110000
//11001111
//&
//11111111
//11001111
	static final int PFLAG_HAS_BOUNDS   = 0x00000010;
    static final int PFLAG_DRAWN        = 0x00000020;
    static final int PFLAG_FORCE_LAYOUT = 0x00001000;
    /* * 在android sdk framework源码里面有许多的位运算,其中赋值一般使用|或运算符, * 判断一般使用&与运算符,清空一般使用&和~位运算符. */
    void bit() {
    
// int r = PFLAG_DRAWN | PFLAG_HAS_BOUNDS;
// System.out.println(r);
// System.out.println(Integer.toHexString(r));
//
// r |= PFLAG_FORCE_LAYOUT;
//
// if ((r & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) {
    
// System.out.println("yes");
// } else {
    
// System.out.println("no");
// }

        int privateFlags = 0;
        //为某位赋值
        privateFlags |= (PFLAG_DRAWN | PFLAG_HAS_BOUNDS);

        System.out.println(privateFlags);

        //清空某位的值
        privateFlags &= ~(PFLAG_DRAWN | PFLAG_HAS_BOUNDS);
        System.out.println(privateFlags);

        //为某位赋值
        privateFlags |= PFLAG_FORCE_LAYOUT;
        System.out.println(privateFlags);

        //清空某位的值
        privateFlags &= ~PFLAG_FORCE_LAYOUT;
        System.out.println(privateFlags);
    }

开源项目

BlockCanary

  监视app的UI线程阻塞,原理是什么?
今天看了一篇BC的分析文章,感觉涨了不少知识,虽然有的代码分析还是没看太懂,但是感觉知道了不少以前不知道的东西,这么说来原理也挺简单的。就是在消息处理前记录一下开始的时间戳,消息处理结束后再记录一下时间戳,减去开始的时间戳,如果时间差大于预先设置的阈值就可以认为是发生了卡顿。

  原理就是这么个原理,但是这里有一个问题,如何在消息处理前和处理后插入自己的记录时间戳的代码呢?前方高能,这里看一下代码

public static void loop() {
    
    for (;;) {
    
        Message msg = queue.next(); // might block

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
    
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        ...
        msg.target.dispatchMessage(msg);
        ...

        if (logging != null) {
    
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
    }
}

  这里有2个if判断,条件是其中的Printer类型的变量logging是否为空,这个Printer是个接口,Looper类支持Printer自定义并传入,这样一来,我们就可以在回调方法的参数里面根据log的字符串来判断何时开始何时结束并记录和计算时间差了,大佬就是大佬啊,佩服!佩服!

public interface Printer {
    
    void println(String x);
}

public void setMessageLogging(@Nullable Printer printer) {
    
    mLogging = printer;
}
原网站

版权声明
本文为[ximen502_]所创,转载请带上原文链接,感谢
https://blog.csdn.net/ximen250/article/details/126120577