当前位置:   article > 正文

Android 高级面试-5:四大组件、系统源码等

packagemanager是什么组件?作用是什么?

1、四大组件

1.1 Activity

  • Q:在两个 Activity 之间传递对象还需要注意什么呢?

对象的大小。Intent 中的 Bundle 是使用 Binder 机制进行数据传送的。能使用的 Binder 的缓冲区是有大小限制的(有些手机是 2 M),而一个进程默认有 16 个 Binder 线程,所以一个线程能占用的缓冲区就更小了(有人以前做过测试,大约一个线程可以占用 128 KB)。可以使用 EventBus 来传递数据,而不是直接使用 Intent 进行传递。

  • Q:onSaveInstanceState() 和 onRestoreInstanceState()

当 Activity 被销毁的时候回调用 onSaveInstanceState() 方法来存储当前的状态。这样当 Activity 被重建的时候,可以在 onCreate()onRestoreInstanceState() 中恢复状态。

对于 targetAPI 为 28 及以后的应用,该方法会在 onStop() 方法之后调用,对于之前的设备,这方法会在 onStop() 之前调用,但是无法确定是在 onPause() 之前还是之后调用。

onRestoreInstanceState() 方法用来恢复之前存储的状态,它会在 onStart()onPostCreate() 之间被调用。此外,你也可以直接在 onCreate() 方法中进行恢复,但是基于这个方法调用的时机,如果有特别需求,可以在这个方法中进行处理。

  • Q:SingleTask 启动模式
  • Q:Activity 启动模式
  1. standard:默认,每次启动的时候会创建一个新的实例,并且被创建的实例所在的栈与启动它的 Activity 是同一个栈。比如,A 启动了 B,那么 B 将会与 A 处在同一个栈。假如,我们使用 Application 的 Context 启动一个 Activity 的时候会抛出异常,这是因为新启动的 Activity 不知道自己将会处于哪个栈。可以在启动 Activity 的时候使用 FLAG_ACTIVITY_NEW_TASK。这样新启动的 Acitivyt 将会创建一个新的栈。
  2. singleTop:栈顶复用,如果将要启动的 Activity 已经位于栈顶,那么将会复用栈顶的 Activity,并且会调用它的 onNewIntent()。常见的应用场景是从通知打开 Activity 时。
  3. singleTask:单例,如果启动它的任务栈中存在该 Activity,那么将会复用该 Activity,并且会将栈内的、它之上的所有的 Activity 清理出去,以使得该 Activity 位于栈顶。常见的应用场景是启动页面、购物界面、确认订单界面和付款界面等。
  4. singleInstance:这种启动模式会在启动的时候为其指定一个单独的栈来执行。如果用同样的intent 再次启动这个 Activity,那么这个 Activity 会被调到前台,并且会调用其 onNewIntent() 方法。
  • Q:下拉状态栏是不是影响 Activity 的生命周期,如果在 onStop() 的时候做了网络请求,onResume() 的时候怎么恢复
  • Q:前台切换到后台,然后再回到前台,Activity 生命周期回调方法。弹出 Dialog,生命值周期回调方法。
  • Q:Activity 生命周期
  • Q:Activity 上有 Dialog 的时候按 Home 键时的生命周期
  • Q:横竖屏切换的时候,Activity 各种情况下的生命周期

Android 下拉通知栏不会影响 Activity 的生命周期方法。

弹出 Dialog,生命周期:其实是否弹出 Dialog,并不影响 Activity 的生命周期,所以这时和正常启动时 Activity 的生命回调方法一致: onCreate() -> onStart() -> onResume()

这里我们总结一下在实际的使用过程中可能会遇到的一些 Acitivity 的生命周期过程:

  1. 当用户打开新的 Activity 或者切换回桌面:会经过的生命周期为 onPause()->onStop()。因为此时 Activity 已经变成不可见了,当然,如果新打开的 Activity 用了透明主题,那么 onStop() 不会被调用,因此原来的 Activity 只是不能交互,但是仍然可见。
  2. 从新的 Activity 回到之前的 Activity 或者从桌面回到之前的 Activity:会经过的生命周期为 onRestart()->onStart()-onResume()。此时是从 onStop() 经 onRestart() 回到 onResume() 状态。
  3. 如果在上述 1 的情况下,进入后台的 Activity 因为内存不足被销毁了,那么当再次回到该 Activity 的时候,生命周期方法将会从 onCreate() 开始执行到 onResume()。
  4. 当用户按下 Back 键时:如果当前 Activity 被销毁,那么经过的生命周期将会是 onPause()->onStop()->onDestroy()

