补:《Android面试题思考与解答》12月刊(一)

简介: 日子过的好快,12月又过了,也就代表2020也要结束了。不管你在这一年中是开心,是难过,是苦闷,还是平淡,都过去了,向前看,老铁们~

前言


日子过的好快,12月又过了,也就代表2020也要结束了。不管你在这一年中是开心,是难过,是苦闷,还是平淡,都过去了,向前看,老铁们~


新的一年祝大家技术越来越棒,钱越来越多,当然身体健康是最重要的,也希望疫情早点结束 !


照例,12月刊的《面试题思考与解答》奉上。


kotlin为什么被设计出来


kotlin被设计出来并被Google推广,主要有以下优势:


  • 完全兼容Java
  • 更少的空指针异常
  • 更少的代码量,更快的开发速度


kotlin工作原理


首先,我们了解下Java的工作原理:


Java 代码是经过编译才能运行的。首先会编译成class文件,然后通过java虚拟机运行,在Android中也就是ART。


所以,任何语言只要能被编译成符合规格的class文件,就能被java虚拟机运行,也就能运行在我们的Android手机上,kotlin亦是如此。


  • 另外Android studio也提供了一个功能,可以查看kotlin对应的字节码:


Tools -> Kotlin -> Show Kotlin Bytecode


再点击Decomplie还可以反编译成Java文件。


kotlin的空安全


  • java中,我们可以任意初始化一个变量,而不需要赋值,比如String,就有它的默认值null。


String a;


如果要调用对象的参数,必须判空:


if (a!=null){
   Log.d("lz","length="+a.length());
}else{
   Log.d("lz","length=null");
}


  • kotlin中,为了保证减少空指针的问题,不允许直接设置为空,可以通过?=的方式设置可以为空。


val a: String ? = null


1)赋值的时候,可以直接使用?来表示这个对象可能为空,如果为空则表达式结果也为空,而不用进行非空判断。


//如果 b 非空,就返回 b.length,否则返回 null
val length = b?.length
//如果 b 非空,就返回 String类型的b,否则返回 null
val str = b as? String


也就是通过问号来表示对象为空则整个表达式结果为空,而不会报错空指针。


2)如果需要设定为空的时候返回的表达式值不为空,可以用操作符?:来表示,也叫Elvis操作符。


//b为空则表达式返回-1
val length = b?.length ?: -1


3)如果要将值转换为非空类型,就可以使用 !!来标识非空,但是这种操作符就有可能会抛出空指针异常,如果实际对象为空的话。所以这种操作符相当于去除了空判断。


//如果b为空,空指针异常
val length = b!!.length


val和var


val,全称value,声明一个不可变的变量,这种变量在初始赋值之后就再也不能重新赋值了,所以相当于java中的final变量。


var,全称variable(可变的),所以是用来声明一个可变的变量,可以重复赋值。

kotlin中这么设计的原因主要是把不可变变量 这种因素和可变变量拿到同一级来设计,也就是说我们以后编码设计变量的时候,必须要考虑这个变量是不可变还是可变的,养成良好习惯,不是以前在java中需要添加final这种稍微繁琐的举动。


扩展函数(Extension Function)


扩展函数,其实就是扩展类的函数,可以在已有的类中添加新的方法,比继承更加简洁优雅方便。


  • 扩展函数比如:


fun Activity.showToast( msgId:Int){
    Toast.makeText(this,msgId,Toast.LENGTH_SHORT).show()
}


这样任何的Activity里面就可以直接调用showToast方法来展示Toast了。


  • 同样,可以设置扩展属性,比如:


var <T> MutableList<T>.lastData: T
    //获取List中最后一个对象
    get() = this[this.size - 1]
    //设置List中最后一个对象的值
    set(value) {
        this[this.size - 1] = value
    }


用法:


var strs = mutableListOf<String>()
strs.lastData="heihei"
Log.e(TAG,"lastdata= ${strs.lastData}")


这里还涉及到两个知识点:


  • kotlin中,在使用对象的get和set方法,可以直接省略,直接使用属性名即可,会根据表达式的实际功能来添加对应的set或者get方法。
  • kotlin中,对于$符号表示 串模板,就是可计算的代码片段,可以将其计算结果链接到字符串中。


扩展属性原理


kotlin这个扩展功能确实设计的很巧妙,那就一起来研究下它的原理:


