APP & program

Android 四大组件面试题

2022-04-04  本文已影响0人  星邪Ara

1.1 Activity 与 Fragment 之间常见的几种通信方式?

viewModel 做数据管理,activity 和 fragment 公用同个viewModel 实现数据传递

1.2 LaunchMode 的应用场景?

LaunchMode 有四种,分别为 Standard,SingleTop,SingleTask 和 SingleInstance,每种模式的实现原理一楼都做了较详细说明,下面说一下具体使用场景:

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操作

getApplication()和getApplicationContext() 区别?

  1. 对于Activity/Service来说, getApplication()和getApplicationContext()的返回值完全相同; 除非厂商修
    改过接口;
  2. BroadcastReceiver在onReceive的过程, 能使用getBaseContext().getApplicationContext获取所在Application, 而无法使用getApplication;
  3. 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如何进行保活?

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不影响。

  1. 不设置configChanges属性不会回调onConfigurationChanged,且切屏的时候会回调生命周期方法。
  2. 只有设置了orientation 和 screenSize 才会保证都不会走生命周期,且切屏只回调onConfigurationChanged。
  3. 设置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属性

  1. orientation 屏幕在纵向和横向间旋转
  2. keyboardHidden 键盘显示或隐藏
  3. screenSize 屏幕大小改变了
  4. fontScale 用户变更了首选的字体大小
  5. locale 用户选择了不同的语言设定
  6. keyboard 键盘类型变更,例如手机从12键盘切换到全键盘
  7. touchscreen或navigation 键盘或导航方式变化,一般不会发生这样的事件
    常用的包括:orientation keyboardHidden screenSize,设置这三项界面不会走Activity的生命周期,只会回调onConfigurationChanged方法。

screenOrientation属性

  1. unspecified 默认值,由系统判断状态自动切换
  2. landscape 横屏
  3. portrait 竖屏
  4. user 用户当前设置的orientation值
  5. behind 下一个要显示的Activity的orientation值
  6. sensor 使用传感器 传感器的方向
  7. 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)

上一篇下一篇

猜你喜欢

热点阅读