2020年Android中高级面试题汇总,攒了一年的面试题及解答

2020年Android初级面试题汇总

JVM的加载原理

1.编译机制

从下图可以看出java文件经过了一次编译后,java代码编译成java字节码(class文件)。然后再不同平台上使用不同的虚拟机(JVM)解释,解释成机器码,然后执行。由此可见,如果我们要在mac系统上运行,只需要装一个mac 的java虚拟机就可以了。这也就是java的一次编译,到处运行

2020年Android中高级面试题汇总,攒了一年的面试题及解答_第1张图片

2.类的加载机制

2020年Android中高级面试题汇总,攒了一年的面试题及解答_第2张图片

1.加载

加载简单来说分为三步。

第一步:获取二进制字节流也就是上面的class文件。

第二步:将静态的存储结构转换为方法区中的运行时数据结构。

第三步:生成一个对象放入java堆中,做为对方法区的引用。

2.验证

验证主要是检验如下的几项是否正确:

class文件的表示(魔数),class文件的版本号,class文件的每个部分是否正确(字段表、方法表等),验证常量池(常量类型、常量类型数据结构是否正确,utf-8是否标准),元数据验证(父类验证,继承验证,final验证),字节码(指令)验证,符号引用验证(是否能根据符号找到对应的字段、表、方法等)
如果一项不对,就会验证失败。

3.准备

准备阶段为类变量分配内存 和设置类变量初始化。这个过程中,只对static类变量进行内存分配,这个时候只是分配内存,没有进行复制,所有的类变量都是初始化值。如果是final的话,会直接对应到常量池中。会在准备阶段直接赋值。

4.解析

解析阶段是读符号引用进行解析。将符号引用解析为直接引用(指向目标的指针或者偏移量)。主要涉及到的解析有类,接口,字段,方法等。

5.初始化

初始化就是执行方法的过程, 对静态变量,静态代码块进行初始化,对类进行初始化。

6.使用

使用阶段就是使用这个class。

7.卸载

卸载阶段就是不在使用,将class给卸载。

3.类加载器

2020年Android中高级面试题汇总,攒了一年的面试题及解答_第3张图片

如图所示,每当我们类被加载的时候,都会去走类加载器。
我们自定义加载器有一个父类,就是AppClassLoader,而AppClassLoader也有一个父类加载器ExtClassLoader,ExtClassLoader同样也有一个父类加载器BootstarpClassLoader,BootstarpClassLoader就是最终的加载器了。

当加载类的时候,会启动类加载器,会通过自己定义的类加载器去找AppClassLoader,然后通过AppClassLoader找到ExtClassLoader,再通过ExtClassLoader找到最终BootstarpClassLoader。可能看到这里,有人人就有疑问了,为什么我有自己的加载器,干嘛还要去找父类的加载器呢?这这种机制叫双亲委任机制,目的就是为了加载累的安全。也就是说我父类加载的不给子类去加载,这样保证最终都是BootstarpClassLoader去加载,可以保证一个类只会加载一次。而你判断两个对象时候一样的时候,最重要的一个条件就是是不是一个加载器去加载的

全局变量和局部变量的区别

1.全局变量也称为外部变量,它是在函数外部定义的变量。 它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。 只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern。 但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明

2.局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内, 离开该函数后再使用这种变量是非法的

  1. 在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。

4.从作用域看

全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。

静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。

局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。

静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。

5.从分配内存空间看:

全局变量,静态局部变量,静态全局变量都在静态存储区分配空间,而局部变量在栈里分配空间。

全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。

1)、静态变量会被放在程序的静态数据存储区(全局可见)中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
2)、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。

从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。

Tips:

A.若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;

B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;

C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题,因为他们都放在静态数据存储区,全局可见;

D.如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的的函数)

E.函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。

静态方法锁和非静态方法锁区别

1.一个锁的是类对象,一个锁的是实例对象。
2.若类对象被lock,则类对象的所有同步方法(static synchronized func)全被lock。
3.若实例对象被lock,则该实例对象的所有同步方法(synchronized func)全被lock。
4.关于同一个类的方法上的锁,来自于调用该方法的对象,如果调用该方法的对象是相同的,那么锁必然相同,否则就不相同。比如 new A().x() 和 new A().x(),对象不同,锁不同,如果A的单利的,就能互斥。
5.静态方法加锁,能和所有其他静态方法加锁的 进行互斥

线程本地ThreadLocal的作用

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

ThreadLocal与synchronized同步机制的比较

在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

ThreadLocal是线程局部变量,是一种多线程间并发访问变量的解决方案。和synchronized等加锁的方式不同,ThreadLocal完全不提供锁,而使用以空间换时间的方式,为每个线程提供变量的独立副本,以保证线程的安全。

单例模式几种写法

1.饿汉式

public class SingletonInstance {
    //私有构造方法
    private static SingletonInstance (){

    }
    //声明成员变量
    private static SingletonInstance singletonInstance = new SingletonInstance();
    //对外提供接口获取该实例
    public static SingletonInstance getSingletonInstance(){
        return singletonInstance ;
    }

}

