2019Android面试总结

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);
        }

    }

12.说一下ThreadLocal为什么是线程安全的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值