前言
以下是一些常用的Android framework面试题及答案
有需要没需要都可以备着,希望能帮到大家。
【想看更多相关面试题及答案请关注下哟】
1、 说一下垃圾回收机制?可以手动回收吗?
答:Java的垃圾回收机制主要基于堆内存进行。堆内存被划分为不同的区域,如新生代和老年代。新生代中又分为Eden区和两个Survivor区(S0和S1)。当Eden区没有足够的空间进行对象分配时,JVM会发起一次Minor GC(新生代收集),对新生代中的垃圾对象进行回收。同时,老年代中的垃圾对象则通过Major GC(老年代收集)进行回收。
判断一个对象是否应被回收,通常基于其是否还有引用指向它。任何时刻计数值为0的对象,即没有任何引用指向它,就被认为是垃圾对象,会被垃圾回收机制回收。但需要注意的是,有些情况下,如对象间存在循环引用,垃圾回收机制可能无法正确识别这些对象,导致内存泄漏。
在Java中,垃圾回收是自动进行的,程序员不需要也不能手动进行垃圾回收。这是Java语言设计的一大优点,它大大简化了程序员的内存管理任务,减少了内存泄漏和内存溢出等问题的发生。
2、Java的四种类型引用说一下?
Java中的四种类型引用包括强引用、软引用、弱引用和虚引用。这些引用类型在Java的内存管理和垃圾回收机制中扮演着重要的角色。
- 强引用:强引用是Java中最常见的引用类型,也是默认的引用类型。当一个对象具有强引用时,垃圾回收器不会回收该对象,即使系统内存不足也不会回收。只有在没有任何强引用指向该对象时,垃圾回收器才会将其回收。
- 软引用:软引用是一种相对强引用较弱的引用类型。当系统将要发生内存溢出异常前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。因此,软引用主要使用在需要缓存一些数据的场景中,比如图片缓存或者数据缓存。当内存不足时,垃圾回收器可以回收这些缓存数据,从而避免系统崩溃。
- 弱引用:弱引用是一种比软引用更弱的引用类型。无论当前系统内存空间足够与否,只要JVM的垃圾回收机制运行起来,那些被弱引用关联的对象必定会被回收。弱引用主要用来描述一些可能还有用但并非必需的对象,比如一些可能被缓存起来的数据。
- 虚引用:虚引用是Java中最弱的一种引用类型。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用必须和引用队列(ReferenceQueue)联合使用。虚引用主要使用在需要跟踪对象被垃圾回收的场景中,比如NIO中的DirectByteBuffer等。
3、类加载机制说一下?自己写个Object可以被加载吗,答不可以,为什么,答双亲委派模型,说下双亲委派模型。
类加载机制是Java运行时环境(JRE)中的一个重要概念,它描述了Java类从被加载到虚拟机内存开始,到卸载出内存为止的整个生命周期。这个生命周期主要包括七个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。
关于自己写的Object类是否可以被加载,答案是不可以。这是因为在Java中,Object类是所有类的根类,已经由Java语言本身定义好了。如果自己尝试定义一个名为Object的类,那么在加载时,由于双亲委派模型的存在,这个自定义的Object类会被拒绝加载。
双亲委派模型是Java类加载器的工作方式。当一个类加载器收到加载类的请求时,它首先会把这个请求委派给父类加载器处理。只有当父类加载器无法加载这个类时,子类加载器才会尝试自己去加载。这样可以确保Java类的安全性和一致性。对于Object这样的核心类,由于已经被Java的启动类加载器(也就是最顶层的类加载器)加载过了,所以任何试图重新定义Object类的请求都会被拒绝。
4、 CAS和volatile说一下,AQS说一下,CAS和volatile在AQS上如何使用的,AQS有哪些应用。
volatile是Java提供的一种轻量级的同步机制,它保证被volatile修饰的变量的读写具有原子性(对于复合操作如i++等则不保证原子性)。此外,volatile还可以禁止指令重排序,确保多线程环境下变量的可见性和有序性。
-
AQS是一个用于构建锁和同步器的框架,它使用了一个int类型的变量来表示同步状态,并通过CAS和volatile来保证这个状态的原子性和可见性。AQS内部维护了一个FIFO的等待队列,用于管理等待访问共享资源的线程。当一个线程无法获取到共享资源时,它会被放入等待队列中,并进入等待状态。
-
在AQS中,CAS和volatile的使用主要体现在以下方面:
CAS用于实现状态变量的原子更新。当线程尝试获取或释放锁时,AQS会使用CAS操作来更新同步状态,确保操作的原子性。
volatile用于修饰同步状态变量,确保多线程环境下该变量的可见性。每个线程都能看到该变量的最新值,从而避免脏读等问题。
AQS的应用非常广泛,主要包括:
- 实现各种同步机制,如互斥锁、读写锁、信号量、倒计时器等。其中最常见的应用就是锁的实现,如ReentrantLock、ReentrantReadWriteLock等。
- 用于实现自定义同步机制,如有界队列、线程池等。
5、 线程池执行任务的流程?有哪些参数?
- 当有新任务提交到线程池时,首先会判断当前线程池中是否有空闲线程。如果有空闲线程,则直接分配一个空闲线程来执行该任务。
- 如果当前线程池中没有空闲线程,则进一步判断当前线程池中的核心线程数是否已经达到设定的核心线程数。如果未达到核心线程数,线程池会创建新的核心线程来执行新任务。
- 如果核心线程数已满,线程池会将新任务放入工作队列(阻塞队列)中等待。工作队列会按照FIFO(先进先出)的原则对任务进行排队。
- 如果工作队列也已满,线程池会判断当前线程数是否已经达到设定的最大线程数。如果未达到最大线程数,线程池会创建非核心线程来处理任务。
- 如果当前线程数已经达到最大线程数,且工作队列也已满,线程池会采用拒绝策略来处理新提交的任务,例如抛出异常或丢弃任务等。
线程池的主要参数包括:
- corePoolSize(核心线程数):线程池创建后初始化的线程数。这些线程会一直存活,即使空闲状态也不会被回收,除非设置了- allowCoreThreadTimeout为true。当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理任务。
- queueCapacity(队列容量):用于存放待执行的任务的队列大小。当核心线程数达到最大时,新任务会放在队列中排队等待执行。
- maxPoolSize(最大线程数):线程池允许创建的最大线程数。当队列已满,且当前线程数小于最大线程数时,线程池会创建新的非核心线程来处理任务。
6、 Activity A和 Activity B如何传递数据?
-
使用Intent传递数据:
在Activity A中,你可以创建一个Intent对象,并使用putExtra方法将需要传递的数据添加到Intent中。数据的类型可以是基本数据类型、String、Serializable对象或它们对应的数组等。
然后,通过startActivity方法启动Activity B,并将这个Intent对象作为参数传递。
在Activity B中,你可以通过getIntent方法获取到这个Intent对象,并使用getXXXExtra方法获取传递过来的数据。 -
使用Application全局对象传递数据:
你可以创建一个自定义的Application类,并在其中定义全局变量来存储需要传递的数据。
在Activity A中,你可以通过这个全局对象设置数据。
在Activity B中,你可以同样通过这个全局对象获取数据。这种方式适用于需要在多个Activity之间共享数据的情况。 -
使用静态变量传递数据:
在某个类中定义静态变量,并在Activity A中设置这个静态变量的值。
在Activity B中,你可以直接访问这个静态变量来获取数据。但需要注意的是,静态变量在整个应用程序的生命周期内都是存在的,因此要确保在使用完毕后及时清理,避免内存泄漏。 -
使用startActivityForResult和setResult方法传递数据:
在Activity A中,使用startActivityForResult方法启动Activity B,并指定一个请求码。
在Activity B中,你可以在完成任务后使用setResult方法设置返回的数据和结果码,并通过finish方法结束Activity B。
在Activity A中,你需要重写onActivityResult方法,并在其中根据请求码和结果码获取Activity B返回的数据。
7、我们应用程序在后台可能会被杀死,重启的时候如何恢复之前的页面?
- 使用Service来保活应用程序:
如果应用程序中有Service组件在后台运行,可以在Service的onStartCommand()方法中返回START_STICKY或START_REDELIVER_INTENT标志。这样,当系统杀死应用程序后,会尝试重新启动Service。在Service的onStartCommand()方法中,可以进行一些初始化工作,如重新启动其他组件等。但需要注意的是,这种方式只能保证Service被重新启动,不能保证整个应用程序都能被启动。 - 使用AlarmManager来重新启动应用程序:
可以通过AlarmManager设置一个定时任务,在应用程序被杀死后的一段时间内重新启动应用程序。这种方式可以在一定程度上恢复应用状态,但需要考虑到用户体验和电池消耗的问题。 - 保存和恢复Activity状态:
对于需要恢复的数据较少的应用,可以通过Activity的onSaveInstanceState和onRestoreInstanceState方法来保存和恢复Activity的状态。
如果应用中存在较多的静态变量,可以考虑采用数据持久化的方式,将所有的静态对象改为单例模式,并附加一些持久化缓存。当应用重新启动时,可以从缓存中恢复数据。 - 清空Activity栈并直接启动应用的第一个Activity:
当应用在后台被杀死时,不做Activity栈的恢复,而是清空Activity栈,并且直接启动应用的第一个Activity(通常是欢迎界面)。这样,应用就相当于重启了,虽然用户需要重新进行之前的操作,但至少保证了应用的稳定性和一致性。
8、Kotlin协程挂起和恢复的原理说一下?
- 挂起函数(Suspend Function):
在Kotlin协程中,当一个函数被标记为suspend时,它表示这个函数可能会挂起当前的协程执行。这通常发生在异步操作、I/O操作或等待某个条件成立时。挂起函数只能在其他挂起函数或协程构建器中调用。 - 挂起点的实现:
挂起函数内部会使用suspendCoroutine或类似的协程构建器来创建一个挂起点。当挂起点被触发时,当前协程的执行会暂停,并释放其占用的线程资源。这允许其他协程或线程在相同的线程上继续执行。 - 恢复执行:
当挂起的原因消失(例如,异步操作完成、I/O操作完成或条件成立),挂起的协程需要被恢复执行。这通常通过调用resume方法或类似的机制来完成。恢复操作会将协程重新放回调度器,以便它可以在合适的时机继续执行。 - 调度器(Dispatcher):
Kotlin协程使用调度器来管理协程的执行。调度器负责决定何时以及在哪个线程上恢复挂起的协程。Kotlin提供了多种调度器,如Dispatchers.Main(用于UI线程)、Dispatchers.IO(用于I/O操作)和Dispatchers.Default(用于默认计算任务)。 - 底层平台支持:
Kotlin协程的挂起和恢复机制依赖于底层平台的支持。在JVM上,Kotlin协程利用Java的线程和阻塞机制来实现挂起和恢复。而在Kotlin Native或Kotlin/JS等其他平台上,协程的实现可能会有所不同,但基本原理是相似的。
写在最后
整份资料成百上千道面试真题,全都展现出来肯定是不太现实的。每一道题都是站在企业的角度去思考,站在招聘者的角度去回答。整份资料已经整理成PDF的版本。有需要的朋友可以扫描下方二维码,免费获取更多相关资料。