2 Star 20 Fork 43

胡老皮 / 2023年Android面试题合集解析

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
Android UI绘制相关面试题.md 15.92 KB
一键复制 编辑 原始数据 按行查看 历史
胡老皮 提交于 2023-02-07 03:27 . Android UI绘制相关面试题

这套Android面试题汇总大全,希望对大家有帮助哈~

全部答案,更新日期:2月6日,直接下载吧

全部答案,更新日期:2023年2月6日,直接下载吧!

下载链接:高清全答案解析,累计935页+大厂面试题 PDF

1、Android 补间动画和属性动画的区别?

特性 补间动画 属性动画
view动画 支持 支持
非view动画 不支持 支持
可扩展性和灵活性
view属性是否变化 无变化 发生变化
复制动画能力 局限 良好
场景应用范围 一般 满足大部分应用场景

2、简述一下 Android 中 UI 的刷新机制?

界面刷新的本质流程

  1. 通过ViewRootImpl的 scheduleTraversals()进行界面的三大流程。
  2. 调用到scheduleTraversals()时不会立即执行,而是将该操作保存到待执行队列中。并给底层的刷新信号注册监听。
  3. 当 VSYNC信号到来时,会从待执行队列中取出对应的scheduleTraversals()操作,并将其加入到主线程 的消息队列中。
  4. 主线程从 消息队列中取出并执行三大流程:onMeasure()-onLayout()-onDraw() 同步屏障的作用
  1. 同步屏障用于阻 塞 住所有的同步消息(底层VSYNC的回调onVsync方法提交的消息是异步消息)
  2. 用于保证界面刷新功能的performTraversals()的优先执行。 同步屏障的原理?
  1. 主线程的Looper会一直循环调用MessageQueue的next方法并且取出队列头部的Message执行,遇到同步屏障(一种特殊消息)后会去寻找异步消息执行。如果没有找到异步消息就会一直阻塞下去,除非将同步屏障取出,否则永远不会执行同步消息。
  2. 界面刷新操作是异步消息,具有最高优先级
  3. 我们发送的消息是同步消息,再多耗时操作也不会影响UI的刷新操作

3、LinearLayout, FrameLayout,RelativeLayout 哪个效率高, 为什么?

对于比较三者的效率那肯定是要在相同布局条件下比较绘制的流畅度及绘制过程,在这里流畅度不好表达,并且受其他外部因素干扰比较多,比如CPU、GPU等等,我说下在绘制过程中的比较,

1、Fragment是从上到下的一个堆 叠的方式布局的,那当然是绘制速度最快,只需要将本身绘制出来即可,但是由于它的绘制方式导致在复杂场景中直接是不能使用的,所以工作效率来说Fragment仅使用于 单一场景,

2、LinearLayout 在两个方向上绘制的布局,在工作中使用页比较多,绘制的时候只需要按照指定的方向绘制,绘制效率比Fragment要慢,但使用场景比较多,

3、RelativeLayout 它的没个子控件都是需要相对的其他控件来计算,按照View树的绘制流程、在不同的分支上要进行计算相对应的位置,绘制效率最低,但是一般工作中的 布局使用较多,所以说这三者之间效率分开来讲个有优势、不足,那一起来讲也是有优势、不足,所以不能绝对的区分三者的效率,好马用好铵 那需求来说

4、Window和DecorView是什么?DecorView又是如何和Window建立联系的?

Window是WindowManager 最顶层的视图,它负责背景(窗口背景)、Title之类的标准的UI元素, Window是一个抽象类,整个Android系统中, PhoneWindow是 Window的唯一实现类。至于 DecorView,它是一个顶级 View,内部会包含一个竖直方向的LinearLayout,这个有上下两部分,分为 titlebar contentParent两个子元素,contentParent的id是content,而我们自定义的 的布局就是contentParent 里面的一个子元素。View 层的所有事件都要先经过 DecorView后才传递给我们的 View。DecorView是 Window 的一个变量,即 DecorView 作为一切视图的根布局,被 Window 所持有,我们自定义的View 会被添加到 DecorView,而DecorView又会被添加到Window 中加载和渲染显示。此处放一张它们的简单内部层次结构图:

在这里插入图片描述

5、谈谈Android的事件分发机制?

当点击的时候,会先调用顶级viewgroup的dispatchTouchEvent,如果顶级的viewgroup拦截了此事件(onInterceptTouchEvent返回true),则此事件序列 由顶级viewgroup处理。如果顶级viewgroup设置setOnTouchListener,则会回调接口中的onTouch,此时顶级的viewgroup中的onTouchEvent不再回调,如果不设 置setOnTouchListener则onTouchEvent会回调。如果顶级viewgroup设置setOnClickListener,则会回调接口中的onClick。如果顶级viewgroup不拦截事件,事件就会向下传递给他的子view,然后子view就会调用它的dispatchTouchEvent方法。

6、谈谈自定义View的流程?

1 安卓View的绘制流程(比较简单,想要深入的可以去看源码) 2,安卓自定义View的绘制步骤

自定义View是一个老生常谈的问题,对于一个Android开发者来说是必须掌握的知识点,也是Android开发进阶的必经之路。

要想安卓理解自定义View的流程,首先我们要了解View的绘制流程。分析之前,我们先来看底下面这张图:

View的绘制流程

在这里插入图片描述

DecorView是一个应用窗口的根容器,它本质上是一个FrameLayout。DecorView有唯一一个子View,它是一个垂直LinearLayout,包含两个子元素,一个是TitleView(ActionBar的容器),另一个是ContentView(窗口内容 的容器)。关于ContentView,它是一个FrameLayout(android.R.id.content),我们平常用的setContentView 就是设置它的子View。上图还表达了每个Activity都与一个Window(具体来说是PhoneWindow)相关联,用户界面则由Window所承载。

ViewRoot

在介绍View的绘制前,首先我们需要知道是谁负责执行View绘制的整个流程。实际上,View的绘制是由ViewRoot 来负责的。每个应用程序窗口的decorView都有一个与之关联的ViewRoot对象,这种关联关系是由WindowManager来维护的。

那么decorView与ViewRoot的关联关系是在什么时候建立 的呢?

答案是Activity启动时,ActivityThread.handleResumeActivity()方法中建立了它们两者的关联关系。这里我们不具体分析它们建立关联的时机与方式,感兴趣的同学可以参考相关源码。下面我们 直入主题,分析一下ViewRoot是如何完成View的绘制的。

View绘制的起点

当建立好了decorView与ViewRoot的关联后,ViewRoot类的requestLayout()方法会被调用,以完成应用程序用户界面的初次布局。实际被调用的是ViewRootImpl类的requestLayout()方法,这个方法的源码如下:

@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 检查发起布局请求的线程是否为主线程
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}

上面的方法中调用了scheduleTraversals()方法来调度一次 完成的绘制流程,该方法会向主线程发送一个“遍历”消息, 最终会导致ViewRootImpl的performTraversals()方法被调用。下面,我们以performTraversals()为起点,来分析View的整个绘制流程。

三个阶段

View的整个绘制流程可以分为以下三个阶段:

  • measure: 判断是否需要重新计算View的大小,需要的话则计算;
  • layout: 判断是否需要重新计算View的位置,需要的话则计算;
  • draw: 判断是否需要重新绘制View,需要的话则重绘制。

这三个子阶段可以用下图来描述:

在这里插入图片描述

measure阶段

此阶段的目的是计算出控件树中的各个控件要显示其内容的话,需要多大尺寸。起点是ViewRootImpl的

measureHierarchy()方法,这个方法的源码如下:

private boolean measureHierarchy(final View
host, final WindowManager.LayoutParams lp, final
Resources res,
final int desiredWindowWidth, final int
desiredWindowHeight) {
// 传入的desiredWindowXxx为窗口尺寸
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
. . .boolean goodMeasure = false;
if (!goodMeasure)
{ childWidthMeasureSpec =
getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec =
getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight !=
host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
return windowSizeMayChange;}

上面的代码中调用getRootMeasureSpec()方法来获取根MeasureSpec,这个根MeasureSpec代表了对decorView 的宽高的约束信息。具体的内部方法您可以直接再AS进行查看,不再赘述。

layout阶段

layout阶段的基本思想也是由根View开始,递归地完成整个控件树的布局(layout)工作。

View.layout()

我们把对decorView的layout()方法的调用作为布局整个控件树的起点,实际上调用的是View类的layout()方法,源码如下:

public void layout(int l, int t, int r, int
b) {
// l为本View左边缘与父View左边缘的距离
// t为本View上边缘与父View上边缘的距离
// r为本View右边缘与父View左边缘的距离
// b为本View下边缘与父View上边缘的距离
. . . boolean changed =
isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags &
PFLAG_LAYOUT_REQUIRED) ==
PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
. . .
}. . . }

这个方法会调用setFrame()方法来设置View的mLeft、mTop、mRight和mBottom四个参数,这四个参数描述了View相对其父View的位置(分别赋值为l,t,r,b),在setFrame()方法中会判断View的位置是否发生了改变,若发生了改变,则需要对子View进行重新布局,对子View的局部是通过onLayout()方法实现了。由于普通View( 非ViewGroup)不含子View,所以View类的onLayout()方法为空。因此接下来,您可以通过源码查看ViewGroup类的onLayout()方法的实现,不再赘述。

draw阶段

对于本阶段的分析,我们以decorView.draw()作为分析的起点,也就是View.draw()方法,它的源码如下:

public void draw(Canvas canvas) {
. . .
// 绘制背景,只有dirtyOpaque为false时才进行绘
制,下同
int saveCount;
if (!dirtyOpaque)
{ drawBackground(canvas
);
}
. . .
// 绘制自身内容
if (!dirtyOpaque) onDraw(canvas);
// 绘制子View
dispatchDraw(canvas);
. . .
// 绘制滚动条等
onDrawForeground(canvas);
}

