Android面试题解(全)

本文深入探讨了Android开发中的关键概念,包括内存管理、进程优先级、启动模式、生命周期、事件分发、滑动冲突解决、数据持久化以及多线程间的通信。详细讲解了如何避免内存溢出(OOM)、理解Activity的启动模式、使用Handler和Binder进行进程间通信,以及如何处理滑动冲突,特别强调了单例模式在内存管理中的应用和生命周期的理解。此外,还介绍了ListView与RecyclerView的区别、滑动冲突的解决方案,以及如何在ViewPager中实现页面切换。通过对Android系统级别的理解,开发者能够更好地优化应用性能和用户体验。

Android

(一)RecyclerView 与 ListView 的主要区别:

  1. 使用
  2. 布局效果
  3. 局部刷新
  4. 嵌套滚动机制
  1. 布局效果对比
  2. 常用功能和API对比
  3. RecyclerView 和 ListView 在 Android L 引入嵌套滚动机制之后的对比
(1)简单使用

ListView

  1. 继承重写BaseAdapter类
  2. 自定义 ViewHolder 和 convertView 一起完成复用优化工作(在自定义的ADP、adapter中完成)

RecyclerView:

  1. 继承重写RecyclerView.Adapter与RecyclerView.ViewHolder
    • 在ViewHolder中传入View参数,获取布局实例findViewById()
    • onCreateViewHolder()--------加载布局,创建ViewHolder实例
    • onBindViewHolder()--------子项数据赋值
    • getItemCount()-------子项数
  2. 设置LayoutManager,以及layout的布局效果
    • LayoutManager-------LinearLayoutManager,GridLayoutManager,staggeredGridLayoutManager
(2)布局效果

**ListView:**布局单一,只有纵向效果

**RecyclerView:**布局效果丰富(3种);LayoutManager的API中自定义Layout:

(3)实现嵌套滚动机制
  • 在事件分发机制中,Touch事件在进行分发的时候,由父View向子View传递,一旦子View消费这个事件的话,那么接下来的事件分发的时候,父View将不接受,由子View进行处理;但是与Android的事件分发机制不同,嵌套滚动机制(Nested Scrolling)可以弥补这个不足,能让子View与父View同时处理这个Touch事件,主要实现在于NestedScrollingChild与NestedScrollingParent这两个接口;而在RecyclerView中,实现的是NestedScrollingChild,所以能实现嵌套滚动机制;

  • ListView就没有实现嵌套滚动机制;

(4)局部刷新

**RecyclerView:**notifyItemChanged(),局部刷新

ListView:

  • notifyDataSetChanged() ,全局刷新(每个item的数据都会重新加载一遍),非常消耗资源;

  • 如何实现局部刷新?

    • 在adapter里定义一个方法notifyDataSetChangedAt(View view,int position),然后在该方法处理逻辑(自定义局部刷新方法)

      public void notifyDataSetChangedAt(View view,int position) {
            if(view != null){
                int data = datas.get(position);
                data = data+1;
                datas.set(position,data);
                TextView textView = (TextView) view.findViewById(R.id.textview);
                textView.setText(String.valueOf(data));
            }
       }
      ————————————————
      //在外部调用,实现调用
      mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
           @Override
           public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
               /*int data = datas.get(position);
               data = data+1;
               datas.set(position,data);
               adapter.notifyDataSetChanged();*/----->刷新全部数据
               adapter.notifyDataSetChangedAt(view,position);----》刷新局部数据
           }
      });
      
      

【扩展】:View的重新绘制过程(尝试引导话题)

onMeasure()–>onLayout()–>onDraw()


(二)View的工作流程

measure:

  • View
  • ViewGroup

layout:

  • layout()
  • onLayout()

draw:

draw()中调用的4个方法


PhoneWindow是窗口类,继承自抽象类Window,也是唯一子类。WindowManager是Window管理接口,继承自ViewManager,他的唯一实现类是WindowManagerImpl。WindowManagerImpl并没有真正实现windowManager接口逻辑,而是把逻辑转给了WindowManagerGlobal,WindowManagerGlobal是全局单例。Window和View的联系通过ViewRootImpl桥梁,同时ViewRootImpl还负责管理view树、绘制view树、和WMS通信。WMS即WindowManagerService,是Window的真正管理类。

