Jetpack的学习

高级组件开发,Jetpack的学习

Jetpack简介

Jetpack的家族还是十分强大的,主要由基础,架构,行为,界面这四个部分组成,像通知,权限,Fragment都属于Jetpack

目前Android官方最为推荐的项目架构就是MVVM,因而Jetpack中的许多组件就是专门为MVVM架构量身打造的

ViewModel

ViewModel的一个重要作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据.也就是说,只要是界面上能看到的数据,他的相关变量都应该存放在ViewModel中,而不是Activity中,这样可以在一定程度上减少Activity中的逻辑.

另外,ViewModel中还有一个非常重要的特性,当手机发生横竖屏旋转的时候,Activity会被重建,同时存放在Activity中的数据也会丢失.而VIewModel的生命周期和Activity不同,他可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当Activity退出的时候才会跟着Activity一起销毁.

ViewModel的基本用法

先加入如下的依赖:

 implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

通常来说,比较好的编程规范是给每一个Activity和Fragment都创建一个对应的ViewModel,因此这里我们就为MainActivity创建一个MainViewModel类,并且让他继承ViewModel,如下:

public class MainViewModel extends ViewModel {
    // 可以添加成员变量和方法
}

所有与界面有关的数据都应该放在ViewModel中,那么我们要实现一个计数器的功能,就可以在ViewModel中加入一个counter变量用于计数,如下:

public class MainViewModel extends ViewModel {
    private int counter = 0;

    public int getCounter() {
        return counter;
    }

    public void setCounter(int counter) {
        this.counter = counter;
    }
}

现在需要在界面上添加一个按钮,每点击一次按钮就让计数器加一,并且把最新的计数器显示在界面上

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/infoText"
        android:layout_gravity="center_horizontal"
        android:textSize="32sp" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/plusOneBtn"
        android:layout_gravity="center_horizontal"
        android:text="Plus One" />
</LinearLayout>

现在开始实现计数器的逻辑,修改MainActivity中的代码

public class MainActivity extends AppCompatActivity {
    private MainViewModel viewModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewModel = new ViewModelProvider(this).get(MainViewModel.class);

        Button plusOneBtn = findViewById(R.id.plusOneBtn);
        plusOneBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                viewModel.setCounter(viewModel.getCounter() + 1);
                refreshCounter();
            }
        });

        refreshCounter();
    }

    private void refreshCounter() {
        TextView infoText = findViewById(R.id.infoText);
        infoText.setText(String.valueOf(viewModel.getCounter()));
    }
}

需要注意的是,我们绝对不可以直接去创建ViewModel的实例,而是一定要通过ViewModelProvider来获取ViewModel的实例.

这是因为ViewModel有独立的生命周期,并且其生命周期要长于Activity.如果我们在onCreate()方法中创建ViewModel的实例,那么每次onCreate()方法执行的时候,ViewModel都会创建一个新的实例,这样当手机屏幕发生旋转的时候,就无法保留其中的数据.

此时就可以开始运行了.

向ViewModel传递参数

由于所有ViewModel的实例都是通过ViewModelProvider来获取的,因此我们没有任何地方可以向VIewModel的构造函数中传递参数.

当然,这个问题也不难解决,只需要借助ViewModelProvider.Factory就可以实现了.

现在的计数器虽然在屏幕旋转的时候不会丢失数据,但是如果退出程序之后再重新打开,那么之前的计数就会被清零了.现在就要保证即使在退出程序后又重新打开的情况下,数据仍然不会丢失.

实现这个功能就需要在 退出程序的时候对当前的计数进行保存,然后在重新打开程序的时候读取之前保存的计数,并传递给MainViewModel,因此,这里修改MainViewModel中的代码,如下

import androidx.lifecycle.ViewModel;

public class MainViewModel extends ViewModel {
    private int counter;

    public MainViewModel(int countReserved) {
        this.counter = countReserved;
    }

    public int getCounter() {
        return counter;
    }

    public void setCounter(int counter) {
        this.counter = counter;
    }
}

接下来的问题是如何向MainViewModel的构造函数传递数据了,就需要使用ViewModelProvider.Factory,下面看看如何实现.

新建一个MainViewModelFactory类,并让他实现ViewModelProvider.Factory接口

public class MainViewModelFactory implements ViewModelProvider.Factory {
    private int countReserved;

    public MainViewModelFactory(int countReserved) {
        this.countReserved = countReserved;
    }

    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {

            return (T) new MainViewModel(countReserved);

    }
}

在布局中添加一个清零按钮

<Button
    android:id="@+id/clear"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="清0"/>