具体地,当存在两个 Activity,分别是 A 和 B 的时候,在各种情况下,它们的生命周期将会经过:

  1. Back 键 Home 键
    1. 当用户点击 A 中按钮来到 B 时,假设 B 全部遮挡住了 A,将依次执行:A.onPause()->B.onCreate()->B.onStart()->B.onResume->A.onStop()
    2. 接1,此时如果点击 Back 键,将依次执行:B.onPause()->A.onRestart()->A.onStart()->A.onResume()->B.onStop()->B.onDestroy()
    3. 接2,此时如果按下 Back 键,系统返回到桌面,并依次执行:A.onPause()->A.onStop()->A.onDestroy()
    4. 接2,此时如果按下 Home 键(非长按),系统返回到桌面,并依次执行A.onPause()->A.onStop()。由此可见,Back 键和 Home 键主要区别在于是否会执行 onDestroy()。
    5. 接2,此时如果长按 Home 键,不同手机可能弹出不同内容,Activity 生命周期未发生变化。
  2. 横竖屏切换时 Activity 的生命周期
    1. 不设置 Activity 的 android:configChanges 时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次。
    2. 设置 Activity 的 android:configChanges=“orientation” 时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次。
    3. 设置 Activity 的 android:configChanges=“orientation|keyboardHidden” 时,切屏不会重新调用各个生命周期,只会执行 onConfiguration() 方法。
  • Q:Activity 之间的通信方式
  1. Intent + onActivityResult() + setResult()
  2. 静态变量(跨进程不行)
  3. 全局通信,广播或者 EventBus
  • Q:AlertDialog, PopupWindow, Activity 区别

AlertDialog 是 Dialog 的子类,所以它包含了 Dialog 类的很多属性和方法。是弹出对话框的主要方式,对话框分成支持包的和非支持包的,UI 效果上略有区别。

AlertDialog 与 PopupWindow 之间最本质的差异在于

  1. AlertDialog 是非阻塞式对话框;而PopupWindow 是阻塞式对话框。AlertDialog 弹出时,后台还可以做事情;PopupWindow 弹出时,程序会等待,在PopupWindow 退出前,程序一直等待,只有当我们调用了 dismiss() 方法的后,PopupWindow 退出,程序才会向下执行。我们在写程序的过程中可以根据自己的需要选择使用 Popupwindow 或者是 Dialog.
  2. 两者最根本的区别在于有没有新建一个 window,PopupWindow 没有新建,而是通过 WMS 将 View 加到 DecorView;Dialog 是新建了一个 window (PhoneWindow),相当于走了一遍 Activity 中创建 window 的流程。

Activity 与 Dialog 类似,都会使用 PhoneWindow 来作为 View 的容器。Activity 也可以通过设置主题为 Dialog 的来将其作为对话框来使用。Dialog 也可以通过设置 Theme 来表现得像一个 Activity 一样作为整个页面。但 Activity 具有生命周期,并且它的生命周期归 AMS 管,而 Dialog 不具有生命周期,它归 WMS 管。

  • Q:Activity 与 Service 通信的方式

前提是是否跨进程,如果不跨进程的话,EventBus 和 静态变量都能传递信息,否则需要 IPC 才行:

  1. Binder 用于跨进程的通信方式,AIDL 可以用来进行与远程通信,绑定服务的时候可以拿到远程的 Binder,然后调用它的方法就可以从远程拿数据。那么如果希望对远程的服务进行监听呢?可以使用 AIDL 中的 oneway 来定义回调的接口,然后在方法中传入回调即可。也可以使用 Messenger,向远程发送信息的时候,附带本地的 Messenger,然后远程获取本地的 Messenger 然后向其发送信息即可,详见 IPC 相关一文:《Android 高级面试-2:IPC 相关》
  2. 广播:使用广播实现跨进程通信
  3. 启动服务的时候传入值,使用 startService() 的方式

关于 Activity 相关的内容可以参考笔者的文章:《Android 基础回顾:Activity 基础》

1.2 Service

  • Q:怎么启动 Service
  • Q:Service 的开启方式
  • Q:Service 生命周期

其他,

  1. Service 有绑定模式和非绑定模式,以及这两种模式的混合使用方式。不同的使用方法生命周期方法也不同。
    1. 非绑定模式:当第一次调用 startService() 的时候执行的方法依次为 onCreate()->onStartCommand();当 Service 关闭的时候调用 onDestory()
    2. 绑定模式:第一次 bindService() 的时候,执行的方法为 onCreate()->onBind();解除绑定的时候会执行 onUnbind()->onDestory()
  2. 我们在开发的过程中还必须注意 Service 实例只会有一个,也就是说如果当前要启动的 Service 已经存在了那么就不会再次创建该 Service 当然也不会调用 onCreate() 方法。所以,
    1. 当第一次执行 startService(intent) 的时候,会调用该 Service 中的 onCreate()onStartCommand() 方法。
    2. 当第二次执行 startService(intent) 的时候,只会调用该 Service 中的 onStartCommand() 方法。(因此已经创建了服务,所以不需要再次调用 onCreate() 方法了)。
  3. bindService() 方法的第三个参数是一个标志位,这里传入 BIND_AUTO_CREATE 表示在Activity 和 Service 建立关联后自动创建 Service,这会使得 MyService 中的 onCreate() 方法得到执行,但 onStartCommand() 方法不会执行。所以,在上面的程序中当调用了bindService() 方法的时候,会执行的方法有,Service 的 onCreate() 方法,以及 ServiceConnection 的 onServiceConnected() 方法。
  4. 在 3 中,如果想要停止 Service,需要调用 unbindService() 才行。
  5. 如果我们既调用了 startService(),又调用 bindService() 会怎么样呢?这时不管你是单独调用 stopService() 还是 unbindService(),Service 都不会被销毁,必须要将两个方法都调用 Service 才会被销毁。也就是说,stopService() 只会让 Service 停止,unbindService() 只会让 Service 和 Activity 解除关联,一个 Service 必须要在既没有和任何 Activity 关联又处理停止状态的时候才会被销毁。
  • 进程保活
  • App 中唤醒其他进程的实现方式

1.3 Broadcast

  • Q:BroadcastReceiver,LocalBroadcastReceiver 区别
  • Q:广播的使用场景
  • Q:广播的使用方式,场景
  • Q:广播的分类?
  • Q:广播(动态注册和静态注册区别,有序广播和标准广播)

分类

  1. 按照注册方式:静态注册和动态注册两种:
    1. 静态广播直接在 manifest 中注册。限制:
      1. 在 Android 8.0 的平台上,应用不能对大部分的广播进行静态注册,也就是说,不能在 AndroidManifest 文件对有些广播进行静态注册;
      2. 当程序运行在后台的时候,静态广播中不能启动服务。
    2. 动态广播与静态广播相似,但是不需要在 Manifest 中进行注册。注意当页面被销毁的时候需要取消注册广播!
  2. 按照作用范围:本地广播和普通广播两种,
    1. 普通广播是全局的,所有应用程序都可以接收到,容易会引起安全问题。
    2. 本地广播只能够在应用内传递,广播接收器也只能接收应用内发出的广播。本地广播的核心类是 LocalBroadcastManager,使用它的静态方法 getInstance() 获取一个单例之后就可以使用该单例的 registerReceiver()unregisterReceiver()sendBroadcast() 等方法来进行操作了。
  3. 按照是否有序:有序广播和无序广播两种,无序广播各接收器接收的顺序无法确定,并且在广播发出之后接收器只能接收,不能拦截和进行其他处理,两者的区别主要体现在发送时调用的方法上。优先级高的会先接收到,优先级相等的话则顺序不确定。并且前面的广播可以在方法中向 Intent 写入数据,后面的广播可以接收到写入的值。

1.4 ContentProvider

  • Q:Android 系统为什么会设计 ContentProvider,进程共享和线程安全问题

ContentProvider 在 Android 中的作用是对外共享数据,提供了数据访问接口,用于在不同应用程序之间共享数据,同时还能保证被访问数据的安全性。它通常用来提供一些公共的数据,比如用来查询文件信息,制作音乐播放器的时候用来读取系统中的音乐文件的信息。

与 SQLiteDatabase 不同,ContentProvider 中的 CRUD 不接收表名参数,而是 Uri 参数。内容 URI 是内容提供器中数据的唯一标识符,包括权限和路径。

并发访问时,不论是不同的进程还是同一进程的不同线程,当使用 AMS 获取 Provider 的时候返回的都是同一实例。我们使用 Provider 来从远程访问数据,当 query() 方法运行在不同的线程,实际上是运行在 Provider 方的进程的 Binder 线程池中。通过 Binder 的线程池来实现多进程和多线程访问的安全性。

参考:Android ContentProvider的线程安全(一)

1.5 Fragment

  • Q:Fragment 各种情况下的生命周期
  • Q:Activity 与 Fragment 之间生命周期比较

  • Q:Fragment 之间传递数据的方式?
  1. 同一 Activity 的 Fragment 之间可以使用 ViewModel 来交换数据;
  2. 使用 EventBus,广播,静态的;
  3. 通过 Activity 获取到另一个 Fragment,强转之后使用它对外提供的 public 方法进行通信;
  4. 通过 Activity 获取到另一个 Fragment,该 Fragment 实现某个接口,然后转成接口之后进行通信(也适用于 Activity 与 Fragment 之间),强转之后使用它对外提供的 public 方法进行通信;
  • Q:如何实现 Fragment 的滑动

1.6 Context

  • Q:Application 和 Activity 的 Context 对象的区别

Context 的继承关系如下所示,所以,Android 中的 Context 数量 = Activity 的数量 + Service 的数量 + 1 (Application)

Context 的用途比较广,比如用来获取图片、字符串,显式对话框等,大部分情况下,使用哪个 Context 都可以,少数情况下只能使用特定的 Context. 比如启动 Activity 的时候,要求传入 Activity 的 Context,因为 AMS 需要直到启动指定 Activity 的 Activity 的栈。一般情况下,能使用 Application 的 Context 尽量使用它的,因为它的生命周期更长。

Context 之间使用的是装饰者设计模式,其中 Context 是一个抽象的类。ContextWrapper 内部实际使用 ContextImpl 实现的,因此所有的逻辑基本是在 ContextImpl 中实现的。然后对于 ContextThemeWrapper,它在 ContextWrapper 的基础之上又进行了一层装饰,就是与主题相关的东西。

新版的 Activity 启动中将 Activity 的各个回调执行的逻辑放在了各个 ClientTransactionItem 中,比如 LaunchActivityItem 表示用来启动 Activity。 最终执行逻辑的时候是调用它们的 execute() 方法并使用传入的 ClientTransactionHandler 真正执行任务。而这里的 ClientTransactionHandler 实际上就是 ActivityThread,所以它将调用到 Activity 的 handleLaunchActivity() 启动 Activity. 然后程序进入到 performLaunchActivity() 中。这个方法中会创建上面的 Application 和 Activity 对应的 Context:

  1. private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  2. // ...
  3. // 创建 Activity 的 Context
  4. ContextImpl appContext = createBaseContextForActivity(r);
  5. Activity activity = null;
  6. try {
  7. java.lang.ClassLoader cl = appContext.getClassLoader();
  8. // 创建新的 Activity
  9. activity = mInstrumentation.newActivity(
  10. cl, component.getClassName(), r.intent);
  11. // ...
  12. } catch (Exception e) {
  13. // ... handle exception
  14. }
  15. try {
  16. // 创建应用的 Application
  17. Application app = r.packageInfo.makeApplication(false, mInstrumentation);
  18. if (activity != null) {
  19. CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
  20. // Activity 的配置
  21. Configuration config = new Configuration(mCompatConfiguration);
  22. if (r.overrideConfig != null) {
  23. config.updateFrom(r.overrideConfig);
  24. }
  25. // 创建窗口
  26. Window window = null;
  27. if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
  28. window = r.mPendingRemoveWindow;
  29. r.mPendingRemoveWindow = null;
  30. r.mPendingRemoveWindowManager = null;
  31. }
  32. // 关联 Activity 和 Context
  33. appContext.setOuterContext(activity);
  34. activity.attach(appContext, this, getInstrumentation(), r.token,
  35. r.ident, app, r.intent, r.activityInfo, title, r.parent,
  36. r.embeddedID, r.lastNonConfigurationInstances, config,
  37. r.referrer, r.voiceInteractor, window, r.configCallback);
  38. // ...
  39. // 设置 Activity 的主题
  40. int theme = r.activityInfo.getThemeResource();
  41. if (theme != 0) {
  42. activity.setTheme(theme);
  43. }
  44. // 回调 Activity 的生命周期方法
  45. activity.mCalled = false;
  46. if (r.isPersistable()) {
  47. mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
  48. } else {
  49. mInstrumentation.callActivityOnCreate(activity, r.state);
  50. }
  51. r.activity = activity;
  52. }
  53. r.setState(ON_CREATE);
  54. mActivities.put(r.token, r);
  55. } catch (SuperNotCalledException e) {
  56. throw e;
  57. } catch (Exception e) {
  58. // ... handle exception
  59. }
  60. return activity;
  61. }
  62. public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
  63. // ...
  64. try {
  65. // 创建 Application 的 Context
  66. ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
  67. app = mActivityThread.mInstrumentation.newApplication(
  68. cl, appClass, appContext);
  69. // 关联 Application 和 Context
  70. appContext.setOuterContext(app);
  71. } catch (Exception e) {
  72. // ... handle exception
  73. }
  74. // ...
  75. return app;
  76. }
  77. 复制代码

1.7 其他

  • Q:AndroidManifest 的作用与理解

声明了四大组件、应用版本、权限等的配置信息,会在解析 APK 的时候由 PMS 进行解析,然后解析的结果会被缓存到 PMS 中。

2、Android API

2.1 AsyncTask

  • Q:AsyncTask 机制,如何取消 AsyncTask
  • Q:多线程(关于 AsyncTask 缺陷引发的思考)
  • Q:Asynctask 有什么优缺点
  • Q:AsyncTask 机制、原理及不足?

AsyncTask 是 Android 提供的用来执行异步操作的 API,我们可以通过它来执行异步操作,并在得到结果之后将结果放在主线程当中进行后续处理。

AsyncTask 的缺点是在使用多个异步操作和并需要进行 Ui 变更时,就变得复杂起来(会导致多个 AsyncTask 进行嵌套)。如果有多个地方需要用到 AsyncTask,可能需要定义多个 AsyncTask 的实现。

