Android面试知识点总结(一)—— 基础篇

436 阅读31分钟

前序

最近正好工作需要调整一下,需要对知识体系系统的进行一些复习,包括常见的基础和Framework、通信、热门框架原理等等,结果就是在复习的过程中习惯性的记了些笔记,最后就越记越多,想着就是分享出来,一方面是后续使用复习不用从头开始,有个参考,第二个就是做个备份和给有需要的人。以下的知识点,一部分是参考各个大佬的博客做了一些额外的精简整理,一部分是自己进行的一些总结,问题肯定会存在一些,比如不全,或者有说的不是很清楚甚至于错误的地方,希望大家指出来,然后最近也还会持续的对一些知识点进行相应的整理与更新,做成一个系列,这也我第一次发布到平台,望大家都给点意见,谢谢

目录

Android面试知识点总结(一)—— 基础篇

Android面试知识点总结(二)—— 进阶篇

Android面试知识点总结(三)—— 通信/服务/网络篇

Android面试知识点总结(四)—— 框架原理/Android常用组件原理篇

equals与==区别和hashCode的区别

  • ==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所指向的内存空间的值是不是相同
  • ==是指对内存地址进行比较 , equals()是对字符串的内容进行比较
  • ==指引用是否相同, equals()指的是值是否相同
  • hashCode()方法是从 Object 类继承过来的,也是用来比较两个对象是否相等,Object 类中的 hashCode 方法,返回对象在内存中地址转换成的一个 Int 值,所以如果未重写 hashCode 方法,任何对象的 hashCode 方法返回的值都是不相等的

String、StringBuffer、StringBuilder

String是final类,不能被继承,修改即创建新的对象;StringBuffer线程安全,StringBuilder非线程安全,但速度更快

final、finally、finalize 的区别

  • final 是用于修饰类、成员变量和成员方法,类不可被继承,成员变量不可变,成员方法不可被重写;
  • finally 与 try...catch共同使用,确保无论是否出现异常都能被调用到;
  • finalize 是类的方法,垃圾回收前会调用此方法,子类可以重写 finalize 方法实现对资源的回收。

sleep 、wait、yield 的区别,wait 的线程如何唤醒它?

参考:Android程序员需要了解的并发编程知识

参考:sleep()方法和await()方法有什么区别

  • wait 是 Object 的方法,wait 是对象锁,锁定方法不让继续执行,当执行 notify 方法后就会继续执行

  • sleep 是 Thread 的方法,sleep 是使线程睡眠,让出cpu,结束后自动继续执行

  • yield()方法:使当前线程让出CPU占有权,但让出的时间是不可设定的。也不会释放锁资源。所有执行yield()的线程有可能在进入到就绪状态后会被操作系统再次选中马上又被执行。

    1. yield() 、sleep()被调用后,都不会释放当前线程所持有的锁。
    2. 调用wait()方法后,会释放当前线程持有的锁,而且当前被唤醒后,会重新去竞争锁,锁竞争到后才会执行wait方法后面的代码。
  • wait通常被用于线程间交互,sleep通常被用于暂停执行,yield()方法使当前线程让出CPU占有权。

具体来说sleep()方法和await()方法的区别主要表现在以下几个方面?

  • 原理不同

    • sleep()方法是Thread类的静态方法,是线程用来控制自身流程的,它会使线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,此线程会自动被唤醒。

      例如:当线程执行报时功能时,每一秒钟打印出一个时间,那么此时就需要在打印方法前面加上一个sleep()方法,以便让自己每隔1s执行一次。

    • wait()方法是Object类的方法,用于线程间通信,这个方法会使当前拥有该对象锁的线程等待,直到其他线程调用notify()方法或者notifyAll()方法才会被唤醒,不过也可以给线程指定时间被唤醒。

  • 对锁的处理机制不同

    • 由于sleep()方法的主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间通信,因此调用sleep()方法并不会释放锁。

    • wait方法则不同,当调用wait()方法后,线程会释放掉它锁占用的锁,从而使线程所在对象中的其他synchronized数据可被别的线程使用

  • 使用区域不同 由于wait()方法的特殊意义,因此它必须放在同步控制方法或者同步语句块中使用,而sleep()方法则可以放在任何地方使用

