2021 年最新 Android 开发岗面试笔试总结,android 开发视频播放器
activity界面显示流程
:activity 启动后,不会立马去显示界面上的 view,而是等到 onResume 的时候才会真正显示 view 的时机,首先会触发 windowManager.addView 方法,在该方法中触发代理对象 WindowManagerGlobal 的 addView 方法,代理对象的 addView 方法中创建了 viewRootImpl,将 setContentView 中创建的 decorView 通过 viewRootImpl 的 setView 方法放到了 viewRootImpl 中,最终经过 viewRootImpl 一系列的方法最终调用 performTraversals 方法。
view的绘制
:主要指 view 的 onMeasure、onLayout、onDraw 几个方法,其实要了解几个方法,需要追溯到 android 中本身界面的结构,首先整体是一个 PhoneWindow 的对象,然后是一个 DecorView,DecorView 里面包括一个 ViewStub 的 ToolBar,然后下面是一个 FramLayout,也就是我们经常在 Activity 中 setContentView 中的 content 内容。说完了 android 界面的结构,下面就是说下如何绘制的,绘制首先是触发到 DecorView 的 onMeasure 方法,它的测量规则包含了手机屏的宽高,并且测量模式是 MeasureSpec.EXACTLY。所以这里明白了 DecorView(FrameLayout)的测量参数是什么意思了,紧接着就是测量它下面的 ViewGroup 了,其中 ViewGroup 里面有个 measureChild 方法去测量孩子,这里会问到几种父布局的测量模式和子 View 的测量模式组合:
| ViewGroup 的测量 mode | MeasureSpec.EXACTLY | MeasureSpec.AT_MOST | MeasureSpec.UNSPECIFIED |
| --- | --- | --- | --- |
| childDimension>0 | size=childDimension;mode=EXACTLY | size= childDimension;mode=EXACTLY | size= childDimension;mode=EXACTLY |
| childDimension == LayoutParams.MATCH_PARENT | size=Viewgroup 的 size;mode=EXACTLY | size=Viewgroup 的 size;mode=AT_MOST | size=Viewgroup 的 size;mode=UNSPECIFIED |
| childDimension == LayoutParams.WRAP_CONTENT | size=Viewgroup 的 size;mode=AT_MOST | size=Viewgroup 的 size;mode=AT_MOST | size=Viewgroup 的 size;mode=UNSPECIFIED |
测量处理完了之后,紧接着就是 View 的 onLayout,其中 onLayout 的作用是给 View 固定好位置,该方法传进来的几个参数是相对于自己的 parent 的位置,左上角是(0,0)的坐标。最后就是我们的 onDraw,该方法是我们需要在画布上画东西的方法,一般包括画背景、
画图层等等。
App 启动流程
从 Linux 内核系统到 init 进程的分裂,以及后面会启动一个叫
Zygote
的进程开始,而 Zygote 会分裂出系统的核心服务进程SystemServer
,也就是SystemServer
里面包括了底层的ActivityManagerService
、PackageManagerService
、WindowManagerService
等,这些核心服务都是通过 Zygote.init 启动的,ActivityManagerService
就是我们后面通过 binder 的 ipc 通信机制来与客户端ActivityThread
建立通信的。当我们点击了应用之后,系统的
Launcher
应用会通过startActivity
的方式启动应用,而 Intent 的获取会经过如下几部:
(1) ActivityManagerService
会通过PackageManager
的resolveIntent()
收集这个intent
对象的指向信息。
(2)指向信息被存储在一个intent
对象中。
(3)下面重要的一步是通过grantUriPermissionLocked()
方法来验证用户是否有足够的权限去调用该intent
对象指向的Activity
。
(4)如果有权限, ActivityManagerService
会检查并在新的 task 中启动目标 activity.
(5)现在, 是时候检查这个进程的ProcessRecord
是否存在了。
所以如果
ProcessRecord
不是 null,ActivityManagerService
会创建新的进程来实例化该activity
。ActivityManagerService
调用startProcessLocked()
方法来创建新的进程, 该方法会通过前面讲到的 socket 通道传递参数给 Zygote 进程.Zygote
孵化自身, 并调用ZygoteInit.main()
方法来实例化ActivityThread
对象并最终返回新进程的 pid。随后就是我们熟悉的 ActivityThread.main 方法通过 Looper.prepare 和 Looper.loop 方法开启消息循环
紧接着就是创建 Application 对象的过程,先是创建好 ContextImpl 对象,然后通过 makeApplication 方法将 app 进程与 Application 建立联系,这里的 Application 创建交给了 Instrumentation 的对象,其实后面 activity 的创建,生命周期的回调都是通过它来触发的。
创建完 Application 后,紧接着就是我们熟悉的 Activity,activity 的创建同样交给了 Instrumentation 对象,上面说过 ActivityManagerService 会将携带的 Intent 对象交给了 Lanucher 应用,Lanucher 的 startActivity 经过一系列的操作,最终会走 Instrumentation 的 execStartActivity 方法,该方法里面会去请求 ActivityManagerService 服务,最终通过 binder 通信将信息传给了客户端的 ApplicationThread,最终会触发 ApplicationThread 的 scheduleLaunchActivity 方法,该方法将消息发送给了 ActivityThread 的 handler 对象,最终交给了 Instrumentation 对象创建 activity。后面也就触发一系列的生命周期方法。
Eventbus 原理
EventBus是一款在android开发中使用的发布/订阅事件的总线框架,基于观察者模式,将事件的接收者和发送者分开,基本包括了如下几个步骤:
注册事件的订阅方法
:该步骤主要是找到订阅者下面有哪些方法需要被订阅
订阅操作
:将需要被订阅的方法放到类似 HashMap 的数据结构中存储起来,方便后面发送事件和取消注册等资源的释放的时候使用
发送事件
:该步骤首先遍历事件队列,然后从队列中取出事件,并且将事件从队列中移除,拿到事件后,判断事件处于的什么线程,如果是非 UI 线程,则需要 Handler 去处理,如果是的话,则直接通过反射调用被观察的方法。
反注册
:该步骤就没什么好说的,主要是上面存储到 HashMap 中的被订阅的方法的移除,释放在内存中的资源。
Rxjava 的操作符有哪些,说说他们的作用
just
:将同种数据源组合放到被观察者上面
from
:将类似数组、集合的数据源放到被观察者上面
map
:将一种数据源,转化成另外一种
flatmap
:将一种数据源,转化成另外一种数据,并且被转化的数据是乱序排列的
concatmap
:将一种数据源,转化成另外一种数据,并且被转化的数据是按照先前的数据源顺序排序的
toList
:将数组的形式转化成 List 集合
subscribeOn
:设置 Observable 的 call 方法所在的线程,也就是数据来源的线程
observeOn
:设置 subscribe 的 call 方法所在的线程,也就是数据处理的线程
filter
:在被观察者的数据层过滤数据
onErrorResumeNext
:出错的时候,可以指定出错的时候的被观察者
retryWhen
:出错的时候,重新走一遍被订阅的过程
concat
:合并相同类型的被观察者到一个被观察者身上,有点类似集合、数组拼接数据。
zip
:处理多种不同结果集的数据发射,一般用得多的地方是多个网络请求组合然后统一处理业务逻辑。
还有很多操作符就自己去看,这些操作符已经够面试用的了。
线程锁 锁方法和类对象啥的有啥区别
线程锁锁方法
:是需要等到该线程用完了该方法才能释放同步锁
线程锁锁类对象
:是需要等到该线程用完了该类对象才能释放同步锁
区别
:是锁方法的区域要小 锁类对象包括了该类的所有属性
AsyncTask 原理
AsyncTask 主要是对 android 中 java 的线程池的封装,该类中默认开启了两个线程池,一个线程池负责任务的排队处理,保证任务被单个处理,另外一个线程池用来专门处理任务,最后任务处理完了,交给 Handler 发送消息到主线程,然后 Handler 处理线程,交给了onPostExecute
方法。
内部过程
:
AsyncTask 初始化阶段创建了
WorkerRunnable
对象,它是处理doInBackground
的 Callable 对象,接着创建了FutureTask
对象,它是将上面WorkerRunnable
包装了一层的Runnable
和Future
对象,实际上线程池要执行的任务就是该WorkerRunnable
对象。在执行任务过程中,通过
SerialExecutor
对象来排队处理FutureTask
,里面通过ArrayDeque
来按顺序取出FutureTask
,取出后交给了THREAD_POOL_EXECUTOR
对象,它是在静态代码块中创建的线程池,所以说THREAD_POOL_EXECUTOR
才是正真执行任务的关键地方。执行完后,剩下的就是主线程的 Handler 将消息发送到主线程去处理。
问题
:
AsyncTask 内部会创建一个线程池?
两个线程池,一个线程池负责排队处理任务;另一个线程池用来负责处理FutureTask
,也就是将上面WorkerRunnable
包装了一层的Runnable
对象。
AsyncTask 对此执行 excute 方法会怎样?
直接抛出IllegalStateException
(非法状态异常)
说说 MVP 和 MVVM 的特点
MVP
:主要是分离了 M 层和 V 层的代码,通过 P 层来建立他们的关联,实现 M 层和 V 层的解耦。缺点就是每增加一个功能,需要增加相应的接口回调。没办法,MVP 的核心就是通过接口实现隔离,将相关的业务层交给了 P 层。
如果要细说 mvp 需要注意几点:
p 层的逻辑处理单一的功能,不要融合一个模块下的增删改查的整个功能。
由于 p 层持有了 v 层的引用,通常在 p 层使用弱引用来持有 view 层实例,在 p 层销毁的时候需要将 v 层的引用销毁掉。
契合类指的 p 层和 v 层的接口类放在一个 contract 接口类中,契合类方便管理业务层的功能,将单个功能放到一个 contract 契合类中。比如我们有一个添加书架的功能:
public interface AddBookShelfContract {
interface View extends BaseContract.BaseView {
void addBookShelfSuccess(BookShelfItem... bookShelfItem);
void addBookShelfFail();
void alreadyBookShelf(BookShelfItem bookShelfItem);
}
interface Presenter extends BaseContract.BasePresenter<View> {
void addBookShelf(String tokenId, BookShelfItem... bookShelfItem);
}
}
MVVM
:主要是用到了观察者模式,通过数据的改变来通知相应的 View 改变的过程。M 层和上面的 MVP 中的 M 层是一样的,都是网络请求+数据缓存来实现该层的,里面的双 V,一个指的 ViewModel 实现的,另外一个 AndroidDataBinding 实现 V 层,ViewModel 层获取到 M 层的数据后,通过观察者模式通知 AndroidDataBinding 在 UI 上的改变。缺点的话,只能吐糟下 AndroidDataBinding 了,在 xml 中写逻辑的时候,一点提示代码都没有,感觉完全是在写 js 似的,可读性肯定对于初级的来说还是有点难看懂的。
Android 中用到的观察者模式有哪些地方
观察者模式是由一个发送者(发送者是笔者自己的称呼,觉较之被观察者贴切得多)和一个观察者构成的、发送者在状态改变时(用户操作、程序主动改变等)主动通知所有观察者作相应的刷新。
android 中最经典要说 ListView 的数据源发生变化了,刷新列表的事例。在 setAdapter 的时候,生成一个AdapterDataSetObserver
,紧接着就是订阅上该观察者,该观察者onChange
方法里面有requestLayout
方法,该方法是触发 UI 发生变化的方法。在BaseAdapter
里面可以看到notifyDataSetChanged
实际上触发的是DataSetObservable
被观察者的notifyChanged
方法,notifyChanged
会触发AdapterDataSetObserver
的onChange
方法。所以最终会走 listView 的requestLayout
,最后刷新了 UI。
说说 Google 新出的 Lifecycle 框架
将类的生命周期方法移交到 Lifecycle 中管理,实现对类的生命周期的监听,从而在 Lifecycle 中处理生命周期的逻辑代码。这里涉及到几个对象:
LifecycleObserver接口
( Lifecycle 观察者):实现该接口的类,通过注解的方式,可以通过被 LifecycleOwner 类的 addObserver(LifecycleObserver o)方法注册,被注册后,LifecycleObserver 便可以观察到 LifecycleOwner 的生命周期事件。
LifecycleOwner接口
(Lifecycle 持有者):实现该接口的类持有生命周期(Lifecycle 对象),该接口的生命周期(Lifecycle 对象)的改变会被其注册的观察者 LifecycleObserver 观察到并触发其对应的事件。
Lifecycle
(生命周期):和 LifecycleOwner 不同的是,LifecycleOwner 本身持有 Lifecycle 对象,LifecycleOwner 通过其 Lifecycle getLifecycle()的接口获取内部 Lifecycle 对象。
State
(当前生命周期所处状态):几种事件状态。
Event
(当前生命周期改变对应的事件):当 Lifecycle 发生改变,事件状态的回调 event。
OKhttp 原理
okhttp 主要实现了异步、同步的网络操作,创建了不同的 call 对象,这里的call
对象是一个个的 runnable 对象,由于我们的任务是很多的,因此这里有Dispatcher
包装了线程池来处理不同的call
,其中该类中创建了三种队列,分别用于存放正在执行的异步任务,同步队列,以及准备的队列。最后在执行每个任务的时候,采用队列的先进先出原则,处理每一个任务,都是交给了后面的各种拦截器来处理,有请求准备的拦截器、缓存拦截器、网络连接的拦截器,每一个拦截器组成了一个责任链的形式。到最后返回response
信息。
OkHttp 的底层是通过 Java 的 Socket 发送 HTTP 请求与接受响应的(这也好理解,HTTP 就是基于 TCP 协议的),但是 OkHttp 实现了连接池的概念,即对于同一主机的多个请求,其实可以公用一个 Socket 连接,而不是每次发送完 HTTP 请求就关闭底层的 Socket,这样就实现了连接池的概念。而 OkHttp 对 Socket 的读写操作使用的 OkIo 库进行了一层封装。
Retrofit 原理
retrofit 基于 okHttp 封装成 RESTFUL 网络请求框架,通过工厂模式配置各种参数,通过动态代理、注解实现网络请求。retrofit 利用了工厂模式,将分为生产网络请求执行器(callFactory)、回调方法执行器(callbackExecutor)、网络请求适配器(CallAdapterFactory)、数据转换器(converterFactory)等几种工厂。
callFactory 负责生产 okHttp 的 call,大家都知道 okHttp 通过生成 call 对象完成同步和异步的 http 请求。
callbackExecutor 通过判断不同的平台,生成对应平台的数据回调执行器。其中 android 端的回调执行器是通过 handler 回调数据。
CallAdapterFactory 是数据解析工厂,一般我们配置 json 的数据解析适配器就行。
converterFactory 是数据转换的工厂,一般我们配置 Rxjava 的数据转换就行。
retrofit 通过动态代理模式实现接口类配置的注解、参数解析成 HTTP 对象,最后通过 okHttp 实现网络请求。
RxJava 的线程切换原理
RxJava 通过
subscribeOn
指定被观察者发生的线程,observeOn
指定观察者发生的线程。其中 Schedulers.IO 生成的是IoScheduler
。通过观察者与被观察者订阅的过程中,首先会触发被观察者的subscribeActual
方法,在该方法中,可以看到最终会走scheduler
的schedule
方法,所以上面提到的IoScheduler
实际是调用了它的schedule
方法,最终会在NewThreadWorker
里面生成ScheduledExecutorService
对象,而ScheduledExecutorService
实际是由ScheduledThreadPoolExecutor
创建的一个核心线程,最大线程个数是 Integer.MAX_VALUE 的线程池。最终会由ScheduledThreadPoolExecutor
的submit
或schedule
方法执行传过来的Runnable
对象,而Runnable
执行的是被观察者的subscribe
方法。所以解释了被观察者的subscribe
方法是在子线程中执行的。observeOn
是观察者发生的线程,AndroidSchedulers.mainThread()
实质是HandlerScheduler
对象,而在观察者部分,最终观察部分会走Scheduler
的scheduleDirect
方法,而HandlerScheduler
的该方法里面包装了一个ScheduledRunnable
对象,通过主线程的 handler.postDelayed 处理这个 runnable 对象。
RecyclerView 源码、缓存分析
RecyclerView 使用了强大的分工操作,显示、排版由 LayoutManager 处理,数据显示由 adapter 处理,item 上下左右动态加入绘制由 ItemDecoration 处理,item 的动画由 ItemAnimator 处理。面试主要分析 recyclerView 缓存,recyclerView 缓存是由内部类 Recycler 维护,其中一级缓存有mAttachedScrap
,里面放的都是当前屏幕正在显示的 viewHolder 的缓存,二级缓存是mCachedViews
,里面放的都是移出到屏幕外的 viewHolder 缓存,mRecyclerPool
是 recyclerView 的三级缓存,一般用在 RecyclerView 嵌套 RecyclerView 的时候用得到,比如外层的 RecyclerView 的 item 中有 RecyclerView,那么里面的 RecyclerView 通过共用外层的 RecyclerView 的 RecyclerPool 来减少里面 RecyclerView 的 ViewHolder 创建。
Binder 机制
binder 机制是 android 端进程间通信的基石,采用 aidl 的 ipc 通信方式,我们可以利用它来定义两个进程相互通信的接口。他是基于 Service 实现的一种线程间通信机制。它的本质是 C/S 架构的,需要一个服务器端,一个客户端。 AIDL 通信方式里面有四个对象,一个是 IInterface,专门用来负责接口的调度,Stub 用来负责通信的响应和发送给 service 端的数据,Proxy 负责两个进程通信的包装,算是间接调用 Stub 的包装类,service 是服务端处理数据的关键类。用一张图来表示如下:
Android Jetpack
android jetpack 是 google 专门为开发者快速开发 app 的一套组件,快速搭建 mvvm 框架的实现,其中包括 Lifecyle、LiveData、ViewModel、Room、DadaBinding、Navigation、Paging、WorkManager 等一系列优秀的框架。
Lifecycle:实现和 activity、fragment 生命周期感知的框架,实现数据层和 view 层销毁的时候解绑。原理是 Lifecycler 为每个活动组件添加了一个没有界面的 Fragment,利用 Fragment 周期会根据活动声明周期变化的特性实现的特性,从而实现生命周期的感知,然后根据注解的 Event 查找执行相应的方法。
LiveData:提供了一种数据改变的同时,主动去告诉 ui,让 ui 层做出相应的逻辑判断。原理是内部保存了 LifecycleOwner 和 Observer,利用 LifecycleOwner 感知并处理声明中期的变化,Observer 在数据改变时遍历所有观察者并回调方法。
ViewModel:它是我们 view 层和 model 层的桥梁,是数据驱动界面的关键地方,也是我们 ui 层在数据丢失的情况下,viewModel 还能继续保持原有的数据,原理是将数据保存到 ViewModel 中,然后为活动中添加一个 HolderFragment,HolderFragment 中保存了 ViewStore 的实例,ViewStore 中使用 Map 保存了 ViewModel,从而在活动重新创建时获取到原来的 ViewModel。
Room:是 model 层本地数据库的框架,通过实体映射到对应的 db 表结构,将实体映射到 db 关系型数据库里面。跟 greendao 差不多,room 数据库版本升级数据迁移比 greendao 迁移要麻烦,个人还是比较喜欢 greendao 来实现本地数据库。 DadaBinding:是一个可以通过在 xml 布局文件中实现 ui 逻辑的框架,并且它的 ui 层和数据层双向驱动还是挺不错的。
Navigation:是后面新出来的可视化管理 fragment 的组件,通过在 xml 中配置 fragment 之间跳转的关系。
系统打包经过了哪几个流程
打包资源文件,通过 AAPT(Android Asset Packaging Tool)打包成 R.java 类(资源索引表)以及.arsc 资源文件。
处理 AIDL 文件,检查 app 中是否有 aidl 文件,如果有会通过 aidl 工具(源码位于 system/tools/aidl)打包成 java 接口类
编译 R.java 源码部分以及 aidl.java 通过 javac 生成对应的.class 文件。
将上面生成的.class 文件和第三方 jar 或者 library 通过 dx 工具打包生成 dex 文件。
生成未签名的 apk,包括 apkbuilder 工具将所有没有编译的资源、.arsc 资源、.dex 文件打包到一个完成 apk 文件中
生成签名的 apk,包括 jarsigner 工具对未签名的 apk 验证签名。得到一个签名后的 apk(signed.apk)
zipAlign 工具对齐上面签名的 apk 文件。
Kotlin
kotlin 使用了一组新的语法糖,kotlin 不让变量初始化空的类型,强大的非空设计思想比 java 要人性化,在代码编写阶段就有提示开发者的好处。还有它的内联函数,函数体作为返回值的各种简写方式使更多的人愿意接受 kotlin。它的协成开发比较好的控制线程之间切换的多层嵌套的问题,以及它简洁的语法,比较受开发者青睐。
好了,大概 Android 面试的 Android 篇就写这么多。如果大家觉得还有那些要补上可以留言我!!!
评论