如果 AsyncTask 以一个非静态的内部类的形式声明在 Activity 中,那么它会持有 Activity 的匿名引用,如果销毁 Activity 时 AsyncTask 还在执行异步任务的话,Activity 就不能销毁,会造成内存泄漏。解决方式是,要么将 AsyncTask 定义成静态内部类,要么在 Activity 销毁的时候调用 cancel() 方法取消 AsyncTask.在屏幕旋转或 Activity 意外结束时,Activity 被创建,而 AsyncTask 会拥有之前 Activity 的引用,会导致结果丢失。

AsyncTask 在 1.6 之前是串行的,1.6 之后是并行的,3.0 之后又改成了串行的。不过我们可以通过调用 executeOnExecutor() 方法并传入一个线程池,来让 AsyncTask 在某个线程池中并行执行任务。

AsyncTask 的源码就是将一个任务封装成 Runnable 之后放进线程池当中执行,执行完毕之后调用主线程的 Handler 发送消息到主线程当中进行处理。任务在默认线程池当中执行的时候,会被加入到一个双端队列中执行,执行完一个之后再执行下一个,以此来实现任务的串行执行。

了解 AsyncTask 的源码,可以参考笔者的这篇文章:《Android AsyncTask 源码分析》

  • Q:介绍下 SurfaceView

SurfaceView 以及 TextureView 均继承于 android.view.View,它们都在独立的线程中绘制和渲染,常被用在对绘制的速率要求比较高的应用场景中,用来解决普通 View 因为绘制的时间延迟而带来的掉帧的问题,比如用作相机预览、视频播放的媒介等。

SurfaceView 提供了嵌入在视图层次结构内的专用绘图图层 (Surface)。图层 (Surface) 处于 Z 轴,位于持有 SurfaceView 的窗口之后。SurfaceView 在窗口上开了一个透明的 “洞” 以展示图面。Surface 的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。注意,如果 Surface 上面有透明控件,那么每次 Surface 变化都会引起框架重新计算它和顶层控件的透明效果,这会影响性能。SurfaceView 使用双缓冲,SurfaceView 自带一个 Surface,这个 Surface 在 WMS 中有自己对应的WindowState,在 SurfaceFlinger 中也会有自己的 Layer。这样的好处是对这个Surface的渲染可以放到单独线程去做。因为这个 Surface 不在 View hierachy 中,它的显示也不受 View 的属性控制,所以不能进行平移,缩放等变换,也不能放在其它 ViewGroup 中,一些 View 中的特性也无法使用。

TextureView 在 Andriod 4.0 之后的 API 中才能使用,并且必须在硬件加速的窗口中。和 SurfaceView 不同,它不会在 WMS 中单独创建窗口,而是作为 View hierachy 中的一个普通 View,因此可以和其它普通 View 一样进行移动,旋转,缩放,动画等变化。它占用内存比 SurfaceView 高,在 5.0 以前在主线程渲染,5.0 以后有单独的渲染线程。

更多内容请参考:Android:解析 SurfaceView & TextureView

2.2 View 体系

事件分发机制等

  • Q:Android 事件分发机制
  • Q:事件传递机制的介绍
  • Q:View 事件传递
  • Q:触摸事件的分发?

Activity 的层级:Activity->PhoneWindow->DecorView

当触摸事件发生的时候,首先会被 Activity 接收到,然后该 Activity 会通过其内部的 dispatchTouchEvent(MotionEvent) 将事件传递给内部的 PhoneWindow;接着 PhoneWindow 会把事件交给 DecorView,再由 DecorView 交给根 ViewGroup。剩下的事件传递就只在 ViewGroupView 之间进行。

事件分发机制本质上是一个深度优先的遍历算法。事件分发机制的核心代码:

  1. boolean dispatchTouchEvent(MotionEvent e) {
  2. boolean result;
  3. if (onInterceptTouchEvent(e)) { // 父控件可以覆写并返回 true 以拦截
  4. result = super.dispatchTouchEvent(e); // 调用 View 中该方法的实现
  5. } else {
  6. for (child in children) {
  7. result = child.dispatchTouchEvent(e); // 这里的 child 分成 View 和 ViewGroup 两者情形
  8. if (result) break; // 被子控件消费,停止分发
  9. }
  10. }
  11. return result;
  12. }
  13. 复制代码

对于 dispatchTouchEvent() 方法,在 View 的默认实现中,会先交给 onTouchEvent() 进行处理,若它返回了 true 就消费了,否则根据触摸的类型,决定是交给 OnClickListener 还是 OnLongClickListener 继续处理。