Kotlin Any和 Java Object的区别?

参考:Object类和Any详解

  • Any是Kotlin类结构的根,每个kotlin都继承或间接继承于Any类

    里面有三个open的方法equals、hashCode和toString,其中equals和hashCode如果需要修改就必须同时修改

  • java中Object也是class结构的根,每个类继承或者间接继承于Object

    相比于Kotlin,java中的class方法丰富的多,十二个。其中7个本地方法包含一个静态本地方法,5个可以被子类覆盖的方法

Kotlin中的Any只存在于编译期,运行期就不存在了,在运行期,Any变成了Object,在kotlin中也可以将Any强转为Object

val obj = any as Object
synchronized(obj){
	obj.wait()
}

Kotlin专门处理一些Java类型。这些类型不是按原样从Java加载的,而是映射到相应的Kotlin类型。映射只在编译时起作用,运行时表示保持不变。Java的原语类型映射到相应的Kotlin类型(保持平台类型)

泛型中<? super T> 和<? extends T>的区别

参考:十分钟拉下Java/kotlin泛型

  • <? extends T> 是上界通配符,匹配的是T及其子类,能读不能写,能用父类型去获取数据,不确定具体类型,不能传,对象只作为返回值传出

    协变:父子关系一致 -> 子类可以作为参数传进来 -> <? extends T> -> 上界通配符

    ps:Kotlin中使用out关键字表示协变 -> <out T>

  • <? super T> 是下界通配符,匹配的是T及其父类,能写不能读,能传入子类型,不确定具体类型,不能读,对象只作为参数传入(但可以用Object读)

    逆变:父子关系颠倒 -> 父类可以作为参数传进来 -> <? super T> -> 下界通配符

    ps:Kotlin中使用in关键字表示逆变 -> <in T>

  • 型变

    型变(协变、逆变)虽然拓展了参数的类型范围,但也导致不能按照泛型类型读取元素,Java只有使用处型变,而Kotlin在使用处和声明处都有

    向上转型 -> 子类转换成父类 (隐式),安全,可以访问父类成员;

    向下转型 -> 父类转换成子类 (显式),存在安全隐患,子类可能有一些父类没有的方法;

    另外,在某些特殊场景,泛型参数同时作为参数和返回值,可以使用 @UnsafeVariance 注解来解决型变冲突,如 Kotlin\Collections.kt 中的:indexOf()、get()方法

  • PECS(Producer Extends Consumer Super)原则

    • 频繁往外读取内容的,适合用上界extends
    • 经常往里插入的,适合用下界super

    因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是T的基类,那往里存粒度比T小的都可以。但往外读取元素,只有所有类的基类Object对象才能装下,且元素的类型信息就全部丢失

    • 若参数为T<? extends T> 意味着子类是不确定的,因此只能读取数据,而不能插入数据
    • 若参数为T<? super T> 意味着父类是不确定的,因此只能插入数据,而不能读取数据
  • 泛型边界限制

    使用的类型要求是继承于Y或者是它自身,又称泛型上界

    限制上界的好处:可以直接调用父类或父类接口方法

    ps:Kotlin中用冒号代替extends -> <T : Y>

  • 无限定通配符<?>

    无限定通配符<?>等价于**<? extends Object>** , Kotlin中使用星投影<*>,等价于<out Any>

View的事件分发机制

参考:彻底理解 View 的事件分发机制

参考:基于源码分析 Android View 事件分发机制

View 的事件分发是由外向内的,总是从父 View 传递给子 View

三个方法

  • dispatchTouchEvent 分发:用来进行事件的分发。如果事件能够传递给当前 View,那么此方法一定会被调用,返回结果受当前 ViewonTouchEvent 和 下级 ViewdispatchTouchEvent 方法的影响,表示是否消耗当前事件。
  • onInterceptTouchEvent 拦截:在 dispatchTouchEvent 内部调用,用来判断是否拦截某个事件,如果当前 View 拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
  • onTouchEvent 消费:在 dispatchTouchEvent 内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前 View 无法再次接受到事件。

