[Kotlin] Princípio de implementação de por palavra-chave preguiçosa

prefácio

A palavra-chave lazy do Kotlin é muito usada, significa que a variável é inicializada lentamente, e só é inicializada quando é usada pela primeira vez. Então, como ele consegue essa função? Este artigo revela seu princípio de implementação sob a ótica do bytecode e da linguagem Java.

Processo de inicialização de variável ViewModel e ViewBinding

Vamos pegar os exemplos mais comuns em dois projetos: ViewModel e ViewBinding para entender porque a inicialização atrasada é necessária.

Veja um pedaço de código:

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")
     }
}

É muito comum o uso de ViewModel e ViewBinding na biblioteca Jetpack.Variáveis ​​dos tipos ViewModel e ViewBinding requerem inicialização atrasada e não podem ser inicializadas no momento da declaração. ViewModel é porque ele precisa contar com a variável de membro mApplication de Activity internamente, e mApplication é atribuído ao anexar. A inicialização de ViewBinding precisa contar com a variável layoutInflater de Window, e a variável Window também é atribuída ao anexar.

Primeiro observe como o ViewModel é inicializado. No ViewModelProviders.ofmétodo a seguir, ele será chamado checkApplicationpara determinar se o aplicativo está vazio e uma exceção será lançada se estiver vazio:

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 é uma variável membro de Activity, que é atribuída ao anexar:

 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;
        ...
   }

A variável layoutInflater é a mesma, precisa ser obtida através da variável mWindow, e mWindow também é atribuído em anexo:

 public LayoutInflater getLayoutInflater() {
    
    
        return getWindow().getLayoutInflater();
    }

O método attach da Activity é executado antes do método onCreate, então essas duas variáveis ​​podem ser acessadas no método onCreate.

Portanto, as variáveis ​​de tipo ViewModel e ViewBinding precisam de inicialização atrasada.

Vamos começar a ir direto ao ponto, como implementar a inicialização atrasada com a palavra-chave lazy.

Implementação de bytecode da palavra-chave preguiçosa

Visualize o conteúdo do bytecode da MainActivity acima da seguinte maneira:

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

A observação do bytecode revela várias mudanças:

(1), o tipo da variável viewModel é alterado para kotlin.Lazytype e o nome da variável também é alterado para viewModel$delegate. Olhando para o nome, sei que a ideia de entrega é usada.

(2) Utilizar o método estático lazy do LazyKt no método init da MainActivity, ou seja, o método de construção, para viewModel$delegateatribuir valores às variáveis. A lógica de implementação de inicialização em {} after by lazy é encapsulada na variável de tipo Function0 MainActivity$viewModel$2.

INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy

Observação: o tipo de parâmetro de entrada do método estático LazyKt.lazy é Function0, que representa um retorno de chamada com zero parâmetros (ou seja, sem parâmetros):

package kotlin.jvm.functions

public interface Function0<out R> : kotlin.Function<R> {
    
    
    public abstract operator fun invoke(): R
}

(3) Um método get é gerado para MainActivity: getViewModel(), e o tipo de retorno desse método é exatamente o tipo que precisamos: com/devnn/demo/MainViewModel.

Você pode ver a implementação interna deste método através do bytecode getViewModel():

viewModel$delegateO método getValue() da variável (o tipo é kotlin.Lazy) é chamado para retornar um Object, que é forçado a retorná com/devnn/demo/MainViewModel-lo.

O mistério está neste método Lazy getValue.

Em seguida, continue a examinar a implementação do getValue de kotlin.Lazy:

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)
}

Pode-se ver que, quando o valor não é UNINITIALIZED_VALUEinicializado, ele é inicializado inserindo o inicializador de parâmetro (ou seja, Function0), atribuindo um valor ao valor e, em seguida, retornando o valor.

Isso é um pouco semelhante ao modo preguiçoso do modo singleton em Java.

by lazyO princípio do bytecode que foi analisado até agora , o processo geral é substituir o tipo variável pelo tipo Lazy, e então retornar o tipo real através do método getValue da classe Lazy. getValueNo método, é julgado se é a primeira visita por julgamento vazio.

A chave é delegar a inicialização da variável para a classe Lazy do tipo geral por meio da ideia de delegação.

A inicialização atrasada de ViewBinding é igual a ViewModel, portanto, não é mais analisada.

Implementação Java da palavra-chave by lazy

O código do kotlin pode ser convertido em código Java. Vamos verificar seu código Java para verificar se é o mesmo da análise acima:

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();
   }

Pode-se ver que é exatamente o mesmo que a análise acima, apenas decompila o bytecode em código Java.

A inicialização da variável membro do Java é feita no construtor (método init) .

O princípio de implementação de Kotlin por palavra-chave preguiçosa é apresentado aqui.

Supongo que te gusta

Origin blog.csdn.net/devnn/article/details/127811334
Recomendado
Clasificación