android74道高级面试题(如果这些知识还不会)

作者:王三端

activity作为android四大组件之一,地位就不用多说了吧,该组件看起来是比较简单的,但是也涉及到很多知识点,要想全部理解并在合适的业务场景下使用,也是需要一定的技术沉淀,本文主要是对activity一些重要知识点进行总结整理,可能平时不一定用到,但是一定要有所了解。

当然这些知识点并没有设计过多源码部分,比如activity的启动流程什么的,主要是零散的知识点,对于activity的启动流程网上文章太多了,后面自己也准备重新梳理下,好记性不如烂笔头,在不断学习整理的过程中,一定会因为某个知识点而豁然开朗。

android74道高级面试题(如果这些知识还不会)(1)

一、生命周期

1.1 两个页面跳转

从MainActivity跳转到SecordActivity的生命周期,重点关注Main的onPause和onStop与Secord几个关键生命周期的顺序,以及从Secord返回时与Main的生命周期的交叉:

android74道高级面试题(如果这些知识还不会)(2)

可以发现Main页面的onPause生命周期之后直接执行Secord的onCreate,onStart,onResume,所以onPause生命周期内不要执行耗时操作,以免影响新页面的展示,造成卡顿感。

1.2 弹出Dialog

  • 单纯的弹出Dialog是不会影响Activity的生命周期的;
  • 启动dialog theme的Activity的时候,启动的activity只会执行onPause方法,onStop不会执行,被启动的Activity会正常走生命周期,back的时候,启动的Activity会对应执行onResume方法;

android74道高级面试题(如果这些知识还不会)(3)

1.3 横竖屏切换

  • AndroidManifest不配置configChanges时,横竖屏切换,会销毁重建Activity,生命周期会重新走一遍;
  • 当ActivityconfigChanges="orientation|screenSize"时,横竖屏切换不会重新走Activity生命周期方法,只会执行onConfigurationChanged方法,如需要可以在此方法中进行相应业务处理;

如横竖屏切换时需要对布局进行适配,可在res下新建layout-port、layout-land目录,并提供相同的xml布局文件,横竖屏切换时即可自动加载相应布局。(前提是未配置configChanges忽略横竖屏影响,否则不会重新加载布局)

1.4 启动模式对生命周期的影响

1、A(singleTask)启动(startActivity)B(standard),再从B启动A,生命周期如下:

A启动B:A_onPause、B_onCreate、B_onStart、B_onResume、A_onStop

第二步:B_onPause、A_onNewintent、A_onRestart、A_onStart、A_onResume、B_onStop、B_onDestory

2、A(singleTask)启动A,或者A(singleTop)启动A

A_onPause、A_onNewIntent、A_Resume

3、singleInstance模式的activity

多次启动A(singleInstance),只有第一次会创建一个单独的任务栈(全局唯一),再次启动会调用A_onPause、A_onNewIntent、A_Resume。

二、启动模式

Activity的启动模式一直是standard、singleTop、singleTask、singleInstance四种,Android 12新增了singleInstancePerTask启动模式,在这里不一一介绍,仅介绍重要知识点。

2.1 singleTask

Activity是一个可以跨进程、跨应用的组件,当你在 A App里打开 B App的Activity的时候,这个Activity会直接被放进A的Task里,而对于B的Task,是没有任何影响的。

从A应用启动B应用,默认情况下启动的B应用的Activity会进入A应用当前页面所在的任务栈中,此时按home建,再次启动B应用,会发现B应用并不会出现A启动的页面(前提是A应用启动的不是B应用主activity,如果是必然一样),而是如第一次启动一般.

如果想要启动B应用的时候出现被A应用启动的页面,需要设置B应用被启动页的launchmode为singleTask,此时从A应用的ActivityA页面启动B应用的页面ActivityB(launchmode为singleTask),发现动画切换方式是应用间切换,此时ActivityB和ActivityA分别处于各自的任务栈中,并没有在一个task中,此时按Home键后,再次点击启动B应用,发现B应用停留在ActivityB页面。

如果想要实现上述效果,除了设置launchmode之外,还可以通过设置allowTaskReparenting属性达到同样的效果,Activity 默认情况下只会归属于一个 Task,不会在多个Task之间跳来跳去,但你可以通过设置来改变这个逻辑,如果你不设置singleTask,而是设置allowTaskReparenting为true,此时从A应用的ActivityA页面启动B应用的页面ActivityB(设置了allowTaskReparenting为true),ActivityB会进入ActivityA的任务栈,此时按Home键,点击启动B应用,会进入ActivityB页面,也就是说ActivityB从ActivityA的任务栈移动到了自己的任务栈中,此时点击返回,会依次退出ActivityB所在任务栈的各个页面,直到B应用退出。