三个事件

  • 注意:
    1. ViewViewGroupdispatchTouchEventonTouchEvent 方法默认返回值都是 false,即不拦截任何事件
    2. ActivityViewGroupView 的 dispatchTouchEvent 中拦截事件,所有的事件只能传递到拦截的地方,而处于其内部的控件将无法收到任何事件
  1. 对于 ACTION_DOWN 事件,会从最外层的 dispatchTouchEvent 传递到最内层的 dispatchTouchEvent,然后由最内层的 onTouchEvent 传递到最外层的 onTouchEvent
  2. 对于 ACTION_MOVEACTION_UP 来说,只会传递到 Activity 的 dispatchTouchEvent 和 目标的onTouchEvent 中。

总结

  1. 事件传递遵循由外向内(外层的 dispatchTouchEvent 到内层的 dispatchTouchEvent),然后由内向外(内层的 onTouchEvent 到外层的 onTouchEvent)的顺序如果事件在由外向内传递过程中被拦截(消费)了,那么由内向外的过程就不会发生
  2. 在 onTouchEvent 中拦截(消费)事件,不影响事件由外向内传递到拦截处的过程。只影响事件由内向外传递的过程,对于 ACTION_MOVE,其内外层的 onTouchEvent 均不会被调用。ACTION_MOVE 和 ACTION_UP 事件在使用任何拦截策略下,他们的传递流程都是一样的
  3. 单纯在 onInterceptTouchEvent 中拦截不消费,ACTION_DOWN 会在拦截处停止传递,ACTION_MOVE会回传到Activity。如果同时在 onInterceptTouchEvent 和 onTouchEvent 中拦截(消费)事件,后续的 ACTION_MOVE 可以传递到拦截处不会走到Activity
  4. 事件序列以 down 事件开始,中间含有数量不定的 move 事件,最终以 up 事件结束
  5. View 设置的 OnTouchListener,其优先级比 onTouchEvent 要高,如果 OnTouchListeneronTouch 方法的回调返回 true 那么 onTouchEvent 方法将不会被调用。如果返回 false,则当前 ViewonTouchEvent 方法被回调。

对于一个根 ViewGroup 来说,点击事件产生后,首先会传递给它的 dispatchTouchEvent 方法,如果这个 ViewGrouponInterceptTouchEvent 返回为 true, 就表示它要拦截当前事件,接着事件就会交给该 ViewGrouponTouchEvent 方法去处理。如果 onInterceptTouchEvent 返回为 false,就表示它不拦截当前事件,这是当前事件就会传递给它的子元素,接着由子元素的 dispatchTouchEvent 来处理点击事件,如此反复直到事件被最终处理

设计模式

参考:从android中学习23种设计模式

设计模式的七大原则

开闭原则(Open Closed Principle)

  • 对类的扩展是开放,对修改关闭。在程序需要扩展的时候,对于一个类,不要去修改原来的代码,而是通过继承的方式去扩展这个类。
  • 目的:降低维护风险

单一职责原则(Single Responsiblity Principle)

  • 每个类应该且只有一个职责。
  • 目的:提高可读性

里式替换原则(Liskov Substitution Principle)

  • 子类继承父类时,可以实现父类的抽象方法,不要重写父类的方法,子类增加自己特有的方法。
  • 目的:防止继承带来的问题

依赖倒转原则(Dependency Inversion Principle)

  • 程序要依赖于抽象接口,不要依赖于具体实现,针对接口编程。
  • 目的:利于代码升级

接口隔离原则(Interface Segregation Principle)

  • 庞大的接口拆分成更小的和更具体的接口,一个接口只用于一个业务逻辑。
  • 目的:使功能解耦,高内聚、低耦合

迪米特原则(Principle of Least Knowledge)

  • 一个对象应当对其他对象尽可能少的了解。
  • 目的:自己做自己的事情

合成复用原则(Composite Reuse Principle)

  • 使用对象组合,而不是继承来达到复用的目的。继承破坏了封装性,父类的任何改变,都可能导致子类出问题。优先考虑合成复用,A类和B类的合成使用,而不是B继承A的使用。
  • 目的:少用继承,降低耦合

23种设计模式

设计模式分为三类:创建型模式、结构型模式、行为型模式

创建型

