Android Architecture Components

Android Architecture Components(

2017-07-23  本文已影响1947人  madroid

简介

Android Architecture components 是一组 Android 库,它可以帮助我们以一种健壮的、可测试和可维护的方式构建 APP。

注意: 这个架构目前仍处于 alpha 开发状态。在正式版发布之前 API 可能会改变,你可能会遇到稳定性和性能问题。

下面将会介绍使用生命周期感知(lifecycle-aware)组件去构建APP:

如果你已经安装了 Android Studio 2.3 或者更高版本并且熟悉 Activity 生命周期 ,那么你可以继续向下看了。

配置环境

step 1

这部分代码比较简单,首先是布局文件:

<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>

然后在 Activity 的 onCreate 函数中开始计时即可,如下:

public class ChronoActivity1 extends AppCompatActivity {

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

        Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer);

        chronometer.start();
    }
}

Chronometer 是 Android 提供的一个计时器组件,该组件继承自 TextView,它显示从某个起始时间开始过去了多少时间。

想要在屏幕旋转时计时器状态不被更改,您可以使用 ViewModel ,因为此类可以在配置更改(例如屏幕旋转)时不会被回收。

注:mainfest 文件中可以配置 Activity 在屏幕旋转时不重新创建 Activity ,从而保持计时器的状态,这种情况不在本篇谈论范围内。

注:如源码中的 google maven 不能访问,可将项目中 gradle 文件改为如下

    repositories {
        jcenter()
        maven {
            url "https://dl.google.com/dl/android/maven2/"
        }
    }

添加 ViewModel

在此步骤中,您可以使用 ViewModel 在屏幕旋转之间保持状态,并解决您在上一步中观察到的行为(旋转屏幕时计时器会重置)。在上一步中,运行了一个显示计时器的 Activity。当配置更改(如屏幕旋转)会导致 Activity 被销毁,此定时器将重置。

您可以使用 ViewModelActivityFragment 的整个生命周期中保留数据。如上一步所示,使用 Activity 来管理 APP 的数据是不明智的。 ActivityFragment 是一种短命的对象,它们随着用户与应用程序交互而频繁创建和销毁。 ViewModel 还适用于管理与网络通信相关的任务,以及数据的操作和持久化。

使用ViewModel来保持计时器的状态

首先需要创建一个ViewModel,如下

public class ChronometerViewModel extends ViewModel {

    @Nullable
    private Long startDate;

    @Nullable
    public Long getStartDate() {
        return startDate;
    }

    public void setStartDate(final long startDate) {
        this.startDate = startDate;
    }
}

打开 ChronoActivity2 并检查该 Activity 如何检索并使用 ViewModel 的,如下:

public class ChronoActivity2 extends LifecycleActivity {

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

        // ViewModelProviders 会提供一个新的或者之前已经创建过的 ViewModel
        ChronometerViewModel chronometerViewModel
                = ViewModelProviders.of(this).get(ChronometerViewModel.class);

        // 获取 chronometer 
        Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer);

        if (chronometerViewModel.getStartDate() == null) {
            // 如果开始时间为 null , 那么这个 ViewModel 是刚被创建的
            long startTime = SystemClock.elapsedRealtime();
            chronometerViewModel.setStartDate(startTime);
            chronometer.setBase(startTime);
        } else {
            // 否则这个 ViewModel 是从 ViewModelProviders 中被检索出来的,所以需要设置刚开始的开始时间
            chronometer.setBase(chronometerViewModel.getStartDate());
        }

        chronometer.start();
    }
}

其中

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

thisLifecycleOwner 的实例。 只要 LifecycleOwner 的范围是活跃的(alive),框架就可以使 ViewModel 处于活跃状态。如果其所有者因为配置更改(例如屏幕旋转)而被毁坏(destroyed),ViewModel 则不会被销毁。生命周期所有者会重新连接到现有的 ViewModel ,如下图所示:

Viewmodel Lifecycle

注意ActivityFragment 的范围从 createdfinished(或终止),您不能与被 destroyed 混淆。请记住,当一个设备旋转时, Activity 被销毁,但是与它关联的任何 ViewModel 的实例并不会被销毁。

试试看

运行应用程序并确认执行以下任一操作时定时器不会重置:

  1. 旋转屏幕。
  2. 导航到另一个应用程序,然后返回。
定时器不会重置

但是,如果你或系统退出应用程序,则定时器将重置。

注意: 系统会在生命周期所有者(例如 ActivityFragment )的整个生命周期中将 ViewModel 实例保存在内存中 。 系统不会将 ViewModel 的实例持久化到长期存储。

使用 LiveData 包装数据

这一步使用自定义的 Timer 实现上一步的中的 chronometer 。将上面的逻辑添加到 LiveDataTimerViewModel 类中,将 Activity 的主要功能放在管理用户和UI之间的交互上。

Activity 会在定时器通知时更新UI。为了避免内存泄露, ViewModel 不应该持有 Activity 的实例。比如,配置更改(例如屏幕旋转)会使 Activity 被系统回收,如果这时 ViewModel 仍持有Activity 的实例那么 Activity 就不会被系统回收从而导致内存泄露 。系统会保留 ViewModel 的实例至到 Activity 或生命周期持有者不再存在。

注意: 在 ViewModel 中持有 Context 或者 View 的实例可能会导致内存泄露。避免引用 Context 或者 View 类的实例的字段。 ViewModel 中的 onCleared() 方法可用于取消订阅或清除对具有较长生命周期的其他对象的引用,但不用于清除 Context 或者 View 对象的引用。

可以设置 ActivityFragment 来观察数据源,当数据更改时将会接收通知,而不是直接从 ViewModel 修改视图。这就是观察者模式。

注意: 要想将数据可被观察,可将数据包装在LiveData类中。

如果你使用过 Data Binding Library 或其他响应式库(如RxJava),你应该很熟悉观察者模式。LiveData 是一个特殊的可观测类,它可以感知生命周期,并且只在声明周期活跃时通知观察者。

LifecycleOwner

ChronoActivity3LifecycleActivity 的子类,他提供了声明周期的状态。如下:

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

LifecycleRegistryOwner 接口的作用是用来将 ViewModelLiveData 绑定到 ActivityFragment 生命周期中。如果Fragment 的话可以继承 LifecycleFragment 类。

Update ChronoActivity

  1. ChronoActivity3 中添加如下代码,在 subscribe() 方法中创建订阅者:
public class ChronoActivity3 extends LifecycleActivity {

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

        mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
    }
}
  1. 接下来在 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() 不允许在子线程中调用,所以需要post主线程执行
                // 其实也可以直接使用 postValue() 方法,该方法会在内部post到主线程执行
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        //setValue时,观察者可监听到值的变化
                        mElapsedTime.setValue(newValue);
                    }
                });
            }
        }, ONE_SECOND, ONE_SECOND);

    }

    public LiveData<Long> getElapsedTime() {
        return mElapsedTime;
    }
}
  1. 运行 APP 你会发现界面每秒都在更新,除非你导航到另外一个 APP。如果你的设备支持多窗口,或者旋转屏幕并不会影响APP显示。如下图:

注意LiveData 只有在 Activity 或者 LifecycleOwner 活跃(active)时才发送更新。如果你导航到其他的APP,Activity 中的日志打印将会停止,至到你重新回到APP。当 LiveData 对象的各自的生命周期所有者处于 STARTED 或者 RESUMED,我们才称 LiveData 是活跃的。

订阅生命周期事件

许多 Android 组件或库要求你

没有做上面的步骤可能会导致内存泄露或者产生bugs。

生命周期所有者可以传递给生命周期感知组件生命周期的状态,以确保他们知道生命周期的当前状态。

