網站首頁 編程語言 正文
前言
kotlin的by lazy關鍵字是很常用的,它表示延時初始化變量,只在第一次使用時才給它初始化。那么它是如何實現這種功能的呢?這篇文章從字節碼和Java語言的角度揭密它的實現原理。
ViewModel和ViewBinding變量初始化過程
先舉兩個項目中最常見的例子:ViewModel和ViewBinding,了解一下為什么需要延時初始化。
看一段代碼:
class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) } private val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.i("MainActivity", "onCreate") } }
Jetpack庫中的ViewModel和ViewBinding的使用是非常常見的,ViewModel和ViewBinding類型的變量都是需要延時初始化,不能在聲明時初始化。ViewModel是因為內部需要依賴Activity的成員變量mApplication,而mApplication是在attach時給賦值的。ViewBinding的初始化需要依賴Window的layoutInflater變量,而Window變量也是在attach時賦值的。
先看ViewModel是如何初始化的,在以下ViewModelProviders.of
方法里會調用checkApplication
判斷application是否為空,為空則拋出異常:
public class ViewModelProviders { /** * @deprecated This class should not be directly instantiated */ @Deprecated public ViewModelProviders() { } private static Application checkApplication(Activity activity) { Application application = activity.getApplication(); if (application == null) { throw new IllegalStateException("Your activity/fragment is not yet attached to " + "Application. You can't request ViewModel before onCreate call."); } return application; } @NonNull @MainThread public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) { Application application = checkApplication(checkActivity(fragment)); if (factory == null) { factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application); } return new ViewModelProvider(fragment.getViewModelStore(), factory); }
mApplication是Activity的成員變量,它是在attach時賦值的:
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) { attachBaseContext(context); ... mWindow = new PhoneWindow(this, window, activityConfigCallback); ... mApplication = application; ... }
layoutInflater變量同理,它需要通過mWindow變量獲取,而mWindow也是在attach里賦值的:
public LayoutInflater getLayoutInflater() { return getWindow().getLayoutInflater(); }
Activity的attach方法是早于onCreate方法執行的,所以在onCreate方法里是可以訪問這兩個變量。
所以,ViewModel和ViewBinding類型變量都需要延時初始化。
下面開始進入正題,by lazy關鍵字是如何實現延時初始化。
by lazy關鍵字的字節碼實現
查看以上MainActivity的字節碼內容如下:
public final class com/devnn/demo/MainActivity extends androidx/appcompat/app/AppCompatActivity { ...省略無關字節碼 // access flags 0x12 private final Lkotlin/Lazy; viewModel$delegate @Lorg/jetbrains/annotations/NotNull;() // invisible // access flags 0x12 private final Lkotlin/Lazy; binding$delegate @Lorg/jetbrains/annotations/NotNull;() // invisible // access flags 0x1 public <init>()V L0 LINENUMBER 27 L0 ALOAD 0 INVOKESPECIAL androidx/appcompat/app/AppCompatActivity.<init> ()V L1 LINENUMBER 28 L1 ALOAD 0 NEW com/devnn/demo/MainActivity$viewModel$2 DUP ALOAD 0 INVOKESPECIAL com/devnn/demo/MainActivity$viewModel$2.<init> (Lcom/devnn/demo/MainActivity;)V CHECKCAST kotlin/jvm/functions/Function0 INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy; PUTFIELD com/devnn/demo/MainActivity.viewModel$delegate : Lkotlin/Lazy; L2 LINENUMBER 32 L2 ALOAD 0 NEW com/devnn/demo/MainActivity$binding$2 DUP ALOAD 0 INVOKESPECIAL com/devnn/demo/MainActivity$binding$2.<init> (Lcom/devnn/demo/MainActivity;)V CHECKCAST kotlin/jvm/functions/Function0 INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy; PUTFIELD com/devnn/demo/MainActivity.binding$delegate : Lkotlin/Lazy; L3 LINENUMBER 27 L3 RETURN L4 LOCALVARIABLE this Lcom/devnn/demo/MainActivity; L0 L4 0 MAXSTACK = 4 MAXLOCALS = 1 // access flags 0x12 private final getViewModel()Lcom/devnn/demo/MainViewModel; L0 LINENUMBER 28 L0 ALOAD 0 GETFIELD com/devnn/demo/MainActivity.viewModel$delegate : Lkotlin/Lazy; ASTORE 1 ALOAD 1 INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object; (itf) CHECKCAST com/devnn/demo/MainViewModel L1 LINENUMBER 28 L1 ARETURN L2 LOCALVARIABLE this Lcom/devnn/demo/MainActivity; L0 L2 0 MAXSTACK = 1 MAXLOCALS = 2 // access flags 0x12 private final getBinding()Lcom/devnn/demo/databinding/ActivityMainBinding; L0 LINENUMBER 32 L0 ALOAD 0 GETFIELD com/devnn/demo/MainActivity.binding$delegate : Lkotlin/Lazy; ASTORE 1 ALOAD 1 INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object; (itf) CHECKCAST com/devnn/demo/databinding/ActivityMainBinding L1 LINENUMBER 32 L1 ARETURN L2 LOCALVARIABLE this Lcom/devnn/demo/MainActivity; L0 L2 0 MAXSTACK = 1 MAXLOCALS = 2
觀察字節碼可以發現幾點變化:
(1)、viewModel變量的類型被換成了kotlin.Lazy
類型,變量名字也換成了viewModel$delegate。看名字也知道是用到了委托思想。
(2)、在MainActivity的init方法即構造方法中使用LazyKt的靜態方法lazy,給viewModel$delegate
變量賦值了。by lazy后面{}內初始化實現邏輯封裝在了Function0類型變量MainActivity$viewModel$2
中。
INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy
注:LazyKt.lazy這個靜態方法的入參類型是Function0,它代表零個參數(即沒有參數)的回調:
package kotlin.jvm.functions public interface Function0<out R> : kotlin.Function<R> { public abstract operator fun invoke(): R }
(3)、給MainActivity生成了一個get方法:getViewModel()
,這個方法的返回類型正是我們需要的類型:com/devnn/demo/MainViewModel
。
通過字節碼可以看到這個getViewModel()
方法內部實現:
調用了viewModel$delegate
(類型是kotlin.Lazy)變量的getValue()方法返回一個Object,強轉成com/devnn/demo/MainViewModel
再將其返回。
玄機就在這個Lazy的getValue方法。
然后繼續看kotlin.Lazy的getValue的實現:
internal class UnsafeLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable { private var initializer: (() -> T)? = initializer private var _value: Any? = UNINITIALIZED_VALUE override val value: T get() { if (_value === UNINITIALIZED_VALUE) { _value = initializer!!() initializer = null } @Suppress("UNCHECKED_CAST") return _value as T } override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." private fun writeReplace(): Any = InitializedLazyImpl(value) }
可以看到,當value是UNINITIALIZED_VALUE
即未初始化時,就通過入參initializer(即Function0)初始化,并給value賦值,然后返回這個value。
這里有點類似于Java里的單例模式的懶漢模式。
到這時已經分析完了by lazy
的字節碼原理,大致過程就是將變量類型替換成了Lazy類型,然后通過Lazy類的getValue方法返回真實類型,getValue
方法里通過判空來判斷是否是首次訪問。
關鍵還是通過委托的思想將變量初始化委托給了通用類型Lazy類。
ViewBinding延時初始化跟ViewModel是一樣的,就不再分析了。
by lazy關鍵字的Java實現
kotlin的代碼是可以轉成Java代碼的,我們查看一下它的Java代碼,驗證是否跟上面分析的一樣:
public final class MainActivity extends AppCompatActivity { @NotNull private final Lazy viewModel$delegate = LazyKt.lazy((Function0)(new Function0() { @NotNull public final MainViewModel invoke() { ViewModel var1 = ViewModelProviders.of((FragmentActivity)MainActivity.this).get(MainViewModel.class); Intrinsics.checkNotNullExpressionValue(var1, "of(this).get(MainViewModel::class.java)"); return (MainViewModel)var1; } // $FF: synthetic method // $FF: bridge method public Object invoke() { return this.invoke(); } })); @NotNull private final Lazy binding$delegate = LazyKt.lazy((Function0)(new Function0() { @NotNull public final ActivityMainBinding invoke() { ActivityMainBinding var1 = ActivityMainBinding.inflate(MainActivity.this.getLayoutInflater()); Intrinsics.checkNotNullExpressionValue(var1, "inflate(layoutInflater)"); return var1; } // $FF: synthetic method // $FF: bridge method public Object invoke() { return this.invoke(); } })); private final MainViewModel getViewModel() { Lazy var1 = this.viewModel$delegate; return (MainViewModel)var1.getValue(); } private final ActivityMainBinding getBinding() { Lazy var1 = this.binding$delegate; return (ActivityMainBinding)var1.getValue(); }
可以看到,跟上面的分析是一模一樣的,它就是將字節碼反編譯成了Java代碼而已。
Java的成員變量初始化是在構造方法(init方法)中完成的,有興趣可以查看我的另一個篇文章: Kotlin字節碼層探究構造函數與成員變量和init代碼塊執行順序
關于Kotlin的by lazy關鍵字實現原理就介紹到此。
原文鏈接:https://blog.csdn.net/devnn/article/details/127811334
相關推薦
- 2023-02-07 k8s集群部署時etcd容器不停重啟問題以及處理詳解_云和虛擬化
- 2022-09-06 pandas?如何將字符串映射為數字_python
- 2022-04-18 css實現 快速定位父元素下最后面的幾個子元素,匹配選擇最后幾個子元素
- 2022-06-09 Nginx動靜分離配置實現與說明_nginx
- 2022-08-10 C#結束進程及子進程_C#教程
- 2022-11-22 XML實體注入深入理解_XML示例
- 2022-08-29 教你nginx跳轉配置的四種方式_nginx
- 2024-01-06 RocketMQ消息丟失問題
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支