img

measure------->layout-------->draw

测量-------布局-------绘制

  1. 确定View的测量宽高

  2. 确定View的最终宽高和四个顶点的位置

  3. 将View绘制到屏幕上

  • measure:完成测量过程。由**(final型,不能重写)**measure()方法来完成

    • View的measure()方法中会调用View的onMeasure()方法
    • 如果是ViewGroup则会遍历调用所有的子元素的measure方法-----measureChildren()
      • 对于ViewGroup来说,除了完成自己的measure过程,还会遍历去调用所有子元素的measure方法,各个子元素在递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure方法,但是他提供了一个叫measureChildren的方法。
  • Layout:作用:ViewGroup用来确定子元素的位置

    • layout()方法确定View本身的位置
      • 通过setFrame()方法设定View的四个顶点的位置
      • 调用onLayout()方法:父容器确定子元素的位置
    • onLayout()方法确定所有子元素的位置。(onLayout()是在layout()中被调用个)
      • 在onLayout()方法中调用子元素的layout()方法
      • 层层传递,则可完成整个View树的layout过程
  • draw:将View绘制到屏幕上。

    这4个方法全包括在draw()方法里面调用

    • 绘制背景 drawBackground(canvas)
    • 绘制自己( onDraw(canvas) )
    • 绘制children(dispatchDraw(canvas) )
      • dispatchDraw会层层遍历所有子元素的draw方法
    • 绘制装饰(onDrawScrollerBars(canvas) )

View绘制的过程的传递通过dispatchDraw()来实现的,它会调用所有子元素的draw方法


那么发起绘制的入口在哪里呢?

在介绍发起绘制的入口之前,我们需要先了解Window,ViewRootImpl,DecorView之间的联系。

一个 Activity 包含一个Window,Window是一个抽象基类,是 Activity 和整个 View 系统交互的接口,只有一个子类实现类PhoneWindow,提供了一系列窗口的方法,比如设置背景,标题等。一个PhoneWindow 对应一个 DecorView 跟 一个 ViewRootImpl,DecorView 是ViewTree 里面的顶层布局,是继承于FrameLayout,包含两个子View,一个id=statusBarBackground 的 View 和 LineaLayout,LineaLayout 里面包含 title 跟 content,title就是平时用的TitleBar或者ActionBar,contenty也是 FrameLayout,activity通过 setContent()加载布局的时候加载到这个View上。ViewRootImpl 就是建立 DecorView 和 Window 之间的联系。

这三个阶段的核心入口是在 ViewRootImpl 类的 performTraversals() 方法中。绘制的入口是由ViewRootImpl的performTraversals方法来发起Measure,Layout,Draw等流程的。

private void performTraversals() {
    ......
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ......
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ......
    mView.draw(canvas);
    ......
 }

1. Measure

MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。

  • 对于View默认是测量很简单,大部分情况就是拿计算出来的MeasureSpec的size 当做最终测量的大小

一个MeasureSpec封装了从父容器传递给子容器的布局要求,这个MeasureSpec 封装的是父容器传递给子容器的布局要求,而不是父容器对子容器的布局要求,“传递” 两个字很重要,更精确的说法应该这个MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec。

  • 子View的LayoutParams其实就是我们在xml写的时候设置的layout_width和layout_height 转化而来的

父View的measure的过程会先测量子View,等子View测量结果出来后,再来测量自己,上面的measureChildWithMargins就是用来测量某个子View的

子View的LayoutParams:

  • 具体大小(childDimension)
  • match_parent
  • wrap_content

MeasureSpec一共有三种模式 :

  • UPSPECIFIED : 父容器对于子容器没有任何限制,子容器想要多大就多大(说明父View大小是确定的)

  • EXACTLY: 父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。

  • AT_MOST:子容器可以是声明大小内的任意大小(说明父View大小是不确定的)


