Android试题【一】 - 草稿 - 草稿 - 草稿
1.android得启动模式
- 标准模式(Standard)
Activity得标准模式,这种模式下每次启动Activity都会创建新的Activity- 栈顶复用(singleTop)
如果打开的的Activity已经存在栈顶,则不会重新创建新的Activity,而是复用栈顶的Activity
使用场景:短信发送界面
-栈内复用(signleTask)
如果打开的Activity已经在栈内存在,则不会重新创建新的Activity,二手复栈内的Activity,将改Activity以上的Activity销毁掉
使用场景:浏览器的Activity- 全局唯一模式(singleInstance)
整个手机操作系统只有一个实例,并且运行子啊单独的任务栈中
使用场景:拨号界面的Activity
2.Activity A-B的生命周期
- 问题描述:打开(A)Activity并打开(B)Activity。请描述整个生命周期?
onCreate:表示创建,这个是生命周期的第一个方法。此时Activity还是处于后台,不可见。
onStart:表示启动,这个是生命周期的第二个方法。此时Activity处于可见,但是还是没有出现在前台
onResume:表示继续、重新开始,这个阶段表示Activity处于前台并且是可见的状态
onPause:表示暂停。这个阶段处于可见并在在前台。在这个阶段可以存储数据操作。但是不能进行耗时操作。
onStop;表示暂停。此时Activity处于不可见状态,但是资源还在内存中
onDestory:表示销毁。此时Activity处于不可见状态,并且资源也被回收了。
onRestart:表示重新开始。Acticity处于可见状态。
- 生命周期执行过程
(A)OnCreate->(A)OnStart->(A)OnResume->(A)OnPause->(B)OnCreate->(B)OnStart->(B)OnResume->(A)OnStop
- 为什么(A)Onstop在最后执行
因为在这个跳转过程中会出现立刻返回界面,为了快速恢复。因此在(B)Activity处于可见前台的时候再执行Onstop
3.切换Fragment的replace方法里面做了什么?
fragment本身没有replace和add方法;这里泛指的是fragmentManager中的replace和add的方法;在fragment的容器中有个framelayout;add的时候将每一个fragment叠加到framelayout上面去,显示隐藏则通过show方法或hind方法,生命周期只有再add的会重新执行,后续不会再执行生命周期,而replace的时候将其他fragment去除掉,然后再添加fragment到framelayout上去。每一次replace都会执行一次生命周期。
4.Toast能不能在子线程中弹出,算不算Ui更新?
Toast属于window层的逻辑,与activity并级。所谓的ui更新指的是刷新activity的跟布局。因此不算UI更新
5.Handler相关
- 消息机制模型
Handler:接受和发送Message
Message:消息承载体
MessageQueue:以链表结构,通过enQueueMessage插入Message
Looper:将Handler与MessageQueue关联起来(1.Handler获取主线程的MessageQueue;2.主线程从MessageQueue中获取Message并回调到Handler)
- 消息机制的运行流程
在子线程执行完耗时操作,当Handler发送消息时,将会调用MessageQueue.enqueueMessage,向消息队列中添加消息。当通过Looper.loop开启循环后,会不断从线程池中读取消息,即调用MessageQueue.next,然后调用目标Handler的dispatchMessage方法传递消息,然后返回到Handler所在的线程,目标Handler收到消息,调用handlerMessage方法,接受消息,处理消息。
- MessageQueue、Handler和Looper三者关系
每一个线程只能存在一个Looper,Looper是保存在ThreadLocal中,主线程已经创建了一个Looper,所以在主线程中不需要再创建Looper,但是再其他线程中需要创建Looper,每个线程可以有多个Handler,既一个Looper可以处理来自多个Handler的消息,Looper中维护一个MessageQueue,来维护消息队列,消息队列中的Message可以来自不同的Handler。
- 线程遍历数据
MessageQueue调用EnqueueMessage将Message插入到链表中去,由于外部随时随地向MessageQueue中发送消息,因此MessageQueue调用next方法开启循环来反复取链表头部消息。如果消息时间戳小于或等于当前时间戳则返回队头消息,反之取消息时间戳和当前时间戳差值,并调用navtaPollOnce方法延长一段时间后 再次循环。
- 线程切换
将子线程持有的Handler与主线程关联的mainMessageQueue绑定在一起,主线程负责循环从mainMessageQueue中取出消息,然后调用Handler中的dispatchMessage方法切换线程。- Looper关联Handler与MessageQueue
主线程通过prepareMainLooper以此来完成对sMainLooper的初始化,调用loop方法循环取值并进行处理,如果没有消息则暂停。子线程拿到sMainLoop后就此初始化Handler,这样子线程发送到Handler的消息就存储再mainMessageQueue中
- MessageQueue的创建
nativeInit会创建一个native层的MessageQueue,并将引用地址返回给java层的mPtr变量中。- 消息屏障
PostSyncBarrier,插入一个无target的message,这个屏障之后的所有同步消息都不会执行- NaticePollOnce
通过native层的MessageQueue阻塞nextPollTimeOutMillis毫秒的时间,调用native层的MessageQueue的Looper的epoll_wait,是linux下多路复用IO接口的增强版本。- MessageQueue入队列时会唤醒等到线程nativeWakeup,write,写入了一个1,这个时候epoll就能监听到事件,也就呗唤醒了。
- Handler的post(Runnable)是怎么实现的
post()调用Handler的SendMessageDelayed()方法,根据参数Runnable创建一个Message对象,发送一个延迟未0 的延迟消息,最终调用到Handler的EnqueMessage方法将消息按照事件先后顺序添加到MessageQueue中,然后通过Looper的loop方法不断的从MessageQueue中取消息分发给Handler处理
- Looper.loop()里面是死循环,为什么不会卡死
Android系统是消息驱动的,Handler发送的消息保存在MessageQueue中,只有looper.loop在不断循环取消息,才能让系统继续进行,主线程也是一个线程,不然一旦主线程中的代码执行完成,主线程就会结束,那么整个应用也就结束了,只有让主线程不结束那么应用就会一直运行,
Activity的生命周期都说通过Handler发送消息,然后ActivityThread的内部类H接受消息,并回调相关的生命周期函数,如果没有死循环,就无法不断的从MessageQueue中取消息,也就无法回调各种生命周期函数了
- Loop.prepare做了什么
判断了当前线程是否以及创建了一个Loop,如果以及有了Looper就抛出异常,否则就创建一个新的Looper保存到ThreadLocal中
- MessageQueue是什么时候创建的
在Loop.preare中线判断了Looper是否创建,没有创建则先创建一个Looper,在Looper的构造函数中创建一个MessageQueue。
- handler.sendEmptyMessageDelay和handler.postDelayed的区别
两者都会调用handler.sendMessageDelayed,sendEmptyDelayed内部会根据what创建一个新的Message,postDelayed根据参数Runnable创建一个带有callBack的Message,在分发的时候优先给了handleCallback,事件处理是在传入参数的Runnaable的run方法中,而sendEmptyDelayed最终事件处理在Handler的handlerMessage中
6.Android中的Context、Activity、Application有什么区别
- Context
1.描述的是一个应用程序的环境信息,即上下文
2.该类是一个抽象类
3.通过他我们可以获取应用程序的资源、类或者其他的例如启动Activity、发送广播。接受Intent
4.一个Context等于Activity+service+1
- 相同点
Activity和Application都是Context的子类
- 不同点
三者维护的生命周期不同
Application:维护的是整个项目的生命周期
Context和Activity:维护的是当前Activity的生命周期
7.事件分发机制
- View的事件分发机制
事件分发的本质:就是对MotionEvent事件分发的过程,即当一个MotionEvent产生了以后,系统需要将这个事件传递到一个具体的View上,点击事件的传递顺序:硬件----Activity------ViewGroup-----View
- Activity分发
1.事件先分发给PhoneWindow、PhoneWindow不消费则传递给Activity的OnTouchEvenet方法。
2.PhoneWindow传给DecorView,DecorView传给根布局ViewGroup- ViewGroup分发
1.首先调用OnInterCeptTouchEvent方法,判断ViewGroup自己是否需要,需要则拦截,拦截后就不会传递给ChildView。
2.拦截后,调用VieGroup自己的OntouchEvent方法
3.不拦截则传递给childView,按照视图层次结构依次传递下去。
4.最终的那个childView也不需要时,事件会进行回传,以此调用前面每一层的OnTouchEvent方法。- View分发
1.View会依次调用onTouchListener、onTouchEvent、OnLongClickListener、OnclickListener。
2.在OnTouchListener、OntouchEvent中可以决定是否消费事件、不消费事件则开始回传
3.注册了OnLongClickListener、OnClickListener及设置了Clickable等,View就会消费事件
8.Window、Activity、Decorview以及ViewRoot之间的关系
Activity:一个控制器统筹视图的添加和显示,以及通过其他方法来与Window、以及View进行交互
Window:视图的承载器,内部持有一个DecorView,而这个Decorview才是View的根布局,实际再ACtivity中持有的是其子类PhoneWindow。PhoneWindow中有个内部类DecorView,通过DecorView来加载Activity中设置的布局文件。Window通过WindowManager将Decorview加载其中,并将Decorview交给ViewRoot,进行视图绘制以及其他交互。
Decorview:Decorview是FrameLayout的子类,他被认为是Android视图树的根节点视图,Decorview作为顶级View,一般情况下他内部包含一个竖直方向的linearLayout,再这个LinearLayout里面有上中下三部分组成
ViewRoot:负责View的事件处理和逻辑处理
9.ARouter相关
- ARouter路由原理
ARouter维护了一个路由表Warehouse,其中保存着全部的模块跳转关系,ARouter路由跳转实际上还是调用了startActivity的跳转,使用了原生的Framework机制,只是通过apt注解的形式制造出跳转规则,并人为地拦截跳转和设置跳转条件
- ARouter初始化做了什么
在ARouter的init方法中调用了_ARouter的init方法,在初始化过后接着调用了_ARouter的afterInit方法做后续的工作。
在_ARouter的init中调用了LogisiticsCenter.init,并初始化了一个主线程的Handler。在该方法中调用ClassUtils.getFileNameByPackageName方法在子线程中扫描出第二个参数开头的类添加到Set集合中调用对应接口的loadInto方法,这些接口的loadInto方法具体实现在编辑器自动生成的类中,在这些自动生成的类中,将对应的路由信息添加到Warehose类的静态Map中。
- ARouter是怎么启动Activity或者Fragment的
在编译期,注解处理器会获取到@ARouter注解中的类相关信息Element,然后遍历所有的Element,检查注解使用的合法性,然后用javaPoet生成相关的路由类,这些类保存这跳转相关信息,这些信息根据模块名分组,每个模块中加上@ARouter注解的类被添加到对应的组中,每个组中保存者改类的路由信息,
在运行期,ARouter初始化的时候遍历dex文件中所有的类过滤出包名以com.alibaba.android.arouter.routes开头的类添加到Set集合中,然后遍历该Set集合,然后调用对应类的loadInto方法,将路由信息添加到Warehouse类中的静态Map中,在路由跳转的时候,build方法根据传入的path提取出group,并封装成postCard对象,navigation方法中,从静态Map中根据之前封装到PostCard中的path查询出RouteMeta,然后将要跳转的类信息设置给PostCard对此,最终调用到了_ARouter类的_navigation方法,在该方法中根据postCard对象中的信息用Intent进行跳转
11.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实现网络请求。
- retrofit动态代理
1.首先,通过method把它转换成ServiceMethod。
2.然后,通过serviceMethod,args获取到okHttpCall对象。
3.最后,再把okHttpCall进一步封装并返回Call对象。
12.Appliction启动过程(App启动过程)
应用启动涉及四个进程:调用者进程(点击桌面图标启动,那么就是Launcher进程)、SystemServer进程(AMS管理Activity的启动)、Zygote进程(fork子进程:共享代码空间、数据空间独立)、新的应用进程。
整体流程是:点击桌面图标启动时,启动请求通过Binder方式发送给SystemServer中的ActivityManagerService,AMS接收到启动请求后检查应用进程是否存在,如果不存在则通过Zygote进程fork子进程,之后回到AMS进程处理处理Intent、Flag信息,创建任务栈、Activity进栈,之后转到新进程,创建ActivityThread对象,调用其main方法,main方法所在的就是主线程,其中还会创建Looper绑定主线程,之后调用loop()方法开启消息循环
13.Okhttp原理
- 原理
okhttp主要实现了异步、同步的网络操作,创建了不同的call对象,这里的call对象是一个个的runnable对象,由于我们的任务是很多的,因此这里有Dispatcher包装了线程池来处理不同的call,其中该类中创建了三种队列,分别用于存放正在执行的异步任务,同步队列,以及准备的队列。最后在执行每个任务的时候,采用队列的先进先出原则,处理每一个任务,都是交给了后面的各种拦截器来处理,有请求准备的拦截器、缓存拦截器、网络连接的拦截器,每一个拦截器组成了一个责任链的形式。到最后返回response信息。
OkHttp的底层是通过Java的Socket发送HTTP请求与接受响应的(这也好理解,HTTP就是基于TCP协议的),但是OkHttp实现了连接池的概念,即对于同一主机的多个请求,其实可以公用一个Socket连接,而不是每次发送完HTTP请求就关闭底层的Socket,这样就实现了连接池的概念。而OkHttp对Socket的读写操作使用的OkIo库进行了一层封装
14.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对象
15.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创建
16.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之间跳转的关系。
17.Eventbus原理
EventBus是一款在android开发中使用的发布/订阅事件的总线框架,基于观察者模式,将事件的接收者和发送者分开,基本包括了如下几个步骤:
注册事件的订阅方法:该步骤主要是找到订阅者下面有哪些方法需要被订阅
订阅操作:将需要被订阅的方法放到类似HashMap的数据结构中存储起来,方便后面发送事件和取消注册等资源的释放的时候使用
发送事件:该步骤首先遍历事件队列,然后从队列中取出事件,并且将事件从队列中移除,拿到事件后,判断事件处于的什么线程,如果是非UI线程,则需要Handler去处理,如果是的话,则直接通过反射调用被观察的方法。
反注册:该步骤就没什么好说的,主要是上面存储到HashMap中的被订阅的方法的移除,释放在内存中的资源。
18.View的绘制原理
View的绘制从ActivityThread类中Handler的处理RESUME_ACTIVITY事件开始,在执行performResumeActivity之后,创建Window以及DecorView并调用WindowManager的addView方法添加到屏幕上,addView又调用ViewRootImpl的setView方法,最终执行performTraversals方法,依次执行performMeasure,performLayout,performDraw。也就是view绘制的三大过程。
measure过程测量view的视图大小,最终需要调用setMeasuredDimension方法设置测量的结果,如果是ViewGroup需要调用measureChildren或者measureChild方法进而计算自己的大小。
layout过程是摆放view的过程,View不需要实现,通常由ViewGroup实现,在实现onLayout时可以通过getMeasuredWidth等方法获取measure过程测量的结果进行摆放。
draw过程先是绘制背景,其次调用onDraw()方法绘制view的内容,再然后调用dispatchDraw()调用子view的draw方法,最后绘制滚动条。ViewGroup默认不会执行onDraw方法,如果复写了onDraw(Canvas)方法,需要调用 setWillNotDraw(false);清楚不需要绘制的标记。
requestLayout,invalidate,postInvalidate
相同点:三个方法都有刷新界面的效果。
不同点:invalidate和postInvalidate只会调用onDraw()方法;requestLayout则会重新调用onMeasure、onLayout、onDraw。
调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量onMeasure、布局onLayout、绘制onDraw。
19.RecyclerView与ListView(缓存原理,区别联系,优缺点)
缓存区别:
层级不同:
ListView有两级缓存,在屏幕与非屏幕内。
RecyclerView比ListView多两级缓存,支持多个离屏ItemView缓存(匹配pos获取目标位置的缓存,如果匹配则无需再次bindView),支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。
缓存不同:
ListView缓存View。
RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:
View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
优点
RecylerView提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。
RecyclerView的扩展性更强大(LayoutManager、ItemDecoration等)。
20. Activity的启动过程
app启动的过程有两种情况,第一种是从桌面launcher上点击相应的应用图标,第二种是在activity中通过调用startActivity来启动一个新的activity。
我们创建一个新的项目,默认的根activity都是MainActivity,而所有的activity都是保存在堆栈中的,我们启动一个新的activity就会放在上一个activity上面,而我们从桌面点击应用图标的时候,由于launcher本身也是一个应用,当我们点击图标的时候,系统就会调用startActivitySately(),一般情况下,我们所启动的activity的相关信息都会保存在intent中,比如action,category等等。我们在安装这个应用的时候,系统也会启动一个PackaManagerService的管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,从而得到应用程序中的相关信息,比如service,activity,Broadcast等等,然后获得相关组件的信息。当我们点击应用图标的时候,就会调用startActivitySately()方法,而这个方法内部则是调用startActivty(),而startActivity()方法最终还是会调用startActivityForResult()这个方法。而在startActivityForResult()这个方法。因为startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。而startActivityForResult()这个方法实际是通过Instrumentation类中的execStartActivity()方法来启动activity,Instrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。启动会就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。当然最后是调用的是Application.scheduleLaunchActivity()进行启动activity,而这个方法中通过获取得到一个ActivityClientRecord对象,而这个ActivityClientRecord通过handler来进行消息的发送,系统内部会将每一个activity组件使用ActivityClientRecord对象来进行描述,而ActivityClientRecord对象中保存有一个LoaderApk对象,通过这个对象调用handleLaunchActivity来启动activity组件,而页面的生命周期方法也就是在这个方法中进行调用。。