Android面试题整理
Posted fakerXuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android面试题整理相关的知识,希望对你有一定的参考价值。
你知道什么是单例模式么?如何创建一个单例模式?
- 双重校验方式创建
双重检测锁定(Double-Check Locking)方案属于懒汉式,使用延时加载技术,避免类加载时任务过重和造成资源浪费,同时将synchronized关键字加在代码块中,减少线程同步锁定以提升系统性能。instance实例使用了volatile关键字修饰,主要是避免在多线程环境下由于编译器进行的重排序操作而导致的线程安全问题。JVM在创建一个对象时会进行以下步骤:
1)分配对象内存空间;
2)初始化对象;
3)设置instance指向分配的内存地址;
编译器为了优化性能,可能会将2、3操作调换顺序,假设A线程在执行new Singleton()方法时,由于2、3操作重排序,而初始化对象操作尚未完成时释放了锁。线程B获取锁之后会发现instance已经不为空,当线程B获取到instance对象后如果直接使用就会出错,原因就是对象没有进行初始化操作。而volatile关键字能避免重排序,因此能保证线程安全。总体上来说,双重检测由于加了锁,多线程并发下还是会有效率问题。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 饿汉式
应用程序总是创建并使用单例实例或在创建和运行时开销不大 。在类加载式自行实例化对象。优点在于多线程环境下不会出现线程安全问题,因为类只加载一次。缺点在于,系统加载时消耗额外资源,如果该实例没有使用的情况会造成资源浪费。
class Single{
private Single(){}
private static Single single = new Single();
public static Single getInstance(){
return single;
}
}
- 懒汉式
如果创建开销比较大,希望用到时才创建就要考虑延迟实例化或者Singleton的初始化需要某些外部资源(比如网络或存储设备)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 枚举单例
public enum Singleton{
INSTANCE
}
- 静态内部类单例
静态内部类单例模式是一种比较优秀的实现方式,也是《Effective Java》书中推荐的方式。一方面,使用延时加载,使用时才进行对象初始化,也不会造成造成资源浪费;另一方面,由于JVM在类的加载时已经做了同步处理,不会出现线程安全问题。
public class Singleton{
private Singleton(){
}
public static Singleton getInstance(){
return SingletonFactory.instance;
}
static class SingletonFactory{
private final static Singleton instance = new Singleton();
}
}
序列化
- Serializeable
空接口 标记对象可序列化 底层使用反射 性能不太高 - Parcelable
android提供的接口 不是空接口 只存储属性值 在内存中使用 信息比Serializeable小 省内存 速度快 读的顺序必须和写的顺序一样 不借助IO 由程序员实现序列化和反序列化过程
什么是匿名内部类,它有什么特征?
String中== 与 equals的区别
- == 比较的是内存中存放的位置
- equals()比较的是字符序列
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
@Test
public void testString() {
String str1 = "a" + "b"+ "c";
String str2 = "abc";
String str3 = new String("abc");
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str2 == str3);
System.out.println(str1.equals(str2));
System.out.println(str2.equals(str3));
System.out.println(str1.equals(str3));
System.out.println(str1.hashCode()+" "+str2.hashCode()+" "+str3.hashCode());
}
@Test
public void testString2() {
String str = new String("abc");
System.out.println(str.hashCode());
str += "a";
System.out.println(str.hashCode());
str += "b";
System.out.println(str.hashCode());
String str2="abcab";
System.out.println(str2.hashCode()+" "+str.hashCode());
}
}
为什么说String是不可变的?
- 代码验证
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
@Test
public void testString() {
String str1 = "a" + "b"+ "c";
String str2 = "abc";
String str3 = new String("abc");
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str2 == str3);
System.out.println(str1.equals(str2));
System.out.println(str2.equals(str3));
System.out.println(str1.equals(str3));
System.out.println(str1.hashCode()+" "+str2.hashCode()+" "+str3.hashCode());
}
@Test
public void testString2() {
String str = new String("abc");
System.out.println(str.hashCode());
str += "a";
System.out.println(str.hashCode());
str += "b";
System.out.println(str.hashCode());
String str2="abcab";
System.out.println(str2.hashCode()+" "+str.hashCode());
}
}
- 源码验证
什么是内存泄漏 Java是如何处理它的
- 内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
- 产生的原因:一个长生命周期的对象持有一个短生命周期对象的引用
- 通俗讲就是该回收的对象,因为引用问题没有被回收,最终会产生OOM
import android.content.Context;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyThread t=new MyThread(this);
t.start();
}
class MyThread extends Thread{
Context context;
MyThread(Context context){
this.context=context;
}
@Override
public void run() {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
java内存回收机制 减少OOM的概率
- 减少OOM发生的概率
1. 尽可能少的发生内存泄露
2. 尽可能不在循环中申请内存
3. 尽可能不在调用次数多的函数中申请内存
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
/**
* 可达性分析算法
*/
Object o =new Object();
static Object GCRoot1 =new Object(); //GC Roots
final static Object GCRoot2 =new Object();//GC Roots
public void method() {
//可达
Object object1 = GCRoot1; //=不是赋值,在对象中是引用,传递的是右边对象的地址
Object object2 = object1;
Object object3 = object1;
Object object4 = object3;
}
public void king(){
//不可达(方法运行完后可回收)
Object object5 = o;//o不是GCRoots
Object object6 = object5;
Object object7 = object5;
}
//本地变量表中引用的对象
public void stack(){
Object ostack =new Object(); //本地变量表的对象
Object object9 = ostack;
//以上object9 在方法没有(运行完)出栈前都是可达的
}
}
如何根据泛型T获取它代表的具体类类名
private Class<?> analysisClassInfo(Object object)
{
//getGenericSuperclass()得到包含原始类型,参数化,数组,类型变量,基本数据
Type getType=object.getClass().getGenericSuperclass(); //获取参数化类型
Type[] params=((ParameterizedType)getType).getActualTypeArguments();
return (Class<?>)params[0];
}
动态代理机制你懂么?
Service和IntentService的区别概述
-
Service不是运行在独立的线程,所以不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。
-
IntentService
可用于执行后台耗时的任务,任务执行后会自动停止。
具有高优先级,适合高优先级的后台任务,且不容易被系统杀死。
可以多次启动,每个耗时操作都会以工作队列的方式在IntentService的onHandleIntent回调方法中执行。
如何在Android应用中执行耗时操作
Android中处理耗时操作的基本思路为将耗时操作放到非UI线程执行,常用的是:
- AsyncTask
- Handler
- Thread
- Executors
两个Fragment如何进行通信?
从18年谷歌IO大会开始官方建议 在Activity与Activity Activity与Fragment Fragment与Fragment 之间通信使用LivedataBus
Activity之间如何进行通信?
LiveDataBus
什么是Fragment?它和Activity的关系
Fragment是依赖于Activity的,不能独立存在,Activity是Fragment的一个容器。
一个Activity里可以有多个Fragment。
一个Fragment可以被多个Activity重用。
Fragment有自己的生命周期,并能接收输入事件。
我们能在Activity运行时动态地添加或删除Fragment。
- Fragment的使用能解决以下问题:
模块化(Modularity):我们不必把所有代码全部写在Activity中,而是把代码写在各自的Fragment中,以方便不同业务的UI可以分离出来。
可重用(Reusability):多个Activity可以重用一个Fragment。
可适配(Adaptability):根据硬件的屏幕尺寸、屏幕方向,能够方便地实现不同的布局,这样用户体验更好。
为什么只使用默认的构造方法来创建Fragment
当系统内存不足的情况下,app处于后台条件下,内存会被系统回收,这时用户将app切换到前台,系统会重新将之前回收的内容实例化回来。
这个过程是Android系统通过两个方法来实现的:
onSaveInstantceState();
onRestoreInstantceState();
onSaveInstantceState是系统要回收该界面(Activity、Fragment)时调用的方法,用于保存该(Activity、Fragment)的实例变量到外存。
onRestoreInstantceState是系统恢复该(Activity、Fragment)时调用的方法,用于恢复之前被回收的(Activity、Fragment)实例。
Bundle被用来传递数据 为什么不用HashMap代替
ArrayMap适合于小数据量操作,如果在数据量比较大的情况下,它的性能将退化。HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。而使用Bundle的场景大多数为小数据量。所以使用ArrayMap实现更合适。
Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,Bundle使用Parcelable进行序列化,而HashMap则是使用Serializable进行序列化。
什么是Activity、View、Window
- Activity
是Android四大组件之一,是存放View对象的容器,也是我们界面的载体,可以用来展示一个界面。它有一个SetContentView()方法,可以将我们定义的布局设置到界面上 - View
就是一个个视图对象,实现了KeyEvent.Callback和Drawable.Callback - Window
是一个抽象类,是一个顶层窗口,他的唯一实例是PhoneWindows 它提供标准的用户界面策略,如背景 标题 区域 默认按键处理等
谈谈Serializeable 和 Parcelable接口的区别
Serializable(Java自带):
Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。
Parcelable(android 专用):
除了Serializable之外,使用Parcelable也可以实现相同的效果,
不过不同于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解,
而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。
内存中使用Parcelable
持久化使用Serializable(二进制形式存储,占用磁盘空间会小一些)
解释下Android中的Intent(隐式和显式)
显示Intent是明确目标Activity的类名。
通过Intent(Context packageContext, Class<?> cls)构造方法
通过Intent的setComponent()方法
通过Intent的setClass/setClassName方法
- 显式Intent
- 隐式Intent
隐式Intent通过设置Action、Data、Category,让系统来筛选出合适的Activity。筛选是根据所有的来筛选。
广播和EventBus的区别
广播是四大组件之一,EventBus是开源框架。
广播不能直接执行耗时操作,如果超过10秒,会导致 ANR
广播非常消耗资源,而EventBus非常轻量
广播很容易获取Context和Intent
EventBus切换线程非常方便,只需要修改下注解就行了
广播可以跨进程,而EventBus不可以
什么是 support libary 为什么要引入 support library ?
support library引入主要是因为安卓版本有很多,新版本的更新也快,每个版本都会有一个开发版本号
在进行安卓开发的时候,我们通常需要考虑,应该选择什么样的 API 级别进行开发?
谷歌为此也给出了解决方案,我们在开发过程中可以给出三个设置项:
minSdkVersion <= targetSdkVersion <= compileSdkVersion
当我们targetSdkVersion为 24 时,需要使用 API 28 的新增功能时,这时需要向后兼容,使用低版本的 API 兼容高版本的 API,而支持库就是这个作用,它会跟着每个新发布的 API 级别同步发布,所以这里支持库我们选择与 compileSdkVersion 一样的版本即可。 support libary 只能保证我们针对24开发的APP在28上能正常运行,但运行效果不一定相同。
ANR
Bitmap如何优化以及三级缓存的思想与逻辑
BitmapFactory.Options
-
inPreferredConfig
设置图片解码后的像素格式,如ARGB_8888/RGB_565
ARGB_8888/RGB_565表示的是什么意思呢? -
inSampleSize
设置图片的采样率进行图片的缩放显示。
比如值为2,则加载图片的宽高是原来的 1/2,整个图片所占内存的大小就是原图的 1/4
三级缓存的原理就是当 App 需要引用缓存时,首先到内存缓存中读取,读取不到再到本地缓存中读取,还获取不到就到网络异步读取,读取成功之后再缓存到内存和本地中。
Dalvik和ART的区别
Dalvik环境中,应用每次运行时,字节码都需要通过即时编译器 ( Just In Time,JIT) 转换为机器码。ART 环境中,应用会在安装的时候,就将字节码 预编译(Ahead Of Time,AOT) 成机器码,使其成为真正的本地应用。
ART 占用的空间比 Dalvik 大,就是用 空间换时间。
ART 不用每次运行时都重复编译,减少了CPU 的使用频率,降低了能耗。
Glide
- 从with开始
1. 创建加载引擎Engin对象(包括缓存配置等)
2. 构建registry,注册了众多加载器与编解码器
3. 构建RequestManagerRetriever对象 - 构建RequestManager对象
1. 如果子线程中加载图片或with中的参数为Application类型,则Glide图片加载的生命周期与Application声明周期绑定
2. 否则,Glide图片加载的生命周期与Activity或Fragment的生命周期绑定 - load
创建一个目标为Drawable的图片加载请求,传入需要加载的数据模型(String File URI等) - into
into()方法是整个Glide图片加载流程中逻辑最复杂的地方。
开启Glide的图片加载显示工作:
查找内存缓存,如果不存在则在子线程中加载本地缓存或网络请求解析图片,并回到主线程中展示图片
DataBinding
DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常
你了解Lifecycle么?
- 如何集成Lifecycle
Android面试题及答案整理(2022年最新Android面试题大全带答案)