日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Kotlin?by?lazy關鍵字深入探究實現原理_Android

作者:devnn ? 更新時間: 2022-12-12 編程語言

前言

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

欄目分類
最近更新