事件分发机制和 View 的体系请参考笔者文章:《View 体系详解:坐标系、滑动、手势和事件分发机制》,整体上事件分发机制应该分成三个阶段来进行说明:1).从 Activity 到 DecorView 的过程;2).ViewGroup 中的分发的过程;3).交给 View 之后的实现过程。

  • Q:封装 View 的时候怎么知道 View 的大小
  • Q:点击事件被拦截,但是想传到下面的 View,如何操作?
  • Q:计算一个 view 的嵌套层级

按照广度优先算法进行遍历

2.3 列表控件

  • Q:ListView 的优化?
  • Q:ListView 重用的是什么?

ListView 默认缓存一页的 View,也就是你当前 Listview 界面上有几个 Item 可以显示,,Lstview 就缓存几个。当现实第一页的时候,由于没有一个 Item 被创建,所以第一页的 Item 的 getView() 方法中的第二个参数都是为 null 的。

ViewHolder 同样也是为了提高性能。就是用来在缓存使用 findViewById() 方法得到的控件,下次的时候可以直接使用它而不用再进行 find 了。

关于 ListView 的 ViewHolder 等的使用,可以参考这篇文章:ListView 复用和优化详解

  • Q:RecycleView 的使用,原理,RecycleView 优化
  • Q:Recycleview Listview 的区别,性能
  1. 装饰;
  2. 手势滑动、拖拽;
  3. 顶部悬浮效果;
  • Q:Listview 图片加载错乱的原理和解决方案

2.4 其他控件

  • Q:LinearLayout、RelativeLayout、FrameLayout 的特性、使用场景
  • Q:ViewPager 使用细节,如何设置成每次只初始化当前的 Fragment,其他的不初始化

2.5 数据存储

  • Q:Android 中数据存储方式

SP,SQLite,ContentProvider,File,Server

3、架构相关

  • Q:模块化实现(好处,原因)
  • Q:项目组件化的理解
  • Q:模式 MVP、MVC 介绍
  • Q:MVP 模式

MVC (Model-View-Controller, 模型-视图-控制器),标准的MVC是这个样子的:

  1. 模型层 (Model):业务逻辑对应的数据模型,无 View 无关,而与业务相关;
  2. 视图层 (View):一般使用 XML 或者 Java 对界面进行描述;
  3. 控制层 (Controllor):在 Android 中通常指 Activity 和Fragment,或者由其控制的业务类。

在 Android 开发中,就是指直接使用 Activity 并在其中写业务逻辑的开发方式。显然,一方面 Activity 本身就是一个视图,另一方面又要负责处理业务逻辑,因此逻辑会比较混乱。这种开发方式不太适合 Android 开发。

MVP (Model-View-Presenter)

  1. 模型层 (Model):主要提供数据存取功能。
  2. 视图层 (View):处理用户事件和视图。在 Android 中,可能是指 Activity、Fragment 或者 View。
  3. 展示层 (Presenter):负责通过 Model 存取书数据,连接 View 和 Model,从 Model 中取出数据交给 View。

实际开发中会像下面这样定义一个契约接口

  1. public interface HomeContract {
  2. interface IView extends BaseView {
  3. void setFirstPage(List<HomeBean.IssueList.ItemList> itemLists);
  4. void setNextPage(List<HomeBean.IssueList.ItemList> itemLists);
  5. void onError(String msg);
  6. }
  7. interface IPresenter extends BasePresenter {
  8. void requestFirstPage();
  9. void requestNextPage();
  10. }
  11. }
  12. 复制代码

然后让 Fragment 或者 Activity 等继承 IView,实例化一个 IPresenter,并在构造方法中将自己引入到 IPresenter 中。这样 View 和 Presenter 就相互持有了对方的引用。当要发起一个网络请求的时候,View 中调用 Presenter 的方法,Presenter 拿到了结果之后回调 View 的方法。这样就使得 View 和 Presenter 只需要关注自身的责任即可。

MVP 缺点:1). Presenter 中除了应用逻辑以外,还有大量的 View->Model,Model->View 的手动同步逻辑,造成 Presenter 比较笨重,维护起来会比较困难;2). 由于对视图的渲染放在了 Presenter 中,所以视图和 Presenter 的交互会过于频繁;3). 如果 Presenter 过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密,一旦视图需要变更,那么 Presenter 也需要变更了。

MVVM 是 Model-View-ViewModel 的简写。它本质上就是 MVC 的改进版。MVVM 就是将其中的 View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。

  1. 模型层 (Model):负责从各种数据源中获取数据;
  2. 视图层 (View):在 Android 中对应于 Activity 和 Fragment,用于展示给用户和处理用户交互,会驱动 ViewModel 从 Model 中获取数据;
  3. ViewModel 层:用于将 Model 和 View 进行关联,我们可以在 View 中通过 ViewModel 从 Model 中获取数据;当获取到了数据之后,会通过自动绑定,比如 DataBinding,来将结果自动刷新到界面上。

