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是不可变的?

  1. 代码验证
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());

    }
}
  1. 源码验证

什么是内存泄漏 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
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

ListView和RecyclerView有什么区别

ListView:只能在垂直方向滑动。
RecyclerView:支持水平方向滑动,垂直方向滑动,多行多列瀑布流的方式等。

ListView:有几个默认的Adapter,分别是ArrayAdapter、CursorAdapter和SimpleCursorAdapter。
RecyclerView:Adapter需要自己实现。

ListView:拥有子Item的监听函数:AdapterView.OnItemClickListener。
RecyclerView:需要自己实现接口,来实现子Item的点击事件,虽然比较麻烦,但是扩展性好。

ListView:并不强制使用ViewHolder,如果要使用,则需要自己定义,如果不使用,ListView每次 getView()的时候都需要去findViewById,会造成性能下降,滑动卡顿等,所以推荐使用 ViewHolder。
RecyclerView:必须使用ViewHolder。

ListView:两级缓存。
RecyclerView:四级缓存。

ListView 两级缓存
1. mActiveViews 用于屏幕内ItemView快速重用
2. mScrapViews 用于缓存离开屏幕的ItemView

RecyclerView 四级缓存
1. mChangeScrap与 mAttachedScrap 用于屏幕内ItemView快速重用
2. mCachedViews 默认上限为2,即缓存屏幕外2个ItemView
3. mViewCacheExtension 用户自定义,一般不使用
4. RecycledViewPool 默认上限为5
缓存对象不同,RecyclerView缓存的是ViewHolder,ListView缓存的是View。

如何理解Context上下文

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

onTrimMemory()方法是什么?

onTrimMemory能让activity得到内存情况的通知

@Override  
public void onTrimMemory(int level) {  
    super.onTrimMemory(level);  
    switch (level) {  
    case TRIM_MEMORY_UI_HIDDEN:  
        // 进行资源释放操作  
        break;  
    }  
}  

运行时的回调
TRIM_MEMORY_RUNNING_MODERATE
表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经有点低了
TRIM_MEMORY_RUNNING_LOW 表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经非常低了,我们应该去释放掉一些不必要的资源以提升系统的性能。
TRIM_MEMORY_RUNNING_CRITICAL 表示应用程序仍然正常运行,但是系统已经根据LRU缓存规则杀掉了大部分缓存的进程了。

缓存的回调:
TRIM_MEMORY_UI_HIDDEN
UI组件全部不可见的时候才会触发,一旦触发了之后就说明用户已经离开了我们的程序
TRIM_MEMORY_BACKGROUND 表示手机目前内存已经很低了,系统准备开始根据LRU缓存来清理进程。这个时候我们的程序在LRU缓存列表的最近位置,是不太可能被清理掉的。
TRIM_MEMORY_MODERATE 表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的中间位置。
TRIM_MEMORY_COMPLETE 表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的最边缘位置。

什么是surfaceView?

View的一个子类,是一个专门用于做频繁绘制的View子类,它的绘制操作是在子线程中执行,所以频繁绘制不会阻塞线程,使用它去做一些需要频繁绘制和长时间绘制效果会高很多

  • 子线程绘制
    surfaceView是在一个新起的单独线程中可以重新绘制画面,而View必须在UI的主线程中更新画面。那么在UI的主线程中更新画面,可能会引发问题,比如你更新画面的时间过长,那么你的UI主线程会被你正在画的函数阻塞
    但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要surfaceView中Thread来处理,一般就需要一个Event queue来保存touch Event,这会稍稍复杂一点,因为涉及到线程同步
  • 双缓冲模式
    通俗来讲就是有两个缓冲区,一个后台缓冲区和一个前台缓冲区,每次后台缓冲区接受数据,当填充完整后交换给前台缓冲,这样就保证了前台缓冲里的数据都是完整的
    在后台线程执行繁重的绘图任务,把所有绘制的东西缓存起来;绘制完毕后,再回到UI线程,一次性把所绘制 东西渲染到屏幕上(本质:就是后台线程绘制,UI主线程渲染)

Android中你可以使用什么来进行后台操作?

  • 现有的后台操作机制
    Service(前台服务,后台服务)
    线程池
    WorkManager

LayoutInflater.inflate函数的作用是什么?

在这里插入图片描述
在这里插入图片描述

插件化 热修复 与组件化的区别

  • 插件化
    插件化技术最初起源于免安装运行APK的想法,这个免安装的APK可以理解为插件。支持插件化的APP可以在运行时加载和运行插件,这样便可以将APP中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现APP功能的动态扩展。
  • 热修复
    代码修复(方法级别、类级别)资源修复 so库修复
    让应用能够在无需重新安装的情况实现更新,帮助应用快速建立动态修复能力。
    热补丁节省Android大量应用市场发布d 时间。同时用户也无需重新安装,只要上线就能无感知的更新。热补丁技术当前仍然存在它的局限性,主要表现在:
  1. 补丁只能针对单一客户端版本,随着版本差异变大补丁体积也会增大
  2. 补丁不能支持所有的修改,例如AndroidManifest
  3. 补丁无论对代码还是资源的更新成功率都无法达到100%
  • 组件化
    组件化就是将一个项目拆分成若干个组件,分而治之。比如一个汽车的生产,也是将轮子、灯、座椅等等作为单独的组件,由各自的工厂去生产维护,生产轮子的就专门做轮子,生产座椅的就专门生产座椅,等各个组件都做好后再拿到组装厂统一调度组装使用。
    组件化的目的主要 为 了业务解耦,每个业务模块需要不同的功能。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值