Android 基础面试题(2021年)

Activiey启动:


Dialog不会调用onPause()和onStop(), 非全屏Activity会调用onPause()不会调用onStop(),全屏Activity 会调用onPause()和onStop()。

onStart 可见不可交互
onPause 可见不可交互

Fragment add & replace
Add 之前的fragment没有销毁
replace 之前fragment销毁生命周期。

(SingleTop和SingleTask启动模式的应用场景,除普通的那些还有onRestoreInstanceState与onSaveInstanceState与onNewIntent以及他们的使用场景与区别)注意调用onNewIntent不会再调用onCreate方法了,会直接调用onStart与onResume。如果是已经不可见的Activity(调用了onStop的,则会先调用onRestart之后在调用onStart方法)。

RecyclerView 相比 ListView 在基础使用上的区别主要有如下几点:

  • ViewHolder 的编写规范化了
  • RecyclerView 复用 Item 的工作 Google 全帮你搞定,不再需要像 ListView 那样自己调用 setTag
  • RecyclerView 需要多出一步 LayoutManager 的设置工作
    1、RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:View + ViewHolder(避免每次createView时调用findViewById,不必每次都重新创建很多对象,从而提升性能) + flag(标识状态)

2、ListView缓存View

recyclerView 四级缓存 listview两级

RecyclerView提供了局部刷新的接口。

intentService: 内部通过HandlerThread和Handler实现异步操作,onHandleIntent为异步方法,可以执行耗时操作。

多次启动IntentService,但IntentService的实例只有一个,这跟传统的Service是一样的。

LocalBroadcastReceiver不能静态注册,只能采用动态注册的方式。

LocalBroadcastManager的核心实现其实还是 Handler,因此它是应用内的通信,自然安全性更好,运行效率更高。

2、而 BroadcastReceiver 是全局广播,可以跨进程通信,范围更广,从而导致它的运行效率没有本地广播高效,毕竟一个是本地的通信,一个是跨进程的通信方式,效率肯定相对较低点,但对于实时性不高的应用场景我们可以忽略不计。

事件分发:
整个事件的传递机制,是Android底层收到触摸屏的事件后,使用socket跨进程通信,用InputDispatcher将事件发送给APP进程,由主线程的Looper去取出消息进行处理
Activity--> Window-->DecorView --> 布局View

  •   1、分发:dispatchTouchEvent;
    
  •   2、拦截:onInterceptTouchEvent;(只有view有)
    
  •   3、处理:onTouchEvent;
    

handler:
Message:信息的携带者,持有了Handler,存在MessageQueue中,一个线程可以有多个
Hanlder:消息的发起者,发送Message以及消息处理的回调实现,一个线程可以有多个Handler对象
Looper:消息的遍历者,从MessageQueue中循环取出Message进行处理,一个线程最多只有一个
MessageQueue:消息队列,存放了Handler发送的消息,供Looper循环取消息,一个线程最多只有一个.

  1. 处理Handler消息,是在哪个线程?一定是创建Handler的线程么?
    :
    Looper进行循环的那个线程,也就是创建Handler那个线程。因为是在Looper.loop调用Handler去执行消息的,Looper在哪个线程调用,消息就在哪个线程被Handler处理,因为对应Looper所在线程与创建对应Handler的线程必须是同一条,所以无论从哪个线程发送消息给Handler,最终会在创建Handler那个线程执行这个消息。


HTTPS 在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段。

首先:非对称加密的加解密效率是非常低的,而 http 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的。
另外:在 HTTPS 的场景中只有服务端保存了私钥,一对公私钥只能实现单向的加解密,所以HTTPS 中内容传输加密采取的是对称加密,而不是非对称加密。

LruCache(Least Recently Used)算法的核心思想就是最近最少使用算法。他在算法的内部维护了一个LinkHashMap的链表,通过put数据的时候判断是否内存已经满了,如果满了,则将最近最少使用的数据给剔除掉,从而达到内存不会爆满的状态。

MeasureSpec三种模式:
1.精确模式(MeasureSpec.EXACTLY)
在这种模式下,尺寸的值是多少,那么这个组件的长或宽就是多少。
2.最大模式(MeasureSpec.AT_MOST)
这个也就是父组件,能够给出的最大的空间,当前组件的长或宽最大只能为这么大,当然也可以比这个小。
3.未指定模式(MeasureSpec.UNSPECIFIED)

什么是三级缓存
网络缓存, 不优先加载, 速度慢,浪费流量
本地缓存, 次优先加载, 速度快
内存缓存, 优先加载, 速度最快

