ViewModel和LiveData的简单使用

2020-03-22  本文已影响0人  瑜小贤

首先贴下google链接,先看看官方介绍会好一些
ViewModel 概览
LiveData 概览
android-lifecycles示例

ViewModel

举个例子:我们在界面上有一个计时器,记录我们在这个界面停留的时间,但是当我们旋转屏幕的时候,会导致Activity重新创建实例,onCreate()方法会再次执行,导致计时器会重新从0开始计时。

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.android.lifecycles.step1.ChronoActivity1">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/hello_textview"/>

    <Chronometer
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/hello_textview"
        android:layout_centerHorizontal="true"
        android:id="@+id/chronometer"/>
</RelativeLayout>
public class ChronoActivity1 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //在onCreate()方法中开始倒计时
        Chronometer chronometer = findViewById(R.id.chronometer);
        long startTime = SystemClock.elapsedRealtime();
       //每次onCreate()方法都会重新设置base
        chronometer.setBase(startTime);
        chronometer.start();
    }
}

当然我们可以通过其他手段解决这个问题,例如当屏幕旋转的时候不让Activity重新创建实例。
或者我们可以在onSaveInstanceState()方法中保存相应的数据,然后当Activity重新创建实例
的时候,我们在onCreate()方法中获取保存的数据,然后设置计时器的开始时间。然后我们再
看看ViewModel的表现。

自定义一个ChronometerViewModel继承ViewModel

public class ChronometerViewModel extends ViewModel {

    @Nullable
    private Long mStartTime;

    @Nullable
    public Long getStartTime() {
        return mStartTime;
    }

    public void setStartTime(final long startTime) {
        this.mStartTime = startTime;
    }
}
        // 创建或者直接返回一个已经存在的ViewModel
        ChronometerViewModel chronometerViewModel = ViewModelProviders.of(this)
        .get(ChronometerViewModel.class);
        if (chronometerViewModel.getStartTime() == null) {
            //chronometerViewModel如果没设置过开始时间,那么说明这个新的ViewModel,
           //所以给它设置开始时间
            long startTime = SystemClock.elapsedRealtime();
            chronometerViewModel.setStartTime(startTime);
            chronometer.setBase(startTime);
        } else {
            //否则ViewModel已经在上个Activity的onCreate()方法中创建过了,屏幕旋转以后,
            //ViewModel会被保存,我们直接获取ViewModel里持有的时间
            chronometer.setBase(chronometerViewModel.getStartTime());
        }
        chronometer.start();

这样就可以解决屏幕旋转以后重新从0开始计时的问题了。
我们看一下关键代码

ChronometerViewModel chronometerViewModel = ViewModelProviders.of(this)
    .get(ChronometerViewModel.class);

ViewModelProvidersof()方法,只看方法的注释,不看方法体

    /**
     * 创建一个ViewModelProvider ,只要传入的Activity存活,ViewModelProvider 就会被一直保留
     * @param activity 一个activity, 在谁的生命周期内,ViewModel会被保留
     * @return 一个ViewModelProvider 实例
     */
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        initializeFactoryIfNeeded(checkApplication(activity));
        return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
    }

看一下ViewModel被保留的周期

ViewModelScope
注意:Activity执行了onDestroy()方法并不带表这个Activity就结束了。可以通过ActivityisFinishing()方法来判断。我们发现,在旋转屏幕的时候isFinishing()方法返回false。在按下返回键的时候isFinishing()为true。
@Override
protected void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy: " + isFinishing());
}

ViewModel:ViewModel是一个用来为Activity或者Fragment准备和管理数据的类。ViewModel也可以用来处理Activity/Fragment和应用其他部分的通信。

一个ViewModel的创建总是和一个作用域(一个 Activity/Fragment)有关,并且只要这个作用域存活,那么这个ViewModel会被一直保留。例如,如果作用域是一个Activity,那么ViewModel会保留直到Activity结束。

换句话说,这意味着如果ViewModel的所有者,例如一个Activity由于旋转而被销毁,但是ViewModel并不会销毁,新创建的Activity的实例仅仅是重新关联到已经存在的ViewModel
ViewModel存在的目的是为了获取并保持对Activity/Fragment重要的信息。Activity/Fragment 应该能够观察到 ViewModel的变化。
ViewModel通常通过LiveData或者Data Binding来暴露信息。也可以通过其他任何可观察的对象,例如RxJava中的Observable

ViewModel的唯一的作用是管理UI的数据。ViewModel不能访问UI或者持有Activity/Fragment的引用。
再看一个官网的例子

public class MyViewModel extends ViewModel {
        private MutableLiveData<List<User>> users;
        public LiveData<List<User>> getUsers() {
            if (users == null) {
                users = new MutableLiveData<List<User>>();
                loadUsers();
            }
            return users;
        }

        private void loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }

可以在MyActivity中访问该列表

public class MyActivity extends AppCompatActivity {
        public void onCreate(Bundle savedInstanceState) {
            // Create a ViewModel the first time the system calls an activity's onCreate() method.
            // Re-created activities receive the same MyViewModel instance created by the first activity.

            MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
            model.getUsers().observe(this, users -> {
                // update UI
            });
        }
    }
    
在Fragment之间共享ViewModel

举个例子:在一个Activity中有两个Fragment,每个Fragment里面都有一个SeekBar。当其中一个SeekBar进度改变的时候,也更新另外一个Fragment里面的SeekBar的进度。
Activity什么也没做,就是布局文件里有两个Fragment

public class Activity_step5 extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_step5_solution);
    }
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.android.lifecycles.step5_solution.Activity_step5">

    <fragment
        android:id="@+id/fragment1"
        android:name="com.example.android.lifecycles.step5_solution.Fragment_step5"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <fragment
        android:id="@+id/fragment2"
        android:name="com.example.android.lifecycles.step5_solution.Fragment_step5"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>
public class Fragment_step5 extends Fragment {

    private SeekBar mSeekBar;

    private SeekBarViewModel mSeekBarViewModel;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_step5, container, false);
        mSeekBar = root.findViewById(R.id.seekBar);

        mSeekBarViewModel = ViewModelProviders.of(getActivity())
.get(SeekBarViewModel.class);

        subscribeSeekBar();

        return root;
    }

    private void subscribeSeekBar() {
        // Update the ViewModel when the SeekBar is changed.
        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, 
boolean fromUser) {
                //如果是用户改变了seekbar的进度就更新ViewModel
                if (fromUser) {
                    Log.d("Step5", "Progress changed!");
                    mSeekBarViewModel.seekbarValue.setValue(progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) { }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) { }
        });
        // 当ViewModel改变了时候,更新seekBar的进度
        mSeekBarViewModel.seekbarValue.observe(getActivity(), new Observer<Integer>() {
            @Override
            public void onChanged(@Nullable Integer value) {
                if (value != null) {
                    mSeekBar.setProgress(value);
                }
            }
        });
    }
}

运行程序,可以看到当手动改变其中一个SeekBar的进度,另外一个也会跟着变。

LiveData

上面说了:

ViewModel通常通过LiveData或者Data Binding来暴露信息。也可以通过其他任何可观察的对象,例如RxJava中的ObserVable

LiveData 与普通的Observable不同,LiveData是生命周期感知的,这意味着它尊重其他应用程序组件的生命周期,例如ActivityFragmentServiceLiveData生命周期感知能力确保 LiveData仅仅去更新那些处于生命周期活动状态的观察者。

接着上面的例子:
我们想在Activity之外,每隔一秒钟,更新Activity的UI。
新建一个LiveDataTimerViewModel

public class LiveDataTimerViewModel extends ViewModel {

    private static final int ONE_SECOND = 1000;
    //新建一个LiveData实例
    private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();