你可以用下面的方法查询生命周期的状态:

lifecycleOwner.getLifecycle().getCurrentState()

上面的语句返回的是什么周期的状态,如 Lifecycle.State.RESUMED, 或 Lifecycle.State.DESTROYED

实现 LifecycleObserver 接口的生命周期感知对象还可以观察生命周期所有者的状态的变化:

lifecycleOwner.getLifecycle().addObserver(this);

可以通过注解的方式,实现在各个生命周期事件执行相应的方法:

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

创建生命周期感知组件

在这里我们会创建一个对 Activity 生命周期所有者做出响应的组件,使用 Fragment 作为生命周期所有者时,可以采用类似的原则和步骤。

使用 Android framework 的 LocationManager 去获取经纬度并且展示给用户,这允许你

通常的做法是在 Activity 的 onStart()onResume() 中注册监听器,在 onStop()onPause() 方法中移除监听器,如下:

// 在 activity 中通常这样做

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

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

看下我们是如何改造上面代码的,首先我们需要创建一个 BoundLocationManager 类,它需要实现 LifecycleOwner 接口并且需要传入 LifecycleRegistryOwner 的实例。 BoundLocationManager 类的实例绑定到 Activity 的生命周期上了,如下:

    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;
            //想要观察 Activity 的声明周期,必须将其添加到观察者中。添加下面的代码
            //才能是 BoundLocationListener 实例监听到生命周期
            lifecycleOwner.getLifecycle().addObserver(this);
        }

        //可以使用  @OnLifecycleEvent 注解来监听 Activity 生命周期的变化
        // 可以使用下面的注解来添加 addLocationListener() 方法
        @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);
            }
        }

        // 可以使用下面的注解来移除 removeLocationListener() 方法
        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        void removeLocationListener() {
            if (mLocationManager == null) {
                return;
            }
            mLocationManager.removeUpdates(mListener);
            mLocationManager = null;
            Log.d("BoundLocationMgr", "Listener removed");
        }
    }

注意: 观察者可以观测到提供者的当前状态,所以不需要从构造函数调用 addLocationListener() 方法。他是在观察者添加到生命周期所有者的时候被调用的。

运行APP,在旋转屏幕的时候会有下面的打印

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

使用 Android 模拟器改变设备的位置,UI 就会更新,如下图:

改变设备的位置

在 Fragmnet 之间共享 ViewModel

在 Fragmnet 之间共享 ViewModel 之前,我们需要做一些前期准备

首先需要创建一个 Activity ,该 Activity 中包含两个 Fragment,在其布局文件中添加两个 Fragment 即可,如下:

<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>

其中 Fragment_step5 就是需要共享 ViewModel 的 Fragment,这个 Fragment 也很简单,只是包含了一个 SeekBar 控件,如下:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.android.lifecycles.step5_solution.Fragment_step5">

    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

然后就是创建一个包含 LiveData 的 ViewModel,如下:

public class SeekBarViewModel extends ViewModel {

    public MutableLiveData<Integer> seekbarValue = new MutableLiveData<>();
}

运行这一步中的代码,你会发现这是两个彼此独立的SeekBar实例:

使用 ViewModel 在 Fragment 之间通信,以便当一个 SeekBar 更改时,另一个 SeekBar 将被更新:

使用 ViewMode 进行通信的代码如下:

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 = (SeekBar) 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((LifecycleOwner) getActivity(),
                new Observer<Integer>() {
                    @Override
                    public void onChanged(@Nullable Integer value) {
                        if (value != null) {
                            mSeekBar.setProgress(value);
                        }
                    }
                });
    }
}

注意: 你应该使用 Activity 作为生命周期的持有者,因为每个 Fragment 的生命周期都是独立的。

至此,你应该对 AAC 有一个初步的认识了。

参考

Android Architecture Components

Android lifecycle-aware components codelab

上一篇下一篇

猜你喜欢

热点阅读