(一) Kotlin Android 项目实战

一、前言

安卓开发快七年了,说到底还没有完全地用kotlin实战过一个项目。这段时间打算再次重新捡起Java、Kotlin、Android。也算是对这么多年安卓知识的一次总结吧。 太多零零碎碎的知识了,一直没有认真的总结过,打算用三个月的时间再好好捡起来这些。

二、第一个Kotlin项目

Kotlin的基础语法就不从头开始了,我们就从安卓切入,将kotlin化的安卓项目遇到的一些关键函数以及思想整理一下。

三、New Project

这次在选择语言的时候记得选择主语言Kotlin

Build Config language 选择 Kotlin DSL

点击Finsh后就得到我们的第一个Kotlin项目啦

四、var和val

var:可变变量.可以通过重新分配来更改为另一个值的变量,这种声明变量的方式和java中声明变量的方式一样
val:只读变量.这种声明变量的方式相当于java中的final变量。一个val创建的时候必须初始化,因为以后不能被改变。

kotlin 复制代码
private lateinit var binding: ActivityMainBinding

五、lateinit 和 by lazy

在这里初始化安卓项目时使用了mvvm中的viewBinding,ActivityMainBinding使用了lateinit关键字,和by lazy放在一起对比一下。

5.1 lateinit(延迟初始化)
  • 用于var变量:不可修饰基本类型(Int、Boolean),可修饰非空类型引用(如String、View等)

这里我们后面讨论一下为什么不可修饰基本类型

  • 使用过程:在调用之前主动赋值,否则空对象调用会崩溃
  • 适用场景:生命周期明确、需要在特定的生命周期回调中初始化的对象
kotlin 复制代码
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    //先占坑,稍后绑定(不能用val,必须是var)
    lateinit var navView: BottomNavigationView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        println("赋值之前")
        checkNavView()

        navView = binding.navView
        println("赋值之后")
        checkNavView()

        val navController = findNavController(R.id.nav_host_fragment_activity_main)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        val appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
            )
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }

    fun checkNavView(){
        if(::navView.isInitialized){
            //检查是否初始化
            println("navView 已绑定")
        }else{
            println("navView 还未绑定")
        }
    }
}
csharp 复制代码
2025-04-01 09:46:57.256 17313-17313 System.out              com.example.kotlinapplication        I  赋值之前
2025-04-01 09:46:57.256 17313-17313 System.out              com.example.kotlinapplication        I  navView 还未绑定
2025-04-01 09:46:57.256 17313-17313 System.out              com.example.kotlinapplication        I  赋值之后
2025-04-01 09:46:57.256 17313-17313 System.out              com.example.kotlinapplication        I  navView 已绑定

Tips:注意事项

如果直接使用未初始化的对象,会抛出UninitializedPropertyAccessException

php 复制代码
2025-04-01 09:49:56.993 17658-17658 AndroidRuntime          com.example.kotlinapplication        E  FATAL EXCEPTION: main
                                                                                                    Process: com.example.kotlinapplication, PID: 17658
                                                                                                    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.kotlinapplication/com.example.kotlinapplication.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property navView has not been initialized
                                                                                                    	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3338)
                                                                                                    	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3487)
                                                                                                    	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
                                                                                                    	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
                                                                                                    	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
                                                                                                    	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2071)
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:107)
                                                                                                    	at android.os.Looper.loop(Looper.java:224)
                                                                                                    	at android.app.ActivityThread.main(ActivityThread.java:7561)
                                                                                                    	at java.lang.reflect.Method.invoke(Native Method)
                                                                                                    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
                                                                                                    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:995)
                                                                                                    Caused by: kotlin.UninitializedPropertyAccessException: lateinit property navView has not been initialized
                                                                                                    	at com.example.kotlinapplication.MainActivity.getNavView(MainActivity.kt:17)
                                                                                                    	at com.example.kotlinapplication.MainActivity.onCreate(MainActivity.kt:41)
                                                                                                    	at android.app.Activity.performCreate(Activity.java:7893)
                                                                                                    	at android.app.Activity.performCreate(Activity.java:7880)
                                                                                                    	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1306)
                                                                                                    	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3313)
                                                                                                    	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3487) 
                                                                                                    	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) 
                                                                                                    	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
                                                                                                    	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
                                                                                                    	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2071) 
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:107) 
                                                                                                    	at android.os.Looper.loop(Looper.java:224) 
                                                                                                    	at android.app.ActivityThread.main(ActivityThread.java:7561) 
                                                                                                    	at java.lang.reflect.Method.invoke(Native Method) 
                                                                                                    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) 
                                                                                                    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:995) 
