200个常见Android面试知识,收藏一波

Posted 初一十五啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了200个常见Android面试知识,收藏一波相关的知识,希望对你有一定的参考价值。

写在前面的

大家好 ,专注于android知识的分享。今天将给大家带来的是200个最常见的 Android面试问答。接下来我会持续更新(争取日更,也可能每周3-5篇),每小节大概 10 道题左右,总共会有 200 多道。

大家一定要记得点赞收藏呀,关注我关注我的GitHub并且三连(点赞+收藏+评论)本篇文章,私信我可免费领取份完整版的Android体系知识。

整体内容:

  • APT实现原理
  • 为什么需要泛型?
  • Retrofit原理解析
  • ButterKnife原理
  • Android反射原理与使用
  • 字节码手术刀JavaSSit
  • Android虚拟机
  • 性能优化(启动优化-内存优化-启动速度优化-卡顿优化-布局优化-崩溃优化)
  • FrameworkBinderHandlerPMSAMSWMS
  • Compose14部曲
  • 音视频初中高51部曲(H264.H265.FFmpeg.WebRtc.OpenGl等)
  • 数据结构和算法
  • 二十三种设计模式详解
  • OKhttp.Retrofit.Glide.Dagger2.MVP.MVC.MVVM``Jetpack全家桶
  • 车载基础介绍
  • Flutter(Dart.基本组件.列表.动画.Flutter3.0等)
  • Kotlin

1.APT实现原理

APT是什么?
APT可以在android编译期动态生成预设好格式的java文件

APT相比注解的优势
APT产生的原因是注解的低效率,APT主要解决的是注解+反射执行慢的问题,APT通过预设代码动态生成java代码文件,直接调用java对象,从而替换掉遍历查找注解的耗时操作,在Android这种执行效率不高的移动端,性能提升十分明显

APT可以做到什么?
减少模板代码:可以利用APT在编译期动态生成java代码,省去很多模板代码,节省很多编码量
解耦合,让代码更灵活:可以利用辅助类+反射的形式,做到类与类之间的解耦合,从而减少类之间的直接依赖

哪些框架在使用APT
ButterknifeDagger2DataBindingEventBus3ARouter

使用APT的步骤

  • 创建自定义注解

  • 创建注解处理器 继承自AbstractProcessor ,生成处理注解逻辑的.java文件,封装一个供外部调用的API接口,也就是调用第二步中生成的注解处理逻辑文件中的方法,实现逻辑处理

  • 然后在需要使用的Module中依赖注解,使用自定义注解处理器,然后调用API接口即可

2.为什么需要泛型

  • 拥有不同参数类型却有相同的执行流程的方法,需要使用泛型;

  • 指定数据类型,可以在编译期间发现类型错误,也不需要进行强制类型转换;

泛型类和泛型方法、泛型接口的定义
泛型类

 public class A<T>
    private T data; public A(T d)
   
 public T getData()
   return data;
  ……

泛型接口

 public interface impl<T>public T method();//定义1
    
      class impls<T> implements impl<T>
 
     //调用1,也是泛型
    
      class impls implements impl<String>public String method();
 
    //调用2,指定了具体类型

泛型方法: (完全独立,不一定要声明在泛型类和泛型接口中)

public <T> T method(T,……)  <T>泛型方法的标志
    
      class.<String>method();//调用1
    
      class.method();//调用2

泛型方法辨析
正确判断泛型方法: 开头

泛型中的约束和局限性

  • 不能实例化类型变量:new T()// 不行

  • 静态域和静态方法里不能引用类型变量 private statc T instance;// 不行,因为在对象创建的时候,才知道T的具体类型

  • 静态方法本身可以是泛型方法

  • 泛型只能是类,不能用基础类型,可以用包装类

  • 泛型不支持instanceof

  • 泛型的原生类型,类型不会因为T的改变而改变:Test<T> 的对象 t(String)t(Float) 的类型是一样的

  • 泛型可以声明数组,却不能new:

Test<T>   
  
Test<Float>[] arrays;//可以
  
Test<Float>[] arrays = new Test<Float>[10];//不可以
  • 泛型类不能够extends ExceptionThrowable,try……catch不能够捕获泛型类对象,但可以捕获Throwable
public  <T extends Throwable> void doWork(T x)
  

  
      trycatch(T x)//不行
  
      try catch(Throwable e) throw Tthrow t;//可以
  

泛型类型的继承规则

  • class A extends B;C C和C没有任何关系,类型之间有继承关系不代表泛型之间有继承关系

  • 泛型类可继承自泛型类 class A extends B A a = new B//可以

3.Retrofit2.0原理解析

目前的网络框架基本上都是使用Retrofit+okhttp一起进行使用,那么我们来看看retrofit究竟做了些什么。
结合之前的OkHttp源码解析,在这个的基础上加上了Retrofit,下面是正常使用时候的代码。

初始Retrofit

private void initRetrofit() 
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(new TokenHeaderInterceptor()) // 动态添加token
                .addInterceptor(new NullResponseInterceptor()) // 返回空字符的时候显示
                .connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                .build();
        // JSON 问题的解析处理 Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1
        Gson gson = new GsonBuilder().setLenient().create();
        retrofit = new Retrofit.Builder() // 使用建造者模式
                .baseUrl(Constant.HTTP_URL) // 请求host
                .client(client) // okhttp实例对象
                .addConverterFactory(WGsonConverterFactory.create(gson)) // 添加 转换器工厂
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 请求指定适配器 RxJava
                .build();
    

上面的代码是对Retrofit进行一个初始化的操作。

扩展知识:调用

4.ButterKnife原理

ButterKnife自定义了很多我们常用的注解,比如@BindView@OnClick。现在先来看@BindView的源码,如下所示:

@Retention(RUNTIME) @Target(FIELD)
public @interface BindView 
  /** View ID to which the field will be bound. */
  @IdRes int value();

Retention(CLASS)表明@BindView 注解是编译时注解,@Target(FIELD)则表明@BindView注解应用于成员变量。

接下来使用@BindView注解来绑定TextView 控件,后文会用到这些代码,如下所示:

public class MainActivity extends AppCompatActivity 
    @BindView(R.id.tv_text)
    TextView tvText;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    

扩展知识:5.ButterKnifeProcessor源码分析,ButterKnifebind方法

6.Android反射原理和使用

反射 :在运行状态下,通过class文件对象(Class的对象),去使用构造方法,成员变量,成员方法。

获取class文件对象 : 有三种方法如下:

  • 1)Object类的getClass()方法。
Class c = p.getClass();
  • 2)数据类型的静态的class属性
Class c3 = Person.class;
  • 3)通过Class类的静态方法forName(String className)
Class c4 = Class.forName("cn.itcast_01.Person");

反射获取构造函数并创建对象
思路:反射获取构造器,首先要获取待创建对象的类的class文件,通过class文件获//取构造器,得到构造方法,通过构造方法创建对象。

// 获取字节码文件对象
        Class c = Class.forName("cn.itcast_01.Person");
 
        // 获取构造器对象,此处获取的为带两个参数的构造方法
        Constructor con = c.getConstructor(String.class, int.class); 
 
        // 创建对象,创建对象方法为 newInstance()
        Object obj = con.newInstance("岳飞", 39); 
        System.out.println(obj);

反射获取Field对象属性

  • 1)获取所有公共成员变量
Field[] fields = c.getFields();
  • 2)获取所有成员变量
Field[] fields = c.getDeclaredFields();
  • 3)获取指定成员变量
Field field = c.getField("age");

获取指定成员变量并赋值

        // 获取字节码文件对象
        Class c = Class.forName("cn.itcast_01.Person");
        
        //获取构造器对象,创建person类对象
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();
 
        // 获取单个age成员变量
        Field field = c.getField("age");
        
        // 给obj对象的field字段赋值
        field.set(obj, 20);
                
        System.out.println(obj);

获取私有变量并赋值(在这个例子中age是私有变量)

        // 获取字节码文件对象
        Class c = Class.forName("cn.itcast_01.Person");
 
        // 创建对象
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();
 
        // 获取年龄并赋值
        Field ageField = c.getField("age");
        ageField.set(obj, 26);
 
        // 获取姓名字段        
        Field nameField = c.getDeclaredField("name"); 
 
        //暴力访问
        nameField.setAccessible(true);//因为是私有字段,所以必须解除访问限制,这一点很重要
        nameField.set(obj, "xx"); //通field对象为字段赋值

反射获取Method对象方法

  • 1)获取所有公共方法,包括父类的方法
Method[] methods = c.getMethods();
  • 2)获取本类的所有方法
Method[] methods = c.getDeclaredMethods();
  • 3)获取指定的成员方法
        // 获取字节码文件对象
        Class c = Class.forName("cn.itcast_01.Person");
 
        // 创建对象
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();
        
        //第一种:无参数无返回值
        Method m1 = c.getMethod("show", null);  //show是方法名称,后边是方法参数,null表示无参方法
        m1.invoke(obj, null);
 
        //第二种:带string类型参数无返回值
        Method m2 = c.getMethod("function", String.class); 
        m2.invoke(obj, "岳飞"); //invoke表示对方法进行调用
 
        //第三种:带多个参数有返回值 
        Method m3 = c.getMethod("reutrnValue", String.class,int.class);
        Object ooo = m3.invoke(obj, "张飞",26);
        System.out.println(ooo);
 
        //第四种:私有方法的调用
        Method m4 = c.getDeclaredMethod("hello", null);
        m4.setAccessible(true); //设置访问权限,这一点很重要
        m4.invoke(obj, null);

参数动态封装,赋值

//使用反射添加网络请求传参
FormBody.Builder builder = new FormBody.Builder();
//      如果是BaseModel 基内就不用添加
        if (!model.getClass().getSimpleName().equals(BaseModel.class.getSimpleName())) 
            //通过反射来获取字段名
            Field[] list = model.getClass().getDeclaredFields();
            for (Field field : list) 
                try 
                    Object object = field.get(model);
                    if (object != null && object instanceof String) //这里为空的字段不用传到服务器
//                        Log.i("tag","field.getName():"+field.getName() +";field.get(model):"+field.get(model));
                        builder.add(field.getName(), (String) field.get(model));
                    
                 catch (IllegalArgumentException e) 
                    e.printStackTrace();
                 catch (IllegalAccessException e) 
                    e.printStackTrace();
                
            
        
public class Utils 
    private static final String TAG = "Utils";

    public Object setObjectValue(Object object, String json) throws Exception 
        if (object == null)
            return null;
        Log.d(TAG, "setObjectValue");
        Class<?> clazz = object.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) 
            Log.d(TAG, "field type:" + field.getName());// 打印字段的类型
            if (field.getGenericType().toString().equals("class java.lang.String")) 
                Method method = object.getClass().getMethod("set" + getMethodName(field.getName()), String.class);
                method.invoke(object, parseJson(json, field.getName()));

            

            if (field.getGenericType().toString().equals("class java.lang.Integer"))//
            
                Method method = object.getClass().getMethod("set" + getMethodName(field.getName()), Integer.class);
                method.invoke(object, parseJson(json, field.getName()));
            
            
        
        return object;
    

    /**
     * 解析json
     * */
    private String parseJson(String json_string, String name) 
        String str = "no info";
        JSONObject json;
        try 
            json = new JSONObject(json_string);
            json = json.getJSONObject("studentinfo");
            str = json.getString(name);
         catch (JSONException e) 
            e.printStackTrace();
        
        return str;
    

    /**
     * 将属性名称的首字母变成大写
     * */
    public String getMethodName(String fieldName) 
        byte[] bytes = fieldName.getBytes();
        bytes[0] = (byte) (bytes[0] - 'a' + 'A');
        return new String(bytes);
    


关于kotlin 版本的反射,跟Java区别不大.

7.字节码手术刀JavaSSit

JavaSSit使用总结

自动装箱和拆箱
使用javassit插入代码时,需要编写装箱和拆箱的操作,比如int->Integer

需要这么写:Integer i = new Integer(3);

热部署实现的关键
同一个类字节文件,classLoader类加载器只能加载一次,多次会报错。这样的话 ,我们就不能通过类加载器重新加载改动后的类,只能通过javaAgent的方式去动态加载

使用javaAgent,再结合javassit来修改类字节,从而实现类的动态加载

8.为什么要学虚拟机

面试时 虚拟机是一个高频考点,非常容易考到,如果面试的好 是一个加分项

对虚拟机运行流程有一个深刻认知。这种认知为加深对APP运行机制的理解

对反编译,热修复,插件化有帮助,我们把dex理解了,能帮助我们理解其中原理

学习这些知识不能帮助到业务逻辑,但是他们加深你的视野。对原理的掌握翻译你的工作经验.

虚拟机连环炮系列 Jvm,DalvikArt三者之间的区别
分成两点来答(JVM虚拟机与Android虚拟机区别 , Art虚拟机与Dalvik虚拟机区别)

JVM虚拟机与Android虚拟机区别

Android虚拟机执行的是.dex格式文件 jvm执行的是.class文件
class文件存在很多的冗余信息,dex工具会去除冗余信息
Android虚拟机是基于寄存器的虚拟机 而jvm执行是基于虚拟栈的虚拟机

Art虚拟机与Dalvik虚拟机区别

Dalvik下,应用每次运行都需要通过即时编译器(JIT)将字节码转换为机器码,即每次都要编译加运行,这虽然会使安装过程比较快,但是会拖慢应用以后每次启动的效率。

而在ART 环境中,应用在第一次安装的时候,字节码就会预编译(AOT)成机器码,这样的话,虽然设备和应用的首次启动(安装慢了)会变慢,但是以后每次启动执行的时候,都可以直接运行,因此运行效率会提高。

典型的 空间换时间 128G —>apk

ART占用空间比Dalvik大(字节码变为机器码之后,可能会增加10%-20%),这也是著名的“空间换时间大法"。

Art预编译也可以明显改善电池续航,因为应用程序每次运行时不用重复编译了,从而减少了 CPU 的使用频率,降低了能耗。

虚拟机连环炮系列 那dexclass到底在结构上有什么区别呢

面试官想考你对 dex文件编码结构了不了解。从侧面反映你是否做过热修复,dex加固方面反编译 的技术,这种技术一定对他们目前是非常重要的

dex文件减少整体的文件尺寸 dex更像是一种压缩文件,一次可以表示更多的classclass像是一种单个文件

Android虚拟机加载类时 只对dex需要一次IO可以加载很多新类,而class需要加载多次IOAndroid虚拟机提高查找速度

dex指令更加密集 ()。class指令比较多

dex 寄存器设计方便寻址,class java栈需要更多次loadstore指令

dex适合于移动设备,性能不太高的要求。class适合PC大内存,单指令小的情况下可以快速执行

扩展知识:

9.虚拟机连环炮系列 Arm指令究竟是什么指令,能说说他与字节码指令的区别吗
10.虚拟机连环炮系列 为什么Art虚拟机比Dalvik虚拟机运行速度高

####11.Android内存泄漏常见场景以及解决方案
资源性对象未关闭
对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如Bitmap 等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。

注册对象未注销
例如BraodcastReceiverEventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。

类的静态变量持有大数据对象
尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。

单例造成的内存泄漏
优先使用ApplicationContext,如需使用ActivityContext,可以在传入Context时使用弱引用进行封 装,然后,在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。

非静态内部类的静态实例
该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的内存资源 不能正常回收。此时,我们可以将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如 果需要使用Context,尽量使用Application Context,如果需要使用Activity Context,就记得用完后置 空让GC可以回收,否则还是会内存泄漏。

Handler临时性内存泄漏
Message发出之后存储在MessageQueue中,在Message中存在一个target,它是Handler的一个引用,MessageQueue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的, 则会导致Activity或者Service不会被回收。并且消息队列是在一个Looper线程中不断地轮询处理消息, 当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message 持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回 收,引发内存泄漏。解决方案如下所示:

  • 使用一个静态Handler内部类,然后对Handler持有的对象(一般是Activity)使用弱引用,这 样在回收时,也可以回收Handler持有的对象。
  • ActivityDestroy或者Stop时,应该移除消息队列中的消息,避免Looper线程的消息队列中 有待处理的消息需要处理