这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新。12fps大概类似手动快速翻动书籍的帧率,这明显是可以感知到不够顺滑的。24fps使得人眼感知的是连续线性的运动,这其实是归功于运动模糊的
效果。24fps是电影胶圈通常使用的帧率,因为这个帧率已经足够支撑大部分电影画面需要表达的内容,同时能够最大的减少费用支出。但是低于30fps是
无法顺畅表现绚丽的画面内容的,此时就需要用到60fps来达到想要的效果,当然超过60fps是没有必要的(据说Dart能够带来120fps的体验)。

内存泄露的原因:对象无用了,但仍然可达(未释放),垃圾回收器无法回收。

  • CPU产生的问题:不必要的布局和失效
  • GPU产生的问题:过度绘制(overdraw)
    
onResume 后,View初始化时measure三次,layout两次,draw一次的原因。

LeakCanary 中的 RefWatcher 就是通过弱引用及其队列来实现监控的:
有两个很重要的结构: retainedKeys 和 queue ,
retainedKeys 代表没被gc 回收的对象,
而queue中的弱引用代表的是被gc了的对象,通过这两个结构就可以监控对象是不是被回收了;

flutter选择Dart语言很好的实现JIT,能够让我在开发中实现Hot Reload。
RN是依托Javascript通过“bridge”来和本地代码沟通,因此造成了运行效率的低下。AOT可以将Dart代码编译成本地代码(iOS or Android),Dart语言因而获得更高的运行效率。

JIT,即Just-in-time,动态(即时)编译,边运行边编译;AOT,Ahead Of Time,指运行前编译,是两种程序的编译方式
区别
这两种编译方式的主要区别在于是否在“运行时”进行编译
优劣
JIT优点:
可以根据当前硬件情况实时编译生成最优机器指令(ps. AOT也可以做到,在用户使用是使用字节码根据机器情况在做一次编译)
可以根据当前程序的运行情况生成最优的机器指令序列
当程序需要支持动态链接时,只能使用JIT
可以根据进程中内存的实际情况调整代码,使内存能够更充分的利用
缺点:
编译需要占用运行时资源,会导致进程卡顿
由于编译时间需要占用运行时间,对于某些代码的编译优化不能完全支持,需要在程序流畅和编译时间之间做权衡
在编译准备和识别频繁使用的方法需要占用时间,使得初始编译不能达到最高性能
AOT优点:
在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗
可以在程序运行初期就达到最高性能
可以显著的加快程序的启动
缺点:
在程序运行前编译会使程序安装的时间增加
牺牲Java的一致性
将提前编译的内容保存会占用更多的外

Dalvik
Dalvik使用JIT
使用.dex字节码,是针对Android设备优化后的DVM所使用的运行时编译字节码
.odex是对dex的优化,deodex在系统第一次开机时会提取所有apk内的dex文件,odex优化将dex提前提取出,加快了开机的速度和程序运行的速度
ART
ART 使用AOT
在安装apk时会进行预编译,生成OAT文件,仍以.odex保存,但是与Dalvik下不同,这个文件是可执行文件
dex、odex 均可通过dex2oat生成oat文件,以实现兼容性
在大型应用安装时需要更多时间和空间

在Android N中引入了一种新的编译模式,同时使用JIT和AOT

  • 5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等).
  • BroadcastReceiver在10s内无法结束.
  • Service 15s。

哪些地方是执行在主线程的

  1. Activity的所有生命周期回调都是执行在主线程的.
  2. Service默认是执行在主线程的.
  3. BroadcastReceiver的onReceive回调是执行在主线程的.
  4. 没有使用子线程的looper的Handler的handleMessage, post(Runnable)是执行在主线程的.
  5. AsyncTask的回调中除了doInBackground, 其他都是执行在主线程的.
  6. View的post(Runnable)是执行在主线程的.

Java实现多线程的方式. 有两种实现方法, 继承Thread 或 实现Runnable接口

AsyncTask,HandlerThread,IntentService

默认情况下Handler的handleMessage是执行在主线程的, 但是如果我给这个Handler传入了子线程的looper, handleMessage就会执行在这个子线程中的. HandlerThread正是这样的一个结合体

Service是运行在主线程的, 然而IntentService是运行在子线程的.
实际上IntentService就是实现了一个HandlerThread + ServiceHandler的模式.