按照上面的方法,也就是Tools -> Kotlin -> Show Kotlin Bytecode -> Decomplie, 我们得到showToast扩展函数和使用代码所对应的java代码:


//扩展函数
public final class UtilsKt {
   public static final void showToast(@NotNull Activity $this$showToast, int msgId) {
      Intrinsics.checkParameterIsNotNull($this$showToast, "$this$showToast");
      Toast.makeText((Context)$this$showToast, msgId, 0).show();
   }
}
//使用
UtilsKt.showToast(this, 1900026);


可以看到所谓的扩展函数不过就是自动生成一个带有当前对象的函数,扩展函数的所在类被public final修饰,函数被public static final修饰,然后扩展的那个类被作为方法的一个参数传进去,这样就跟我们用java的时候写的工具类很像。


然后使用的时候就跟我们使用工具类一样调用工具类的方法即可。


可以定义同名的扩展方法吗


在同一个包名下,是不可以定义相同类相同方法名的扩展方法的。但是,在不同包名下,是可以定义的。


比如我在不同的包名下定义了相同的扩展方法:


//Utils2.kt
package com.example.studynote.kotlin
fun Activity.showToast(msg:String){
    Toast.makeText(this,msg,Toast.LENGTH_LONG).show()
}
//Utils.kt
package com.example.studynote
fun Activity.showToast(msg:String){
    Toast.makeText(this,msg,Toast.LENGTH_SHORT).show()
}


具体会用哪个呢?就要看你导入的包是哪个了~


扩展方法可以覆盖掉某个类的已有方法吗


肯定是不能的,如果一个类的扩展方法和它已有方法同名,是可以编译过的。

但是调用的时候会优先调用类中本来就有的方法,而不是扩展方法。


kotlin中有没有用到;的时候


kotlin中一般会把省略,但是有两种情况还是会用到:


  • 枚举中,如果有方法的情况,必须用来分割枚举常量列表和方法


enum class Color {
    RED,
    BLACK,
    BLUE,
    GREEN,
    WHITE;
    fun getTopColor():Color {
        return BLACK
    }
}


  • 两个表达式在一行的时候,当然这种有点累赘,为啥要写成一行呢是吧:


var test="nihao" ; var test2="heihei"


let、apply、with、run


  • let 默认当前这个对象作为闭包的it参数,返回值为函数最后一行,或者return


fun getInt():Int{
        "jimu".let {
            println(it.length)
            return 0
        }
    }


  • apply 在apply函数范围内,可以任意调用该对象的任意方法,并返回该对象


fun getInt(): Int {
        return ArrayList<String>().apply {
            add("jimu")
        }.size
    }


  • with 返回值是最后一行,这点类似let。可以直接调用对象的方法,这点类似apply。


fun getInt(): Int {
        return with(ArrayList<String>()){
            add("jimu")
            size
        }
    }


  • run run和with很像,可以调用对象的任意函数,返回值是最后一行


fun getInt(): Int {
        return ArrayList<String>().run{
            add("jimu")
            size
        }
    }


lateinit和by lazy


上篇说过,Kotlin有空限制,所以有些变量如果不想设置为空的时候初始化该怎么做呢?这就用到延迟初始化了,lateinit和by lazy都能实现。


  • lateinit


lateinit用于修饰var变量,它会让编译器暂时忽略初始化这个事情,到后面用的时候我们在进行初始化,但是不能用到基本数据类型,比如int,double这种。


lateinit var test: String


  • by lazy


by lazy用于val类型的变量,它会暂时不进行初始化,并且在第一次使用的时候自动调用我们设置好的表达式进行初始化。


val str by lazy {
        println("Init lazy")
        "Hello World"
    }


Kotlin中的构造函数


kotlin中构造函数分为主构造函数和次构造函数。


  • 主构造函数


主构造函数没有函数体,直接定义在类名后。每个类都会默认带一个不带参数的构造函数,也可以直接定义参数,如果需要在构造函数中进行初始化工作,可以用init关键字:


class Student {
}
class Student(var name: String) {
    init {
        Log.e(TAG,"name=$name")
    }
}
class Student constructor(var name: String) {
    init {
        Log.e(TAG,"name=$name")
    }
}


  • 次构造函数


除了类名后这种主构造函数,其他的构造函数方法就是通过constructor关键字来定义次构造函数,一个类可以定义多个次构造函数。如果主构造函数和次构造函数同时存在的时候,次构造函数必须调用主构造函数。


class Student{
    private val username: String
    constructor(username: String){
        this.username = username
    }
}
class Student(username: String) {
    private var username: String
    private var age: Int
    init {
        this.username = username
        this.age = 10
    }
    constructor(username: String, age: Int) : this(username) {
        this.age = age
    }
}


协程


Kotlin协程是对线程的一种封装,同样是用来解决并发任务(异步任务)的方案,可以理解为一种线程框架,特点是挂起时不需要阻塞线程,更好的解决了线程切换,魔鬼调用的问题。


简单举个例子,具体的说明大家可以翻翻以前的文章——协程三问。


GlobalScope.launch(Dispatchers.Main) {
    var name = ioTask()
    updateUI(name)
    var name1 = ioTask()
    updateUI(name1)
    var name2 = ioTask()
    updateUI(name2)
}
private suspend fun ioTask(): String {
    var name = ""
    withContext(Dispatchers.IO) {
        //耗时操作,比如网络接口访问
        name = "jimu"
    }
    return name
}


  • GlobalScope.launch去开启一个协程
  • Dispatchers.Main表示运行在主线程
  • suspend关键字用于标记挂起函数的关键字
  • withContext函数用来构建一个协程作用域,可以标明作用线程,比如这里的Dispatchers.IO。这个函数必须在挂起函数或者协程中执行


说说插值器和估值器


  • 插值器


一般指时间插值器TimeInterpolator,是设置 属性值 从初始值过渡到结束值 的变化规律,比如匀速,加速,减速等等。可以通过xml属性和java代码设置。


系统默认的插值器是AccelerateDecelerateInterpolator,即先加速后减速。


//匀速插值器设置
android:interpolator="@android:anim/linear_interpolator"
alphaAnimation.setInterpolator(new LinearInterpolator());


属性动画中,插值器的含义就是要设置时间和属性的变化关系,也就是根据动画的进度(0%-100%)通过逻辑计算 计算出当前属性值改变的百分比。比如匀速关系就是动画进度和属性值改变的进度保持一致,50%时间进度就完成了属性值50%的变化。


//自定义匀速插值器
public class MyLinearInterpolator implements TimeInterpolator {
    @Override
    public float getInterpolation(float input) {
        return input;
    }


  • 估值器


又叫类型估值算法TypeEvaluator,用来设置 属性值 从初始值过渡到结束值 的变化具体数值,刚才介绍的插值器是指变化规律,而这个估值器是决定具体的变化数值,是用来协助插值器完成动画设置。


比如属性动画设置:


ObjectAnimator anim = ObjectAnimator.ofObject(view, "scale", new IntEvaluator(),1,10);
//系统估值器类型
IntEvaluator:针对整型属性 
FloatEvaluator:针对浮点型属性 
ArgbEvaluator:针对Color属性


可以看看IntEvaluator源码,其实就是根据三个参数—估值小数(fraction),开始值(startValue)和 结束值(endValue)然后计算具体属性变化的值:


public class IntEvaluator implements TypeEvaluator<Integer> {
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}


所以要实现一个完整的属性动画,需要估值器和插值器进行协同工作:


  • 首先由TimeInterpolator(插值器)根据时间流逝的百分比计算出当前属性值改变的百分比,并且 插值器 将这个百分比返回,这个时候 插值器 的工作就完成了。
  • 比如 插值器 返回的值是0.5,很显然我们要的不是0.5,而是当前属性的值,即当前属性变成了什么值,这就需要 估值器根据当前属性改变的百分比来计算改变后的属性值,根据这个属性值,我们就可以设置当前属性的值了。


使用动画的注意事项


