Android高频面试专题 - 基础篇(一)Activity

点击上方 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之间传递数据的方式)

  1. 静态static

  2. 单例

  3. Application

  4. 持久化

20. 如何在Application中获取当前Activity实例

在Application类,通过实现Application.ActivityLifecycleCallbacks接口调用registerActivityLifecycleCallbacks,在onActivityResumed(Activity activity) 方法中记录当前显示的Activity,注意内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值