目录
一,安卓部分
1.Activity生命周期
2.在a Activity中启动b Acticity,生命周期如何变化?
3.从b返回a,又如何变化?
4.什么是内存泄漏?安卓中常见内存泄漏场景?
5.Handler为什么会导致内存泄漏?
6.如何监控内存泄漏?
7.Activity和Fragment之间的区别,生命周期?
8.Activity和Fragment如何通信?
二,Java部分
1.说一下HashMap是如何实现的?
JDK1.8之前
JDK1.8之后
2.HashMap是线程安全的吗?为什么?线程不安全会导致什么后果?
3.如何解决HashMap线程不安全的问题?
三,OkHttp
1,OkHttp内部五大拦截器
2,OkHttp缓存策略
3,使用OkHttp时,文件下载和普通的接口请求有什么不同?
1. 响应体处理方式:
2. 缓冲区和流式读取:
3. 流量控制:
4. 错误处理:
5. 进度监听:
四,网络部分
1,Http协议是可靠的吗?
2,TCP为什么可靠?
3,TCP三次握手,四次挥手
一,安卓部分
1.Activity生命周期
2.在a Activity中启动b Acticity,生命周期如何变化?
生命周期依次为(考虑被启动的活动的可见性):
- A onPause
- B onCreate
- B onStart
- B onResume
- A onStop
3.从b返回a,又如何变化?
生命周期依次为:
- B onPause
- A onRestart
- A onStart
- A onResume
- B onStop
- B onDestory
4.什么是内存泄漏?安卓中常见内存泄漏场景?
内存泄漏指应用中的对象不再需要,但仍然被引用,导致GC无法回收,从而长期占用内存,最终可能导致OOM。
常见内存泄漏场景:
- 单例模式:单例模式的单例类被静态变量所持有,当单例对象持有一个短生命周期的对象时,就容易发生内存泄漏
- 非静态内部类创建的静态实例
- hanlder使用不当
- 线程造成的内存泄漏:new Thread(new Runnable(){}).start();创建了一个匿名内部类,隐式持有外部类引用
- 资源没有及时释放
5.Handler为什么会导致内存泄漏?
看我这篇博客:Hanlder源码分析_androidhanlder机制-CSDN博客
6.如何监控内存泄漏?
使用LeakCanary:LeakCanary 是一个开源的 Android 内存泄漏检测库。它可以自动检测内存泄漏并提供详细的堆栈信息,帮助开发者识别和修复问题。
7.Activity和Fragment之间的区别,生命周期?
Activity代表一个单独的用户屏幕界面,是应用中一个功能的切入点,一个Activity可以包含多个Fragment;
Fragment是一个模块化的组件,通常作为Activity的一部分来管理,是可重用的组件,通常依赖Activity进行生命周期的管理,适用于灵活设计UI;
8.Activity和Fragment如何通信?
1.接口回调
在Fragement中定义回调接口:
public class MyFragment extends Fragment {// 定义回调接口public interface OnDataPass {void onDataPass(String data);}private OnDataPass mDataPasser;@Overridepublic void onAttach(Context context) {super.onAttach(context);try {mDataPasser = (OnDataPass) context; // 确保宿主Activity实现了接口} catch (ClassCastException e) {throw new ClassCastException(context.toString() + " must implement OnDataPass");}}public void passData(String data) {if (mDataPasser != null) {mDataPasser.onDataPass(data); // 传递数据到Activity}}
}
在Activity中实现回调接口:
public class MyActivity extends AppCompatActivity implements MyFragment.OnDataPass {@Overridepublic void onDataPass(String data) {// 处理从Fragment传递过来的数据Log.d("MyActivity", "Received data: " + data);}
}
2.ViewModel和LiveData
创建ViewModle共享数据:
public class MyViewModel extends ViewModel {private final MutableLiveData<String> liveData = new MutableLiveData<>();public LiveData<String> getLiveData() {return liveData;}public void setData(String data) {liveData.setValue(data);}
}
在Activity中使用ViewModel:
public class MyActivity extends AppCompatActivity {private MyViewModel viewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);viewModel = new ViewModelProvider(this).get(MyViewModel.class);viewModel.getLiveData().observe(this, data -> {// 处理从ViewModel中获取的数据Log.d("MyActivity", "Received data: " + data);});}// 通过ViewModel传递数据public void sendDataToFragment(String data) {viewModel.setData(data);}
}
在Fragment中使用ViewModel:
public class MyFragment extends Fragment {private MyViewModel viewModel;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);viewModel = new ViewModelProvider(getActivity()).get(MyViewModel.class);viewModel.getLiveData().observe(this, data -> {// 处理接收到的数据Log.d("MyFragment", "Received data: " + data);});}
}
3.EventBus
二,Java部分
1.说一下HashMap是如何实现的?
JDK1.8之前
JDK1.8之前HashMap底层是数组和链表结合在一起使用也就是链表散列
HashMap通过key的hashCode经过扰动函数处理过后得到hash值,然后通过(n - 1)& hash 判断当前元素存放的位置(这里的n指的是数组的长度),如果当前位置存在元素的话,就判断该元素于要存入的元素的hash值以及key是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突
扰动函数指的是HashMap的hash方法。使用hash方法也就扰动函数是为了防止一些实现比较差的hashCode方法,也就是说使用扰动函数可以减少碰撞
hash方法源码:
static final int hash(Object key) {int h;// key.hashCode():返回散列值也就是hashcode// ^:按位异或// >>>:无符号右移,忽略符号位,空位都以0补齐//将高位信息混合到低位return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
h ^ (h >>> 16)的作用:
在哈希表中,哈希值通常会被映射到一个较小的范围(例如哈希表数组的大小)。如果直接使用hashCode,可能会导致哈希冲突,通过将高位信息混合到地位中,可以增加哈希值的随机性,从而减少冲突
JDK1.8之后
JDK1.8以后再解决哈希冲突时有了较大的变化。
当链表长度大于阈值(默认为8)时,会首先调用treeifyBin方法。这个方法会根据HashMap数组来决定是否转换为红黑树。只有当数组长度大于或者等于64的情况下,才会执行转换红黑树操作,以减少搜索时间。
否则,就是只是执行resize方法对数组扩容。
2.HashMap是线程安全的吗?为什么?线程不安全会导致什么后果?
HashMap线程不安全,因为操作没有同步(synchronized)
线程不安全的后果:
- 数据覆盖:多个线程并发地操作HashMap,例如线程A插入了一个新元素,线程B插入了另一个元素,可能线程B会覆盖掉A插入的数据
- 程序奔溃:多个线程同时操作时,可能会触发一些不一致的内部状态,导致HashMap的数据结构被破坏。比如,扩容操作可能会导致数组下标越界,链表死循环
- 死锁:当读和写操作并发进行时,可能导致一些死锁问题
3.如何解决HashMap线程不安全的问题?
使用ConcurrentHashMap:ConcurrentHashMap 是 Java 并发包中的一个线程安全的 Map
实现。它通过分段锁(Segment Locks)来提高并发性能,不会像 synchronizedMap 那样对整个 Map
加锁,因此它在多线程环境下比 Collections.synchronizedMap() 更高效。它支持高并发读取和部分写入操作。
三,OkHttp
1,OkHttp内部五大拦截器
- 重试和重定向拦截器 RetryAndFollowUpInterceptor:重试拦截器在交出前(交给下一个拦截器),负责判断用户是否取消了请求。在获得了响应之后,会根据响应码判断是否需要重定向,如果满足所有条件就会重启执行所有拦截器
- 桥接拦截器(处理请求头和响应头)BridgeInterceptor:在交出之前,负责将Http协议必备的请求头加入请求之中(如Host,Connection),并添加一些默认的行为(如RZIP压缩);获得响应后调用保存cookie接口并解析GZIP数据
- 缓存拦截器 CacheInterceptor:交出之前读取并判断是否使用缓存;获取响应后判断是否缓存
- 连接拦截器 ConnectInterceptor:交出之前,负责创建或找到一个连接,并获取socket流;获取响应后不进行额外处理
- 网络请求拦截器(执行实际的网络请求)CallServerInterceptor:进行真正的与服务器通信,向服务器发送数据,解析读取的响应数据
2,OkHttp缓存策略
Http缓存规则:
- 强缓存:强缓存指的是浏览器判断本地缓存是否过期,如果没有过期,则直接使用本地缓存。是否使用缓存由浏览器(客户端)决定;强缓存利用Cache-Control(相对时间)和Expires(绝对时间)两个头部字段来实现,Cache-Control的优先级更高;第一次请求数据时,服务器会在响应头部加上Cache-Control字段,规定过期时间,再次请求数据时,浏览器根据Cache-Control字段判断缓存是否过期,没有就直接使用本地缓存,否则就重新向服务端发送请求,并在接收后更新Cache-Control字段。
- 协商缓存:由服务端通知客户端是否可以使用本地缓存,称为协商缓存;协商缓存主要基于Last-Modified(最后修改时间)或Etag(唯一资源标识符)来实现,通过比较最后修改时间或是通过Etag判断资源是否被修改过,如果没有则返回304,客户端使用本地缓存,如果有修改过,则返回最新的数据。
3,使用OkHttp时,文件下载和普通的接口请求有什么不同?
1. 响应体处理方式:
- 普通接口请求(如获取JSON数据):通常响应体(Response.body())是一个较小的、可以直接转换成字符串或JSON对象的数据,开发者可以直接调用 response.body().string() 或response.body().json() 来处理。
- 文件下载请求:响应体通常是一个较大的二进制数据流,不能直接转换为字符串。我们需要将它流式读取并写入文件系统中,而不是将所有数据加载到内存中。这是为了避免内存溢出的问题,尤其在下载大文件时尤为重要。
2. 缓冲区和流式读取:
- 普通接口请求:响应体的数据可以一次性加载到内存中进行处理,通常是直接调用response.body().string(),然后用Gson等库将它解析成JSON对象。
- 文件下载请求:下载文件时,OkHttp会将响应体以流的方式逐块读取,通常会结合BufferedSink或者直接通过输出流(OutputStream)将数据写入到磁盘上。这个过程是流式的,能够逐步将文件数据写入到文件中,而不会一次性将文件全部加载到内存中。
3. 流量控制:
- 普通接口请求:对于普通的API请求,响应体一般较小,完全通过内存处理即可,因此不需要考虑流量控制或者数据块的读取。
- 文件下载请求:下载大文件时,需要考虑流量控制和网络延迟的处理。比如,可以使用分块下载或者限制单次读取的字节数。使用缓冲区和流的方式能有效控制内存使用,避免内存溢出。
4. 错误处理:
- 普通接口请求:如果响应是错误的(如HTTP 4xx/5xx状态码),通常会直接抛出异常或者通过
response.isSuccessful()
进行判断。 - 文件下载请求:文件下载时,除了要检查HTTP状态码,还要考虑文件下载过程中的中断、超时等问题。例如,下载文件时需要处理连接超时、读取超时等情况,同时还要保证文件写入的完整性。
5. 进度监听:
- 普通接口请求:通常不需要考虑请求过程中的进度,除非是大数据量的响应(如分页加载数据等)。
- 文件下载请求:通常需要显示下载进度。在OkHttp中,可以通过拦截器(Interceptor)来获取下载进度,例如计算已下载的字节数与文件总字节数的比率,实时更新进度条。
四,网络部分
1,Http协议是可靠的吗?
Http协议是应用层协议,基于TCP(传输层)协议,TCP协议是可靠的,所以Http协议也是可靠的
2,TCP为什么可靠?
TCP协议通过一系列机制来保证可靠性:
-
基于数据块传输:应用数据被分割成TCP认为最适合发送的数据块,在传输给网络层,数据块被称为报文或段
-
对失序数据包重新排序以及去重:TCP给每一个数据包一个序列号,对接收到的数据根据数据包进行排序,去掉重复序列号的数据就可以实现数据包去重
-
校验和:TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检验数据在传输过程中的任何变化。如果收到段的检验和由差错,TCP将丢弃这个报文段和不确认收到此报文段
-
重传机制:在数据包丢失或由延迟的情况下,重新发送数据包,直到收到对方的确认应答(ACK)。TCP重传机制主要有:基于计时器的重传(超时重传)。快速重传(基于接收端的反馈信息来引发重传),SACK(在快速重传的基础上,返回最近收到的报文段的序列号范围,这样客户端就知道,哪些数据包已经到达服务器了)、D-SACK(重复 SACK,在 SACK 的基础上,额外携带信息,告知发送方有哪些数据包自己重复接收了)。
-
流量控制:TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议(TCP利用滑动窗口实现流量控制)
-
拥塞控制:当网络拥塞时,减少数据的发送。TCP在发送数据的时候,需要考虑两个因素:一是接收方的接收能力,二是网络的拥塞程度。接收方的接受能力由滑动窗口表示,表示接收方还有多少缓冲区可以用来接收数据。网络的拥塞程度由拥塞窗口表示,它是发送方根据网络状况自己维护的一个值,表示发送方认为可以在网络中传输的数据量。发送方发送数据的大小是滑动窗口和拥塞窗口的最小值。这样可以保证发送方既不会超过接收方的接收能力,也不会造成网络的过度拥塞。
3,TCP三次握手,四次挥手
三次握手:
四次挥手: