如何应对 Android 面试官 -> 玩转 Jetpack ViewModel

Android App开发之Jetpack架构 还记得15年那个时候,好像Android就是解析数据,列表展示,了解下四大组件生命周期,已经可以干活了,还记得当年一个View只要能滑动,就大呼自定义控件牛逼。 不过,近几年的变化颇大。 为了丰富大家日常开发的UI效果效果: Material Design进入大家的视野,后面陆陆续续涌现出一大批UI组件:DrawerLayout,AppbarLayout,CoordinateLayout,ToolBar,NestedScrollView,FlexBoxLayout等... 在UI组件日益完善的同时,也 阅读详情

前言


在这里插入图片描述

本章重点讲解 ViewModel 的基础使用和原理;

因为 ViewModel 的原理比较简单,本章结合 LiveData、DataBinding、Lifecycle 一起搞一个小 demo 来讲解 ViewModel 的原理和基础使用;

ViewModel 用来干什么?


  • 一个容器,如果数据打包在 ViewModel 中,当配置发生变化的时候,这些数据是不会丢失的,保证数据的稳定性;
  • 作为和界面 Controller 直接交互的最上层数据中心
    • 和界面 Controller 直接交互、数据中心;Activity Fragment 获得 ViewModel 对象,并从中获取数据显示到界面;
    • 最上层 ViewModel 中的数据格式未必是最底层格式,而是针对各个界面定制后的数据格式,ViewModel会向下层真正的数据中心取数据,并整理成上层格式;
  • 在Activity被销毁(转屏、系统语言切换)的时候,ViewModel 并没有被销毁,因此重建的 Activity 可以直接拿到数据,无需重新初始化——相当于是修复了 Activity Fragment 重建时数据也会被重建的 bug(虽然不是bug)
  • 可以在多个 Controller (Activity 和 Fragment 或者 多个 Fragment) 之间共享同一个 ViewModel,达到共享数据的结果,实际上就是为 Activity 和 Fragment 加了一个无额外操作成本的抽象的数据层;
    • 只支持竖屏的App 还需要 ViewModel 吗?
      • 需要,它可以让你的Activity 和 Fragment 以及多个 Fragment 之间共享数据
    • 如果用 Compose 可以共享数据吗?
      • 可以在多个 Compose 组件间共享数据
    • 如果你的App只支持竖屏,没有使用 Fragment,也没有自定义View 也不用 Compose 还需要ViewModel吗?
      • 不需要了
  • 减轻 Activity 的负担,让它不要什么都干;

基础使用篇


使用很简单,就是继承 ViewModel

import androidx.lifecycle.ViewModel

class MyViewModel : ViewModel() {
    var number : Int = 0;
}

然后 Activity 中创建它的实例

class MainActivity : AppCompatActivity() {

    private lateinit var myViewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // 不能直接实例化,因为如果能这样写,系统不可控了
        // myViewModel = MyViewModel() 
        
        myViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
            .get(MyViewModel::class.java)
            
        text_number.text = "${myViewModel.number}"

        // 点击事件 lambda
        btn_plus.setOnClickListener {
            text_number.text = "${++myViewModel.number}"
        }    
    }
}

PS: 不能使用 new的方式,这样写,系统就不可控了;应该使用 ViewModel 提供者 ViewModeProvider 来创建;

运行之后,我们改变数值之后,旋转屏幕,可以看到,界面上的数值并不会被重置为 0 ;

组合使用

我们来搞一个小 demo,组合使用 LiveData + DataBinding + ViewModel + Lifecycle

我们就来搞一个『拨号键盘』点击键盘显示对应的数字,以及调用系统的打电话能力;

我们先来创建 ViewModel,PhoneViewModel

public class PhoneViewModel extends AndroidViewModel {
    
    // 结合 LiveData 实现感应能力;
    private MutableLiveData<String> phoneInfo;
    // 上下文环境
    private Context context;
    
    public MainViewModel(Application application) {
        super(application);
        context = application;
    }

    // 提供数据接口 给布局用
    public MutableLiveData<String> getPhoneInfo() {
        if (phoneInfo == null) {
            phoneInfo = new MutableLiveData<>();
            // 设置默认值
            phoneInfo.setValue("");
        }
        return phoneInfo;
    }
    
    /**
     * 输入
     *
     * @param number
     */
    public void appendNumber(String number) {
        phoneInfo.setValue(phoneInfo.getValue() + number);
    }

    /**
     * 删除
     */
    public void backspaceNumber() {
        int length = phoneInfo.getValue().length();
        if (length > 0) {
            phoneInfo.setValue(phoneInfo.getValue().substring(0, phoneInfo.getValue().length() - 1));
        }
    }

    /**
     * 清空
     */
    public void clear() {
        phoneInfo.setValue("");
    }

    /**
     * 拨打
     */
    public void callPhone() {
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:" + phoneInfo.getValue()));
        // 非 Activity 启动拨号 或者是 非 Activity 启动任何的 startActivity 都会奔溃
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
}

这里我们使用继承 AndroidViewModel,AndroidViewModel 需要我们复写构造方法,传入 Application,这样我们就可以在 PhoneViewModel 中拿到 Context;

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

接下来我们来创建 xml 布局文件,并结合 DataBinding 关联 PhoneViewModel

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="vm"
            type="com.llc.jetpack.PhoneViewModel" />
    </data>

    <!-- UI绘制区域 -->
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical"
        android:background="@drawable/phone2_bg">

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1" />

        <!-- 电话号码 -->
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textSize="@dimen/activity_phone_tv"
            android:gravity="center"
            android:text="@{vm.phoneInfo}"
            android:textStyle="bold" />

        <!-- 表格布局 -->
        <TableLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="16dip">

            <!-- 第一列 -->
            <TableRow
                android:layout_width="fill_parent"
                android:layout_height="wrap_content">

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phone1"
                    android:onClick="@{()->vm.appendNumber(String.valueOf(1))}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phone2"
                    android:onClick="@{()->vm.appendNumber(String.valueOf(2))}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phone3"
                    android:onClick="@{()->vm.appendNumber(String.valueOf(3))}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />
            </TableRow>

            <!-- 第二列 -->
            <TableRow
                android:layout_width="fill_parent"
                android:layout_height="wrap_content">

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phone4"
                    android:onClick="@{()->vm.appendNumber(String.valueOf(4))}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phone5"
                    android:onClick="@{()->vm.appendNumber(String.valueOf(5))}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phone6"
                    android:onClick="@{()->vm.appendNumber(String.valueOf(6))}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />
            </TableRow>

            <!-- 第3列 -->
            <TableRow
                android:layout_width="fill_parent"
                android:layout_height="wrap_content">

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phone7"
                    android:onClick="@{()->vm.appendNumber(String.valueOf(7))}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phone8"
                    android:onClick="@{()->vm.appendNumber(String.valueOf(8))}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phone9"
                    android:onClick="@{()->vm.appendNumber(String.valueOf(9))}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />
            </TableRow>

            <!-- 第4列 -->
            <TableRow
                android:layout_width="fill_parent"
                android:layout_height="wrap_content">

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phonexin"
                    android:onClick="@{()->vm.appendNumber(@string/phonexin)}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phone0"
                    android:onClick="@{()->vm.appendNumber(String.valueOf(0))}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />

                <Button
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="@string/phonejin"
                    android:onClick="@{()->vm.appendNumber(@string/phonejin)}"
                    android:textSize="@dimen/activity_phone_bt"
                    android:background="@drawable/phone_selector_number" />
            </TableRow>

            <!-- 第5列 -->
            <TableRow
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="6dip">

                <LinearLayout
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical">

                    <!-- 清空 -->
                    <Button
                        android:layout_width="40dp"
                        android:layout_height="40dp"
                        android:textSize="@dimen/activity_phone_bt"
                        android:background="@drawable/phone_selector_min"
                        android:layout_gravity="center"
                        android:onClick="@{()->vm.clear()}"
                        android:layout_margin="6dip" />

                </LinearLayout>