    private long mInitialTime;

    public LiveDataTimerViewModel() {
        mInitialTime = SystemClock.elapsedRealtime();
        Timer timer = new Timer();

        // 每隔一秒更新一次
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                final long newValue = 
(SystemClock.elapsedRealtime() - mInitialTime) / 1000;
                // setValue() 不能再后台线程调用,所以使用post到主线程
                mElapsedTime.postValue(newValue);
            }
        }, ONE_SECOND, ONE_SECOND);

    }

    public LiveData<Long> getElapsedTime() {
        return mElapsedTime;
    }
}
public class ChronoActivity3 extends AppCompatActivity {

    private LiveDataTimerViewModel mLiveDataTimerViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.chrono_activity_3);

        mLiveDataTimerViewModel = ViewModelProviders.of(this)
.get(LiveDataTimerViewModel.class);

        subscribe();
    }

    /**
     * 新建一个Observer,然后订阅 mLiveDataTimerViewModel.getElapsedTime()
     */
    private void subscribe() {
        final Observer<Long> elapsedTimeObserver = new Observer<Long>() {
            @Override
            public void onChanged(@Nullable final Long aLong) {
                String newText = ChronoActivity3.this.getResources().getString(
                        R.string.seconds, aLong);
                ((TextView) findViewById(R.id.timer_textview)).setText(newText);
                Log.d("ChronoActivity3", "Updating timer");
            }
        };

        mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
    }
}

运行上面的代码你发现,只有在Activity活动的时候(也就是生命周期状态是STARTEDRESUMED的时候),log日志才会输出,当你点击HOME键或者打开其他的APP的时候,log日志不会输出。当你回到Activity的时候,日志才会接着输出。

看一下LiveDataobserve()方法

/**
    * 把给定的观察者添加到观察者列表中,事件是在主线程分发的。如果LiveData已经被设置了
    * 数据,那么数据会被发送给这个新添加的观察者。
    *
    * 只有当生命周期持有者owner处在活动状 态的时候{@link Lifecycle.State#STARTED} or
    * {@link Lifecycle.State#RESUMED} ,这个 observer 才会收到事件。
    *
    * 如果owner 到了销毁状态 {@link Lifecycle.State#DESTROYED},这个observer 会被自动移
    * 除。
    * 
    * 当这个owner处于不活动的状态的时候,如果数据改变了,这个observer不会收到任何更
    * 新。当owner重新回到了active的状态,这个oberver会自动收到 最新的数据
    * 
    * 只要指定的LifecycleOwner 没有被销毁,LiveData 就一直持有observer和owner的强应用
    *当LifecycleOwner 被销毁了,LiveData 会移除obser和owner的引用
    *
    * 如果指定的owner已经处于销毁状态,方法直接返回。 {@linkLifecycle.State#DESTROYED} 
    * 
    * 如果指定的owner和oberver元组已经在观察这列表里了,方法直接返回
    *
    * 如果observer已经和另外一个关联owner在观察者列表里了,LiveData 抛出
    *IllegalArgumentException
    *
    * @param owner    生命周期持有者,用来控制observer
    * @param observer 观察者,用来接收事件
    */
    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && existing.owner != wrapper.owner) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

注意,AppCompatActivitySupportActivity的子类,而SupportActivity实现了LifecycleOwner。所以AppCompatActivity是一个生命周期持有者。

public class SupportActivity extends Activity implements LifecycleOwner {
...
}

订阅生命周期事件

我们已经知道SupportActivity是一个生命周期持有者了。一个生命周期持有者会在不同的生命周期发出不同的生命周期事件。我们可以观察这些事件,并根据这些事件,进行基于生命周期的操作。
LifecycleOwner 获取Lifecycle的方法

public interface LifecycleOwner {
    /**
     * 返回当前生命周期持有者的生命周期
     */
    @NonNull
    Lifecycle getLifecycle();
}

