以下主要针对往期收录的面试题进行一个分类归纳整理,方便大家统一回顾和参考。本篇是第三集~
强调一下:【因篇幅问题:文中只放部分内容,全部文档需要的可找作者获取。
】
第一篇面试题在这: Android中高级进阶开发面试题冲刺合集(一)
第二篇面试题在这: Android中高级进阶开发面试题冲刺合集(二)
Android 方面
Android 考察点比较纷杂,以下针对之前收录的面试题做一个大概的划分:
Android 四大组件相关
1.Activity 与 Fragment 之间常见的几种通信方式?
参考答案:
1、对于Activity和Fragment之间的相互调用 (1)Activity调用Fragment 直接调用就好,Activity一般持有Fragment实例,或者通过Fragment id 或者tag获取到Fragment实例 (2)Fragment调用Activity 通过activity设置监听器到Fragment进行回调,或者是直接在fragment直接getActivity获取到activity实例 2、Activity如果更好的传递参数给Fragment 如果直接通过普通方法的调用传递参数的话,那么在fragment回收后恢复不能恢复这些数据。google给我们提供了一个方法 setArguments(bundle) 可以通过这个方法传递参数给fragment,然后在fragment中用getArguments获取到。能保证在fragment销毁重建后还能获取到数据
2.谈谈 Android 中几种 LaunchMode 的特点和应用场景?
参考答案:
LaunchMode
有四种,分别为 Standard
,SingleTop
,SingleTask
和 SingleInstance
,每种模式的实现原理一楼都做了较详细说明,下面说一下具体使用场景:
- Standard: Standard 模式是系统默认的启动模式,一般我们 app 中大部分页面都是由该模式的页面构成的,比较常见的场景是:社交应用中,点击查看用户A信息->查看用户A粉丝->在粉丝中挑选查看用户B信息->查看用户A粉丝… 这种情况下一般我们需要保留用户操作 Activity 栈的页面所有执行顺序。
- SingleTop: SingleTop 模式一般常见于社交应用中的通知栏行为功能,例如:App 用户收到几条好友请求的推送消息,需要用户点击推送通知进入到请求者个人信息页,将信息页设置为 SingleTop 模式就可以增强复用性。
- SingleTask: SingleTask 模式一般用作应用的首页,例如浏览器主页,用户可能从多个应用启动浏览器,但主界面仅仅启动一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。
- SingleInstance: SingleInstance 模式常应用于独立栈操作的应用,如闹钟的提醒页面,当你在A应用中看视频时,闹钟响了,你点击闹钟提醒通知后进入提醒详情页面,然后点击返回就再次回到A的视频页面,这样就不会过多干扰到用户先前的操作了。
3.BroadcastReceiver 与 LocalBroadcastReceiver 有什么区别?
参考答案:
- BroadcastReceiver 是跨应用广播,利用Binder机制实现,支持动态和静态两种方式注册方式。
- LocalBroadcastReceiver 是应用内广播,利用Handler实现,利用了IntentFilter的match功能,提供消息的发布与接收功能,实现应用内通信,效率和安全性比较高,仅支持动态注册。
4.对于 Context,你了解多少?
参考答案:
- 在 Android 平台上 , Context 是一个基本的概念,它在逻辑上表示一个运行期的“上下文”
- Context 体现到代码上来说,是个抽象类,其主要表达的行为有:使用系统提供的服务、访问资源 、信息 存储相关以及AMS的交互
- 在 Android 平台上,Activity、Service 和 Application 在本质上都是个 Context
- Android 中上下文访问应用资源或系统服务的动作都被统一封装进 ContextImpl 类中.Activity、 Service、Application 内部都含有自己的 ContextImpl,每当自己需要访问应用资源或系统服务时,就是 把请求委托给内部的 ContextImpl
- ContextWrapper 为 Context 的包装类, 它在做和上下文相关的动作时,基本上都是委托给 ContextImpl 去做
- ContextImpl 为一个上下文的核心部件,其负责和Android平台进行通信. 就以启动 activity 动作来说,最后 会走到 ContextImpl 的 startActivity(),而这个函数内部大体上是进一步调用 mMainThread.getInstrumentation().execStartActivity(),从而将语义发送给Android系统
- Context数量 = Activity数量 + Service数量 + 1 上面的1表示 Application 数量,一个应用程序里面可以有多个 Application,可是在配置文件 AndroidManifest.xml中只能注册一个, 只有注册的这个 Application 才是真正的 Application
5.IntentFilter 是什么?有哪些使用场景?匹配机制是怎样的?
参考答案:
1.IntentFilter是意图过滤器,常用于Intent的隐式调用匹配。 2.IntentFilter有3种匹配规则,分别是action、categroy、data。
action的匹配原则: IntentFilter可以有多个action,Intent最多能有1个。 1.如果IntentFilter中不存在action,那么所有的intent都无法通过。 2.如果IntentFilter存在action。 a.如果intent不存在,那么可以通过。 b.如果intent存在,那么intent中的action必须是IntentFilter中的其中一个,对比区分大小写。
category的匹配原则: IntentFilter可以有多个category,Intent也可以有多个。 1.如果IntentFilter不存在category,那么所有的intent都无法通过,因为隐式调用的时候,系统默认给Intent附加了“android.intent.category.DEFAULT”。 2.如果IntentFilter存在category a.如果intent不存在,可以通过。 b.如果intent存在,那么intent中的所有category都包含在IntentFilter中,才可以通过。
data的匹配原则: IntentFilter可以有多个data,Intent最多能有1个。 IntentFilter和Intent完全匹配才能通过,也适用于通配符。
匹配规则: Intent需要匹配多组intent-fliter中的任意一组,每一组包含action、data、category,即Intent必须同时满足这三者的过滤规则。 在同一个应用中,尽量使用显示意图,因为显示意图比隐式意图的效率高。
6.谈一谈 startService 和 bindService 方法的区别,生命周期以及使用场景?
参考答案:
1、生命周期上的区别
执行startService时,Service会经历onCreate->onStartCommand。当执行stopService时,直接调用onDestroy方法。调用者如果没有stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。
执行bindService时,Service会经历onCreate->onBind。这个时候调用者和Service绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。
多次调用startService,该Service只能被创建一次,即该Service的onCreate方法只会被调用一次。但是每次调用startService,onStartCommand方法都会被调用。Service的onStart方法在API 5时被废弃,替代它的是onStartCommand方法。
第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind方法并不会被多次调用,即并不会多次创建服务和绑定服务。
2、调用者如何获取绑定后的Service的方法
onBind回调方法将返回给客户端一个IBinder接口实例,IBinder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。我们需要IBinder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象。
3、既使用startService又使用bindService的情况
如果一个Service又被启动又被绑定,则该Service会一直在后台运行。首先不管如何调用,onCreate始终只会调用一次。对应startService调用多少次,Service的onStart方法便会调用多少次。Service的终止,需要unbindService和stopService同时调用才行。不管startService与bindService的调用顺序,如果先调用unbindService,此时服务不会自动终止,再调用stopService之后,服务才会终止;如果先调用stopService,此时服务也不会终止,而再调用unbindService或者之前调用bindService的Context不存在了(如Activity被finish的时候)之后,服务才会自动停止。
那么,什么情况下既使用startService,又使用bindService呢?
如果你只是想要启动一个后台服务长期进行某项任务,那么使用startService便可以了。如果你还想要与正在运行的Service取得联系,那么有两种方法:一种是使用broadcast,另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了。
另外,如果你的服务只是公开一个远程接口,供连接上的客户端(Android的Service是C/S架构)远程调用执行方法,这个时候你可以不让服务一开始就运行,而只是bindService,这样在第一次bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是远程服务,那么效果会越明显(当然在Servcie创建的是偶会花去一定时间,这点需要注意)。
4、本地服务与远程服务
本地服务依附在主进程上,在一定程度上节约了资源。本地服务因为是在同一进程,因此不需要IPC,也不需要AIDL。相应bindService会方便很多。缺点是主进程被kill后,服务变会终止。
远程服务是独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被kill的是偶,该服务依然在运行。缺点是该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。
对于startService来说,不管是本地服务还是远程服务,我们需要做的工作都一样简单。
7.Service 如何进行保活?
参考答案:
1:跟各大系统厂商建立合作关系,把App加入系统内存清理的白名单
2:白色保活
用startForeground()启动前台服务,这是官方提供的后台保活方式,不足的就是通知栏会常驻一条通知,像360的状态栏。
3:灰色保活
开启前台Service,开启另一个Service将通知栏移除,其oom_adj值还是没变的,这样用户就察觉不到app在后台保活。 用广播唤醒自启,像开机广播、网络切换广播等,但在国产Rom中几乎都被堵上了。 多个app关联唤醒,就像BAT的全家桶,打开一个App的时候会启动、唤醒其他App,包括一些第三方推送也是,对于大多数单独app,比较难以实现。
4:黑色保活
1 像素activity保活方案,监听息屏事件,在息屏时启动个一像素的activity,提升自身优先级; Service中循环播放一段无声音频,伪装音乐app,播放音乐中的app优先级还是蛮高的,也能很大程度保活效果较好,但耗电量高,谨慎使用; 双进程守护,这在国产rom中几乎没用,因为划掉app会把所有相关进程都杀死。 3、实现过程:
1)、用startForeground()启动前台服务
前台Service,使用startForeground这个Service尽量要轻,不要占用过多的系统资源,否则系统在资源紧张时,照样会将其杀死。
DaemonService.java
可以参考下面的 Android实现进程保活方案解析
8.简单介绍下 ContentProvider 是如何实现数据共享的?
参考答案:
当一个应用程序要把自己的数据暴露给其他程序时,可以通过ContentProvider来实现。 其他应用可以通过ContenrResolver来操作ContentProvider暴露的数据。
如果应用程序A通过ContentProvider暴露自己的数据操作接口,那么不管A 是否启动,其他程序都可以通过该接口来操作A的内部数据,常有增、删、查、改。
ContentProvider是以Uri的形式对外提供数据,ContenrResolver是根据Uri来访问数据。
步骤
定义自己的ContentProvider类,该类需要继承Android系统提供的ContentProvider基类。
在Manifest.xml 文件中注册ContentProvider,(四大组件的使用都需要在Manifest文件中注册) 注册时需要绑定一个URL。
例如: android:authorities=“com.myit.providers.MyProvider” 说明:authorities就相当于为该ContentProvider指定URL。 注册后,其他应用程序就可以通过该Uri来访问MyProvider所暴露的数据了。 其他程序使用ContentResolver来操作。
调用Activity的ContentResolver获取ContentResolver对象 调用ContentResolver的insert(),delete(),update(),query()进行增删改查。 一般来说,ContentProvider是单例模式,也就是说,当多个应用程序通过ContentResolver来操作ContentProvider提供的数据时,ContentResolver调用的数据操作将会委托给同一个ContentResolver。
9.说下切换横竖屏时 Activity 的生命周期变化?
参考答案:
竖屏: 启动:onCreat->onStart->onResume. 切换横屏时: onPause-> onSaveInstanceState ->onStop->onDestory
onCreat->onStart->onSaveInstanceState->onResume.
但是,我们在如果配置这个属性:android:configChanges=“orientation|keyboardHidden|screenSize” 就不会在调用Activity的生命周期,只会调用onConfigurationChanged方法
10.Activity 中 onNewIntent 方法的调用时机和使用场景?
参考答案:
Activity 的 onNewIntent方法的调用可总结如下:
在该Activity的实例已经存在于Task和Back stack中(或者通俗的说可以通过按返回键返回到该Activity )时,当使用intent来再次启动该Activity的时候,如果此次启动不创建该Activity的新实例,则系统会调用原有实例的onNewIntent()方法来处理此intent.
且在下面情况下系统不会创建该Activity的新实例:
1,如果该Activity在Manifest中的android:launchMode定义为singleTask或者singleInstance.
2,如果该Activity在Manifest中的android:launchMode定义为singleTop且该实例位于Back stack的栈顶.
3,如果该Activity在Manifest中的android:launchMode=“singleInstance”,或者intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)标志.
4,如果上述intent中包含 Intent.FLAG_ACTIVITY_CLEAR_TOP 标志和且包含 Intent.FLAG_ACTIVITY_SINGLE_TOP 标志.
5,如果上述intent中包含 Intent.FLAG_ACTIVITY_SINGLE_TOP 标志且该实例位于Back stack的栈顶.
上述情况满足其一,则系统将不会创建该Activity的新实例.
根据现有实例所处的状态不同onNewIntent()方法的调用时机也不同,总的说如果系统调用onNewIntent()方法则系统会在onResume()方法执行之前调用它.这也是官方API为什么只说"you can count on onResume() being called after this method",而不具体说明调用时机的原因.
11.Intent 传输数据的大小有限制吗?如何解决?
参考答案:
先说结论:
有大小限制
再说原因:
Intent 是消息传递对象,用于各组件间通信。各组件以及个程序间通信都用到了进程间通信。因此 Intent 的数据传递是基于 Binder 的,Intent 中的数据会存储在 Bundle 中,然后 IPC 过程中会将各个数据以 Parcel 的形式存储在 Binder 的事物缓冲区(Binder transaction buffer)进程传递,而 Binder 的事物缓冲区有个固定的大小,大小在 1M 附近。因为这 1M 大小是当前进程共享的,Intent 中也会带有其他相关的必要信息,所以实际使用中比这个数字要小很多。
解决方式:
- 降低传递数据的大小,或考虑其他方式,见2;
- IPC: 将大数据缓存到文件,或者存入数据库,或者图片使用 id 等;使用 Socket;
- 非 IPC:可以考虑共享内存,EventBus 等
12.说说 ContentProvider、ContentResolver、ContentObserver 之间的关系?
参考答案:
ContentProvider * 内容提供者, 用于对外提供数据,比如联系人应用中就是用了ContentProvider, * 一个应用可以实现ContentProvider来提供给别的应用操作,通过ContentResolver来操作别的应用数据
ContentResolver * 内容解析者, 用于获取内容提供者提供的数据 * ContentResolver.notifyChange(uri)发出消息
ContentObserver * 内容监听者,可以监听数据的改变状态 * 观察(捕捉)特定的Uri引起的数据库的变化 * ContentResolver.registerContentObserver()监听消息
概括: 使用ContentResolver来获取ContentProvider提供的数据, 同时注册ContentObserver监听数据的变化
13.说说 Activity 加载的流程?
参考答案:
App 启动流程(基于Android8.0):
- 点击桌面 App 图标,Launcher 进程采用 Binder IPC(具体为ActivityManager.getService 获取 AMS 实例) 向 system_server 的 AMS 发起 startActivity 请求
- system_server 进程收到请求后,向 Zygote 进程发送创建进程的请求;
- Zygote 进程 fork 出新的子进程,即 App 进程
- App 进程创建即初始化 ActivityThread,然后通过 Binder IPC 向 system_server 进程的 AMS 发起 attachApplication 请求
- system_server 进程的 AMS 在收到 attachApplication 请求后,做一系列操作后,通知 ApplicationThread bindApplication,然后发送 H.BIND_APPLICATION 消息
- 主线程收到 H.BIND_APPLICATION 消息,调用 handleBindApplication 处理后做一系列的初始化操作,初始化 Application 等
- system_server 进程的 AMS 在 bindApplication 后,会调用 ActivityStackSupervisor.attachApplicationLocked,之后经过一系列操作,在 realStartActivityLocked 方法通过 Binder IPC 向 App 进程发送 scheduleLaunchActivity 请求;
- App进程的 binder 线程(ApplicationThread)在收到请求后,通过 handler 向主线程发送 LAUNCH_ACTIVITY 消息;
- 主线程收到 message 后经过 handleLaunchActivity,performLaunchActivity 方法,然后通过反射机制创建目标 Activity;
- 通过 Activity attach 方法创建 window 并且和 Activity 关联,然后设置 WindowManager 用来管理 window,然后通知 Activity 已创建,即调用 onCreate
- 然后调用 handleResumeActivity,Activity 可见
补充:
- ActivityManagerService 是一个注册到 SystemServer 进程并实现了 IActivityManager 的 Binder,可以通过 ActivityManager 的 getService 方法获取 AMS 的代理对象,进而调用 AMS 方法
- ApplicationThread 是 ActivityThread 的内部类,是一个实现了 IApplicationThread 的 Binder。AMS通过 Binder IPC 经 ApplicationThread 对应用进行控制
- 普通的 Activity 启动和本流程差不多,至少不需要再创建 App 进程了
- Activity A 启动 Activity B,A 先 pause 然后 B 才能 resume,因此在 onPause 中不能做耗时操作,不然会影响下一个 Activity 的启动
Android 异步任务和消息机制
1.HandlerThread 的使用场景和实现原理?
参考答案&