                <LinearLayout
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical">

                    <!-- 拨打 -->
                    <ImageView
                        android:layout_width="46dip"
                        android:layout_height="46dip"
                        android:src="@drawable/phone_selector_call"
                        android:onClick="@{()->vm.callPhone()}"
                        android:layout_gravity="center" />

                </LinearLayout>

                <LinearLayout
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical">

                    <!-- 删除一个字符 -->
                    <Button
                        android:layout_width="60dp"
                        android:layout_height="wrap_content"
                        android:textSize="@dimen/activity_phone_bt"
                        android:background="@drawable/phone_selector_backspace"
                        android:layout_gravity="center"
                        android:onClick="@{()->vm.backspaceNumber()}"/>

                </LinearLayout>

            </TableRow>

        </TableLayout>

    </LinearLayout>
</layout>

可以看到一个小 tips,在 DataBinding 下,我们的每个控件其实是不需要声明 id 的;

接下来,我们在 MainActivity 中进行绑定;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding dataBinding; // DataBinding 初始化
    private PhoneViewModel phoneViewModel; // PhoneViewModel 初始化

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        
        // extends ViewModel
        // phoneViewModel = new ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(PhoneViewModel.class);

        // extends AndroidViewModel
        phoneViewModel = new ViewModelProvider(getViewModelStore(), new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(PhoneViewModel.class);
        // 让 ViewModel 和 DataBinding 建立关联
        dataBinding.setVm(phoneViewModel);
        // 让 LiveData 和 DataBinding 建立关联
        dataBinding.setLifecycleOwner(this);
    }
}

因为 PhoneViewModel 继承了 AndroidViewModel,所以 PhoneViewModel 实例的创建要改成

new ViewModelProvider(getViewModelStore(), new ViewModelProvider.AndroidViewModelFactory(getApplication()))
    .get(PhoneViewModel.class)

看到这里的时候,有人就要提出疑问了,为什么 xml 中的 TextView 的 android:text=“@{vm.phoneInfo}” 就可以渲染数据,而不用在 Activity 中调用 phoneInfo.observe 方法,在 onChange 回调中调用 textView.setText 方法?

上一章的 DataBinding 源码分析的时候,我们可以猜测下,应该是 ActivityMainBindingImpl 中帮我们生成了 getValue 的调用,以及 setText 的逻辑;
在这里插入图片描述

核心代码是下面这两行,让 DataBinding 和 LiveData 以及 ViewModel 组合在一起,就想 ViewPage 和 TabLayout 组合一样;

// 让 ViewModel 和 DataBinding 建立关联
dataBinding.setVm(phoneViewModel);
// 让 LiveData 和 DataBinding 建立关联
dataBinding.setLifecycleOwner(this);

这样,我们就构建了一个数据驱动UI的典型小 demo;
在这里插入图片描述

源码篇


一共使用了三个参数 this、ViewModelProvider.NewInstanceFactory()、PhoneViewModel.class

this

phoneViewModel = new ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(PhoneViewModel.class);

我们进入这个构造方法看下:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

这里调用了 owner.getViewModelStore() 方法,owner 就是我们传入的 this 当前 Activity;我们进入这个 getViewModelStore 方法看下:

public ViewModelStore getViewModelStore() {
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

这里就是创建 new ViewModelStore() ViewModelStore

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

可以看到,这里面持有了一个 HashMap 用来存储 ViewModel 的具体实现;

本质上就是我们创建的 Activity 就是这个 ViewModelStore 用来存储和这个 Activity 关联的 ViewModel;

Factory

Factory 用来反射创建我们要创建的 ViewModel 实例;我们进入这个 NewInstanceFactory 看下:

public static class NewInstanceFactory implements Factory {

    private static NewInstanceFactory sInstance;

    @NonNull
    static NewInstanceFactory getInstance() {
        if (sInstance == null) {
            sInstance = new NewInstanceFactory();
        }
        return sInstance;
    }

    @SuppressWarnings("ClassNewInstance")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        try {
            return modelClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        }
    }
}

