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);
}
//View.java
private LongSparseLongArray mMeasureCache;
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...省略
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
//int存储的宽高放入一个long类型中,使用了移位运算符。与运算符&这里,主要是用来清空高32位的符号位,负数的情况符号位为1
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
开源项目
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;
}