(三)事件分发机制

  1. 三种方法的解析
  2. 具体流程分析(点击事件为例):图
  3. View处理事件优先级:3种的对比
  • dispatchTouchEvent()-------分发事件
  • onInterceptTouchEvent()--------判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件。只存在于ViewGroup中,为什么?)------->解释原因:结合传递顺序
    • 如果拦截事件,则调用onTouchEvent()来处理点击事件。
    • 不拦截-----> child.dispatchTouchEvent()
    • 在 ViewGroup 的dispatchTouchEvent()内部调用
  • onTouchEvent()-------处理点击事件,在dispatchTouchEvent()中调用,返回结果表示是否消耗当前事件。
//这个是伪代码表示,并不是实际的代码!!!!!
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){  //拦截事件
        consume = onTouchEvent(ev);
    } else {						//不拦截事件
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}
img

点击事件的传递过程:

Activity ----> ViewGroup----> View

顶级View接收到事件后,就会按照事件分发机制区分发事件。

img

对比上面的例子,Activity对应的就是项目经理,viewGroup就是你的老大,而你就相当于view了;

**Activity和View是没有onInterceptTouchEvent()**事件拦截这个方法的?

  • Activity是处于分发机制的最顶端,如果一开始就把事件拦截了,那么会导致整个屏幕都无法响应用户的操作

  • 而view处于事件分发的最末端,它不需要拦截,事件分发到View的时候,view能处理就处理,不处理就返回给他的父控件;

流程分析,以点击事件为例:

当用户点击了屏幕,首先Activity先监测到,事件先传递到Activity中,Activity通过它的dispatchTouchEvent将事件分发到phoneWindow,phonewindow则会调用superdispatchTouchEvent方法(抽象方法)的内部是调用了其内部类DecorView,而DecorView又会调用dispatchTouchEvent去进行事件分发,如果不拦截事件,那么就会继续下传到rootview**(ViewGroup),rootview中的操作是一样的,同样在dispatchTouchEvent内部调用onInterceptTouchEvent去判断是否拦截**(讨论),不拦截就会把事件分发给下一个viewgroupA,拦截就直接在onTouchEvent返回true,viewgroupA中做的判断也是一样,最后事件传递到view1,view1是最底层控件,不会有onInterceptTouchEvent,它的选择就只有处理或不处理(讨论),处理就在onTouchEvent进行处理并返回true,不处理的话事件也不会被销毁,view1这时会把事件回传,经过上述流程后回传给activity,如果Activity还不处理,那么这个事件才会被销毁
img

**【补充】**回答后面加粗的2,3,4

View处理事件优先级

OnTouchListener 【onTouch()】 > onTouchEvent()方法 > OnClickListener【onClick()】

OnTouchListener中有onTouch()方法

OnClickListener中有onClick()方法

先说onTouchEvent与setOnTouchListener中onTouch的区别
其逻辑可参看下面伪代码

public boolean dispatchTouchEvent(MotionEvent event){
    if(mOnTouchListener != null && mOnTouchListener.onTouch(this,event)){
        return true;
    }
    return onTouchEvent(event);
}

结合上面代码的含义与事件分发机制得出以下结论
(1)dispatchTouchEvent(MotionEvent event)返回true,表示本次事件被消耗,然后会有新事件传入。若返回false则不会有新事件传入。
(2)mOnTouchListener.onTouch方法返回的是true,onTouchEvent将不被执行。只有前者返回false,后者才会执行.
(3)只要onTouchEvent方法中的DOWN与UP事件都执行了,就会执行setOnClickListener中的onClick或者回调View.onClick,只是后者优先执行。
(4)总体优先级 setTouchListener > onTouchEvent > onClick > setClickListener
注意:DOWN或UP就是一个事件,不是DOWM+MOVE+UP才是一个事件,加起来是我们称作一系列事件
重写onTouchEvent时千万别删了super.onTouchEvent(event)——本人手贱,为此付出过惨痛代价。

  • 可见是首先执行OnTouchListener,之后为onTouchEvent,最后才执行onClickListener内的方法

  • 至于为什么OnTouchListener和onTouchEvent执行了两次,onClick()执行力一次

    • 是因为在DOWN和UP时两个方法都被调用
    • 至于onClickListener则只在UP的时候调用

(四)Android的Handler机制

看字节一面复盘

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值