容器中的对象没清理造成的内存泄漏
在退出程序之前,将集合里的东西clear,然后置为null,再退出程序

WebView
WebView都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为 WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业 务的需要选择合适的时机进行销毁,达到正常释放内存的目的。

使用ListView时造成的内存泄漏
在构造Adapter时,使用缓存的convertView

Bitmap
80%的内存泄露都是Bitmap造成的,BitmapRecycle()方法,不用时要及时回收。但是如果遇到要用Bitmap直接用Glide就完事了,自己写10有8.9得出错。

扩展知识:

12.APP启动速度优化

13.卡顿优化

视频

1.面试必问&卡顿前奏 透过渲染看看卡顿,一秒明白卡顿原理(渲染篇)

2.腾讯面试必问卡顿优化之卡顿原理全解析与如何快速定位到卡顿问题
分析工具
SystraceAndroid提供的一款工具,需要运行在Python环境下。
Systrace主要是分析掉帧的情况的。帧:android手机一般都是60帧,所以1帧的时间=1000毫秒/60=16.6毫秒。也就是android手机16.6毫秒刷新一次,超过这个数就是掉帧了

会有三色球,绿=正常,黄=一点不正常,红=掉帧严重。
少几个没啥事,大面积的出现红、黄,就需要研究为啥掉帧了。

可以看上面有一条CPU的横轴,绿色=正在执行,蓝色=等待,可以运行。紫色=休眠。白色=休眠阻塞。如果是出现了紫色就说明IO等耗时操作导致掉帧。如果紫+蓝比较多,说明cpu拿不到时间片,cpu很忙。

CPU Profile

这个就能看那个方法运行多少时间等,所以可以直接用android studio自带的分析。

一般是在top down里一直点,耗时较多的,然后点到自己熟悉的地方,挨个分析。
这是一个漫长的耗时的过程,可能找半天只找到几个地方能优化,然后每个几毫秒,加起来也没有直观的变快,但是性能优化就是这样的一个过程,积少成多。
如:我经过查找就发现adapter的 notifyDataSetChanged因为不小心,有些地方多次调用了。
甚至还有没有在线程进行io操作。

14.布局优化

减少层级
自定义Viewmeasurelayoutdraw这三个过程,都需要对整个视图树自顶向下的遍历,而且很多情况都会多次触发整个遍历过程(Linearlayoutweight等),所以如果层级太深,就会让整个绘制过程变慢,从而造成启动速度慢、卡顿等问题。

onDraw在频繁刷新时可能多次触发,因此 onDraw更不能做耗时操作,同时需要注意内存抖动。对于布局性能的检测,依然可以使用systracetraceview按 照绘制流程检查绘制耗时函数。

工具Layout Inspector
DecorView开始。content往下才是自己写的布局。

重复的布局使用include。一个布局+到另一个上,如果加上以后,有两一样的ViewGroup,可以把被加的顶层控件的ViewGroup换成merge
ViewStub:失败提示框等。需要展示的时候才创建,放在那不占位置。

过度渲染
一块区域内(一个像素),如果被绘制了好几次,就是过度渲染。
过度绘制不可避免,但是应该尽量的减少。
手机->开发者模式->GPU 过度绘制 打开以后能看到不同颜色的块,越红就说明过度绘制的越严重。对于严重的地方得减少过度绘制。

1.移除布局中不需要的背景。
2.降低透明度。
3.使视图层次结构扁平化。

布局加载优化
异步加载布局,视情况而用。

new AsyncLayoutInflater(this)
.inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() 
@Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) 
setContentView(view);
//......

);

扩展知识:15.崩溃优化

今天先更新这一些,回头继续。。。。

以上是关于200个常见Android面试知识,收藏一波的主要内容,如果未能解决你的问题,请参考以下文章

被面试官的一波Glide连环炮整麻了

必收藏2023最新Android大厂面试题免费分享,帮你扫除知识点盲区

必收藏2023最新Android大厂面试题免费分享,帮你扫除知识点盲区

某知名公司面试:MYSQL连环问(经典面试题,建议收藏)

建议收藏复盘:2021年最新最全最实用的Android岗学习资料/面试真题

第35期 TCP协议的12连环炮,我只能扛住3个