单例模式(Singleton Pattern)

  • 目的:主要就是一个类,频繁的创建,销毁

    如:Looper.getMainLooper()

  • 优点:内存中只有一个实例,减少开销

  • 缺点:没有接口,不能继承,违背了单一职责原则

工厂模式(Factory Pattern)

  • 目的:解决接口选择问题

    如:BitmapFactory,获取系统服务(根据名字)

  • 优点:想要创建对象,只要知道名字就行,屏蔽了内部实现,只要关心接口

  • 缺点:增加一个产品的时候,需要增加一个实现类和一个工厂

抽象工厂模式(Abstract Factory Pattern)

  • 目的:解决接口选择问题

    如:在android中,抽象工厂比较少,可以把整个Service看做一个大抽象工厂,通过名字去拿服务,拿到服务对象,再调用对象里的抽象方法

  • 优点:当产品种类很多的时候 ,根据名字拿到的当前产品种类的对象

  • 缺点:扩展非常困难,增加一个同类新的产品,就需要增加一个新的工厂

原型模式(Prototype Pattern)

  • 目的:快速,高效的创建对象

    如:复制一个新对象,Okhttp中RealCall.clone,Intent中clone

  • 优点:提高性能,没有构造函数的限制

  • 缺点:配合克隆方法进行,需要考虑深拷贝和浅拷贝的问题

建造者模式(Builder Pattern)

  • 目的:配置一个复杂对象

    如:Dialog,Okhttp,Retrofit

  • 优点:独立,容易扩展

  • 缺点:如果对象复杂,会有很多建造者类

结构型

代理模式(Proxy Pattern)

  • 目的:增强他的职责,比如访问远程的服务器,如果直接访问可能会带来一些麻烦,通过一个代理去帮我们访问

    如:通过IRemoteService的Stub类拿对象,这个对象是通过一个代理类创建出来的。换个角度看,代理模式也就是实现了一个接口,并完成了一个方法,这个方法可以去做任何事情

  • 优点:职责清晰,易扩展

  • 缺点:增加了代理可能会导致速度慢,代理类会比较复杂

外观模式(Facade Pattern)

  • 目的:降低系统复杂度

    如:Context,Context可以打开Activity,可打开Service,广播等等

  • 优点:提高灵活性,安全性

  • 缺点:违反了开闭原则、迪米特原则、单一原则,改东西相对麻烦

装饰器模式(Decorator Pattern)

  • 目的:解决扩展子类膨胀的问题

    如:比如摊煎饼,可以摊煎饼前,煎个鸡蛋,摊煎饼后,撒点酱,装饰东西,实际作用的前后装饰一些其他内容

    ContextImpl也就是个装饰模式,我们肯定会在startActivity()前后做点业务操作。

  • 优点:灵活扩展

  • 缺点:过多的装饰,会很复杂

享元模式(Flyweight Pattern)

  • 目的:为了解决大量创建相同对象,可能造成OOM

    相当于是缓存了一块地方,把对象放进去,需要对象的时候就从这里面取,如果相同需求,则会返回已有的对象。 如:在android中,获取Message,可以通过Message.obtain()去获取Message。在JVM中缓存了很多字符串。 可以说我们任何时候都在使用享元模式

  • 优点:减少重复创建对象,降低内存

  • 缺点:提高了系统的复杂度,如果固定了一些对象,当被改变时候,会造成混乱

组合模式(Composite Pattern)

  • 目的:用来描述整体和部分的关系,

    二叉树的结点,既可以是根节点,又可以是叶子节点。

    如:在android中,View和ViewGroup的关系,ViewGroup既可以是一个View,又可以包含View

  • 优点:调用简单,结点可以自由增加

  • 缺点:在组合模式中,依赖的都是实现类,而不是接口,违反了依赖倒置原则

适配器模式(Adapter Pattern)

  • 目的:解决两个不兼容接口的桥梁,兼容转换

    如:在android中,ListView、RecyclerView都是用了适配器模式,ListView适配了Adapter,ListView只管ItemView,不管具体怎么展示,Adapter只管展示。就像读卡器,读卡器作为内存和电脑之间的适配器

  • 优点:让两个没有关联的类一起运行,提高复用

  • 缺点:使用过多,会让系统变的凌乱

桥接模式(Bridge Pattern)

  • 目的:将两个能够独立变化的类分开,不用继承,继承会造成类的爆炸增长

    更直观的说,比如一个USB线左边可以插不同的手机,右边可以插不同的电源。

    如: 在android中,整个View的视图,View、Button、ViewGroup等等都是在View这个维度上的变化,都有onDraw()方法来实现不同的视图。另一个维度就是把View绘制到屏幕上

  • 优点:抽象分离,易扩展,细节透明

  • 缺点:会增加系统的设计难度

行为型

模板模式(Template Pattern)

  • 目的:固定了一些方法,只要照着做就行

    它的主旨就是抽象出公共的方法,子类照着重写就行,行为由父类控制

    如:在BaseActivity中实现onCreate通用设置,在AActivity中使用onCreate完成具体的业务

  • 优点:行为由父类控制,便于维护

  • 缺点:导致类增多,系统变大

策略模式(Strategy Pattern)

  • 目的:解决很多if else的情况

    给RecyclerView选择布局方式的时候,就是选择一个策略

  • 优点:可以避免多重判断条件,扩展性好

  • 缺点:类会增多

中介者模式(Mediator Pattern)

  • 目的:解决对象与对象之间的耦合关系

    在android中,无时无刻都在使用中介者,MVP 的 P ,MVC的 C ,MVVM 的 VM

  • 优点:降低复杂度,各个类之间解耦

  • 缺点:中介者会过于庞大不好维护

观察者模式(Observer Pattern)

  • 目的:一个对象改变通知其他对象,保证协作

    如:LiveData,Flow,RxJava

  • 优点:观察者和被观察者是抽象耦合的,也就是说通过抽象方法,给具体的类通知

  • 缺点:如果观察者有很多,被观察者发消息,会慢,如果不小心观察者和被观察者有依赖,会循环引用

状态模式(State Pattern)

  • 目的:对象依赖一个状态,对象可以根据状态改变自己的行为

    如:在android中,就比如播放器,依赖自身的状态,进行播放暂停操作。再比如Fragment,Fragment走自己的onCreate等方法,也是依赖Activity的生命周期状态进行操作。

  • 优点:状态转换与逻辑合在一起,而不是通过if语句隔开

  • 缺点:会使结构比较复杂,也会增加类的个数

责任链模式(Chain of Responsibility Pattern)

  • 目的:主要用来解耦,客户只要把消息发到责任链上,无需关注请求细节和传递过程

    如:在android中,事件分发机制,父View接到事件,传递给子View。在第三方库okhttp中一个网络请求用的拦截器。

  • 优点:解耦,简化操作

  • 缺点:性能会有一点影响,调试不太方便

备忘录模式(Memento Pattern)

  • 目的:不破坏封装的前提下,捕获一个对象的内部状态,并保存,之后能根据状态恢复到原来的样子

    如:在android中,Activity的onSaveInstanceState保存数据在onCreate里 恢复数据

  • 优点:提供了一种可以恢复的机制,封装了信息,用户不需要过多关心细节

  • 缺点:消耗资源,如果保存内容过多过大,会占用很多资源

迭代器模式(Iterator Pattern)

  • 目的:遍历一个对象

    Iterator接口,必须实现下一个对象和是否有下一个对象。 如:在java中HashMap的内部类KeySet有Iterator,android中访问数据库有Cursor,都是是用了迭代器模式

  • 优点:访问一个聚合数据,聚合数据不会暴露内部内容

  • 缺点:会增加类的个数,增加系统复杂性

解释器模式(Interpreter Pattern)

  • 目的:对于一些固定问法结构如xml,构建一个类解释它

    如:在android中,通常会定义xml布局,然后setContentView(xml),把xml放进去,这个里面就用了解释器模式,通过XmlResourceParser等一些方法,把xml解释成对象

  • 优点:扩展性好、灵活、增加了新的表达方式

  • 缺点:使用场景少,难维护,通常要用到递归