注意:allowTaskReparenting在不同Android版本上表现有所不同,Android9以下是生效的,Android9,10又是失效的,但Android11又修复好了,在使用时一定要好好测试,避免一些因版本差异产生的问题。

2.2 singleInstance

singleInstance具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activity时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。

2.3 singleInstancePerTask

释义:singleInstancePerTask的作用和singleTask几乎一模一样,不过singleInstancePerTask不需要为启动的Activity设置一个特殊的taskAffinity就可以创建新的task,换句话讲就是设置singleInstancePerTask模式的activity可以存在于多个task任务栈中,并且在每个任务栈中是单例的。

多次启动设置singleInstancePerTask模式的Activity并不会多次创建新的任务栈,而是如singleInstance模式一样,把当前Activity所在的任务栈置于前台展示,如果想每次以新的任务栈启动需要设置FLAG_ACTIVITY_MULTIPLE_TASK和FLAG_ACTIVITY_NEW_DOCUMENT,使用方式如下:

intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);

此时,每次启动Activity就会单独创建新的任务栈。

注意:测试需要在Android12的真机或者模拟器上,否则默认为Standard模式

三、taskAffinity

taskAffinity可以指定任务栈的名字,默认任务栈是应用的包名,前提是要和singleTask,singleInstance模式配合使用,standard,singleTop模式无效,当app存在多个任务栈时,如果taskAffinity相同,则在最近任务列表中只会出现处于前台任务栈的页面,后台任务栈会“隐藏”在某处,如果taskAffinity不同,最近任务列表会出现多个任务页面,点击某个就会把该任务栈至于前台。

四、清空任务栈

activity跳转后设置FLAG_ACTIVITY_CLEAR_TASK即可清空任务栈,并不是新建一个任务栈,而是清空并把当前要启动的activity置于栈底,使用场景比如:退出登录跳转到登录页面,可以以此情况activity任务栈。

intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);

注意:FLAG_ACTIVITY_CLEAR_TASK必须与FLAG_ACTIVITY_NEW_TASK一起使用.

五、Activity.FLAG

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_NEW_TASK并不像起名字一样,每次都会创建新的task任务栈,而是有一套复杂的规则来判断:

  • 通过activity类型的context启动,如果要启动的Activity的taskAffinity与当前Activity不一致,则会创建新的任务栈,并将要启动的Activity置于栈底,taskAffinity一致的话,就会存放于当前activity所在的任务栈(注意启动模式章节第三点taskAffinity的知识点);
  • taskAffinity一致的情况下,如果要启动的activity已经存在,并且是栈根activity,那么将没有任何反应(启动不了要启动的activity)或者把要启动的activity所在的任务栈置于前台;否则如果要启动的activity不存在,将会在当前任务栈创建要启动的activity实例,并入栈;
  • taskAffinity一致的情况下,如果要启动的activity已经存在,但不是栈根activity,依然会重新创建activity示例,并入栈(前提是:要启动的activity的launchMode为standard,意思就是是否会创建新实例会受到launchMode的影响);
  • 非activity的context启动activity时(比如在service或者broadcast中启动activity),在android7.0之前和9.0之后必须添加FLAG_ACTIVITY_NEW_TASK,否则会报错(基于android-32的源码,不同版本可能不同):

//以下代码基于android 12 public void startActivity(Intent intent, Bundle options) { warnIfCallingFromSystemProcess(); final int targetSdkVersion = getApplicationInfo().targetSdkVersion; //检测FLAG_ACTIVITY_NEW_TASK if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && (targetSdkVersion < Build.VERSION_CODES.N || targetSdkVersion >= Build.VERSION_CODES.P) && (options == null || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) { //未设置FLAG_ACTIVITY_NEW_TASK,直接抛出异常 throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " " context requires the FLAG_ACTIVITY_NEW_TASK flag." " Is this really what you want?"); } //正常启动activity mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity) null, intent, -1, options); }

注意:FLAG_ACTIVITY_NEW_TASK的设置效果受到taskAffinity以及其他一些配置的影响,实际使用过程中一定要进行充分测试,并且不同的android版本也会表现不同,极端场景下要仔细分析测试,选择最优方案;

提示:通过adb shell dumpsys activity activities命令可以查看activity任务栈;

六、多进程

正常情况下,app运行在以包名为进程名的进程中,其实android四大组件支持多进程,通过manifest配置process属性,可以指定与包名不同的进程名,即可运行在指定的进程中,从而开启多进程,那么,开启多进程有什么优缺点呢?

