面试题记录

记录下面试闻到的一些问题,好多都忘记了啊,八股文还是有用的


面试题记录

23 号的面试,面试官提了一些比较基础的问题,回答的好差啊,都快忘的干净了。这里重新记录一下,顺便回顾回顾问了哪些问题。

ViewModule 是如何创建的

通过一个专门的工厂类 ViewModelProvider ,这个设计是 ViewModel 能够实现其生命周期独立性的关键。

Example

1
val vm = ViewModelProvider(this).get(MyViewModel::class.java)

ViewModelStoreOwner 提供 ViewModelStore

每一个 Activity 和 Fragment 都实现了 ViewModelStoreOwner 接口,该接口定义了一个方法 getViewModelStore()。这个方法会返回一个唯一的 ViewModelStore 实例,这个实例在 Activity/Fragment 的整个生命周期(包括配置变更时的重建)中都是保持不变的。

ViewModelStore 是一个简单的容器,它内部维护了一个 HashMap<String, ViewModel>,用来存储与当前 ViewModelStoreOwner 关联的所有 ViewModel 实例。

在Fragment中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

    public ViewModelStore getViewModelStore() {
        if (mFragmentManager == null) {
            throw new ...;
        }
        if (getMinimumMaxLifecycleState() == Lifecycle.State.INITIALIZED.ordinal()) {
            throw new ;
        }
        return mFragmentManager.getViewModelStore(this);
    }

ViewModelProvider 的实例化

当你创建 new ViewModelProvider(this) 时,ViewModelProvider 会接收这个 ViewModelStoreOwner(也就是你的 Activity 或 Fragment)。它会从这个 ViewModelStoreOwner 中获取到对应的 ViewModelStore 实例。

get(MyViewModel::class.java) 方法的调用

当你调用 get() 方法时,ViewModelProvider 会执行以下逻辑:

  • 检查 ViewModelStore: 它首先会查看从 ViewModelStoreOwner 获取到的 ViewModelStore 中,是否已经存在一个指定类型 (MyViewModel) 的 ViewModel 实例。它会使用一个内部生成的键(通常是 ViewModel 类的全限定名)来查找。

  • 如果存在: 如果找到了,ViewModelProvider 会直接返回这个已经存在的 MyViewModel 实例。这是在屏幕旋转等配置变更时,数据能够保留的关键。

  • 如果不存在: 如果 ViewModelStore 中还没有 MyViewModel 的实例,ViewModelProvider 就会创建一个新的 MyViewModel 实例。

    • 使用 ViewModelProvider.Factory 创建: 默认情况下,ViewModelProvider 使用一个默认的 Factory 来创建 ViewModel。这个默认的 Factory 只能创建没有构造函数参数的 ViewModel。
    • 自定义 Factory: 如果你的 ViewModel 需要通过构造函数传递参数(例如,Repository 实例、SavedStateHandle),你就需要提供一个自定义的 ViewModelProvider.Factory 实现。例如,AndroidViewModelFactory 或 SavedStateViewModelFactory 就是系统提供的一些工厂,你也可以自己实现 ViewModelProvider.Factory 接口。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 示例:自定义 Factory