2.懒汉式

public class SingletonInstance {
    //私有构造方法
    private SingletonInstance (){

    }
    //声明成员变量
    private static SingletonInstance singletonInstance ;
    //对外提供接口获取该实例
    public static SingletonInstance getSingletonInstance(){
        if(singletonInstance == null){
            singletonInstance = new SingletonInstance();
        }
        return singletonInstance ;
    }

}

3.double check lock(dcl)

public class SingletonInstance {
    //私有构造方法
    private SingletonInstance (){

    }
    //声明成员变量
    private static SingletonInstance singletonInstance ;
    //对外提供接口获取该实例
    public static SingletonInstance getSingletonInstance(){
        if(singletonInstance == null){
            synchronized (SingletonInstance.class){
                //两次判断是否为null
                if(singletonInstance==null){
                    singletonInstance = new SingletonInstance();
                }
            }
        }
        return singletonInstance ;
    }

}

4.静态内部类

public class SingletonInstance {
    //私有构造方法
    private SingletonInstance (){

    }

    private static class Builder{
        //声明成员变量
        private static SingletonInstance singletonInstance = new SingletonInstance();
    }
    //对外提供接口获取该实例
    public static SingletonInstance getSingletonInstance(){
        return Builder.singletonInstance ;
    }

}

死锁

参考链接

死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁

1.死锁产生的原因

a. 竞争资源

系统中的资源可以分为两类:

可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;

另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。

产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁

b. 进程间推进顺序非法

若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁

2.产生死锁的必要条件

1.互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
2.请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
4.环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。

3.解决死锁的基本方法

1.资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
2.可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

下次更新。。。

1.事件分发,dispatch和intercept和ontouchevent是如何执行的, onTouchEvent是如何执行的
2.场景1:addView方法加载A布局然后remove(A),然后addView(B布局)方法执行 场景2:addView方法加载A然后addView(B), requestView方法在场景1和2分别是如何执行的, 各自执行效率如何
3.布局A经过scale进行属性缩放, 缩放之后y轴对应的点坐标有没有变化
4.数组和链表区别
5.rxjava中map和flapmap区别
6.viewGroup和view的onDraw方法区别
7.recyclerview的缓存机制,item不可见时是否回执行onBind方法
8.requestLayout,measureLayout,invalidata区别
9.图片压缩加载
10.项目中的设计模式理解
11.进程间通讯
12.单例模式双重校验机制
13.内存优化
14.ANR
15.webview处理
16.hashmap怎么实现的,扩展大小机制
17.图片压缩,狂傲压缩和质量压缩
18.https的ssl
19.有哪些进程通信,binder机制底层
20.activity里面的setContentView做了什么
21.acitvity,window,surface三者区别
22.listview和recyclerview区别
23.recyclerview四级缓存,自定义缓存如何做
24.GC回收机制,是如何世道那个对象被回收的
25.算法,输入两个链表,让其合一起输出一个链表

面试前的系统复习路线

有时候,选择比努力更加重要,机遇比奋斗更加重要。但是,机会只留给有准备的人。我们只有时刻准备着,才能在机会到来的时候,去抓住它。

这里给大家分享一下我的面试复习路线,有需要的朋友可以参考一下:

1、看视频进行系统学习

前几年的Crud经历,让我明白自己真的算是菜鸡中的战斗机,也正因为Crud,导致自己技术比较零散,也不够深入不够系统,所以重新进行学习是很有必要的。我差的是系统知识,差的结构框架和思路,所以通过视频来学习,效果更好,也更全面。关于视频学习,个人可以推荐去B站进行学习,B站上有很多学习视频,唯一的缺点就是免费的容易过时。

2、进行系统梳理知识,提升储备

客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

系统学习方向:

  • 架构师筑基必备技能:深入Java泛型+注解深入浅出+并发编程+数据传输与序列化+Java虚拟机原理+反射与类加载+动态代理+高效IO

  • Android高级UI与FrameWork源码:高级UI晋升+Framework内核解析+Android组件内核+数据持久化

  • 360°全方面性能调优:设计思想与代码质量优化+程序性能优化+开发效率优化

  • 解读开源框架设计思想:热修复设计+插件化框架解读+组件化框架设计+图片加载框架+网络访问框架设计+RXJava响应式编程框架设计+IOC架构设计+Android架构组件Jetpack

  • NDK模块开发:NDK基础知识体系+底层图片处理+音视频开发

  • 微信小程序:小程序介绍+UI开发+API操作+微信对接

  • Hybrid 开发与Flutter:Html5项目实战+Flutter进阶

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

3、读源码,看实战笔记,学习大神思路

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

4、面试前夕,刷题冲刺

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:

以上内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。或者关注主页扫描加 微信 获取。

你可能感兴趣的:(2020年Android中高级面试题汇总,攒了一年的面试题及解答)