5.1 by lazy(惰性初始化)
  • 用于val变量:初始化后不可变,支持任何类型(包括基本类型)
  • 自动初始化:第一次访问时执行初始化代码,之后直接返回缓存值
  • 线程安全:默认多线程安全(可配置为高性能非安全模式)
  • 适用场景:高成本操作(如读取文件)、单例对象、按需加载的配置
kotlin 复制代码
class AppConfig {

    val config by lazy{
        println("读取配置文件...")
        loadConfigFromFile()
    }

    private fun loadConfigFromFile():Map<String,String>{
        //耗时处理逻辑
        return mapOf("theme" to "dark","language" to "zh")
    }
}
scss 复制代码
//使用 by lazy
val appConfig = AppConfig()
println("第一次触发读取,输出:读取配置文件..." + appConfig.config)
println("直接返回缓存,不再读取" + appConfig.config)
ini 复制代码
2025-04-01 10:02:33.165 19527-19527 System.out              com.example.kotlinapplication        I  读取配置文件...
2025-04-01 10:02:33.165 19527-19527 System.out              com.example.kotlinapplication        I  第一次触发读取,输出:读取配置文件...{theme=dark, language=zh}
2025-04-01 10:02:33.165 19527-19527 System.out              com.example.kotlinapplication        I  直接返回缓存,不再读取{theme=dark, language=zh}
线程模式
csharp 复制代码
//高性能模式(非线程安全,适用于单线程)
val fastLazyValue by lazy(LazyThreadSafetyMode.NONE){
    //初始化代码(确保只在单线程调用)
}

懒委托提供了几种懒加载模式供选择

1、LazyThreadSafetyMode.SYNCHRONIZED

同步模式,确保只有单个线程可以初始化实例,这种模式下初始化是线程安全的,当by lazy没有指定模式的时候,默认使用这种模式

2、LazyThreadSafetyMode.PUBLICATION:

并发模式,在多线程下允许并发初始化,但是只有第一个返回的值作为实例,这种模式下是线程安全的。和LazyThreadSafetyMode.SYNCHRONIZED最大区别是,这种模式在多线程并发访问下初始化效率是最高的,本质上面是用空间换时间,哪个的线程执行快就让哪个先返回结果,其他线程执行的结果抛弃掉。

3、LazyThreadSafetyMode.NONE

普通模式,这种模式不会使用锁来限制多线程访问,所以是线程不安全的,所以请勿在多线程并发的情况下使用。

lateinit vs by lazy

再回到上面的问题,为什么lateinit不能修饰基本类型(如Int、Boolean等) lateinit的设计初衷是允许延迟初始化一个非空引用类型(如String、MyClass等).但它的底层实现实际上是通过一个可空引用来跟踪初始化状态的。(比如 private var_value:String?=null)来跟踪初始化状态的。当首次访问lateinit变量时,Kotlin会检查该引用是否为null.

而基本类型在JVM上是非空且不可为null的(例如int、boolean).如果允许lateinit修饰基本类型,就无法用null表示"未初始化"的状态, 导致无法实现延迟初始化的机制.

六、对比

标题 lateinit by lazy
变量类型 var(可变) val(不可变)
初始化方式 手动赋值 首次访问时自动初始化
空值处理 必须非空 可返回任意类型(包括可空类型)
适用类型 非空对象(非Int等基本类型) 所有类型(包括基本类型)
线程安全 需自行保证 默认安全(可配置)
崩溃风险 未初始化直接使用会崩溃 初始化代码异常才会崩溃
典型场景 Android控件绑定、依赖注入 单例、配置加载、高成本资源初始化

总结

  • lateinit 适用于非空类型的 var 属性,需要自行确保在属性访问前被初始化,不保证线程安全,不能用于基本数据类型,没有默认值

  • by lazy 适用于任何类型(可空和非空都行)的属性(var 和 val),在首次访问时才完成延迟初始化(懒加载),默认线程安全,适用于延迟初始化开销较大的对象

相关推荐
似霰1 小时前
安卓adb shell串口基础指令
android·adb
fatiaozhang95273 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
louisgeek4 小时前
Kotlin 面试知识点
kotlin
CYRUS_STUDIO4 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师5 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师5 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫5 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白5 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
dpxiaolong6 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
tangweiguo030519877 小时前
Android 混合开发实战:统一 View 与 Compose 的浅色/深色主题方案
android