命令模式(Command Pattern)

  • 目的:也是用来解耦的。通常请求者和实现者是一种耦合关系,但一些场合对行为记录、撤销、重做,这种请求和处理就不太适合在一起

    如:在android中,PackageManagerService用到了命令模式,实现对apk的解析、管理等操作。这个命令模式比较宽泛,将一个请求封装成一个对象,对这个请求有记录或者撤销等操作。比如线程池使用、okhttp一个请求用的Call,都可以视作命令。

  • 优点:解耦,易扩展

  • 缺点:命令类可能会变的很多

访问者模式(Visitor Pattern)

  • 目的:解决稳定的数据结构和易变的操作耦合问题

    如:一个动作的抽象类,图片系统和相机系统完成了这个动作。App1 实现了访问者接口,表示App1可以访问图片系统和相机系统

  • 优点:符合单一职责,易扩展

  • 缺点:违反了迪米特原则、依赖倒置原则,依赖了具体实现类,不是依赖抽象

六种单例方式

饿汉式

饿汉式是最简单的一种实现,在类装载过程中,完成实例化,避免多线程问题

public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();

    private EagerSingleton() {
    }

    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

懒汉式

在多线程环境下保证单例对象唯一

优点: 只有在使用时才会实例化单例,一定程度上节约了资源。

缺点: 第一次加载时要立即实例化,反应稍慢。每次调用getInstance()方法都会进行同步,这样会消耗不必要的资源。这种模式一般不建议使用。

public class Singleton {
    private static Singleton instance;
    private Singleton() {
    }
    // synchronized方法,多线程情况下保证单例对象唯一
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

双重检查模式(DCL:Double CheckLock)

DCL模式是使用最多的单例模式实现方式

优点: 资源利用率高,既能够在需要的时候才初始化实例,又能保证线程安全,同时调用getInstance()方法不进行同步锁,效率高。

缺点: 第一次加载时稍慢,由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生概率很小。

public class Singleton {
    private static Singleton instance = null;
    private Singleton() {
    }
    public static Singleton getInstance() {
        // 两层判空,第一层是为了避免不必要的同步
        // 第二层是为了在null的情况下创建实例
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

静态内部类

第一次加载不会初始化instance,只有在第一次调用getInstance()方法时,虚拟机会加载初始化instance。这方式既保证线程安全,单例对象的唯一,也延迟了单例的初始化,推荐使用这种方式来实现单例模式

public class Singleton {
    private Singleton() {
    }
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }
}

枚举

默认枚举实例的创建是线程安全的,即使反序列化也不会生成新的实例,任何情况下都是一个单例

public enum SingletonEnum {
    INSTANCE;
    public void doSomething() {
        System.out.println("do something");
    }
}

容器实现

可以管理多个单例类型,使用时根据key获取对象对应类型的对象。这种方式可以通过统一的接口获取操作,隐藏了具体实现,降低了耦合度

public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();
    public static void regsiterService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }
    public static Object getService(String key) {
        return objMap.get(key);
    }
}

动画类型及相应属性区别

Android动画分为两大类:视图动画 & 属性动画

视图动画

  • 作用对象:视图(View)

  • 具体分类:补间动画&逐帧动画

  • 具体介绍:

    • 作用对象:视图控件,如TextView、Button、View等等

      不可作用于View组件的属性。如背景、颜色等

    • 原理:通过确定开始视图的样式和结束视图的样式,中间动画变化过程,由系统补全来确定一个动画

      结束的视图样式:平移、缩放、旋转、透明度等等

    • 动画类型:平移动画Translate、缩放动画Scale、旋转动画Rotate、透明度动画Alpha

    • 特点:

      优:使用简单、方便,已封装好基础动画效果

      缺:仅控制整体实体效果,无法控制属性 (逐帧动画容易引起OOM)

    • 应用场景:

      • 视图中,标准、基础的动画效果
      • Activity、Fragment的切换效果
      • 视图组(ViewGroup)中子元素的出场效果

属性动画

  • 背景:Android系统一开始就提供了两种实现动画的方式(帧动画、补间动画)

    • 逐帧动画、补间动画可扩展性有较大的局限性
  • 作用对象局限

    • 只能作用在视图View

    • 有些情况下动画效果只是视图的某个属性对象,而不是整个视图

    • 只是改变了View的视觉效果,无法改变具体的属性

  • 动画效果单一,只能实现常规的平移、旋转、缩放、透明度,遇到其他的场景就无法实现了

  • 作用对象:任意对象,不仅仅局限于View视图对象
  • 原理:
    • 在一定时间间隔内,通过不断对值进行改变,不断将该值赋给对象的属性从而实现对该对象在该属性上的动画效果
    • 可以使任意对象的任意属性
  • 特点:作用对象进行了扩展,不只是View对象,甚至无对象也可以,动画效果丰富

差值器&估值器

类型定义作用应用场景备注
插值器(Interpolator)一个辅助动画实现的接口设置属性值从初始值过度到结束值的变化规律
- 如勾选、加速、减速等等
- 即确定了动画效果变化的模式
实现非线性运动的动画效果- Android内置了9种内置的差值器
- 可自定义差值器
估值器(TypeEvaluator)一个辅助动画插值器的接口
- 属性动画特有的属性
设置属性值从初始值过度到结束值的变化具体数值
- 插值器决定值的变化规律,即决定变化趋势
- 而截下来的具体变化数值则交给估值器
协助插值器实现非线性运动的动画效果- Android内置了3种内置的估值器实现
- 可自定义估值器实现

两类动画的根本区别在于:是否改变动画本身的属性

  • 视图动画:无改变动画的属性 因为视图动画在动画过程中仅对图像进行变换(平移、缩放、旋转和透明),从而达到了动画效果
  • 属性动画:改变了动画属性 因属性动画在动画过程中对动态改变了对象属性,从而达到了动画效果

Java锁(Synchronized)

参考:Java各种锁小结

Synchronized的综述

  • 同步机制: synchronized是Java同步机制的一种实现,即互斥锁机制,它所获得的锁叫做互斥锁
  • 互斥锁: 指的是每个对象的锁一次只能分配给一个线程,同一时间只能由一个线程占用
  • 作用: synchronized用于保证同一时刻只能由一个线程进入到临界区,同时保证共享变量的可见性、原子性和有序性
  • 使用: 当一个线程试图访问同步代码方法(块)时,它首先必须得到锁,退出或抛出异常时必须释放锁

使用 volatile 关键字:使变量的值发生改变时尽快通知其他线程

  • Volatile 关键字详解:

    编译器为了加快程序的运行的速度,对一些变量的写操作会先在寄存器或者 CPU 缓存上进行,最后写入内存中,而在这个过程中,变量的新值对于其他线程是不可见的,当对使用 volatile 标记的变量进行修改时,会将其它缓存中存储的修改前的变量清除,然后重新读取

观锁和悲观锁的使用场景

  • 乐观锁适用于读多写少的场景。对于资源的竞争较小
  • 悲观锁适用于写多读少的场景。对于资源的竞争较大

资源竞争较小的情况,使用Synchornized同步锁进行线程阻塞和唤醒以及用户态和内核态的切换会额外消耗CPU资源,而CAS不需要进入内核,不需要切换线程操作自旋几率小,资源消耗少。资源竞争较大情况下,使用CAS,则会导致大量线程处在自旋状态,会严重浪费CPU资源

Synchronized和ReentrantLock区别

参考:ReentrantLock和synchronized区别

参考:Android程序员需要了解的并发编程知识

  • 可重入性:两者的锁都是可重入的,差别不大,有线程进入锁,计数器自增1,等下降为0时才可以释放锁

  • 锁的实现:synchronized是基于JVM实现,操作系统级别(用户很难见到,无法了解其实现),ReentrantLock是JDK实现的,可以查到到对应实现的源码

  • 公平锁:synchronized只能是非公平锁。而ReentrantLock可以实现公平锁和非公平锁两种

  • 超时设置:synchronized不能设置超时,而ReentrantLock可以设置超时

  • 中断等待:synchronized不能中断一个等待锁的线程,而ReentrantLock可以中断一个试图获取锁的线程。

  • 性能区别:在JDK1.6前,二者的性能差别差很多,synchronized 是重量级锁,效率低下;在JDK1.6后,当synchronized引入了偏向锁、轻量级锁(自选锁)机制后,二者的性能差别不大,官方推荐synchronized(写法更容易、在优化时其实是借用了ReentrantLock的CAS技术,试图在用户态就把问题解决,避免进入内核态造成线程阻塞)

  • 功能区别

    • 便利性:synchronized更便利,它是由编译器保证加锁与释放,不会产生死锁。ReentrantLock是需要手动释放锁,所以为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。
    • 锁的细粒度和灵活度:ReentrantLock优于synchronized
  • ReentrantLock独有的功能

  • 可指定是公平锁还是非公平锁,所谓公平锁就是先等待的线程先获得锁

    • 提供了一个Condition类,可以分组唤醒需要唤醒的线程
    • 提供能够中断等待锁的线程的机制,lock.lockInterruptibly()

