(一) 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),在首次访问时才完成延迟初始化(懒加载),默认线程安全,适用于延迟初始化开销较大的对象

相关推荐
8931519601 小时前
Android开发融云获取多个会话的总未读数
android·android开发·android教程·融云获取多个会话的总未读数·融云未读数
高林雨露1 小时前
Java对比学习Kotlin的详细指南(一)
java·学习·kotlin
zjw_swun1 小时前
实现了一个uiautomator玩玩
android
pengyu1 小时前
系统化掌握Dart网络编程之Dio(二):责任链模式篇
android·flutter·dart
水w2 小时前
【Android Studio】如何卸载干净(详细步骤)
android·开发语言·android studio·activity
亦是远方2 小时前
2025华为软件精英挑战赛2600w思路分享
android·java·华为
jiet_h2 小时前
深入解析KSP(Kotlin Symbol Processing):现代Android开发的新利器
android·开发语言·kotlin
清晨細雨2 小时前
UniApp集成极光推送详细教程
android·ios·uni-app·极光推送
Li_na_na012 小时前
解决安卓手机WebView无法直接预览PDF的问题(使用PDF.js方案)
android·pdf·uni-app·html5
CYRUS_STUDIO2 小时前
Frida Hook Native:jobjectArray 参数解析
android·c++·逆向