2019面试总结
1.View的绘制流程
1.源码角度分析:
View首先会创建
2.步骤角度分析:
View的绘制流程大致分为为三步,onMeasure,onLayout,onDraw,类似一个地递归过程,从上到下一次进行绘制。
onMeasure:方法是进行测量,通过循环遍历每一个子View,获取每一个子View的宽高,子View的宽高是由父容器的测量规格(SpecMode)和子View的LayoutParams来确定,MeasureSpec测量规格是一个32为int值,他的高2为代表的是测量模式(SpecMode),后30为代表是测量大小(SpecSize),测量模式分为三种:
第一种:EXACTLY,他表示父容器给子View指定了大小,对应的是match_parent或者是一个具体值
第二种:AT_MOST,他表示父容器给子View指定了一个可用大小,只要子View不超过这个大小就可以
第三种:UNSPECIFIED,他表示父容器没有对子View左任何限制,子View想要多大就可以多大
最终通过每一个子View的宽高来计算自己的宽高。
onLayout:onLayout是进行摆放每一个子View,前提是不是GONE,该方法和onMeasure类似,通过循环遍历获取每一个子View,获取每一个子View的LayoutParams,确定每一个子View的位置,进行摆放
onDraw:该方法是最后一个方法,进行绘制,绘制背景,绘制自己,绘制每一个子View,绘制装饰
总结:
onMeasure:循环调用childView.measure(宽的规格,高的规格)
onLayout:循环调用setChildFrame方法,方法中调用childView.layout(左,上,右,下)
onDraw:1.绘制自己(背景,内容) drawBackground(canvas);
2.绘制子View dispatchDraw(canvas);
3.绘制装饰 onDrawForeground(canvas);
2.事件分发机制
2.1.View的事件分发:
View的事件分发两个重要的方法:
1.dispatchTouchEvent:事件分发
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
//ListenerInfo 存放所有listen的信息,如OnTouchListener,onClickListener等
ListenerInfo li = mListenerInfo;
//如果li不为空, 是否是enable,且还有onTouchListener.onTouch方法返回true。 最终返回true
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果result=true 就不会调用View的onTouchEvent方法
//如果result=false 就会调用View的onTouchEvent方法
//目前还没看到我们点击事件
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
2.onTouchEvent:事件消费
public boolean onTouchEvent(MotionEvent event) {
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
...
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//调用了OnClick点击事件
performClickInternal();
}
}
}
mIgnoreNextUpEvent = false;
break;
}
return false;
}
//点击事件
public boolean performClick() {
...
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
现象1.当onTouchListener onTouchEvent onClickListener三者都有的情况下 前提onTouchListener返回false
执行结果:
onTouchListener.DOWN—>onTouchEvent.DOWN
onTouchListener.MOVE—>onTouchEvent.MOVE
onTouchListener.UP—>onTouchEvent.UP
onClickListener
现象2.当onTouchListener onTouchEvent onClickListener三者都有的情况下 前提onTouchListener返回true
执行结果:
onTouchListener.DOWN
onTouchListener.MOVE
onTouchListener.UP
现象3.当onTouchListener onTouchEvent onClickListener三者都有的情况下 前提onTouchListener返回false,
onTouchEvent返回true
执行结果:
onTouchListener.DOWN—>onTouchEvent.DOWN
onTouchListener.MOVE—>onTouchEvent.MOVE
onTouchListener.UP—>onTouchEvent.UP
现象4.当onTouchListener onTouchEvent onClickListener三者都有的情况下 前提onTouchListener返回true
执行结果:
onTouchListener.DOWN
onTouchListener.MOVE
onTouchListener.UP
现象5.当dispatchTouchEvent onTouchListener onTouchEvent onClickListener四者都有的情况下 dispatchTouchEvent返回true
执行结果:
什么都不会执行。
总结:
1.当执行onTouchListener前提,View的dispatchTouchEvent的super.dispatchTouchEvent必须调用
2.当执行onTouchEvent前提,View的onTouchListener的onTouch方法返回false
3.当执行onClickListener前提,View的onTouchEvent的super.onTouchEvent方法必须调用
——————————————————————————————————————————————
2.2.ViewGroup的事件分发
ViewGroup的事件分发三个重要的方法:
1.dispatchTouchEvent:事件分发
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 清除TouchTargets mFirstTouchTarget置为空
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//检查拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 父容器不拦截,默认是false 调用onInterceptTouchEvent
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
}
TouchTarget newTouchTarget = null;
//是否取消了canceled = false,默认不拦截 intercepted = false
if (!canceled && !intercepted) {
...
final int childrenCount = mChildrenCount;
//上边newTouchTarget= null, childrenCount不等于0
if (newTouchTarget == null && childrenCount != 0) {
final View[] children = mChildren;
//倒序循环子View
for (int i = childrenCount - 1; i >= 0; i--) {
...
newTouchTarget = getTouchTarget(child);
//重要方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
break;
}
}
}
}
}
...
return handled;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//child为空,就调用父类View的dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
//child不为空 就会调用child.dispatchTouchEvent(event)
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
return handled;
}
2.onInterceptTouchEvent:事件消费
默认情况下返回false。
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
3.onTouchEvent:事件消费
public boolean onTouchEvent(MotionEvent event) {
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
...
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//调用了OnClick点击事件
performClickInternal();
}
}
}
mIgnoreNextUpEvent = false;
break;
}
return false;
}
//点击事件
public boolean performClick() {
...
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
总结:
1.ViewGroup的dispatchTouchEvent方法返回true,还是false都会调用onInterceptTouchEvent方法。
2.onInterceptTouchEvent如果返回true,表示拦截事件,事件就不会向下进行传递给子View,就会调用ViewGroup.onTouchEvent方法。否则就会调用子View的dispatchTouchEvent方法。
3.onInterceptTouchEvent默认返回false,表示不拦截,都传递给子View
4.如果所有子View都没有进行消费事件,最终事件会向上进行传递,传递给父容器的onTouchEvent。
3.postInvalidate和Invalidate的区别
1.两者最终都是调用Invalidate方法来更新UI。区别在于invalidate()方法是在UI线程自身中使用,而postInvalidate()是非UI线程中使用。
2.postInvalidate他可以在非UI线程中进行更新UI,他通过ViewRootImpl中的Handler切换线程发送消息,最总还是调用了Invalidate方法。
4.Handler消息机制原理
Handler的运行需要通过MessagerQueue和Looper对象来支撑,Handler的作用是将一个任务切换到指定的线程中。
1.通过Loope.prepare()创建Looper对象和MessageQueue对象,通过ThreadLocal来保证每一个线程只有一个Looper对象,ThreadLocal保证了每个线程Looper的唯一性,ThreadLocal是线程的内部存储类, 他可以在指定的线程中存储数据,也只有在指定的线程中获取到数据,每一个线程对应一个Looper,他是通过set,get方法来进行获取,内部通过ThreadLocaLMap来进行存储,key就是当前的Thread线程,value就是Looper对象。
2.handler通过sendMessage发送消息。将消息添加到MessagerQueue中,通过msg.when来进行排序。
3.通过Looper.loop开启消息循环,获取Looper和MessagerQueue对象,通过while循环来获取消息队列中的每一个消息。通过调用handler.dispatchMessage调用handlerMessage方法。
5.MessagerQueue为什么采用单链表方式存储Message?
链表增加删除速度快,而数组查询快
3.1 LinkedList采用的就是链表
3.2 ArrayList才用的就是数组
6.属性动画的原理
动画分为三种:
1.帧动画
帧动画是将每一张图片资源连贯播放,形成动画效果。
2.补间动画
AlphaAnimation(透明度,0~1)
ScaleAnimation(大小缩放,X、Y轴缩放,还包括缩放中心pivotX、pivotY)
TranslationAnimation(位移,X、Y轴位移)
RotateAnimation(旋转,包括缩放中心pivotX、pivotY)
只要定义开始、结束的帧,和指定动画持续时间,开启动画。
3.属性动画
在一定的时间内,通过不断修改对象的属性值,不断刷新视图绘制,从而达到动画效果。内部使用的是通过拼接属性通过set+属性进行拼接。
7.性能优化
1.启动优化
减少Application和MainActivity中的初始化操作。当我们打开一个应用程序,首先会创建我们的Application和MainActivity,减少onCreate方法中的初始化操作,减少我们应用程序启动的时间。
冷气动和热启动:
冷启动:在我们启动一个应用程序时,系统中没有任何该应用的进程信息,通俗点就是第一次启动,或者应用进程被系统给杀死后重新启动
热启动:在我们启动一个应用程序时,系统中存在该应用的进程信息,通俗点就是已经打开过了,只是退到后台重新打开
区别:
1.冷启动是系统中没有该应用的进程,会创建和初始化Application和MainActivity,进行一些测量和绘制等操作。
2.热启动是系统中已经存在该应用进程,只会创建和初始化MainActivity进行一些测量和绘制等操作。
3.一个应用的创建到销毁只需要创建和初始化一次Application
2.布局优化
1.可以使用include,通用的布局可以使用include进行引入,开发过程中尽量减少设置Visibility为GONE,可以使用INVISIBLE,减少我们系统View的绘制流程,onMeasure,onLayout
2.可以使用merge,减少布局的层级嵌套,他需要和include结合使用。
3.可以使用ViewStub,布局中如果初始化时不需要显示的,但又不得不在我们布局中事先定义好,设置为invisible和gone,这样初始化的时候我们还是会加载该内容,我们可以使用ViewStub,初始化的时候不会进行加载,具有懒加载功能,当我们需要的时候才进行加载。
3.绘制优化
1.降低onDraw()的复杂度,方法中减少对象的创建,因为该方法会多次被调用,大量创建对象会导致内存抖动,频繁的调用GC,因为GC是执行在我们主线程中的,当我们调用的时候也是会卡顿的
2.减少布局的嵌套,将我们系统Window的背景移除,减少不必要的背景绘制,可以使用裁剪去除不必要的背景
4.OOM优化
OOM:内存溢出,指我们应用程序申请的内存大小超过了系统所能给的大小。
OOM原因:
1.大量内存泄漏导致内存溢出。
原因:当某个对象不在使用,某些地方仍持有该对象的引用,导致该对象无法被回收,导致内存泄漏。
解决:可以使用软引用,当内存不足的时候会被进行回收。
2.内存抖动造成内存移除,内存抖动是在短时间内大量的创建对象和销毁对象,避免频繁的创建和销毁对象。
解决:可以使用线程池,当我们没有任务时,在一定的时间内线程才会被销毁。
3.加载大量的图片。占用内存过大
解决:可以降低图片的采样率,将图片进行等比例压缩。
4.内部类持有外部类的引用,导致无法被回收。可以使用静态内部类。
5.资源的回收问题,当我们页面销毁时将我们的资源进行回收。
5.ANR优化
ANR(Application Not Responding):应用程序无响应
原因:
1.主线程中做了耗时操作(比如访问网络,IO操作等)
2.Activity中5s内未完成用户的输入事件
3.BroadCastReceiver的onReceiver方法10s内未完成
4.Service中20s内执行完成
Activity中所有生命周期的回掉方法都之行在主线程中
BroadCastReceiver的onReceiver也是执行在主线程中,所有不能做耗时操作。
Service执行在主线程中,所以不能做耗时操作,可以使用IntentSerice,它内部使用了HandlerThread + Thread来进行异步消息传递,当任务执行完成时,他也会自动销毁,不需要我们手动调用stopService
解决:
1.可以使用Handler消息机制,来进行线程的切换。
2.避免在Activity的onCreate和onResume生命周期中做耗时操作。
3.开启子线程来处理IO,网路等耗时操作
如何定位ANR:
发生ANR会在开发机器上查看 /data/anr/traces.text 文件。
8.说下你对线程池的理解,如何创建一个线程池与使用。
线程的执行时间:t = t1(线程创建时间) + t2( run执行时间) + t3(线程销毁时间)
线程池:线程池是用来解决线程的反复创建和销毁,可以复用线程减少线程的创建,当大量创建和销毁线程时可以使用线程池。
线程池创建:
//缓存队列 设置最大数量
private BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(40);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5, //核心线程数量,就是线程池中核心线程数量
10, //线程池最大线程数,就是线程池中最大线程数
30, //线程存活时间
TimeUnit.SECONDS, //时间单位 秒,毫秒等
sPoolWorkQueue, //缓存队列
new ThreadFactory() { //线程工厂,当我们线程池中需要创建线程就会调用newThread来创建, 为什恶魔不写死,因为有时候我们要给线程设置参数name
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r,"自己线程的名字");
thread.setDaemon(false); //设置不是守护线程
//return thread;
return new Thread(r);
}
});
}
线程池使用:
for (int i = 0; i < 30; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("下载完成"+Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
};
//加入线程队列寻找合适的时机去执行
threadPoolExecutor.execute(runnable);
}
9.View.inflate,LayoutInflater.from三个的区别,哪个方法最好?
1. View.inflate(context,R.layout.item_recycler,null);
2. LayoutInflater.from(context).inflate(R.layout.item_recycler,parent);
3. LayoutInflater.from(context).inflate(R.layout.item_recycler,parent,false);
10.java语言的特点与是OOP思想
特点:
1.面向
11.ScrollView嵌套ListView为什么显示不全
1.首先我们要知道View的绘制流程 onMeasure绘制,首先或通过MeasureSpec.getMode获取宽高的测量模式,AT_MOST对应wrap_content,EXACTLY对应的是具体的值100dp或者match_parent
,还有一个UNSPECIFIED对应就是尽可能大,这个很少使用
我们知道了这个我们查看一下ScrollView的源码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//1.查看父类的onMeasre方法
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!mFillViewport) {
return;
}
...
}
//3.ScrollView中重写父容器的方法
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
heightUsed;
//4.传递给子孩子的测量模式为UNSPECIFIED尽可能大,再看一下嵌套的ListView的源码
final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
ScrollView父类FrameLayout源码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//2.遍历每一个子View,看一下这个个方法,这个方法ScrollView中重写了父容器的方法我们回到ScrollView的这个方法
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
ListView源码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
...
//5.获取高的模式如果是UNSPECIFIED,高度值就padding上下值+一个子孩子的值,所以只会出现一个,就显示不全了。
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
if (heightMode == MeasureSpec.AT_MOST) {
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
}