简单起见,在上面的代码中我们省略了实现滑动时渐变边框效果相关的逻辑。实际上,View类的onDraw()方法为 空,因为每个View绘制自身的方式都不尽相同,对于decorView来说,由于它是容器View,所以它本身并没有什么要绘制的。dispatchDraw()方法用于绘制子View,显 然普通View(非ViewGroup)并不能包含子View,所以View类中这个方法的实现为空。

ViewGroup类的dispatchDraw()方法中会依次调用drawChild()方法来绘制子View,drawChild()方法的源码如下:

protected boolean drawChild(Canvas canvas,
View child, long drawingTime) {
return child.draw(canvas, this,
drawingTime);
}

这个方法调用了View.draw(Canvas, ViewGroup,long)方法来对子View进行绘制。在draw(Canvas,ViewGroup, long)方法中,首先对canvas进行了一系列变换,以变换到将要被绘制的View的坐标系下。完成对canvas的变换后, 便会调用View.draw(Canvas)方法进行实际的绘制工作,此 时传入的canvas为经过变换的,在将被绘制View的坐标系下的canvas。

进入到View.draw(Canvas)方法后,会向之前介绍的一样, 执行以下几步:

  • 绘制背景;
  • 通过onDraw()绘制自身内容;
  • 通过dispatchDraw()绘制子View; 绘制滚动条

至此,整个View的绘制流程我们就分析完了。

7、针对RecyclerView你做了哪些优化?

1 onBindViewHolder 这个方法含义应该都知道是绑定数据,并且是在UI线程,所以要尽量在这个方法中少做一些业务处理

2 数据优化 采用androidSupport 包下的DIffUtil集合工具类结合RV分页加载会更加友好,节省性能

3item优化 减少item的View的层级,(pps:当然推荐把一个item自定义成一个View,如果有能力的话),如果item的高度固定的话可以设置setHasFixedSize(true),避免requestLayout浪费资源

4 使用RecycledViewPool RecycledViewPool是对item进行缓存的,item相同的不同RV可以才使用这种方式进行性能提升

5 Prefetch预取这是在RV25.1.0及以上添加的新功能

6 资源回收 通过重写RecyclerView.onViewRecycled(holder)来合理的 回收资源。

8、谈谈如何优化ListView?

ViewHolder什么的持有View预加载/懒加载数据什么的大招:用RecyclerView替换ListView 绝招:直接删除控件

9、谈谈自定义LayoutManager的流程?

1.确定Itemview的LayoutParamsgenerateDefaultLayoutParams

2.确定所有itemview在recyclerview的位置,并且回收和复 用itemviewonLayoutChildren

3.添加滑动canScrollVertically

10、什么是 RemoteViews?使用场景有哪些?

RemoteViews RemoteViews翻译过来就是远程视图.顾名思义,RemoteViews不是当前进程的View,是属于SystemServer进程.应用程序与RemoteViews之间依赖Binder实现了进程间通信. 用法通常是在通知栏

//1.创建RemoteViews实例
RemoteViews mRemoteViews=new
RemoteViews("com.example.remoteviewdemo", R.layout.remoteview_layout);
//2.构建一个打开Activity的PendingIntent Intent
intent=new
Intent(MainActivity.this,MainActivity.class);
PendingIntent
mPendingIntent=PendingIntent.getActivity(Main
Activity.this, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT);
//3.创建一个Notification
mNotification = new
Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(mPendingIntent)
.setContent(mRemoteViews)
.build();
//4. 获 取 NotificationManager
manager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE
);
Button button1 = (Button)
findViewById(R.id.button1);
button1.setOnClickListener(new
OnClickListener() {
@Override
public void onClick(View v) {
//弹出通知
manager.notify(1, mNotification);}
});

11、谈一谈获取View宽高的几种方法?

12、谈一谈插值器和估值器?

13、getDimension、getDimensionPixelOffset 和getDimensionPixelSize 三者的区别?

14、请谈谈源码中StaticLayout的用法和应用场景?

15、有用过ConstraintLayout吗?它有哪些特点?

16、关于LayoutInflater,它是如何通过inflate 方法获取到具体View的?

17、谈一谈Fragment懒加载?

18、谈谈RecyclerView的缓存机制?

19、请谈谈View.inflate和LayoutInflater.inflate的区别?

20、请谈谈invalidate()和postInvalidate() 方法的区别和应用场景?

由于篇幅原因,此处仅展示部分内容,查看更多Android面试题点击直接查看

全部答案,更新日期:2023年2月6日,直接下载吧!

下载链接:全部答案,整理好了

新增:高清全答案解析,累计935页+大厂面试题 PDF

1
https://gitee.com/hu-laopi/NewDevBooks.git
git@gitee.com:hu-laopi/NewDevBooks.git
hu-laopi
NewDevBooks
2023年Android面试题合集解析
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891