最终通过 create 方法调用到我们传入的 class 对象的 newInstance() 方法来创建实例;

ViewModel.class

我们进入这个 get 方法看下:

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

用 class 对象的 CanonicalName 作为 key;

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        if (mFactory instanceof OnRequeryFactory) {
            ((OnRequeryFactory) mFactory).onRequery(viewModel);
        }
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

先判断 ViewModelStore 中是否有,没有则调用 Factory 的 create 方法进行创建,然后保存到 ViewModeStore 中;

为什么横竖屏切换的时候,数据不会销毁

由 AMS 可以知道,当发生横竖屏切换的时候,最终会回调到 Activity.java 中的 retainNonConfigurationInstances方法;

我们进入这个方法看下:

NonConfigurationInstances retainNonConfigurationInstances() {
    // 核心逻辑 1
    Object activity = onRetainNonConfigurationInstance();
    HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
    mFragments.doLoaderStart();
    mFragments.doLoaderStop(true);
    ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

    if (activity == null && children == null && fragments == null && loaders == null
            && mVoiceInteractor == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;
    nci.children = children;
    nci.fragments = fragments;
    nci.loaders = loaders;
    if (mVoiceInteractor != null) {
        mVoiceInteractor.retainInstance();
        nci.voiceInteractor = mVoiceInteractor;
    }
    return nci;
}

核心逻辑1:onRetainNonConfigurationInstance(); 我们进入这个方法看下:

public Object onRetainNonConfigurationInstance() {
    return null;
}

Activity.java 中是一个空实现,说明是其子类进行了实现,我们进入子类看下,点击左边的向下剪头;

public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();
    
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // 核心逻辑1 获取上一次的 NonConfigurationInstances 并获取其中的 ViewModelStore
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }
    // 核心逻辑2 将 viewModelStore 保存到 NonConfigurationInstances 中
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

核心逻辑1 获取上一次的 NonConfigurationInstances 并获取其中的 ViewModelStore,横屏切换竖屏,获取的是竖屏时候的 NonConfigurationInstances 并获取其中的 ViewModelStore 来恢复竖屏的数据;

核心逻辑2 这也就能对上,getViewModelStore 方法中,为什么会优先从 NonConfigurationInstances 中获取;

ViewModel 生命周期

在这里插入图片描述

可以看到,只有在 Activity 执行了 onDestory,ViewModel 的数据才会清空;

屏幕旋转 发生了 onDestroy 和 onCreate 为什么 ViewModel 的数据没有销毁

ComponentActivity 中也会通过 Lifecycle 监听生命周期;

getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            if (!isChangingConfigurations()) {
                getViewModelStore().clear();
            }
        }
    }
});

这里有个 isChangingConfigurations() 的判断,如果没有发生屏幕旋转,则在 Activity 销毁的时候清空 ViewModelStore,如果发生了旋转,则不会清空;

好了,ViewModel 就讲到这里吧~

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~

从零搭建YOLOv2硬件加速器:基于PYNQ-Z2的Vivado HLS与Jupyter Notebook实战 本文详细介绍了如何从零搭建YOLOv2硬件加速器,基于PYNQ-Z2开发板,结合Vivado HLS与Jupyter Notebook实现高效目标检测。通过硬件加速,YOLOv2推理速度提升8倍,功耗降低80%。文章涵盖硬件准备、Vivado HLS工程配置、IP核集成、PYNQ部署及常见问题解决,为开发者提供完整实战指南。 阅读详情

相关推荐

Lumerical FDTD新手必看:从零开始搭建光子晶体仿真环境(附完整脚本)

本文为Lumerical FDTD新手提供光子晶体仿真环境的完整搭建指南,涵盖从基础参数设置到脚本编写的全流程。通过典型光子晶体带隙分析案例,详细讲解结构建模、仿真参数配置及后处理技巧,并附赠完整MATLAB脚本,帮助用户快速掌握FDTD仿真核心技术。

3c4x5z6v7b的博客 1050

Cannot access ‘androidx.lifecycle.HasDefaultViewModelProviderFactory‘ which is a supertype of ‘com.e