Lifecycle类用来定义具有Android生命周期的对象。
LifecycleON_CREATE, ON_START, ON_RESUME 事件会在生命周期持有者的相应的生命周期方法后才发出。ON_PAUSE, ON_STOP, ON_DESTROY 事件会在生命周期持有者相应的生命周期方法之前发出。 例如, ON_START 会在onStart方法之后发出,ON_STOP 会在onStop方法之前发出。

Lifecycle类的内部类Lifecycle.Event是一个枚举类,定义了生命周期持有者发出的所有事件类型

枚举值 描述
ON_ANY 可以用来匹配任何事件
ON_CREATE onCreate事件
ON_DESTROY onDestroy事件
ON_PAUSE onPause事件
ON_RESUME onResume事件
ON_CREATE onCreate事件
ON_START onStart事件
ON_STOP onStop事件

Lifecycle类的内部类Lifecycle.State也是一个枚举类,定义了生命周期持有者所有的生命周期状态。如下图所示。

state

举个例子:我们想在Activity活动的时候,注册一个LocationListener来获取位置信息,然后在onPause的时候,移除监听器,那我们可以通过Activity的生命周期事件来实现。

private class MyLocationListener implements LocationListener {
        @Override
        public void onLocationChanged(Location location) {
            //位置改变的时候,改变界面上的经纬度
            TextView textView = findViewById(R.id.location);
            textView.setText(location.getLatitude() + ", " + location.getLongitude());
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
        }

        @Override
        public void onProviderEnabled(String provider) {
            Toast.makeText(LocationActivity.this,
                    "Provider enabled: " + provider, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onProviderDisabled(String provider) {
        }
    }

要观察生命周期事件,首先要实现LifecycleObserver接口。
BoundLocationListener类实现了 LifecycleObserver接口,当生命周期持有者LifecycleOwner 处于ON_RESUME的状态的时候,我们获取定位服务,并在位置变化的时候通知LocationListener更新信息。当生命周期持有者LifecycleOwner 处于ON_PAUSE的状态的时候我们移除LocationListener

public class BoundLocationManager {

    public static void bindLocationListenerIn(LifecycleOwner lifecycleOwner,
                                              LocationListener listener, Context context) {
        new BoundLocationListener(lifecycleOwner, listener, context);
    }

    @SuppressWarnings("MissingPermission")
    static class BoundLocationListener implements LifecycleObserver {
        private final Context mContext;
        private LocationManager mLocationManager;
        private final LocationListener mListener;

        public BoundLocationListener(LifecycleOwner lifecycleOwner,
                                     LocationListener listener, Context context) {
            mContext = context;
            mListener = listener;
            lifecycleOwner.getLifecycle().addObserver(this);
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        void addLocationListener() {
            mLocationManager =
                    (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
            mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
 mListener);
            Log.d("BoundLocationMgr", "Listener added");

            // Force an update with the last location, if available.
            Location lastLocation = mLocationManager.getLastKnownLocation(
                    LocationManager.GPS_PROVIDER);
            if (lastLocation != null) {
                mListener.onLocationChanged(lastLocation);
            }
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        void removeLocationListener() {
            if (mLocationManager == null) {
                return;
            }
            mLocationManager.removeUpdates(mListener);
            mLocationManager = null;
            Log.d("BoundLocationMgr", "Listener removed");
        }
    }
}

然后将生命周期持有者和生命周期事件观察者绑定。这里忽略定位权限的处理。

public class LocationActivity extends AppCompatActivity {
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.location_activity);
            bindLocationListener();
        }

 private void bindLocationListener() {
        BoundLocationManager.bindLocationListenerIn(this, mGpsListener, 
getApplicationContext());
    }

}

运行程序,不断旋转手机的时候,输入如下

D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed

感谢:https://www.jianshu.com/p/721cdcdf11b2

上一篇下一篇

猜你喜欢

热点阅读