一、基础面试题
1、String、StringBuffer、StringBuilder三者的区别?
- String是不可变类,每次改变都会创建新的对象,旧对象会被回收,频繁创建回收所以效率比较低。
- StringBuffer线程安全的,大多数方法带有synchronized关键字,因为非多线程环境下效率低,较少使用。
- StringBuilder非线程安全,是可变的变量,不会频繁创建回收性能好。
2、什么是值传递和引用传递?
- 对象被值传递,意味着传递了对象的一个副本。改变了对象副本,也不会影响源对象的值。
- 对象被引用传递,意味着传递的并非实际的对象,而是对象的引用。外部引用对象所做的改变会反映到所有的对象上。
3、java里 equals和== 区别?
- java中equals和==的区别 值类型是存储在内存中的堆栈(简称栈),而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其本身则存储在堆中
- ==操作比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同
- equals操作表示的两个变量是否是对同一个对象的引用,即堆中的内容是否相同
- ==比较的是2个对象的地址,而equals比较的是2个对象的内容,显然,当equals为true时,==不一定为true。
4、HashMap和Hashtable有什么区别?
- HashMap和Hashtable都实现了Map接口,因此大多数特性基本相似
- HashMap允许键和值是null,而Hashtable不允许键或者值是null
- Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境
- HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)
5、ArrayList和LinkedList有什么区别?
- ArrayList和LinkedList都实现了List接口
- ArrayList是基于索引的数据接口,底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。
- LinkedList是以元素链表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)
- 相对于ArrayList,LinkedList的插入,添加,删除操作速度更快
- LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素
6、View的post方法和Handler的post方法有什么区别?
- View.post(附加到视图的消息队列):主要用于在视图完成布局之后执行任务,确保视图树已经准备就绪。
java 代码解读复制代码view.post(new Runnable() {
@Override
public void run() {
// 视图的测量和布局已完成
}
});
- Handler.post(附加到Handler的消息队列):更通用,用于将任务调度到特定的 Handler 关联的线程,可以是主线程或其他工作线程。
java 代码解读复制代码Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
//将任务附加到主线程中,可更新UI或其他任务
}
});
7、静态内部类是什么?和非静态内部类的区别是什么?
内部类被static声明,则该内部类为静态内部类。
静态内部类与非静态内部类的区别:
- 非静态内部类能够访问外部类的静态和非静态成员,静态类只能访问外部类的静态成员。
- 非静态内部类不能脱离外部类被创建,静态内部类可以。
8、抽象类和接口
抽象类的设计目的,是代码复用;接口的设计目的,是对类的行为进行约束。
- 接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法
- 接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化
- Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public
- 类可以实现很多个接口,但是只能继承一个抽象类
- Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量
9、重载和重写是什么意思,区别是什么?
- 重写就是重新写的意思,当父类中的方法对于子类来说不适用或者需要扩展增强时,子类可以对从父类中继承来的方法进行重写。
- 重载则是在同一个类中,允许存在多个同名方法,只要它们的参数列表不同即可。
二、多线程和并发面试题
1、Java中线程和进程的区别以及它们在Android中的应用?
- 线程是进程中的一个实体,是CPU调度和分派的基本单位,比进程更小的能独立运行的基本单位。进程是系统进行资源分配和调度的一个独立单位。
- 在Android中,每个应用都运行在自己的进程中,而应用内的多个线程共享进程的资源。如:Android的主线程(UI线程)负责UI的更新和事件处理,后台线程用于执行耗时的操作,如:网络请求,大量计算,以避免阻塞主线程。
2、Android中的Handler机制及其工作原理。
Handler是Android中用于在不同线程之间进行通信的类。它允许线程发送和处理Message或Runnable对象到一个线程的MessageQueue,并且可以异步地执行任务。
Handler的工作机制包括以下几个关键部分:
- MessageQueue:一个消息队列,用于存储待处理的消息。
- Looper:一个循环,不断地从MessageQueue中取出消息并分发给Handler。
- Handler:一个实例,用于发送消息到MessageQueue,并处理消息。
当调用Handler的sendMessage
或post
方法时,消息会被放入MessageQueue中。Looper会不断地从队列中取出消息,并将其分发给对应的Handler。Handler通过实现handleMessage
方法来处理消息。
3、什么是线程池,以及在Android中如何使用线程池?
线程池是一种执行器(Executor),用于在一个后台线程中执行任务。线程池的主要优点是减少了在创建和销毁线程时所产生的性能开销。通过重用已经创建的线程来执行新的任务,线程池提高了程序的响应速度,并且提供了更好的系统资源管理。
在Android中,可以使用java.util.concurrent
包中的Executors
工厂方法来创建线程池,如:Executors.newFixedThreadPool
创建一个固定大小的线程池,或者Executors.newCachedThreadPool
创建一个可根据需要创建新线程的线程池。
Java 代码解读复制代码ExecutorService threadPool = Executors.newFixedThreadPool(4);
threadPool.execute(new Runnable() {
@Override
public void run() {
// 执行的任务
}
});
4、什么是死锁,以及如何在Android中避免死锁?
死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种僵局。当线程A等待线程B持有的资源,而线程B又在等待线程A持有的资源时,就发生了死锁。
开发中合理使用锁和并发工具可以有效地避免死锁的发生,以下是一些避免死锁的措施:
- 避免锁的嵌套:尽量减少锁的使用,或者避免多个锁的嵌套。
- 使用定时锁:使用
tryLock
尝试获取锁,如果失败则等待一段时间后重试。 - 有序获取锁:确保所有线程以相同的顺序获取锁。
- 使用超时机制:在获取锁时使用超时机制,例如
lock.tryLock(10, TimeUnit.SECONDS)
。
5、同步代码块和同步方法的区别以及它们在Android中的应用?
同步代码块和同步方法都是用于控制多个线程对共享资源访问的手段。
- 同步方法:方法使用
synchronized
关键字修饰,确保在同一时间只有一个线程可以访问该方法。 - 同步代码块:代码块使用
synchronized
关键字包裹,只对特定代码段进行同步。
在Android中,通常使用同步方法来保护对共享资源的访问,例如,当多个线程需要访问同一个数据结构时。同步代码块提供了更细粒度的控制,可以在需要同步的代码段使用,以减少同步带来的性能开销。
三、Android内存方面的面试题
1、什么是内存泄漏?开发过程中如何检测和解决内存泄漏?
内存泄漏是指应用程序中对象不再被使用但仍被引用,导致GC无法回收,而造成的内存消耗。如:Acitivity的引用被静态变量或长生命周期的对象持有。
常用检测内存泄漏的方法:
- 使用LeakCanary检测应用中的内存泄漏或使用Android Studio的Profiler工具监控内存使用情况。
解决内存泄漏的方法包括:
- 如使用BroadcastReceiver和Service时确保在
Activity
的onDestroy
方法中取消对应注册。 - 使用弱引用(
WeakReference
)来减少对对象的强引用。 - 避免在静态变量中持有
Activity
或Context
的引用
2、如何减少Android应用的内存占用?
- 优化布局:使用(
ConstraintLayout
)减少布局过度嵌套,使用ViewStub
和include
标签来延迟加载和复用布局。 - 优化资源:移除无用资源,使用资源压缩,按需选用图片格式和加载工具(
Glide
、Coil
) - 合理使用Bitmap:使用
inBitmap
来复用Bitmap的内存。 - 代码优化:如使用StringBuilder代替String,避免不必要的对象创建。
- 使用内存分析工具:定期使用Android Profiler工具分析内存使用情况。
3、如何优化Android内存使用?
- 优化数据结构:使用更高效的数据结构,减少内存使用。
- 避免内存抖动:减少短时间内大量对象的创建和销毁,避免频繁的垃圾回收。
- 优化图片加载:使用Glide或Coil等库进行图片加载和缓存。
- 避免内存泄漏:确保及时释放不再使用的对象和资源,使用LeakCanary等工具检测内存泄漏,并修复。
- 使用内存缓存:如LRUCache,合理使用内存缓存来提高性能。
- 减少内存分配:避免在主线程进行大量的内存分配。
- 对象复用:对于频繁创建和销毁的对象,使用对象池进行复用。
- 使用ProGuard或R8:移除无用的代码和资源,减少应用体积。
- 合理使用线程:避免创建过多的线程,使用线程池来管理线程。
4、什么是OOM(Out of Memory)异常以及如何避免它。
OOM异常发生在应用程序尝试分配的内存超过了系统可用的内存时。
- 内存分配:合理分配内存,避免一次性分配大量内存。
- 监控内存使用:使用LeakCanary检测应用中的内存泄漏,使用Android Profiler监控应用的内存使用情况。
- 内存优化:优化内存使用,避免内存泄漏和内存抖动。
- 优化后台服务:避免使用Service进行长时间任务,使用
JobScheduler
和WorkManager
来管理后台任务。 - 合理释放内存:使用
trimMemory
方法:在ComponentCallbacks
中实现onTrimMemory
方法。
四、Android ANR面试题和优化方案
1、什么是ANR?它在Android中是如何产生的?
- ANR是指应用程序未响应,通常是因为主线程被阻塞导致无法及时响应用户输入或消息广播。
- Android系统对一些操作有严格的时间限制,如输入事件处理、广播接收等,如果在规定时间内没有得到处理,就会触发ANR。
2、描述你是如何定位(检测ANR)和解决ANR问题的?
- 定位ANR通常依赖于Android系统生成的Trace文件,通过分析这些文件可以找到导致阻塞的代码位置。
- Profiler工具可以通过监控CPU使用情况来帮助识别可能导致ANR的代码区域。通过查看卡顿和性能分析,可以发现响应时间过长的操作。
- 解决ANR的方法包括将耗时操作移至后台线程、优化锁的使用、减少主线程工作量等。
3、如何实现一个自定义的ANR监控方案?
- 自定义ANR监控方案通常涉及到监控系统发送的SIGQUIT信号,以及分析/data/anr目录下的Trace文件。
4、如何优化应用的主线程以减少ANR的发生?
- 避免长时间操作:如将耗时I/O操作放在后台线程中执行,使用协程(
Coroutine
)、ExecutorService
等来在后台线程处理任务。 - 使用合适的并发工具:如利用
HandlerThread
来处理特定任务,避免主线程阻塞。使用IntentService
来处理长时间运行的任务,它会自动在工作线程中执行onHandleIntent
。 - 优化布局:使用(
ConstraintLayout
)减少布局过度嵌套,使用ViewStub
和include
标签来延迟加载和复用布局。 - 减少资源文件:压缩图片资源,使用合适的图片格式(如WebP),移除不必要的资源和库,减少APK大小。
- 及时处理输入事件:确保
onTouchEvent
、onKeyDown
等方法快速返回,不在这些方法中执行耗时操作。 - 优化服务:如服务(
Service
)中有耗时操作,考虑使用startService
和bindService
来异步处理。 - 避免内存泄漏:确保及时释放不再使用的资源,如关闭
Cursor
、注销BroadcastReceiver
等。 - 使用
JobScheduler
:对于需要定时执行的任务,可以使用JobScheduler
来在后台线程中执行。 - 避免主线程中的大型数据处理:对于大型数据集的处理,如解析大型JSON或XML,应该在后台线程中进行。
- 使用
ViewModel
和LiveData
。
评论记录:
回复评论: