Kotlin by lazy關(guān)鍵字深入探究實(shí)現(xiàn)原理
前言
kotlin的by lazy關(guān)鍵字是很常用的,它表示延時初始化變量,只在第一次使用時才給它初始化。那么它是如何實(shí)現(xiàn)這種功能的呢?這篇文章從字節(jié)碼和Java語言的角度揭密它的實(shí)現(xiàn)原理。
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是因?yàn)閮?nèi)部需要依賴Activity的成員變量mApplication,而mApplication是在attach時給賦值的。ViewBinding的初始化需要依賴Window的layoutInflater變量,而Window變量也是在attach時賦值的。
先看ViewModel是如何初始化的,在以下ViewModelProviders.of
方法里會調(diào)用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方法執(zhí)行的,所以在onCreate方法里是可以訪問這兩個變量。
所以,ViewModel和ViewBinding類型變量都需要延時初始化。
下面開始進(jìn)入正題,by lazy關(guān)鍵字是如何實(shí)現(xiàn)延時初始化。
by lazy關(guān)鍵字的字節(jié)碼實(shí)現(xiàn)
查看以上MainActivity的字節(jié)碼內(nèi)容如下:
public final class com/devnn/demo/MainActivity extends androidx/appcompat/app/AppCompatActivity { ...省略無關(guān)字節(jié)碼 // 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
觀察字節(jié)碼可以發(fā)現(xiàn)幾點(diǎn)變化:
(1)、viewModel變量的類型被換成了kotlin.Lazy
類型,變量名字也換成了viewModel$delegate??疵忠仓朗怯玫搅宋兴枷搿?/p>
(2)、在MainActivity的init方法即構(gòu)造方法中使用LazyKt的靜態(tài)方法lazy,給viewModel$delegate
變量賦值了。by lazy后面{}內(nèi)初始化實(shí)現(xiàn)邏輯封裝在了Function0類型變量MainActivity$viewModel$2
中。
INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy
注:LazyKt.lazy這個靜態(tài)方法的入?yún)㈩愋褪荈unction0,它代表零個參數(shù)(即沒有參數(shù))的回調(diào):
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
。
通過字節(jié)碼可以看到這個getViewModel()
方法內(nèi)部實(shí)現(xiàn):
調(diào)用了viewModel$delegate
(類型是kotlin.Lazy)變量的getValue()方法返回一個Object,強(qiáng)轉(zhuǎn)成com/devnn/demo/MainViewModel
再將其返回。
玄機(jī)就在這個Lazy的getValue方法。
然后繼續(xù)看kotlin.Lazy的getValue的實(shí)現(xiàn):
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) }
可以看到,當(dāng)value是UNINITIALIZED_VALUE
即未初始化時,就通過入?yún)nitializer(即Function0)初始化,并給value賦值,然后返回這個value。
這里有點(diǎn)類似于Java里的單例模式的懶漢模式。
到這時已經(jīng)分析完了by lazy
的字節(jié)碼原理,大致過程就是將變量類型替換成了Lazy類型,然后通過Lazy類的getValue方法返回真實(shí)類型,getValue
方法里通過判空來判斷是否是首次訪問。
關(guān)鍵還是通過委托的思想將變量初始化委托給了通用類型Lazy類。
ViewBinding延時初始化跟ViewModel是一樣的,就不再分析了。
by lazy關(guān)鍵字的Java實(shí)現(xiàn)
kotlin的代碼是可以轉(zhuǎn)成Java代碼的,我們查看一下它的Java代碼,驗(yàn)證是否跟上面分析的一樣:
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(); }
可以看到,跟上面的分析是一模一樣的,它就是將字節(jié)碼反編譯成了Java代碼而已。
Java的成員變量初始化是在構(gòu)造方法(init方法)中完成的,有興趣可以查看我的另一個篇文章: Kotlin字節(jié)碼層探究構(gòu)造函數(shù)與成員變量和init代碼塊執(zhí)行順序
關(guān)于Kotlin的by lazy關(guān)鍵字實(shí)現(xiàn)原理就介紹到此。
到此這篇關(guān)于Kotlin by lazy關(guān)鍵字深入探究實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Kotlin by lazy內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實(shí)現(xiàn)viewpager實(shí)現(xiàn)循環(huán)輪播效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)viewpager實(shí)現(xiàn)循環(huán)輪播效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-03-03RecyclerView實(shí)現(xiàn)水波紋點(diǎn)擊效果
這篇文章主要為大家詳細(xì)介紹了RecyclerView實(shí)現(xiàn)水波紋點(diǎn)擊效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-01-01Android自定義View實(shí)現(xiàn)相機(jī)對焦框
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)相機(jī)對焦框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05Android開發(fā)中TextView 實(shí)現(xiàn)右上角跟隨文本動態(tài)追加圓形紅點(diǎn)
這篇文章主要介紹了android textview 右上角跟隨文本動態(tài)追加圓形紅點(diǎn)的實(shí)例代碼,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-11-11RadioGroup實(shí)現(xiàn)單選框的多行排列
這篇文章主要為大家詳細(xì)介紹了RadioGroup實(shí)現(xiàn)單選框的多行排列,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-11-11Android Studio一直處于Building的兩種解決方法
很多朋友都遇到過打開別人的項目一直處于Building‘XXX’Gradle project info的情況。下面小編給大家?guī)砹薃ndroid Studio一直處于Building的解決方法,感興趣的朋友一起看看吧2018-08-08Android 處理OnItemClickListener時關(guān)于焦點(diǎn)顏色的設(shè)置問題
這篇文章主要介紹了Android 處理OnItemClickListener時關(guān)于焦點(diǎn)顏色的設(shè)置問題的相關(guān)資料,需要的朋友可以參考下2017-02-02