Cannot access ‘androidx.lifecycle.HasDefaultViewModelProviderFactory’ which is a supertype of ‘com.example.a19.MainActivity’. Check your module classpath for missing or conflicting dependencies 今天创建了个项目,突然出现了这样的问题,怎么处理都没用,网上找了一堆没发现答案,故此记录。 增加这个依赖到你的build即

petterp的博客 5639

opencv实践项目-图片拼接之缝合线算法

opencv 图片缝合线算法

wyw0000的博客 2624

Cannot access androidx.lifecycle.HasDefaultViewModelProviderFactory

报错“Cannot access androidx.lifecycle.HasDefaultViewModelProviderFactory” 可正常运行 解决方法: 添加库: implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-runtime:2.2.0'

u013739139的博客 9736

【电磁场计算】磁偶极子在外磁场中的受力

最近在总结电磁场计算公式的时候发现,关于磁偶极子受力的计算公式,在不同文献中的说明有点出入,在此特地写一篇博客澄清一下。

sky4403的博客 7463

手把手教你用TJA1145A实现CAN节点智能唤醒(含数据帧过滤技巧)

本文详细介绍了如何使用TJA1145A收发器实现CAN节点的智能唤醒功能,包括数据帧过滤技巧和超低功耗设计。通过硬件级帧过滤和双条件唤醒机制,TJA1145A在保持微安级休眠电流的同时,能对特定报文做出毫秒级响应,显著提升电池供电设备的续航能力。

weixin_28721917的博客 249

Cannot access ‘androidx.activity.FullyDrawnReporterOwner‘ which is a supertype of

【代码】Cannot access 'androidx.activity.FullyDrawnReporterOwner' which is a supertype of。

chuyouyinghe的专栏 568

NumPy 与 Pandas 的详细应用(含实例)

本文系统介绍了Python数据分析中NumPy和Pandas两大核心库的应用。NumPy专注于高效数值计算,提供多维数组和向量化运算功能;Pandas则擅长结构化数据处理,支持数据清洗、分组统计等操作。文章详细讲解了库的安装、基础操作(数组/DataFrame创建、索引切片)、进阶功能(缺失值处理、分组聚合),并通过电商销售分析案例演示了实际应用场景。最后总结了二者的协同优势:NumPy负责底层数值计算,Pandas处理高级数据操作,配合使用可显著提升数据处理效率。建议读者从基础操作入手,结合实际案例练习,

weixin_42030577的博客 2297

无法访问 androidx.activity.result.ActivityResultCaller or ActivityResultRegistryOwner

无法访问 androidx.activity.result.ActivityResultCaller or ActivityResultRegistryOwner

mxthing的专栏 3681

麒麟OS(Kylin OS)操作系统minikube+docker安装使用及遇到的问题

一 ,安装minikube及操作工具kubectl 1,安装kubectl 使用以下命令下载最新版本: curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" 要下载指定版本(例如v1.22.0版本) ,请键入: curl -LO https://dl.k8s.io/release/v1.22.0/bin/linux/amd..

qq_23396017的博客 2643

Jetson Nano之CSI摄像头操作方法

jetson端csi摄像头的应用方法

ZONGXP的博客 1万+

如何应对 Android 面试官 -> 玩转 Jetpack Room

Room: Android官方ORM框架简化SQLite操作 摘要:Room是Android Jetpack中的官方ORM框架,相比传统SQLiteOpenHelper具有编译期SQL检查、减少模板代码、API友好等优势。使用Room涉及三个核心组件:Entity(数据表实体)、DAO(数据访问对象)和Database(数据库入口)。通过注解方式声明Entity类定义表结构,使用@Dao接口定义CRUD操作,@Database抽象类整合管理。Room结合APT技术自动生成实现类,极大简化了SQLite操作流

IT_Android的博客 1120

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

2024年Android最全Android Sunflower 带您玩转 Jetpack,阿里面试官必问

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。不断奔跑,你就知道学习的意义所在!

2401_84545804的博客 1047

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Android Jetpack之ViewModel