然后修改MainActivity中的代码

public class MainActivity extends AppCompatActivity {
    private MainViewModel viewModel;
    private SharedPreferences sp;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sp = getPreferences(Context.MODE_PRIVATE);
        int countReserved = sp.getInt("count_reserved", 0);
        viewModel = new ViewModelProvider(this, new MainViewModelFactory(countReserved))
                .get(MainViewModel.class);

        Button plusOneBtn = findViewById(R.id.plusone);
        plusOneBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                viewModel.setCounter(viewModel.getCounter() + 1);
                refreshCounter();
            }
        });

        Button clearBtn = findViewById(R.id.clear);
        clearBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                viewModel.setCounter(0);
                refreshCounter();
            }
        });

        refreshCounter();
    }

    @Override
    protected void onPause() {
        super.onPause();
        SharedPreferences.Editor editor = sp.edit();
        editor.putInt("count_reserved", viewModel.getCounter());
        editor.apply();
    }

    private void refreshCounter() {
        TextView infoText = findViewById(R.id.textview);
        infoText.setText(String.valueOf(viewModel.getCounter()));
    }
}

sp 是之前创建的 SharedPreferences 对象的引用,用于访问应用程序的私有存储空间。

getInt(“count_reserved”, 0) 是一个方法调用,用于从 SharedPreferences 中获取名为 “count_reserved” 的键对应的整数值。这个方法接受两参数:

“count_reserved”:作为键的名称,用于检索相应的值。
0:默认值,如果 “count_reserved” 键不存在,就会返回这个默认值。
如果 “count_reserved” 键存在于 SharedPreferences 中,那么 getInt() 方法将返回存储的整数值。如果 “count_reserved” 键不存在,它将返回默认值 0。

这段代码通常用于初始化应用程序的变量或状态,以确保在第一次运行应用程序或用户之前没有指定值时,能够有一个合理的默认值。在这种情况下,countReserved 变量将存储从 SharedPreferences 中读取的整数值,用于后续的操作,例如创建 MainViewModel 实例。
@Override
protected void onPause() {
super.onPause();
SharedPreferences.Editor editor = sp.edit();
editor.putInt(“count_reserved”, viewModel.getCounter());
editor.apply();
}

super.onPause();:首先,调用了父类的 onPause() 方法,以确保执行了父类的默认操作。

SharedPreferences.Editor editor = sp.edit();:创建了一个 SharedPreferences.Editor 对象,并将其赋值给名为 editor 的变量。SharedPreferences.Editor 用于编辑 SharedPreferences 对象中的数据,允许你添加、修改或删除键值对。

editor.putInt(“count_reserved”, viewModel.getCounter());:使用 editor 对象,将名为 “count_reserved” 的键和 viewModel 中的 counter 值存入 SharedPreferences。具体来说,它将 viewModel 中的 counter 值写入 SharedPreferences 的 “count_reserved” 键中,以便在应用程序的下一次运行时使用。

editor.apply();:最后,调用 apply() 方法来提交对 SharedPreferences 的编辑操作。这确保了数据的修改被立即应用并保存到 SharedPreferences 中。

总结:这段代码的目的是在用户离开应用程序(暂停或停止活动)时将 viewModel 中的 counter 值保存到应用程序的 SharedPreferences 中,以便下次应用程序运行时可以恢复这个值。这通常用于保持应用程序状态,以确保用户在应用程序暂停或重新启动时不会丢失之前的数据。

Lifecycles

Lifecycles组件就是为了可以让任何一个类都能轻松的感知到Activity的生命周期,同时又不需要在Activity中编写大量的逻辑处理.

新建一个MyObserver类,并让它实现LifecycleObserver接口

public class MyObserver implements LifecycleObserver {
    private Lifecycle lifecycle;