Model层。也叫模型层,主要负责和数据交互的任务。模型层主要功能有定义数据结构。从数据库读、取数据,数据格式验证,读数据进行加工处理。

Model层类似与三层架构中的DAL 层。主要与数据库进行交互。而且进行简单的数据处理。

View层,即视图层,负责全部界面层的任务。事实上就是写入数据和显示数据。主要功能有获得数据,显示数据。决定界面技术(HTML,XML,Flash等)。界面排版;向Controller返回数据,决定数据传送方式,数据验证。

View层类似于三层中的UI层,主要是和用户进行数据交互的。

Controller层。集控制层。接受用户输入的数据。调用模型和视图完毕用户的需求。当用户单击超链接或者发送HTML表单时。控制器事实上不做不论什么的处理和输出,它仅仅是依据实际情况决定调用哪个模型或者视图去处理这个请求,然后决定使用哪个视图来显示返回的处理结果。

MVP模式的优点是实现了View和Model的解耦,缺点是Presenter职责太重。
MVVM模式同样实现了View和Model的解耦。不过和MVP模式不同的是,MVVM是从数据驱动的角度出发来解决这个问题的。

当ViewPager管理的页面有大量数据时候,也就是如果viewpager管理的页面是一个ListView时候,推荐使用FragmentStatePagerAdapter。这是因为FragmentStatePagerAdapter在页面不可见的时候,会销毁这个Fragment。所以FragmentStatePagerAdapter比FragmentPagerAdapter占用内存更小。

Serializable和Parcelable对比 
 1.在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
 2.Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
 3.Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化.(原因是在不同的Android版本当中,Parcelable可能会不同,因此数据的持久化方面仍然是使用Serializable)
 4.Parcelable 不仅仅需要声明,还需要实现内部的相应方法.writeToParcel;重写describeContents方法,默认值为0;Public static final Parcelable.CreatorCREATOR = new CREATOR(){...}
 5.Parcelable是以Ibinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable,既然是内存方面比价有优势,那么自然就要优先选择.
 6.在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上.

Handler内存泄露的原因是什么?
"内部类持有了外部类的引用,也就是Hanlder持有了Activity的引用,从而导致无法被回收呗。"
完整的引用。
主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity

永不崩溃app:
主线程崩溃,其实都是发生在消息的处理内,包括生命周期、界面绘制。
所以如果我们能控制这个过程,并且在发生崩溃后重新开启消息循环,那么主线程就能继续运行

如在ViewGroup的onInterceptTouchEvent方法中,如果在ACTION_DOWN事件中返回了true,那么后续的事件将直接发给onTouchEvent,而不是继续发给onInterceptTouchEvent。拦截一系列事件。

onLongClick的发生是由单独的线程完成的,并且在ACTION_UP之前,而onClick的发生是在ACTION_UP后,因此同一次用户touch操作就有可能既发生onLongClick又发生onClick。这样是不是不可思议?所以及时向系统表示“我已经完全处理(消费)了用户的此次操作”,是很重要的事情。例如,我们如果在onLongClick()方法的最后return true,那么onClick事件就没有机会被触发了。

android这里的代码,如果想要能够让react-native调用到,需要实现三个步骤:
1.写出你想要调用的模块(继承ReactContextBaseJavaModule)
2.把这个模块导出(实现导出接口ReactPackage)
3.把这个模块注册发布(在MainApplication中注册)

了解了Handler的内部原理后,再来分析由Handler引起的内存泄露问题:

  1. 当定义了一个非静态的Handler内部类时,内部类会隐式持有外围类的引用。
  2. Handler执行sendMessageAtTime方法时,Message的target参数会持有Handler对象。
  3. 当Message没有被执行时(比如now<when),若退出了Activity,此时Message依然持有Handler对象,而Handler持有Activity的对象,导致内存泄露。
    解决方案:
  4. 将Handler定义为静态内部类。
  5. 退出Activity时清空MessageQueue中对应的Message。

Jetpack
Lifecycle 的存在,主要是为了解决 生命周期管理 的一致性问题。
LiveData 的存在,主要是为了帮助 新手老手 都能不假思索地 遵循 通过唯一可信源分发状态 的标准化开发理念,从而在快速开发过程中 规避一系列 难以追溯、难以排查、不可预期 的问题。
ViewModel 的存在,主要是为了解决 状态管理 和 页面通信 的问题。
DataBinding 的存在,主要是为了解决 视图调用 的一致性问题。

posted @ 2021-04-16 15:56  FreeFunCode  阅读(5345)  评论(0编辑  收藏  举报