Android Jetpack組件學習 ViewModel & LiveData

一、簡介

  • ViewModel - 提供了一種創建和檢索綁定到特定生命週期的對象的方法。ViewModel通常存儲視圖數據的狀態,並與其他組件通信,例如數據存儲庫或處理業務邏輯層。
  • LifecycleOwner / LifecycleRegistryOwner -無論是LifecycleOwnerLifecycleRegistryOwner都是AppCompatActivitySupport Fragment類實現的接口。您可以將其他組件訂閱到實現這些接口的所有者對象,以觀察對所有者生命週期的更改。
  • LiveData - 允許您觀察應用程序的多個組件之間的數據更改,而無需在它們之間創建明確,嚴格的依賴關係路徑。LiveData尊重應用程序組件的複雜生命週期,包括activity,fragment,service或任何定義在app中的LifecycleOwnerLiveData通過暫停對已停止的LifecycleOwner對象的訂閱以及取消對已完成的LifecycleOwner對象的訂閱。

二、實踐

1、使用viewModel

先寫一個有一個計時器的activity

<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"/>
 Chronometer chronometer = findViewById(R.id.chronometer);
 chronometer.start();

如果配置更改(例如屏幕旋轉)破壞活動時會發現計時器被重置
添加ViewModel
使用 ViewModel在activity或fragment的整個生命週期中保留數據。要管理應用程序數據,activity是一個糟糕的選擇。如之前所示activity和fragment是短暫的對象,當用戶與應用程序交互時,這些對象會頻繁創建和銷燬。此外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;
    }
}

在activity中添加代碼

//ViewModelStore提供了一個新的視圖模型或之前創建的視圖模型。
ChronometerViewModel chronometerViewModel
                = ViewModelProviders.of(this).get(ChronometerViewModel.class);

 if (chronometerViewModel.getStartTime() == null) {
      //它是一個新的ViewModel
      long startTime = SystemClock.elapsedRealtime();
      chronometerViewModel.setStartTime(startTime);
      chronometer.setBase(startTime);
  } else {
      //否則保留了視圖模型,將chronometer初始化
      chronometer.setBase(chronometerViewModel.getStartTime());
  }

這時會發現計時器的狀態沒有被重置
this指的是一個LifecycleOwner實例。只要LifecycleOwner的作用域是活動的,框架就保持ViewModel的活動狀態。如果其所有者因配置更改(例如屏幕旋轉)而被銷燬,ViewModel不會被銷燬。所有者的新實例重新連接到現有的ViewModel,如下圖所示:
ViewModel

注意

activity或fragment的範圍從created到finished(或terminated),您不能將其與已destroyed混淆。請記住,當設備旋轉時,activity將被銷燬,但與之關聯的任何ViewModel實例不會被銷燬。
系統在生命週期所有者的整個生命週期(例如activity或fragment)中將ViewModel實例保存在內存中。系統不會將ViewModel實例持久化到持久存儲中。
#####2、使用LiveData包裝數據
現在使用Timer自定義一個計時器,此邏輯添加到LiveDataTimerViewModel類中,並使activity專注於管理用戶和UI之間的交互。
當計時器通知時,activity會更新UI。爲了避免內存泄漏,ViewModel不包括對活動的引用。例如,配置更改(例如屏幕旋轉)可能導致ViewModel中的引用被引用到應該進行垃圾回收的activity 。系統將保留ViewModel的實例,直到相應的activity或生命週期所有者不再存在。

注意

在ViewModel中存儲對 ContextView的引用可能導致內存泄漏,避免使用引用Context或View類實例的字段,onCleared()方法對於取消訂閱或清除對具有更長生命週期的其他對象的引用非常有用,但不能用於清除對Context或View對象的引用。
您可以將活動或片段配置爲觀察數據源,在更改時接收數據,而不是直接從ViewModel修改視圖。這稱爲觀察者模式。要將數據公開爲可觀察對象,請將該類型包裝在LiveData類中。如果您使用了數據綁定庫或其他響應庫(如RxJava),您可能熟悉觀察者模式。 LiveData是一個特殊的可觀察類,它能感知生命週期,並只通知活躍的觀察者。