    public MyObserver() {
        this.lifecycle = lifecycle;
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    public void activityStart() {
        Log.d("MyObserver", "activityStart");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    public void activityStop() {
        Log.d("MyObserver", "activityStop");
    }
}

我们在方法上使用了@OnLifecycleEvent注解,并传入了一种生命周期事件.生命周期的事件类型一共有7种:ON_CREATE,ON_START,ON_PAUSE,ON_STOP和ON_DESTORY分别匹配Activity中相应的生命周期回调;另外还有一种ON_ANY类型,表示可以匹配Activity的任何周期回调

但是到这里还是无法正常工作的,因为当Activity的生命周期发生变化的时候并没有人去通知MyOberserver,这个时候就得借助LifecycleOwner了.

LifecycleOwner在MainActivity中的声明,需添加:

  MyObserver myObserver = new MyObserver();
  lifecycle.addObserver(myObserver);

不过当前MyObserver虽然能够感知到Activity的生命周期发生了变化,却没有办法主动获知当前的生命周期状态.此时需要在MyObserver的构造函数中将Lifecycle传进来即可.所以,此时的MyObersver中的内容

public class MyObserver implements LifecycleObserver {
    private Lifecycle lifecycle;

    public MyObserver() {
        this.lifecycle = lifecycle;
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    public void activityStart() {
        Log.d("MyObserver", "activityStart");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    public void activityStop() {
        Log.d("MyObserver", "activityStop");
    }
}

有了Lifecycle对象之后,我们就可以在任何地方调用lifecycle.currentState来主动获知当前的生命周期状态.lifecycle.currentState返回的生命周期是一个枚举类型,一共有INITALIZED,DESTROYED,CREATED,STARTED,RESUMED这五种状态

LiveData

LiveData是Jetpack提供的一种响应式编程软件,他可以包含任何类型的数据,并在数据发生变化的时候通知给观察者.LiveData特别适合与ViewModel结合在一起使用.

LiveData的基本用法

我们一直使用的都是在Activity中手动获取ViewModel中的数据这种交互方式,但是ViewModel却无法将数据的变化主动通知给Activity.

或许可能这样想,把Activity的实例传给ViewModel,这样ViewModel就可以主动对Activity进行通知了?千万不可以这样做.ViewModel的生命周期是长于Activity的,如果把Activity的实例传给ViewModel,就很有可能因为Activity无法释放内存而造成内存泄漏,这是非常错误的写法.

而解决这个问题就需要用到LiveData.LiveData可以包含任何类型的数据,并在数据发生变化的时候通知给观察者.也就是说,我们将计数使用LiveData来包装,然后再Activity中去观察它,就可以主动将数据变化通知给Activity了

修改MainViewModel中的代码,如下:

import androidx.lifecycle.ViewModel;
import androidx.lifecycle.MutableLiveData;
public class MainViewModel extends ViewModel {
    private MutableLiveData<Integer> counter = new MutableLiveData<>();

    public MainViewModel(int countReserved) {
        counter.setValue(countReserved);
    }

    public MutableLiveData<Integer> getCounter() {
        return counter;
    }

    public void plusOne() {
        Integer count = counter.getValue();
        if (count == null) {
            count = 0;
        }
        counter.setValue(count + 1);
    }

    public void clear() {
        counter.setValue(0);
    }
}

我们将counter变量修改成了一个MutableLiveData对象,并指定它的泛型为Int,表示它包含的是整型数据.MutableLiveData是一种可变的LiveDta,他的用法非常简单,主要有3种读写数据的方法,分别是getValue(),setValue()和postValue()方法.getValue()用于获取LiveData中包含的数据,setValue()用于给LiveData设置数据,但是只能在主线程中调用;postValue()方法用于在非主线程中给LiveData设置数据

在init结构体中给counter设置数据,这样之前保存的计数值就可以在初始化的时候得到恢复.新增的plusOne()和clear()这两个方法,分别用于给计数加1以及将计数清零.plusOne()方法中的逻辑是先获取counter中包含的数据,然后给它加一,在重新设置到counter当中

修改MainActivity中的代码:

public class MainViewModel extends ViewModel {
    private MutableLiveData<Integer> counter = new MutableLiveData<>();

    public MainViewModel(int countReserved) {
        counter.setValue(countReserved);
    }

    public MutableLiveData<Integer> getCounter() {
        return counter;
    }

    public void plusOne() {
        Integer count = counter.getValue();
        if (count == null) {
            count = 0;
        }
        counter.setValue(count + 1);
    }

    public void clear() {
        counter.setValue(0);
    }
}

在"Plus One"按钮的点击事件中我们去调用MainViewModel的plusOne()方法,而在"Clear"按钮的点击事件中应该去调用MainViewModel的clear()方法.

接下来,最为关键,调用了viewModel.counter的observer()方法来观察数据的变化.经过对MainViewModel的改造,现在counter变量已经变成了一个LiveData对象,任何LiveData对象都可以调用它的observe()方法来观察数据的变化.observe()方法本身接收两个参数:第一个参数是一个LifecycleOwner对象,Activity本身就是一个LifecycleOwner对象,因此直接传this就好;第二个参数是一个Observer接口,当counter中包含的数据发生变化时,就会回调到这里,因此我们将最新的技术回调到这里即可.

添加依赖:

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'

修改MainViewMoel中的代码:

public class MainViewModel extends ViewModel {
    private MutableLiveData<Integer> _counter = new MutableLiveData<>();

    public MainViewModel(int countReserved) {
        _counter.setValue(countReserved);
    }

    public MutableLiveData<Integer> getCounter() {
        return _counter;
    }

    public void plusOne() {
        Integer count = _counter.getValue();
        _counter.setValue(count != null ? count + 1 : 1);
    }
    public void clear() {
        _counter.setValue(0);
    }
}

其中,可以看到,将原来的counter变量改名为_counter变量,并给它加上private修饰符,这样_counter变量对于外部就是不可见的.然后我们又重新定义了一个counter变量,将他的类型声明为不可变的LiveData,并在它的get()属性方法中返回_counter变量.

map和switchMap

map()方法:

这个方法的基本作用就是将实际包含数据的LiveData和仅用于观察的LiveData进行转换.

有一个例子,具体如下:

public class MainViewModel extends ViewModel {
    private MutableLiveData<User> userLiveData = new MutableLiveData<>();
    public LiveData<String> userName = Transformations.map(userLiveData, user -> user.getFirstName() + " " + user.getLastName());
    
    public MainViewModel(int countReserved) {
        // 构造函数的逻辑,如果需要的话,可以在这里初始化数据
    }
}

这里可以看到,调用了Transformations的map()方法对LiveData的数据类型进行转换.map()方法接收两个参数:第一个参数是原始的LivaData对象;第二个参数是一个转换函数,我们在转换函数里编写具体的逻辑即可.这里的逻辑也很简单,就是将User对象转换成一个只包含用户姓名的字符串.

另外还将userLiveData声明成了private,以保证数据的封装性.外部使用的时候只要观察userName这个LivaData就可以了.当userLivaData的数据发生变化时,map()方法会监听到变化并执行转换函数中的逻辑,然后再将转换之后的数据通知给userName的观察者

switchMap()方法

前面我们所学的内容都有一个前提:LiveData对象的实例都是在ViewModel中创建的.然而在实际的项目中,不可能一直是这种理想情况,很有可能ViewModel中的某个LiveData对象是调用另外的方法获取的

新建一个Repository单例类

public class Repository {
    public LiveData<User> getUser(String userId) {
        MutableLiveData<User> liveData = new MutableLiveData<>();
        liveData.setValue(new User(userId, userId, 0));
        return liveData;
    }
}

这里在Repository类中添加了一个getUser()方法,这个方法接收一个userId参数.按照正常的逻辑,我们应该根据传入的userId参数去服务器请求或者到数据库中查找相应的User对象,但是这里只是示例,因此每次将传入的userId当做用户姓名来创建一个新的User对象即可.

需要注意的是,getUser()方法返回的是一个包含User数据的LiveData对象,而且每次调用getUser()方法都会返回一个新的LiveData实例.

然后我们在MainViewModel中也定义一个getUser()方法,并且让它调用Repository的getUser()方法来获取LiveData对象:

public LiveData<Repository.User> getUser(String userId) {
        return Repository.getUser(userId);
    }

接下来的问题就是,在Activity中如何观察LiveData的数据变化呢?既然getUser()方法返回的是一个LiveData对象,那么可不可以直接在Activity中使用如下呢?

viewModel.getUser(userId).observe(this, user -> {
    // 在这里处理观察到的用户数据
});

这么做是完全错误的.因为每次调用getUser()方法返回的都是一个新的LiveData实例,而上述写法会一直观察老的LiveData实例,从而根本无法观察到数据的变化.你会发现,这种情况下的LiveData是不可观察的.

这个时候,switchMap()方法就可以派上用场了.它的使用场景非常固定:如果ViewModel中的某个LiveData对象是调用另外的方法获取的,那么我们就可以借助switchMap()方法,将这个LiveData对象转成另外一个可观察的LiveData对象.

修改MainViewModel中的代码:

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;

public class MainViewModel extends ViewModel {
    private MutableLiveData<String> userIdLiveData = new MutableLiveData<>();
    public LiveData<User> user = Transformations.switchMap(userIdLiveData, userId -> Repository.getUser(userId));

    public void getUser(String userId) {
        userIdLiveData.setValue(userId);
    }
}

这里新定义了一个新的userIdLiveData对象,用来观察userId的数据变化,然后调用了Transformations的switchMap()方法,用来对另一个可观察的LiveData对象进行转换.

switchMap()方法同样接收两个参数:第一个参数传入我们新增的userIdLiveData,switchMap()会对它进行观察;第二个参数是一个转换函数,注意,我们必须在这个转换函数中返回一个LiveData对象,因为switchMap()方法的工作原理就是要将转换函数中返回的LiveData对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值