优点:

  1. 低耦合:视图(View)可以独立于Model变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
  2. 可重用性:你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 view 重用这段视图逻辑。
  3. 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
  4. 可测试:界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。

关于移动应用架构部分内容可以参考笔者的文章:《Android 架构设计:MVC、MVP、MVVM和组件化》,另外关于 MVVM 架构设计中的 ViewModel 和 LiveData 的机制可以参考:《浅谈 ViewModel 的生命周期控制》《浅谈 LiveData 的通知机制》

4、系统源码

  • Q:画出 Android 的大体架构图
  • Q:App 启动流程,从点击桌面开始
  • Q:Activity 栈
  • Q:简述 Activity 启动全部过程?
  • Q:ActicityThread 相关?

startActivity() -> Process.start() -> Socket -> (SystemServer) -> Zygote.fork() -> VM,Binder 线程池 -> ActivityThread.main()

Instrumentation.execStartActivity() -> IApplicationThread+AMS+H -> 校验用户信息等 -> 解析 Intent -> 回调 IApplicationThread.scheduleTransaction()

  • Q:App 是如何沙箱化,为什么要这么做?

Android是一个权限分离的系统,这是利用 Linux 已有的权限管理机制,通过为每一个 Application 分配不同的 uid 和 gid,从而使得不同的 Application 之间的私有数据和访问(native以及java层通过这种 sandbox 机制,都可以)达到隔离的目的 。与此同时,Android 还在此基础上进行扩展,提供了 permission 机制,它主要是用来对 Application 可以执行的某些具体操作进行权限细分和访问控制,同时提供了 per-URI permission 机制,用来提供对某些特定的数据块进行 ad-hoc 方式的访问。

  • Q:权限管理系统(底层的权限是如何进行 grant 的)

  • Q:动态权限适配方案,权限组的概念

  • Q:大体说清一个应用程序安装到手机上时发生了什么?

  • Q:应用安装过程

应用安装的时候涉及几个类,分别时 PackageManager, ApplicationPackageManager 和 PMS. 它们之间的关系是,PackageManager 是一个抽象类,它的具体实现是 ApplicationPackageManager,而后者的所有实现都是靠 PMS 实现的。PMS 是一种远程的服务,与 AMS 相似,在同一方法中启动。另外,还有 Installer 它是安装应用程序的辅助类,它也是一种系统服务,与 PMS 在同一方法中启动。它会通过 Socket 与远程的 Installd 建立联系。这是因为权限的问题,PMS 只有 system 权限。installd 却是具有 root 权限。(Installd 的作用好像就是创建一个目录)

installd是由Android系统init进程(pid=1),在解析init.rc文件的代码时,通过fork创建用户空间的守护进程intalld。启动时,进入监听socket,当客户端发送过来请求时,接收客户端的请求,并读取客户端发送过来的命令数据,并根据读取客户端命令来执行命令操作。

Android上应用安装可以分为以下几种方式:

  1. 系统安装:开机的时候,没有安装界面
  2. adb 命令安装:通过abd命令行安装,没有安装界面
  3. 应用市场安装,这个要视应用的权限,有系统的权限无安装界面(例如MUI的小米应用商店)
  4. 第三方安装,有安装界面,通过packageinstaller.apk来处理安装及卸载的过程的界面

apk的大体流程如下:

  1. 第一步:拷贝文件到指定的目录:在Android系统中,apk安装文件是会被保存起来的,默认情况下,用户安装的apk首先会被拷贝到/data/app目录下,/data/app目录是用户有权限访问的目录,在安装apk的时候会自动选择该目录存放用户安装的文件,而系统出场的apk文件则被放到了/system分区下,包括/system/app,/system/vendor/app,以及/system/priv-app等等,该分区只有ROOT权限的用户才能访问,这也就是为什么在没有Root手机之前,我们没法删除系统出场的app的原因了。
  2. 第二步:解压缩apk,宝贝文件,创建应用的数据目录:为了加快app的启动速度,apk在安装的时候,会首先将app的可执行文件dex拷贝到/data/dalvik-cache目录,缓存起来。然后,在/data/data/目录下创建应用程序的数据目录(以应用的包名命名),存放在应用的相关数据,如数据库、xml文件、cache、二进制的so动态库等。
  3. 第三步:解析apk的AndroidManifest.xml文件:提取出这个apk的重要信息写入到packages.xml文件中,这些信息包括:权限、应用包名、APK的安装位置、版本、userID等等。
  4. 第四步:显示快捷方式:Home应用程序,负责从PackageManagerService服务中把这些安装好的应用程序取出来。在Android系统中,负责把系统中已经安装的应用程序在桌面中展现出来的Home应用就是Launcher了。

普通安装:

PackagInstaller是安卓上默认的应用程序,用它来安装普通文件。PackageInstaller调用一个叫做InstallAppProgress的activity来获取用户发出的指令。InstallAppProgress会请求Package Manager服务,然后通过installed来安装包文件。它提供了安装的页面和安装进度相关页面,我们平时安装应用时显式的就是它。(源码

最终的安卓过程则是交给 PMS 的 installPackageLI() 方法来完成,它也会先对 manifest 进行解析,然后将解析的结果添加到 PMS 的缓存中,并注册四大组件。

如果是第一次安装的时候就会调用 scanPackageLI() 方法来进行安装。

  1. PMS 在启动 SystemSever 时启动,调用构造方法,对目录进行扫描,包括系统、供应商等的目录,复制 APK,解析 APK,缓存 APK 信息;
  2. ADB -> pm.jar -> PMS -> Installd() -> Installer(系统服务)
  • Q:系统启动流程 Zygote 进程 –> SystemServer 进程 –> 各种系统服务 –> 应用进程?

按下电源之后,首先加载引导程序 BootLoader 到 RAM;然后,执行引导程序 BootLoader 以把系统 OS 拉起来;接着,启动 Linux 内核;内核中启动的第一个用户进程是 init 进程,init 进程会通过解析 init.rc 来启动 zygote 服务;Zygote 又会进一步的启动 SystemServer;在 SystemServer 中,Android 会启动一系列的系统服务供用户调用。

Init 进程会启动之后会解析 init.rc 文件,该文件由固定格式的指令组成,在 AOSP 中有说明它的规则。其中的每个指令对应系统中的类。在解析文件的时候会将其转换成对应的类的实例来保存,比如 service 开头的指令会被解析成 Service 对象。在解析 init.rc 文件之前会根据启动时的属性设置加载指定的 rc 文件到系统中。当解析完毕之后系统会触发初始化操作。它会通过调用 Service 的 start() 方法来启动属性服务,然后进入 app_main.cpp 的 main() 方法。这个方法中会根据 service 指令的参数来决定调用 Java 层的 ZygoteInit 还是 RuntimeInit. 这里会调用后者的 ZygoteInit 初始化 Zygote 进程。

  1. if (zygote) {
  2. runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
  3. } else if (className) {
  4. runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
  5. } else {
  6. app_usage();
  7. }
  8. 复制代码

它调用的方式是通过 runtime 的 start() 方法,而这个 runtime 就是 AndroidRuntime. 但是在执行 Java 类之前得先有虚拟机,所以它会先启动虚拟机实例,然后再调用 ZygoteInit 的方法。所以,AndroidRuntime 的 start() 方法中主要做了三件事情:1).调用函数 startVM() 启动虚拟机;2).调用函数 startReg() 注册 JNI 方法;3).调用 com.android.internal.os.ZygoteInit 类的 main() 函数。

ZygoteInit 用来初始化 Zygote 进程的,它的 main() 函数中主要做了三件事情:

  1. 调用 registerZygoteSocket() 函数创建了一个 socket 接口,用来和 AMS 通讯;(Android 应用程序进程启动过程中,AMS 是通过 Process.start() 函数来创建一个新的进程的,而 Process.start() 函数会首先通过 Socket 连接到 Zygote 进程中,最终由 Zygote 进程来完成创建新的应用程序进程,而 Process 类是通过 openZygoteSocketIfNeeded() 函数来连接到 Zygote 进程中的 Socket.)
  2. 调用 startSystemServer() 函数来启动 SystemServer 组件,Zygote 进程通过 Zygote.forkSystemServer() 函数来创建一个新的进程来启动 SystemServer 组件;(SystemServer 的 main 方法将会被调用,并在这里启动 Binder 中的 ServiceManager 和各种系统运行所需的服务,PMS 和 AMS 等)
  3. 调用 runSelectLoopMode() 函数进入一个无限循环在前面创建的 socket 接口上等待 AMS 请求创建新的应用程序进程。

总结一下:

  1. 系统启动时 init 进程会创建 Zygote 进程,Zygote 进程负责后续 Android 应用程序框架层的其它进程的创建和启动工作。
  2. Zygote 进程会首先创建一个 SystemServer 进程,SystemServer 进程负责启动系统的关键服务,如包管理服务 PMS 和应用程序组件管理服务 AMS。
  3. 当我们需要启动一个 Android 应用程序时,AMS 会通过 Socket 进程间通信机制,通知 Zygote 进程为这个应用程序创建一个新的进程。
  • Q:描述清点击 Android Studio 的 build 按钮后发生了什么?

编译打包的过程->adb->安装过程 PMS->应用启动过程 AMS


Android 高级面试系列文章,关注作者及时获取更多面试资料

本系列以及其他系列的文章均维护在 Github 上面:Github / Android-notes,欢迎 Star & Fork. 如果你喜欢这篇文章,愿意支持作者的工作,请为这篇文章点个赞?!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/272912
推荐阅读
相关标签
  

闽ICP备14008679号