  • OOM问题:这个问题主要出现在帧动画中,当图片数量较多且图片较大时就极易出现OOM,这个在实际开发中要尤其注意,尽量避免使用帧动画。
  • 内存泄露:在属性动画中有一类无限循环的动画,这类动画需要在Activity退出时及时停止,否则将导致Activity无法释放从而造成内存泄露,通过验证后发现View动画并不存在此问题。
  • 兼容性问题:动画在3.0以下的系统有兼容性问题,在某些特殊场景可能无法正常工作,因此要做好适配工作。
  • View动画的问题:View动画是对View的影像做动画,并不是真正改变View的状态,因此有时候会出现动画完成后View无法隐藏的现象,即setVisibility(View.GOEN)失效了,这个时候只要调用view.clearAnimation()清除View动画即可解决问题。
  • 不要使用px:在进行动画的过程中,要尽量使用dp,使用px会导致在不用的设备上有不用的效果。
  • 动画元素的交互:从3.0开始,将view移动(平移)后,属性动画的单击事件触发位置为移动后的位置,但是View动画仍然在原位置。在Android3.0以前的系统中,不管是View动画还是属性动画,新位置都无法触发单击事件同时,老位置仍然能触发单击事件(因为属性动画在Android3.0以前是没有的,是通过兼容包实现的,底层也是调用View动画)。
  • 硬件加速:使用动画的过程中,建议开启硬件加速,这样会提高动画的流畅性。


关于硬件加速,直观上说就是依赖GPU实现图形绘制加速,同软硬件加速的区别主要是图形的绘制究竟是GPU来处理还是CPU,如果是GPU,就认为是硬件加速绘制,反之,软件绘制



目录
相关文章
|
1月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
67 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
|
28天前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
26 8
|
26天前
|
Android开发 开发者
Android经典面试题之SurfaceView和TextureView有什么区别?
分享了`SurfaceView`和`TextureView`在Android中的角色。`SurfaceView`适于视频/游戏,独立窗口低延迟,但变换受限;`TextureView`支持复杂变换,视图层级中渲染,适合动画/视频特效,但性能略低。两者在性能、变换、使用和层级上有差异,开发者需按需选择。
15 1
|
29天前
|
SQL Java Unix
Android经典面试题之Java中获取时间戳的方式有哪些?有什么区别?
在Java中获取时间戳有多种方式,包括`System.currentTimeMillis()`(毫秒级,适用于日志和计时)、`System.nanoTime()`(纳秒级,高精度计时)、`Instant.now().toEpochMilli()`(毫秒级,ISO-8601标准)和`Instant.now().getEpochSecond()`(秒级)。`Timestamp.valueOf(LocalDateTime.now()).getTime()`适用于数据库操作。选择方法取决于精度、用途和时间起点的需求。
32 3
|
1月前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
29 6
|
1月前
|
XML Android开发 数据格式
Android面试题之DialogFragment中隐藏导航栏
在Android中展示全屏`DialogFragment`并隐藏状态栏和导航栏,可通过设置系统UI标志实现。 记得在布局文件中添加内容,并使用`show()`方法显示`DialogFragment`。
35 2
|
1月前
|
Android开发
Android面试题之View的invalidate方法和postInvalidate方法有什么区别
本文探讨了Android自定义View中`invalidate()`和`postInvalidate()`的区别。`invalidate()`在UI线程中刷新View,而`postInvalidate()`用于非UI线程,通过消息机制切换到UI线程执行`invalidate()`。源码分析显示,`postInvalidate()`最终调用`ViewRootImpl`的`dispatchInvalidateDelayed`,通过Handler发送消息到UI线程执行刷新。
27 1
|
27天前
|
消息中间件 调度 Android开发
Android经典面试题之View的post方法和Handler的post方法有什么区别?
本文对比了Android开发中`View.post`与`Handler.post`的使用。`View.post`将任务加入视图关联的消息队列,在视图布局后执行,适合视图操作。`Handler.post`更通用,可调度至特定Handler的线程,不仅限于视图任务。选择方法取决于具体需求和上下文。
27 0
|
1月前
|
Android开发 Kotlin
Android经典面试题之Kotlin中Lambda表达式有哪些用法
Kotlin的Lambda表达式是匿名函数的简洁形式,常用于集合操作和高阶函数。基本语法是`{参数 -&gt; 表达式}`。例如,`{a, b -&gt; a + b}`是一个加法lambda。它们可在`map`、`filter`等函数中使用,也可作为参数传递。单参数时可使用`it`关键字,如`list.map { it * 2 }`。类型推断简化了类型声明。
14 0
|
1月前
|
Android开发 Kotlin
Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
**Kotlin中的匿名函数与Lambda表达式概述:** 匿名函数(`fun`关键字,明确返回类型,支持非局部返回)适合复杂逻辑,而Lambda(简洁语法,类型推断)常用于内联操作和高阶函数参数。两者在语法、返回类型和使用场景上有所区别,但都提供无名函数的能力。
15 0