新建一個LifecycleActivity ,它可以提供生命週期的狀態 ,LifecycleRegistryOwner用於將ViewModel和LiveData實例的生命週期綁定到activity 或fragment(26.1.0 高版本後的 Activity 已經實現了LifecycleOwner 接口,可以不用手動實現)

public class LifecycleActivity extends FragmentActivity
                             implements LifecycleRegistryOwner {...}

新建LiveDataTimerViewModel

public class LiveDataTimerViewModel extends ViewModel {

    private static final int ONE_SECOND = 1000;

    private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();

    private long mInitialTime;

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

        // Update the elapsed time every second.
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
                // setValue() cannot be called from a background thread so post to main thread.
                mElapsedTime.postValue(newValue);
            }
        }, ONE_SECOND, ONE_SECOND);

    }

    public LiveData<Long> getElapsedTime() {
        return mElapsedTime;
    }
}

activity中調用

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

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

可以發現計時器不會隨着屏幕旋轉而重置

3、訂閱生命週期事件

如果沒有在相應生命週期取消訂閱或停止組件或庫,可能會導致內存泄漏。
可以將lifecycle owner對象傳遞給具有lifecycle-aware組件的新實例,以確保它們知道生命週期的當前狀態,可以使用以下語句查詢生命週期的當前狀態

lifecycleOwner.getLifecycle().getCurrentState()

上面的語句返回一個狀態,例如Lifecycle.State.RESUMED,或Lifecycle.State.DESTROYED

實現的生命週期感知對象LifecycleObserver還可以觀察生命週期所有者狀態的變化

lifecycleOwner.getLifecycle().addObserver(this);

您可以對對象使用註解,以指示它在需要時調用適當的方法

@OnLifecycleEvent(Lifecycle.EVENT.ON_RESUME)
void addLocationListener() { ... }

下面創建一個activity,使用LoactionManager獲取當前經緯度顯示給用戶,訂閱更改並使用LiveData自動更新UI,根據activity狀態的更改,創建註冊和註銷LocationManager。
一般我們會在onStart(),onResume()中註冊監聽,onStop(),onPause()中取消註冊

@Override
protected void onResume() {
    mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mListener);
}

@Override
protected void onPause() {
    mLocationManager.removeUpdates(mListener);
}

下面新建一個類BoundLocationManager,要讓類觀察活動的生命週期,必須將其添加爲觀察者。爲此,將BoundLocationManager對象通過將以下代碼添加到其構造函數來觀察生命週期

lifecycleOwner.getLifecycle().addObserver(this);

要在發生生命週期更改時調用方法,可以使用@OnLifecycleEvent註解。使用BoundLocationListener類中的以下註解更新addLocationListener()和removeLocationListener()方法:

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void addLocationListener() {
    ...
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
void removeLocationListener() {
    ...
}
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() {
            // Note: Use the Fused Location Provider from Google Play Services instead.
            // https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi

            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 {

    private static final int REQUEST_LOCATION_PERMISSION_CODE = 1;

    private LocationListener mGpsListener = new MyLocationListener();

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED
                && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
            bindLocationListener();
        } else {
            Toast.makeText(this, "This sample requires Location access", Toast.LENGTH_LONG).show();
        }
    }

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

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

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
                            Manifest.permission.ACCESS_COARSE_LOCATION},
                    REQUEST_LOCATION_PERMISSION_CODE);
        } else {
            bindLocationListener();
        }
    }

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

4、在fragment之間共享ViewModel

下面創建一個activity和2個fragment,每個都有一個seekBar,一個單例的ViewModel,同時應該使用activity作爲生命週期所有者,因爲每個fragment的生命週期是獨立的。

public class SeekBarViewModel extends ViewModel {
    public MutableLiveData<Integer> seekbarValue = new MutableLiveData<>();
}

在一個activity中包含2個Fragment_step5

public class Fragment_step5 extends Fragment {

    private SeekBar mSeekBar;

    private SeekBarViewModel mSeekBarViewModel;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        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() {

        // SeekBar改變時更新ViewModel
        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                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);
                }
            }
        });
    }
}

這樣就能做到在一個fragment中改變seekBar,另一個fragment也會同步改變
改變seekBar

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章