Android 四大组件面试题
1.1 Activity 与 Fragment 之间常见的几种通信方式?
viewModel 做数据管理,activity 和 fragment 公用同个viewModel 实现数据传递
1.2 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的视频页面,这样就不会过多干扰到用户先前的操作了。
1.3 BroadcastReceiver 与 LocalBroadcastReceiver 有什么区别?
BroadcastReceiver 是跨应用广播,利用Binder机制实现,支持动态和静态两种方式注册方式。
LocalBroadcastReceiver 是应用内广播,利用Handler实现,利用了IntentFilter的match功能,提供消息的发布与接收功能,实现应用内通信,效率和安全性比较高,仅支持动态注册。
1.4 对于 Context,你了解多少?
Context 也叫上下文,是有关应用程序环境的全局信息的接口。这是一个抽象类, 它允许访问特定于应用程序的资源和类,以及对应用程序级操作的调用,比如启动活动,发送广播和接收意图等;
Activity, Service, Application 都是 Context 的子类。Context 的具体实现类是 ContextImpl, 还有一个包装类ContextWrapper, ContextWrapper 的子类有 Service,
Application,ContextThemeWrapper, Activity 又是ContextThemeWrapper 的子类,
ContextThemeWrapper 也可以叫 UI Context,跟UI 操作相关的最好使用此类 Context。
ContextWrapper 中有个 mBase,这个 mBase 其实是ContextImpl,它是在Activity, Service, Application 创建时通过 attachBaseContext() 方法将各自对对应ContextImpl 赋值的。对 context 的操作,最终实现都是在 ContextImpl。
对于 startActivity操作
- 当为Activity Context则可直接使用
- 当为其他Context, 则必须带上
FLAG_ACTIVITY_NEW_TASK flags才能使用,因为非 Activity context 启动 Activity 没有 Activity 栈,则无法启动,因此需要加开启新的栈; - 另外UI相关要Activity中使用
getApplication()和getApplicationContext() 区别?
- 对于Activity/Service来说, getApplication()和getApplicationContext()的返回值完全相同; 除非厂商修
改过接口; - BroadcastReceiver在onReceive的过程, 能使用getBaseContext().getApplicationContext获取所在Application, 而无法使用getApplication;
- ContentProvider能使用
getContext().getApplicationContext()获取所在Application. 绝大多数情况下没有问题, 但是有可能会出现空指针的问题, 情况如下:
当同一个进程有多个apk的情况下, 对于第二个apk是由provider方式拉起的, 前面介绍过provider创建过程并不会初始化所在application, 此时执行getContext().getApplicationContext()返回的结果便是NULL. 所以对于这种情况要做好判空.
1.5 IntentFilter是什么?有哪些使用场景?
IntentService是什么
IntentService是Service的子类,继承与Service类,用于处理需要异步请求。用户通过调用Context.StartService(Intent)发送请求,服务根据需要启动,使用工作线程依次处理每个Intent,并在处理完所有工作后自身停止服务。
使用时,扩展IntentService并实现onHandleIntent(android.content.Intent)。IntentService接收Intent,启动工作线程,并在适当时机停止服务。
所有的请求都在同一个工作线程上处理,一次处理一个请\求,所以处理完所以的请求可能会花费很长的时间,但由于IntentService是另外了线程来工作,所以保证不会阻止App的主线程。
IntentService与Service的区别
从何时使用,触发方法,运行环境,何时停止四个方面分析。
何时使用
Service用于没有UI工作的任务,但不能执行长任务(长时间的任务),如果需要Service来执行长时间的任务,则必须手动开店一个线程来执行该Service。
IntentService可用于执行不与主线程沟通的长任务。
触发方法
Service通过调用 startService() 方法来触发。而IntentService通过Intent来触发,开启一个新的工作线程,并在线程上调用 onHandleIntent() 方法。
运行环境
Service 在App主线程上运行,没有与用户交互,即在后台运行,如果执行长时间的请求任务会阻止主线程工作。
IntentService在自己单独开启的工作线程上运行,即使执行长时间的请求任务也不会阻止主线程工作。
何时停止
如果执行了Service,我们是有责任在其请求任务完成后关闭服务,通过调用 stopSelf() 或 stopService()来结束服务。
IntentService会在执行完所有的请求任务后自行关闭服务,所以我们不必额外调用 stopSelf() 去关闭它。
1.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来说,不管是本地服务还是远程服务,我们需要做的工作都一样简单。
1.7 Service如何进行保活?
- 利用系统广播拉活
- 利用系统service拉活
- 利用Native进程拉活<Android5.0以后失效> fork进行监控
- 主进程,利用native拉活
- 利用JobScheduler机制拉活<Android5.0以后>
- 利用账号同步机制拉活
1.8 简单介绍下ContentProvider是如何实现数据共享的?
ContentProvider(内容提供者):对外提供了统一的访问数据的接口。
ContentResolver(内容解析者):通过URI的不同来操作不同的ContentProvider中的数据。
ContentObserver(内容观察者):观察特定URI引起的数据库的变化。通过ContentResolver进行注册,观察数据是否发生变化及时通知刷新页面(通过Handler通知主线程更新UI)。
1.9 说下切换横竖屏时Activity的生命周期?
1.AndroidManifest没有设置configChanges属性竖屏启动:
onCreate -->onStart-->onResume
切换横屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->onRestoreInstanceState-->onResume -->onPause -->onStop -->onDestroy
(Android 6.0 Android 7.0 Android 8.0)
横屏启动:
onCreate -->onStart-->onResume
切换竖屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->onRestoreInstanceState-->onResume -->onPause -->onStop -->onDestroy
(Android 6.0 Android 7.0 Android 8.0)
总结:没有设置configChanges属性Android 6.0 7.0 8.0系统手机 表现都是一样的,当前的界面调用onSaveInstanceState走一遍流程,然后重启调用onRestoreInstanceState再走一遍完整流程,最终destory。
2.AndroidManifest设置了configChanges android:configChanges="orientation"
竖屏启动:
onCreate -->onStart-->onResume
切换横屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->onRestoreInstanceState-->onResume -->onPause -->onStop -->onDestroy
(Android 6.0)
onConfigurationChanged-->onPause -->onSaveInstanceState -->onStop ->onDestroy -->onCreate-->onStart -->onRestoreInstanceState-->onResume -->onPause -->onStop -->onDestroy
(Android 7.0)
onConfigurationChanged
(Android 8.0)
横屏启动:
onCreate -->onStart-->onResume
切换竖屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->onRestoreInstanceState--> onResume -->onPause -->onStop -->onDestroy
(Android 6.0 )
onConfigurationChanged-->onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->onRestoreInstanceState-->onResume -->onPause -->onStop -->onDestroy
(Android 7.0)
onConfigurationChanged
(Android 8.0)
总结:设置了configChanges属性为orientation之后,Android6.0 同没有设置configChanges情况相同,完整的走完了两个生命周期,调用了onSaveInstanceState和onRestoreInstanceState方法;Android 7.0则会先回调onConfigurationChanged方法,剩下的流程跟Android6.0 保持一致;Android 8.0 系统更是简单,只是回调了onConfigurationChanged方法,并没有走Activity的生命周期方法。
3.AndroidManifest设置了configChangesandroid:configChanges="orientation|keyboardHidden|
screenSize"
竖(横)屏启动:onCreate -->onStart-->onResume
切换横(竖)屏:onConfigurationChanged (Android 6.0
Android 7.0 Android 8.0)
总结:设置android:configChanges="orientation|keyboardHidden|screenSize" 则都不会调用Activity的其他生命周期方法,只会调用onConfigurationChanged方法。
4.AndroidManifest设置了configChangesandroid:configChanges="orientation|screenSize"
竖(横)屏启动:onCreate -->onStart-->onResume
切换横(竖)屏:onConfigurationChanged (Android 6.0Android 7.0 Android 8.0)
总结:没有了keyboardHidden跟3是相同的,orientation
代表横竖屏切换 screenSize代表屏幕大小发生了改变,设置了这两项就不会回调Activity的生命周期的方法,只会回调onConfigurationChanged 。
5.AndroidManifest设置了configChangesandroid:configChanges="orientation|keyboardHidden"
总结:跟只设置了orientation属性相同,Android6.0Android7.0会回调生命周期的方法,Android8.0则只回调onConfigurationChanged。说明如果设置了orientation
和 screenSize 都不会走生命周期的方法,keyboardHidden不影响。
- 不设置configChanges属性不会回调onConfigurationChanged,且切屏的时候会回调生命周期方法。
- 只有设置了orientation 和 screenSize 才会保证都不会走生命周期,且切屏只回调onConfigurationChanged。
- 设置orientation,没有设置screenSize,切屏会回调onConfigurationChanged,但是还会走生命周期方法。
注:这里只选择了Android部分系统的手机做测试,由于不同系统的手机品牌也不相同,可能略微会有区别。
另:代码动态设置横竖屏状态(onConfigurationChanged当屏幕发生变化的时候回调)
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
获取屏幕状态(int ORIENTATION_PORTRAIT = 1; 竖屏 int ORIENTATION_LANDSCAPE = 2; 横屏)
int screenNum = getResources().getConfiguration().orientation;
configChanges属性
- orientation 屏幕在纵向和横向间旋转
- keyboardHidden 键盘显示或隐藏
- screenSize 屏幕大小改变了
- fontScale 用户变更了首选的字体大小
- locale 用户选择了不同的语言设定
- keyboard 键盘类型变更,例如手机从12键盘切换到全键盘
- touchscreen或navigation 键盘或导航方式变化,一般不会发生这样的事件
常用的包括:orientation keyboardHidden screenSize,设置这三项界面不会走Activity的生命周期,只会回调onConfigurationChanged方法。
screenOrientation属性
- unspecified 默认值,由系统判断状态自动切换
- landscape 横屏
- portrait 竖屏
- user 用户当前设置的orientation值
- behind 下一个要显示的Activity的orientation值
- sensor 使用传感器 传感器的方向
- nosensor 不使用传感器 基本等同于unspecified仅landscape和portrait常用,代表界面默认是横屏或者竖屏,还可以再代码中更改。
1.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且该实例位于Backstack的栈顶.
3,如果该Activity在Manifest中的android:launchMode定义为singleTop,且上述intent包含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",而不具体说明调用时机的原因.
1.11 Intent传输数据的大小有限制吗?如何解决?
Intent 中的 Bundle 是使用 Binder 机制进行数据传送的,数据会写到内核空间, Binder 缓冲区域;
Binder 的缓冲区是有大小限制的, 有些 ROM 是 1M, 有些ROM 是 2M;
这个限制定义在frameworks/native/libs/binder/processState.cpp 类中,如果超过这个限制, 系统就会报错;
#define BINDER_VM_SIZE ((1*1024*1024) - (4096*2)) ;
因为 Binder 本身就是为了进程间频繁-灵活的通信所设计的, 并不是为了拷贝大量数据;
如果非 ipc 就很简单了, static 变量, eventBus 之类的都可以;
如果是 ipc, 一定要一次性传大文件, 可以用 file 或者 socket;
1.12 说说ContentProvider、ContentResolver、ContentObserver 之间的关系?
ContentProvider
内容提供者, 用于对外提供数据,比如联系人应用中就是用了ContentProvider,
一个应用可以实现ContentProvider来提供给别的应用操作,通过ContentResolver来操作别的应用数据
ContentResolver
内容解析者, 用于获取内容提供者提供的数据
ContentResolver.notifyChange(uri)发出消息ContentObserver
内容监听者,可以监听数据的改变状态
观察(捕捉)特定的Uri引起的数据库的变化
ContentResolver.registerContentObserver()监听消息
概括:
使用ContentResolver来获取ContentProvider提供的数据,同时注册ContentObserver监听数据的变化
1.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 的启动