死锁

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去

  • 死锁产生的必要条件: 产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。
    • 互斥条件:一个资源每次只能被一个进程使用。独木桥每次只能通过一个人。
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。乙不退出桥面,甲也不退出桥面。
    • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。甲不能强制乙退出桥面,乙也不能强制甲退出桥面。
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。如果乙不退出桥面,甲不能通过,甲不退出桥面,乙不能通过。
  • 如何避免死锁
    • 加锁顺序(线程按照一定的顺序加锁)
    • 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁) 通过Lock方法来拿锁

集合

List接口存储一组不唯一有序对象(插入顺序),Set接口存储一组唯一无序对象

HashMap与HashTable

HashMap使用链表存储对象,存储时通过hashCode找到bucket存储Entry对象,查询根据对象equals方法找到键值对,返回对象

HashMap 的大小超过了负载因子「默认0.75」(load factor)容量会自动扩容到原来的2倍,后续通过hash方法找到新的bucket位置

HashMap 与 HashTable 对比

HashMap非synchronized的,性能更好,可以接口null键值对,HashTable是线程安全的,比HashMap慢,不接受null键值对

ConCurrentHashMap

ConCurrentHashMap最外层是一个Segment数组,每个Segment包含一个类似HashMap数据结构差不多的链表数组,内部首先会采用自旋锁机制,达到阈值后切换为互斥,自旋比较占用CPU资源

ArrayList

本质上是一个动态数组在不断添加或者减少的过程中会自动进行扩容或者减容,并且会按照位置关系把数组元素整体(复制)移动一遍

扩容机制 :默认长度为10,如果需要扩容,则调用grow(int minCapacity) 进行扩容,长度为原来的1.5倍

LinkedList

本质上是一个双向链表存储结构,对元素select来看,ArrayList要优于LinkedList,因为后者需要移动指针;对insert跟delete来说,LinkedList比较优势一些,因为ArrayList需要移动数据

CopyOnWriteArrayList

CopyOnWriteArrayList相对于ArrayList来看是线程安全扩容的,增加删除等写操作通过加锁的形式保证数据一致性,通过复制新集合的方式解决遍历迭代的问题。

ArrayDeque

OkHttp双队列(run队列,cache队列)使用即ArrayDeque

  • 特性

    1. 无容量大小限制,容量按需增长
    2. 非线程安全队列,无同步策略,不支持多线程安全访问
    3. 当用作栈时,性能优于Stack,当用于队列时,性能优于LinkedList
    4. 两端都可以操作
    5. 具有fail-fast特征
    6. 不能存储null
    7. 支持双向迭代器遍历

    注意: ArrayDeque的迭代器和大多数容器迭代器一样,都是快速失败(fail-fast),但是程序不能利用这个特性决定是或否进行了并发操作。

SynchronousQueue

OkHttp源码,开线程执行请求时使用的即是SynchronousQueue

  • 特性
  1. SynchronousQueue是BlockingQueue的一种,所以SynchronousQueue是线程安全的
  2. SynchronousQueue不存储任何元素
  3. SynchronousQueue的每一次insert操作,必须等待其他线性的remove操作。每一个remove操作也必须等待其他线程的insert操作
  4. SynchronousQueue可以在**两个线程中传递同一个对象**。一个线程放对象,另外一个线程取对象

SparseArray

RecycleView中缓存池RecycleViewPool使用的就是该集合

一种基础数据结构,功能类似HashMap,但Key值只能为int值,不能是其他类型

下一篇:Android面试知识点总结(二)——进阶篇