点击上方 Android扫地僧 ,选择 星标 公众号
重磅资源、干货分享,快上车!
本篇主要介绍Activity相关面试题,既有基础知识,也有一些比较冷门的,但是面试官会问的比较晦涩的点。未经作者本人允许,严禁转载。
1、Activity生命周期
1.1 正常情况下的生命周期
Activity启动–>onCreate()–>onStart()–>onResume()
点击home键回到桌面–>onPause()–>onStop()
再次回到原Activity时–>onRestart()–>onStart()–>onResume()
退出当前Activity时–>onPause()–>onStop()–>onDestroy()
1.2 异常情况下的生命周期
情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建。
情况2:资源内存不足导致低优先级的Activity被杀死。
可以从图中看出当Activity发生意外的情况的时候,这里的意外指的就是系统配置发生改变(在未配置android:configChanges的前提下),如横竖屏切换(切横屏时会执行一次,切竖屏时会执行两次。),开启/关闭暗黑模式,Activity会被销毁,onPause,OnStop,onDestory函数均会被调用。并且会调用onSaveInstanceState,我们可以在这里保存当前Activity状态,比如:文本框中用户输入的数据,ListView滚动的位置等。并通过onRestoreInstanceState来恢复之前的状态,调用onSaveInstanceState的时机总会发生在onStop之前,至于会不会调用时机发生在onPause方法之前,那就说不定了,这个没有固定的顺序可言,onRestoreInstanceState的调用时机发生在onStart之后。
如果我们不想自己的Activity在系统配置发生改变时被销毁后再重建,只需在清单文件中对应Activity下的android:configChanges中添加对应场景配置项,如orientation|screenSize|keyboardHidden。
1.3 什么时候Activity单独走onPause()不走onStop()?
很少情况下Activity才走“onPause”,网上一些关于对话框弹出后Activity会走“onPause”的说法,经过笔者验证,在某个Activity内弹出对话框并没有走“onPause”,所以网上大部分这样说法的文章要么是没验证,要么直接转载的。官方文档介绍,如果要调用onPause(),Activity必须Leavesforeground,也就是离开前台或者离开栈顶,那么除非start一个设置为android:theme="@android:style/Theme.Dialog"的Activity,当前Activity才会只走onPause()。
1.4 什么时候Activity不执行onDestory()
栈里面的第一个没有销毁的activity会执行ondestroy方法,其他的不会执行。比如说:从mainactivity跳转到activity-A(或者继续从activity-A再跳转到activity-B),这时候,从后台强杀,只会执行mainactivity的onDestroy方法,activity-A(以及activity-B)的onDestroy方法都不会执行;
1.5 进程重要等级和Activity的关系
前台>可见>服务>后台>空
前台:与当前用户正在交互的Activity所在的进程。
可见:Activity可见但是没有在前台所在的进程。
服务:Activity在后台开启了Service服务所在的进程。
后台:Activity完全处于后台所在的进程。
空:没有任何Activity存在的进程,优先级也是最低的。
1.6 onSaveInstanceState()被执行的场景有哪些:
当用户按下HOME键时
长按HOME键,选择运行其他的程序时
锁屏时
从activity A中启动一个新的activity时
屏幕方向切换时
2. Activity启动流程
(1) Activity1调用startActivity,实际会调用Instrumentation类的execStartActivity方法,Instrumentation是系统用来监控Activity运行的一个类,Activity的整个生命周期都有它的影子。(1- 4)
(2) 通过跨进程的binder调用,进入到ActivityManagerService中,其内部会处理Activity栈,通知Activity1 Pause,Activity1 执行Pause 后告知AMS。(5 - 29)
(3) 在ActivityManagerService中的startProcessLocked中调用了Process.start()方法。并通过连接调用Zygote的native方法forkAndSpecialize,执行fork任务。之后再通过跨进程调用进入到Activity2所在的进程中。(30 - 36)
(4) ApplicationThread是一个binder对象,其运行在binder线程池中,内部包含一个H类,该类继承于类Handler。主线程发起bind Application,AMS 会做一些配置工作,然后让主线程 bind ApplicationThread,ApplicationThread将启动Activity2的信息通过H对象发送给主线程。发送的消息是EXECUTE_TRANSACTION,消息体是一个 ClientTransaction,即 LaunchActivityItem。主线程拿到Activity2的信息后,调用Instrumentation类的newActivity方法,其内通过ClassLoader创建Activity2实例。(37 - 40)
(5) 通知Activity2去performCreate,走生命周期流程。(41 - 最后)
https://www.jianshu.com/p/cf6efb2d1832
3. Activity的启动模式
standard模式
standard模式是Activity默认的启动模式。每启动一个Activity就在栈顶创建一个新的实例。闹钟通常采用此种模式。
singleTop模式
singleTop顾名思义,栈顶单例。如果有时候satndard模式并不合理,比如当前Activity已处于栈顶,再次启动此Activity会重新创建实例,不会直接复用。
将某个Activity的启动模式设置为singleTop,启动此Activity时,会先检查栈顶是否是此Activity的实例,如果是,则直接复用,如果不是,才创建实例。浏览器的书签通常采用此种模式。
singleTask模式
singleTask,顾名思义,任务栈中只有一个实例。
启动某个Activity时,会先检查任务栈中是否有该Activity的实例,有就直接复用(把前面所有的Activity出栈),没有才创建并入栈。浏览器的主界面通常采用此模式。
singleInstance模式
会启动一个新的任务栈来管理当前程序中singleInstance模式启动的Activity,在Android系统中,该Activity只有一个实例。
这种模式主要是为了,在不同程序间共享同一个Activity实例。
启动Activity时,若Android系统中不存在该Activity的实例,则创建并入栈;若已存在,不管此实例位于哪个程序的哪个任务栈中,系统都会把该任务栈转移到前台,显示该实例。来电界面通常使用此模式。
我们假设目前有2个任务栈,前台任务栈的情况为AB,而后台任务栈的情况为CD,这里假设CD的启动模式均为singleTask。现在请求启动D,那么整个后台任务栈都会被切换到前台,这个时候整个后退列表变成了ABCD。当用户按back键的时候,列表中的Activity会一一出栈,如下图1所示:
4.scheme跳转协议
先来个完整的URL Schema协议格式:
SchemaName://hostName:8080/pathName?query
通过上面的路径包括 Schema、Host、port、path、query。
•SchemaName代表该Schema 协议名称
•hostName代表Schema作用于哪个地址域
•pathName代表Schema指定的页面
•query代表传递的参数
•8080代表该路径的端口号 ,可以省略
<activity
android:name=".ui.activity.XXXX"
android:configChanges="orientation|keyboard|mcc|mnc|locale|keyboardHidden|uiMode|fontScale">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.VIEW" />
<data
android:host="com.ldx.demo"
android:scheme="example"
android:path="/ActivityDemo1"/>
</intent-filter>
</activity>
最终形成的Uri :example://com.ldx.demo/ActivityDemo1
启动方式:uri就是上面生成的字符串,调用之后就会启动对应的Activity
try{
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
Activity中通过getIntent().getData();和getIntent().getSchema();获取数据。
通常用于以下几种场景:
服务器下发跳转路径,客户端根据服务器下发跳转路径跳转相应的页面;
H5页面点击锚点,根据锚点具体跳转路径App端跳转具体的页面;
App端收到服务器端下发的PUSH通知栏消息,根据消息的点击跳转路径跳转相关页面。
5. 如何将一个 Activity 设置成窗口的样式?
只需要给我们的 Activity 配置如下属性即可。
android:theme="@android:style/Theme.Dialog"
6. 如何用代码安装一个apk文件
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
“application/vnd.android.package-archive”);
startActivity(intent)
7. App的入口
既然Android是基于Java语言的,而Java有main方法,那么Android有吗?Android的主入口在哪里?
答案:有Main方法,main方法在ActivityThread类中的第 6041行 main(String[] args)
8. 下拉状态栏时Activity的生命周期
下拉状态栏对生命周期没有任何影响,弹出AlertDialog、Toast时都没有影响,重新理解下onPause(),应该修正为“被Activity遮挡”
9. 谈谈隐式启动和显示启动Activity的方式
a. 显示启动方式:
直接指定Activity:
Intent intent = new Intent(A.this,B.class);
指定包名、类名
Intent intent = new Intent();
intent.setClassName(“com.android”,“com.android.B.class”);
指定ComponentName
Intent intent = new Intent();
intent.setComponent(new Component(“com.android”,“com.android.B.class”));
b. 隐式启动方式:
只要知道被启动Activity的Action和Category即可,不用知道对应的类名或者是包名,常见的启动浏览器,启动相机等。注意异常处理。
private void startSecondActivityByAction() {
XLog.i(TAG, “startSecondActivityByAction()”);
Intent intent = new Intent();
intent.setAction(“android.intent.action.SECONDACTIVITY_START”);
intent.addCategory(Intent.CATEGORY_DEFAULT);
try {
startActivity(intent);
} catch (Exception e) {
XLog.i(TAG, “start activity error!”);
}
}
10.如何给Activity设置进入和退出的动画?
在AndroidMainfest.xml中为Activity指定theme,theme中设置android:windowAnimationStyle为指定style, style中设置以下属性
通过调用overridePendingTransition(A, B) 可以实时修改Activity的切换动画。但需注意的是:该函数必须在调用startActivity()或者finish()后立即调用,且只有效一次。
其中A是新Activity进入时的动画,B是旧Activity退出时的动画
API21以后可以使用转场动画
11.如何统计Activity的工作时间
定义一个基类Activity,每一个Activity都继承自这个基类,并在这个基类onStart()和onStop()方法中进行上报,继而就可以统计到每个页面的PV、页面留存时间,同时还可以在基类中做一些优化设置
12.a->b->c界面,其中b是SingleInstance的,那么c界面点back返回a界面,为什么
singleInstance模式是存在于另一个任务栈中的。也就是说ActivityA和ActivityC是处于同一个任务栈中的,ActivityB则是存在另个栈中。所以当关闭了ActivityC的时候,它自然就会去找当前任务栈存在的activityA。当前的activity都关闭了之后,才会去找另一个任务栈中的activity。也就是说当在ActivityC中finish之后,会回到ActivityA的界面,在ActivityA里finish之后会回到ActivityB界面。
13. Activity常用的标记位Flags
FLAG_ACTIVITY_NEW_TASK
此标记位作用是为Activity指定“singleTask”启动模式,其效果和在XML中指定相同android:launchMode=“singleTask”
FLAG_ACTIVITY_SINGLE_TOP
此标记位作用是为Activity指定“singleTop”启动模式,其效果和在XML中指定相同android:launchMode=“singleTop”
FLAG_ACTIVITY_CLEAR_TOP
具有此标记位的Activity,当它启动时,在同一个任务栈中位于它上面的Activity都要出栈。此标记位一般会和singleTask启动模式一起出现,此情况下,若被启动的Activity实例存在,则系统会调用它的onNewIntent。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有此标记位的Activity不会出现在历史Activity的列表中,当某些情况我们不希望用户通过历史列表回到我们的Activity时这个标记比较有用。他等同于在XML中指定Activity的excludeFromRecents属性。
android:excludeFromRecents=“true”
14.你知道onNewIntent吗?
如果IntentActivity处于任务栈的顶端,也就是说之前打开过的Activity,现在处于onPause、onStop 状态的话,其他应用再发送Intent的话,执行顺序为:onNewIntent,onRestart,onStart,onResume。
15.Activity进程优先级
优先级最高: 与用户正在进行交互的Activity,即前台Activity。
优先级中等:可见但非前台的Activity,比如:一个弹出对话框的Activity,可见但是非前台运行。
优先级最低:完全存在与后台的Activity,比如:执行了onStop。
16. 有什么方法可以启动一个没有在AndroidManifest.xml中注册过的Activity
通过Hook AMS,插件化技术原理,用一个已经注册过的Activity去欺骗AMS和PMS的检查,然后真正创建Ativity和启动的时机替换成真的Activity。
17. 在Activity中可以多次调用setContentView方法吗?说说不同时机第二次调用setContentView会发生什么?
setContentView方法所指定的View,只有在onCreate方法返回后才会显示在界面上。因此,如果调用了两次setContentView方法,只有最后一次才是有效的。
18.Activity用Intent传递数据和Bundle传递数据的区别?为什么不用HashMap呢
Bundle内部是由ArrayMap实现的,ArrayMap的内部实现是两个数组,一个int数组是存储对象数据对应下标,一个对象数组保存key和value,内部使用二分法对key进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,只适合于小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。而HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。因为使用Bundle的场景大多数为小数据量,我没见过在两个Activity之间传递10个以上数据的场景,所以相比之下,在这种情况下使用ArrayMap保存数据,在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用。
另外一个原因,则是在Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,HashMap使用Serializable进行序列化,而Bundle则是使用Parcelable进行序列化。而在Android平台中,更推荐使用Parcelable实现序列化,虽然写法复杂,但是开销更小,所以为了更加快速的进行数据的序列化和反序列化,系统封装了Bundle类,方便我们进行数据的传输
19.Activity使用Intent传递数据是否有限制 & 如果传递一个复杂的对象,例如一个复杂的控件对象应该怎么做?
intent传递数据有限制,实质上是由Binder内核传递,并不是为了传输大量数据而设计,而是为了进程间频繁通信所设计,内核限制是4M,在APP中限制了不到1M(比1M略小的值),真机中可能还有其他任务在占用,最好在512k以内。否则会报错TransactionTooLargeException。
改进方案:
一. 限制传递数据量
二. 改变数据传输方式(参见Activity之间传递数据的方式)
-
静态static
-
单例
-
Application
-
持久化
20. 如何在Application中获取当前Activity实例
在Application类,通过实现Application.ActivityLifecycleCallbacks接口调用registerActivityLifecycleCallbacks,在onActivityResumed(Activity activity) 方法中记录当前显示的Activity,注意内存泄漏。