多进程下,可以分散内存占用,可以隔离进程,对于比较重的并且与其他模块关联不多的模块可以放在单独的进程中,从而分担主进程的压力,另外主进程和子进程不会相互影响,各自做各自的事,但开启了多进程后,也会带来一些麻烦事,比如会引起Application的多次创建,静态成员失效,文件共享等问题。

所以是否选择使用多进程要看实际需要,我们都知道app进程分配的内存是有限的,超过系统上限就会导致内存溢出,如果想要分配到更多的内存,多进程不失为一种解决方案,但是要注意规避或处理一些多进程引起的问题;

设置多进程的方式:

android:process=":childProcess" //实际上完整的进程名为:包名:childProcess,这种方式声明的属于私有进程。 android:process="com.child.process" //完整的进程名即为声明的名字:com.child.process,这种方式声明的属于全局进程。

七、excludeFromRecents

excludeFromRecents如果设置为true,那么设置的Activity将不会出现在最近任务列表中,如果这个Activity是整个Task的根Activity,整个Task将不会出现在最近任务列表中.

八、startActivityForResult被弃用

使用Activity Result Api代替,使用方式如下:

private val launcherActivity = registerForActivityResult( ActivityResultContracts.StartActivityForResult()) { Log.e("code","resultCode = " it.resultCode) } findViewById<Button>(R.id.btn_jump).setOnClickListener { launcherActivity.launch(Intent(this@MainActivity,SecordActivity::class.java)) } //要跳转的Activity设置回调数据: val resultIntent = Intent() resultIntent.putExtra("dataKey","data value") setResult(1001,resultIntent) finish()

九、Deep link

简单理解,所谓Deep Link就是可以通过外部链接来启动app或者到达app指定页面的一想技术,比如可以通过点击短信或者网页中的链接来拉起app到指定页面,以达到提供日活或者其他目的,一般流程是可以通过在manifest的activity标签中配置固定的schema来实现这种效果,形如:

<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="jumptest" android:host="work" android:port="8801" android:path="/main" /> </intent-filter>

然后在网页中就可以通过如下方式来启动当前activity:

<a href="jumptest://work:8801/main?data=123456">你好</a>

格式 <scheme>://<host>:<port>/<path>?<query>

被启动的app可以通过如下方式拿到传递的参数以及schmea配置项:

val host = schemaIntent.data?.host val path = schemaIntent.data?.path val schema = schemaIntent.data?.scheme val query = schemaIntent.data?.query Log.e("schema","host = $host, path = $path, schema = $schema, query = $query")

结果:

android74道高级面试题(如果这些知识还不会)(4)

注意:

1.intent-filter与Main主Activity搭配使用时,要单独开启一个intent-filter,否则匹配不到。 2.从android12开始,设置了intent-filter标签后,activity的exported必须设置成true,这个要注意(android12之前,其实添加了intent-filter,系统也会默认设置exported为true)。

app link

App link是一种特殊的Deep link,它的作用就是可以使通过网站地址打开app的时候,不需要用户选择使用哪个应用来打开,换种说法就是,我可以设置默认打开次地址的应用,这样一来,就可以直接引导到自己的app。

十、setResult和finish的顺序关系

通过startActivityForResult启动activity,通常会在被启动的activity的合适时机调用setResult来回调数据给上一个页面,然后当前页面返回的时候就会回调onActivityResult,这里要注意setResult的调用时机,请一定要在activity的finish()方法之前调用,否则可能不会生效(不会回调onActivityResult)

原因如下:

private void finish(int finishTask) { if (mParent == null) { int resultCode; Intent resultData; //会在finish的时候把回调数据赋值 synchronized (this) { resultCode = mResultCode; resultData = mResultData; } ··· if (ActivityClient.getInstance().finishActivity(mToken, resultCode, resultData, finishTask)) { mFinished = true; } } else { mParent.finishFromChild(this); } ··· } //setResult对mResultCode,mResultData赋值 public final void setResult(int resultCode) { synchronized (this) { mResultCode = resultCode; mResultData = null; } }

由上述代码可以看出,setResult必须在finish之前赋值,才能够在finish的时候拿到需要callback的数据,以便在合适的时机回调onActivityResult;

十一、onSaveInstanceState()和onRestoreInstanceState()

activity在非正常情况被销毁的时候(非正常情况:横竖屏切换,系统配置发生变化,内存不足后台activity被回收等),当重新回到该activity,系统会重新实例化该对象,如果没有对页面输入的内容进行保存,就会存在内容丢失的情况,此时可以通过onSaveInstanceState来保存页面数据,在onCreate或者onRestoreInstanceState中对数据进行恢复,形如:

override fun onSaveInstanceState(outState: Bundle) { outState.putString("SAVE_KEY","SAVE_DATA") outState.putString("SAVE_KEY","SAVE_DATA2") super.onSaveInstanceState(outState) } //需要判空,savedInstanceState不一定有值 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if(null != savedInstanceState){ saveData = savedInstanceState.getString("SAVE_KEY") ?: "" saveData2 = savedInstanceState.getString("SAVE_KEY2") ?: "" } setContentView(R.layout.activity_main) } //或者在onRestoreInstanceState恢复数据,无需判空,回调此方法一定有值 override fun onRestoreInstanceState(savedInstanceState: Bundle) { saveData = savedInstanceState.getString("SAVE_KEY") ?: "" saveData2 = savedInstanceState.getString("SAVE_KEY2") ?: "" super.onRestoreInstanceState(savedInstanceState) }

注意:请使用onSaveInstanceState(outState: Bundle)一个参数的方法,两个参数的方法和Activity的persistableMode有关。

最后

这里也分享一些珍藏资源,从面试简历模板到大厂面经汇总,从大厂内部技术资料到互联网高薪必读书单,以及Android面试核心知识点(844页)和Android面试题合集2022年最新版(354页)等等,这些资料整理给大家,希望踩过的坑不要再踩,遭遇的技术瓶颈一次性消灭。

如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取!

部分内容展示如下

android74道高级面试题(如果这些知识还不会)(5)

android74道高级面试题(如果这些知识还不会)(6)

01.Android必备底层技术

  • Java序列化:Serializable原理、Parcelable接口原理、Json、XML
  • 注解、泛型与反射:自定义注解、注解的使用、泛型擦除机制、泛型边界、Java方法与Arm指令、Method反射源码、invoke方法执行原理
  • 虚拟机:JVM垃圾回收器机制、JVM内存分配策略、Android虚拟机与JVM底层区别、虚拟机底层Odex本地指令缓存机制、虚拟机如何分别加载class与object、虚拟机类加载模型
  • 并发:Java线程本质讲解、线程原理、线程通信、UnSafe类、线程池
  • 编译时技术:OOP面向切面之AspectJ、字节码手术刀JavaSSit实战、字节码插桩技术(ASM)实战
  • 动态代理:动态代理实现原理、动态代理在虚拟机中运行时动态拼接Class字节码分析、ProxyGenerator生成字节码流程
  • 高级数据结构与算法:HashMap源码、ArrayList源码、排序算法
  • Java IO:Java IO体系、IO文件操作

android74道高级面试题(如果这些知识还不会)(7)

02.Framework

  • Binder:Linux内存基础、Binder四层源码分析、Binder机制、Binder进程通信原理
  • Handler:Loop消息泵机制、Message解析
  • Zygote:init进程与Zygote进程、Zygote启动流程、Socket通信模式、APP启动过程
  • AMS:ActivityThread源码分析、AMS与ActivityThread通信原理、Activity启动机制
  • PMS:PMS源码、APK安装过程分析、PMS对安装包的解析原理
  • WMS:PhoneWindow实例化流程、DecorView创建过程、ViewRootImpl渲染机制

android74道高级面试题(如果这些知识还不会)(8)

03.Android常用组件

  • Activty:Activity管理栈与Activity的启动模式、Activity生命周期源码分析
  • Fragment:Fragment生命周期深入详解、Fragment事务管理机制详解、性能优化相关方案
  • Service:Service启动模式分析、Service管理与通信方案、Service生命周期底层详解

android74道高级面试题(如果这些知识还不会)(9)

04.高级UI

  • UI绘制原理:setContentView()方法下到底做了什么、AppCompatActivity与Activity的区别、UI测量、布局、绘制的底层执行流程
  • 插件换肤:LayoutInflater加载布局分析、Android资源的加载机制、Resource与AssetManager
  • 事件分发机制原理:事件执行U形链与L形链、事件拦截原理
  • 属性动画:VSYNC刷新机制、ObjectAnimator与ValueAnimator源码讲解、Android属性动画:插值器与估值器
  • RecycleView:布局管理器LayoutManager详解、回收池设计思想、适配器模式原理
  • 高阶贝塞尔曲线

android74道高级面试题(如果这些知识还不会)(10)

05.Jetpack

  • Lifecycle:Lifecycle源码、Lifecycle高阶应用
  • ViewModel:ViewModel源码、ViewModel应用技巧
  • LiveData:LiveData源码
  • Navigation:Navigation源码
  • Room:Room源码、Room LiveData监听数据库数据变更刷新页面原理
  • WorkManager内核
  • Pagging原理
  • DataBinding:单向绑定、双向绑定、如何与RecyclerView的配合使用、底层原理

android74道高级面试题(如果这些知识还不会)(11)

06.性能优化

  • 启动优化:系统启动原理、Trace工具分析启动卡顿、类重排机制、资源文件重排机制
  • 内存优化
  • UI渲染优化:UI层级规范及对UI加载的影响、UI卡顿原因及修复、UI绘制、布局、测量原因以及处理方案
  • 卡顿优化:造成卡顿的原因分析、内存抖动与GC回收、回收算法
  • 耗电优化
  • 崩溃优化:项目崩溃异常捕获、优雅的异常处理方案、如何避免异常弹框
  • 安全优化:APP加固实现(防反编译,dex加固)、https防抓包机制(数据传输加载,客户端服务器端双向加密校验)
  • 网络优化:serializable原理、parcelable接口原理、http与https原理详解、protbuffer网络IO详解、gzip压缩方案
  • 大图加载优化:Glide巨图加载机制原理分析、大图多级缓存实现方案
  • 多线程并发优化
  • 储存优化:Android文件系统-sdcard与内存存储、Shared Preference原理、MMAP内存映射
  • 安装包优化:shrinkResources去除无用资源、合理设置多语言、webp实现图片瘦身、合理配置armable-v7的so库、Lint检查工具实践

android74道高级面试题(如果这些知识还不会)(12)

如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取!

07.音视频

  • C/C :数据类型、数组、内存布局、指针、函数、预处理器、结构体、共用体、容器、类型转换、异常、文件流操作、线程
  • H.265/H.265:音视频格式封装原理、编码原理、视频流H264的组装原理切片NAL单元、视频流H264码流分析、切片与宏快,运动矢量、信源编码器、高频滤波、帧间拆分与帧内预测、CTU,PU TU编码结构、DSP芯片解码流程、MediaPlayer与DSP芯片交互机制、投屏架构、MediaProjection与MeidiaCodec交互机制、H265码流交换
  • MediaCodec:dsp芯片、编解码器的生命周期、解码器中输入队列与解析队列设计思想、MediaCodec中平缓解码解析、MediaExtractor 多路复用、MediaMuxer合成器、MediaFormat格式
  • 音视频剪辑:视频剪辑、音频剪辑、音频合成、音谱显示、视频倒放
  • 音视频直播:硬编码、软编码、native实现rtmp推流、摄像头预览帧编码NV21转YUV、视频画面封装拼接Packet包、音频流数据拼接Packet包、RtmpDump实时同步发送音视频数据、MediaProjection、Medicodec编码H264码流、rtmp推流
  • OpenGL与音视频解码:OpenGL绘制流程、矩阵、Opencv详解、人脸识别效果实现
  • OpenGL特效:CPU与GPU运行机制详解、世界坐标,布局坐标,与FBO坐标系、图像镜像与旋转处理、人脸定位与关键点定位、大眼效果、贴纸效果、美颜效果
  • FFmpeg万能播放器:FFmpeg结构体、声音播放原理、Surface的渲染、像素绘制原理与对齐机制、音视频同步原理、视频播放器整体架构
  • Webrtc音视频通话:WebRtc服务端环境搭建与Webrtc编译、1v1视频通话实现方案、群聊视频通话实现思路、多对多视频会议实现、1V1音视频通话实现

android74道高级面试题(如果这些知识还不会)(13)

08.开源框架原理

  • Okhttp
  • Retrofit
  • RxJava
  • Glide
  • Hilt
  • Dagger2
  • EventBus
  • 组件化、插件化、热修复等

android74道高级面试题(如果这些知识还不会)(14)

09.Gradle

  • Groovy语法
  • Gradle Android插件配置
  • Gradle实践等

android74道高级面试题(如果这些知识还不会)(15)

10.kotlin

  • Kotlin语法
  • 扩展使用
  • 进阶使用
  • 实践等

android74道高级面试题(如果这些知识还不会)(16)

11.Flutter

  • Dart语法
  • UI
  • 进阶使用
  • 优化
  • 实践等

android74道高级面试题(如果这些知识还不会)(17)

12.鸿蒙

  • Ability组件
  • 分布式任务
  • 事件总线
  • 鸿蒙线程
  • UI自定义控件等

android74道高级面试题(如果这些知识还不会)(18)

如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取!

Android路漫漫,共勉!

android74道高级面试题(如果这些知识还不会)(19)

android74道高级面试题(如果这些知识还不会)(20)

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页