概述 ViewModel仍然是Model的范畴,是数据对象的载体,但是多了与视图(View)生命周期的绑定关系。可以简单理解为带有生命周期的数据对象。可在Activity, Fragment中使用,保证其在生命周期内的唯一性和一致性,不受配置的更改(例如屏幕旋转)。 使用场景 某页面屏幕旋转导致Activity/ Fragment销毁重建数据不能保存。你可能会说我用onSaveInstanceS...

QiwooMobile的博客 804

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(五)完结

这篇文章介绍了如何用Jetpack组件开发音乐播放器,重点讲解了协程替代RxJava实现网络请求的方法。主要内容包括: 协程实现:通过suspend关键字改造Retrofit API接口,简化线程切换 ViewModel中使用viewModelScope启动协程,实现主线程与异步线程的切换 展示了登录功能的协程实现示例 开始构建搜索模块,提供了fragment_search.xml布局代码片段 文章还列出了前期关于音乐播放器基础搭建、列表展示、播放功能、登录注册等系列文章链接。

IT_Android的博客 657

Android 高级工程师面试参考答案:架构设计、Jetpack 与 Compose

本文探讨了Android开发中架构设计、Jetpack组件和Compose的实践要点。主要内容包括:MVVM相比MVP的优势在于集中管理页面状态,降低维护成本;ViewModel应作为状态协调者而非业务仓库;LiveData、StateFlow和SharedFlow需区分状态和事件;Jetpack组件解决特定复杂度问题;依赖注入提升代码可维护性;MVI适合复杂状态管理;Compose本质是声明式状态驱动UI;重组优化的核心是状态范围控制。文章强调架构选择应基于实际复杂度,而非盲目追随新技术。

帅次的博客 1522

Android jetpack最佳总结和实践,字节跳动面试官

2)类图Lifecycle组件成员Lifecycle被定义成了抽象类,LifecycleOwner、LifecycleObserver被定义成了接口。组件(Activity、Fragment)实现了LifecycleOwner接口,该只有一个返回Lifecycle对象的方getLifecyle(): LifecycleRegistry。Lifecycle的内部类State标明状态、Event表示事件。

2301_79056696的博客 583

Jetpack系列(九) — WorkManager,面试官问的那些安卓原理你都懂吗

考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档,如果有需要的朋友可以私信我【面试】免费领取点击这里领取Android面试资料汇总篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档,如果有需要的朋友可以私信我【面试】免费领取点击这里领取Android面试资料汇总[外链图片转存中…(img-IxXTuInm-1710666301565)][外链图片转存中…(img-rs0k6fe8-1710666301565)]

983

JetPack(二)ViewModel小试牛刀

android:text=“Hello World!”android:textSize=“30sp”app:layout_constraintBottom_toBottomOf=“parent”app:layout_constraintEnd_toEndOf=“parent”app:layout_constraintHorizontal_bias=“0.498”app:layout_constraintLeft_toLeftOf=“parent”app:layout_constraintRight_toRi

2301_82241819的博客 830

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(一)基础搭建

本文介绍了一个基于Jetpack组件的音乐播放器开发框架,采用MVVM架构和Kotlin语言实现。核心设计采用单Activity+多Fragment模式,通过Navigation组件管理页面导航。MainActivity作为唯一Activity,集成了DrawerLayout实现侧滑菜单和SlidingUpPanelLayout实现上下滑动面板,包含三个主要导航区域:主界面、底部播放条和侧边菜单。使用ViewModel实现数据管理,确保横竖屏切换时数据不丢失。框架充分利用了Jetpack组件,遵循Googl

IT_Android的博客 799

2024年安卓最全Jetpack学习之 ViewModel,2024年最新Android的UI界面开发

我们见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。这里面就是包含了一个 Map, k 是 String 也就是ViewModel 的名称, 而 value 就是 ViewModel 的实例。

2401_84545016的博客 470
上一篇: 如何应对 Android 面试官 -> 玩转 Jetpack DataBinding
下一篇: 如何应对 Android 面试官 -> 玩转 Jetpack Room
老A说
博客等级 码龄14年 1181粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值