class MyViewModelFactory(private val repository: MyRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return MyViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

// 在 Activity/Fragment 中使用自定义 Factory
val repository = MyRepository() // 假设你的 Repository 已经初始化
val myViewModel = ViewModelProvider(this, MyViewModelFactory(repository)).get(MyViewModel::class.java)
  • 存储到 ViewModelStore: 新创建的 MyViewModel 实例会被存储到 ViewModelStore 中,以便将来在配置变更时可以重新使用。

总结创建流程:

  1. 宿主 (Activity/Fragment) 提供一个唯一的 ViewModelStore。
  2. ViewModelProvider 负责管理 ViewModel 的生命周期,它会从宿主获取 ViewModelStore。
  3. 当你请求一个 ViewModel 时,ViewModelProvider 会先在 ViewModelStore 中查找。
  4. 如果找到,直接返回已存在的实例。
  5. 如果没找到,就通过一个 Factory 来创建新的实例。
  6. 新创建的实例会被存储到 ViewModelStore 中,以供后续使用。

ViewModule 是如何销毁的

唉,好可以,早知道多开看一下了,前面创建的部分我还记得,但是这一部分忘记了,记得当时回答的是ViewModule 内部去监听宿主的生命周期。其实结果大相径庭。但是销毁方式是差不多的,调用 ViewModule 中的 onCleared 函数

**ViewModel 并不是直接“知道”它的服务对象(Activity 或 Fragment)何时被永久销毁的××

最重要的一句话。根据这句就知道并不是通过监听去实现的,而是通过创建 vm 的 ViewModelStore 去通知 vm 销毁。

Tip:

  1. **Activity 永久销毁时:**当一个 Activity 被用户彻底关闭(例如,用户按下返回键退出 Activity)或者被系统出于内存管理目的而销毁时,这个 Activity 的 onDestroy() 生命周期方法会被调用。在 onDestroy() 方法中,如果系统检测到这个 Activity 是永久性销毁(isFinishing() 返回 true),并且不是因为配置变更而销毁,那么它会通知其内部的 ViewModelStore。 ViewModelStore 收到这个通知后,会遍历它内部存储的所有 ViewModel 实例,并对每个 ViewModel 调用它们的 onCleared() 方法。

  2. Fragment 永久销毁时: 类似地,当一个 Fragment 从其宿主 Activity 中被移除,并且没有被添加到回退栈中,或者其宿主 Activity 被永久销毁时,Fragment 的 onDestroy() 方法也会被调用。在这种情况下,Fragment 内部的 ViewModelStore 也会被通知,进而触发其关联 ViewModel 的 onCleared() 方法。

ViewModule 解决了什么问题

我一直想着 ViewModule 应该是解决了数据和UI解耦的问题,但是,正式的回答并不是这个。

正确的官方的回答应该是:

它的主要目的是解决 Android 应用开发中一直存在的两个痛点:屏幕旋转导致的数据丢失Activity/Fragment 生命周期复杂性带来的数据管理难题

### 解决了屏幕旋转导致的数据丢失问题

ViewModel 如何解决:

  • 生命周期独立性: ViewModel 对象的生命周期比 Activity 或 Fragment 的生命周期更长。它在首次创建后会一直存在,直到其关联的 Activity/Fragment 彻底销毁(例如用户退出应用或系统杀死进程)。
  • 数据持久性: 即使关联的 Activity/Fragment 在配置变更时被销毁并重建,ViewModel 实例也不会被销毁。因此,它内部存储的所有数据都能得以保留,并在新的 Activity/Fragment 实例创建后继续使用。

Ai 提供了一个示例:

假设你有一个计数器应用,用户点击按钮数字会增加。如果没有 ViewModel,屏幕旋转后计数会归零。使用 ViewModel 后,即使屏幕旋转,计数也会保持不变,因为 ViewModel 负责保存和提供这个数字。

### 解决了 Activity/Fragment 生命周期复杂性带来的数据管理难题

ViewModel 如何解决:

  • 职责分离 : ViewModel 鼓励将 UI 相关的状态和数据管理逻辑从 Activity/Fragment 中分离出来。Activity/Fragment 专注于显示 UI 和处理用户交互,而 ViewModel 则负责持有和处理数据。
  • 生命周期感知: ViewModel 与生命周期无关,这意味着你可以在 ViewModel 中安全地执行异步操作(如网络请求),而无需担心 Activity/Fragment 被销毁时导致的问题。即使 Activity/Fragment 被销毁,ViewModel 中的操作仍然可以继续进行,并在新的 Activity/Fragment 实例创建后将结果传递过去。
  • 测试性提升: 由于 ViewModel 不依赖于 Android UI 框架,你可以更容易地对它进行单元测试,从而提高代码质量。

关于长连接知道多少

额,说实话,这个了解真的不多,就是知道这个玩意是干什么用的。但是要具体的,有条条框框的形容出来,真难为哥们了。

以下附上整理了的 Ai 回答,确实工整很多,也挺全面的。

是什么

在 Android 开发中,长连接(Long Connection),也称为持久连接(Persistent Connection),是指客户端(Android 应用)与服务器之间建立的一种持续性网络连接。一旦连接建立,它会保持开放状态,允许双方在同一条连接上进行多次数据传输,而无需每次通信都重新建立和关闭连接。

这与短连接(Short Connection)形成对比,短连接的模式是:发送请求 -> 建立连接 -> 传输数据 -> 关闭连接,每次交互都重复这个过程。

有哪些功能

实时消息推送: 这是长连接最核心的功能之一。例如,聊天应用(微信、WhatsApp)、即时通知、在线游戏中的玩家状态更新,都需要服务器能够主动、即时地将数据推送到客户端,而长连接正是实现这一功能的基石。

降低通信延迟: 由于连接是持续开放的,数据可以随时发送和接收,省去了每次通信前建立连接所需的耗时操作(如 TCP 三次握手),从而显著降低了数据传输的延迟,提升了应用的响应速度。

**提高数据传输效率: **避免了频繁的连接建立和拆除,减少了网络协议的开销,使得数据传输更加高效,节省了宝贵的网络带宽和设备资源。

**支持持续状态同步: **对于需要客户端和服务器之间保持持续状态同步的应用,如在线协作文档、实时股票行情、物联网设备数据上报等,长连接能确保数据流的连贯性。

**节约设备电量和资源: **相比于短连接频繁地创建和销毁连接,长连接在整体上减少了设备的 CPU、内存和网络接口的活跃时间,有助于延长电池续航。

解决了什么问题

“拉取”模式的低效率和高延迟问题:

在没有长连接的情况下,如果应用需要获取最新数据(例如,是否有新消息),它通常只能通过**轮询(Polling)**的方式,即每隔一段时间(如几秒或几十秒)就主动向服务器发送一个请求来检查更新。

问题 这种模式效率低下,无论是没有更新还是有更新,应用都需要发起网络请求,这会消耗大量电量和流量。同时,消息的实时性也无法保证,总会有一定的延迟(取决于轮询间隔)。

解决 长连接允许服务器在有新数据时**立即推送(Push)**给客户端,无需客户端频繁查询,从而解决了实时性差和资源浪费的问题。

频繁建立/关闭连接的资源开销问题:

短连接模式下,每次数据交互都需要完整的 TCP 连接建立(三次握手)和断开(四次挥手)过程。

问题 频繁进行这些操作会产生大量的网络数据包,增加网络流量,并且会消耗服务器和客户端的 CPU 和内存资源,尤其是在高并发场景下,对服务器压力巨大。在移动设备上,这还会加速电池消耗。

解决 长连接一次建立,多次复用,极大地减少了连接管理的开销,提升了系统整体的效率和性能。

弱网络环境下连接不稳定性问题:

移动网络环境复杂多变,信号可能随时中断或切换。

问题 短连接在每次需要通信时都重新建立连接,如果网络不稳定,连接建立失败的概率会增加,导致用户体验不佳。

解决 虽然长连接本身也会受到弱网影响而断开,但通常会配合心跳机制(保持连接活跃)和智能重连机制(断开后尝试恢复),使其在面对网络波动时表现得更加健壮,能更快地恢复通信。

关于 Socket 知道多少

唉,其实这个也是挺基础的问题,奈何网络基础也忘的差不多了哈哈。常回顾吧。

什么是 Socket?

Socket 是网络通信的端点。它可以看作是应用程序之间进行双向通信的管道。每当 Android 应用需要与其他设备(如服务器)进行数据交换时,通常都需要创建并使用 Socket。

Socket 分为两种主要类型:

  1. 流式套接字 (Stream Sockets):
  • 基于 TCP (Transmission Control Protocol) 协议。
  • 提供可靠、有序、基于连接的数据传输。这意味着数据在发送和接收过程中不会丢失,顺序也不会错乱。
  • 适用于传输大量数据,例如文件下载、网页浏览、实时聊天等。
  • 在 Android 中,通常使用 java.net.Socket 和 java.net.ServerSocket 类来实现。
  1. 数据报套接字 (Datagram Sockets):
  • 基于 UDP (User Datagram Protocol) 协议。
  • 提供无连接、不可靠的数据传输。数据包可能丢失、重复或乱序,且没有错误恢复机制。
  • 适用于对实时性要求较高但少量数据传输的场景,例如在线游戏、视频会议、DNS 查询等。
  • 在 Android 中,通常使用 java.net.DatagramSocket 和 java.net.DatagramPacket 类来实现。

Socket 在 Android 中的应用场景

  • **即时通讯(IM)应用:**如微信、QQ,通过 Socket 实现消息的实时发送和接收。
  • **游戏:**尤其是多人在线游戏,常常利用 Socket 进行玩家之间的实时数据同步。
  • **物联网(IoT)设备通信:**Android 应用作为客户端与智能家居设备或传感器进行数据交互。
  • **远程控制:**Android 手机远程控制电脑或其他设备。
  • **文件传输:**在设备之间共享文件。

重要的注意事项

  • **网络权限:**在 Android Manifest 文件中添加网络权限,否则应用将无法进行网络通信。
1
2
3
4
5
6
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!--如果你的应用需要作为服务器接收传入连接,可能还需要:-->

<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
  • 在子线程中执行网络操作: 切记,所有网络操作(包括 Socket 的连接、读写)都必须在子线程中执行,否则会导致 NetworkOnMainThreadException 崩溃。可以使用 AsyncTask、Handler、ExecutorService 或 Kotlin 协程来处理

  • ** 异常处理:** 网络通信中可能会出现各种异常,如 IOException (网络中断、连接超时)、SocketTimeoutException (读写超时) 等。需要进行适当的 try-catch 块来捕获并处理这些异常,确保应用的稳定性。

  • 数据协议: 在实际应用中,客户端和服务器之间需要约定好数据传输的协议(例如:数据格式、字段含义、结束标志等),以便正确地解析和处理数据。

  • 心跳机制: 对于长连接应用(如聊天),为了保持连接活跃并检测连接状态,通常需要实现心跳机制,定时发送小数据包给对方。

  • 安全问题: 如果传输的数据涉及敏感信息,应考虑使用 TLS/SSL (Transport Layer Security/Secure Sockets Layer) 对数据进行加密,防止数据被窃听或篡改。Java 的 SSLSocket 和 SSLServerSocket 类提供了对 TLS/SSL 的支持。

其他

也是问了一些比较基础的东西吧,平时用都用成习惯了,实在是不太记得这些定义和形容啥的。

### ArrayList 和 LinkedList

面试官提问,问了这两个的区别,和在微信朋友列表中使用哪个List,为什么。。。

两者都区别

ArrayList:

  • 底层是基于 动态数组 实现的。
  • 当你创建一个 ArrayList 时,它会分配一个初始容量的数组。当添加的元素超过当前数组容量时,ArrayList 会自动扩容(通常是当前容量的 1.5 倍),并把旧数组中的元素复制到新数组中。
  • 元素存储在连续的内存空间中。

LinkedList:

  • 底层是基于 双向链表 实现的。
  • 每个元素(节点)都包含数据本身,以及指向前一个元素和后一个元素的引用(指针)。
  • 元素在内存中可以是不连续的,它们通过引用连接起来。

性能特性

操作类型 ArrayList LinkedList
随机访问查找 非常快,直接通过索引计算内存地址 慢,需要从头或尾遍历到目标索引
尾部添加删除 通常很快,除非触发扩容 很快,只需修改末尾节点的引用
头部添加删除 慢,需要移动所有现有元素 很快,只需修改头节点的引用
中间添加删除 慢,需要移动 index 之后的所有元素 慢。需要先遍历到 index
空间占用大小 相对较小,只需要存储元素本身 相对较大,需要存储两个额外的指针。

哈希表

**特点: **存储键值对 (Key-Value Pair) 的数据结构。通过键可以快速查找、插入和删除对应的值。Java 中最常用的是 HashMap。

我就记得是个键值对,事实也没错,只是哈希表内容太多了,这里先不记录了

图片加载相关

问了一个问题,在大图加载到小控件的时候,应该怎么设计加载逻辑。这里捋下吧。

  1. 获取图片文件
  2. 缩放到合适尺寸
  3. 加载到View 上

也许还需要更多吧,唉,还挺难的,最怕这类宽泛的题了。

RecycleView 嵌套滑动

问的是解决 垂直 RecycleView 中嵌套一个垂直 RecycleView ,如何解决他的滑动嵌套问题。

这个题目挺难的其实,我知道步骤和流程应该怎么做。但是在面试官提出具体描述下方法,直接抓瞎了。记不全方法名啊 OMG。直接寄了,直接回答不记得具体函数了,寄。

ViewGroup 和 View 的区别

  1. View 是 UI 组件的基本单元。
  2. ViewGroup 是 View 的容器,负责管理和布局其子 View。

**定义:**View 是 Android 中所有 UI 组件的基类。它代表了屏幕上一个可以绘制和响应用户事件的矩形区域。

职责:

  1. 绘制: View 知道如何将自己绘制到屏幕上。
  2. 事件处理: View 可以响应用户交互事件,如点击、触摸、长按等。
  3. 测量和布局: View 负责告诉父 ViewGroup 自己期望的大小,并根据父 ViewGroup 提供的空间进行布局。

**定义:**ViewGroup 是 View 的一个特殊子类,它被设计用来作为其他 View (包括其他 ViewGroup 自身) 的容器。它是一个可以包含和组织多个子视图的布局容器。

职责:

  1. 管理子视图: ViewGroup 负责添加、删除、查找和管理其包含的子 View。
  2. 布局子视图: ViewGroup 负责根据其自身的布局规则(例如线性布局、相对布局、帧布局等)来测量和定位其内部的子 View。
  3. 事件分发: ViewGroup 在接收到触摸事件时,会将其分发给合适的子 View 进行处理。

关系

ViewGroup 实际上是 View 的一个子类。这意味着 ViewGroup 继承了 View 的所有特性(如绘制、事件处理等),并且在此基础上增加了管理和布局子视图的能力。

在 Android UI 编程中,我们通常会构建一个由 ViewGroup 和 View 组成的树形结构。 根节点通常是一个 ViewGroup (例如 ConstraintLayout 或 LinearLayout),它包含一系列的子 View 或子 ViewGroup,层层嵌套,直到最底层的 View (如 TextView 或 Button)。

其他,想起来再加…

Licensed under CC BY-NC-SA 4.0
赣ICP备2022001789号
使用 Hugo 构建
主题 StackJimmy 设计