美文网首页
ViewModel和LiveData的简单使用

ViewModel和LiveData的简单使用

作者: 瑜小贤 | 来源:发表于2020-03-22 00:30 被阅读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

    相关文章

      网友评论

          本文标题:ViewModel和LiveData的简单使用

          本文链接:https://www.haomeiwen.com/subject/lxgpyhtx.html