Android Architecture Components 之 Lifecycle

Android Architecture Components-Lifecycle

Android Architecture Components顾名思义为一组系应用架构级组件库,为管理UI组件的生命周期,数据加载等提供系统级支持。

详细介绍及加入项目方法参考官方:https://developer.android.com/topic/libraries/architecture/

Android开发中Activity/Fragment承载过重任务一直是一大痛点,各种规则第三方库等都收效甚微。但Lifecycle的出现从系统框架层提供了新的MVVM架构方案。

Lifecycle

Lifecycle不是简单的作为一个库以供使用,而是深入到了现有架构中,说明如下

Fragments and Activities in Support Library 26.1.0 and later already implement the LifecycleOwner interface.

即Support Library 26.1.0及以后的版本中的AppCompatActivity和Fragment默认都是实现了LifecycleOwner接口的.

Lifecycle包括LiveData 和 ViewModel两部分。

LiveData

LiveData用来加载数据,并通知在其上的观察者。

一般的使用流程为:创建实例A -> A.observe(owner, observer) -> A加载数据(set/post value)-> observer.onChanged -> 更新UI

其中owner为实现了LifecycleOwner的实例,observer为实现了Observer接口的实例用来在有数据变化时回调其onChanged进行数据更新。

Android Architecture Components 之 Lifecycle

大多数时候LiveData被关联到Activity或Fragment中用来加载数据并更新UI,其观察者模式的本质需要小心谨慎的处理register/unregister事件。这时将实现了LifecycleOwner接口的AppCompatActivity和Fragment直接传进observe方法则只需关心数据的获取和更新而不用担心生命周期等问题。


@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
    .......
}

ViewModel

从MVVM的架构上来说ViewModel连接UI与model,并且ViewModel与View双向绑定。

ViewModel一般的获取方式如下:


ProductViewModel model = ViewModelProviders.of(this).get(ProductViewModel.class);

其中this即Fragment或FragmentActivity的子类,Fragment或FragmentActivity都实现了ViewModelStoreOwner接口

FragmentActivity中的getViewModelStore定义

/**
 * Returns the {@link ViewModelStore} associated with this activity
 *
 * @return a {@code ViewModelStore}
 */
@NonNull
@Override
public ViewModelStore getViewModelStore() {
    ......
    if (mViewModelStore == null) {
        mViewModelStore = new ViewModelStore();
    }
    return mViewModelStore;
}

而ViewModelProviders的of()最终调用的为ViewModelStores.of(activity)

ViewModelProviders


@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(ViewModelStores.of(activity), factory);
}

ViewModelStores.of(activity)则是获取activity/fragment中定义的ViewModelStore对象

ViewModelStores


@MainThread
public static ViewModelStore of(@NonNull FragmentActivity activity) {
    if (activity instanceof ViewModelStoreOwner) {
        return ((ViewModelStoreOwner) activity).getViewModelStore();
    }
    return holderFragmentFor(activity).getViewModelStore();
}

最后到ViewModelProvider的get方法中

ViewModelProvider

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

获取ViewModel的key为DEFAULT_KEY + ":" + canonicalName,若mViewModelStore实例中没有则创建。即每个

ViewModel在每个Activity/Fragment实例中只会创建一次并且在onDestroy时才会clear。所以官方说明强调ViewModel中一定不能引用View或者持有Activity/Fragment的实现接口等引用。

ViewModel's only responsibility is to manage the data for the UI. It should never access your view hierarchy or hold a reference back to the Activity or the Fragment.

项目实战

Github:https://github.com/kevinshine/RedditG

界面加载及更新使用了Lifecycle,数据流使用了reddit的api

合理的使用Lifecycle可以优雅的将各个层串联起来。

加载网络数据并呈现

常规实现,在SubredditPageFragment中使用AsyncTask异步获取数据并更新UI,执行上并没有什么不妥,但数据的获取等逻辑与UI处理写在了一起。


@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    loadData();
}

private void loadData() {
    Log.d(TAG, "loadData:" + mSubName);
    AsyncTask.SERIAL_EXECUTOR.execute(() -> {
        Log.d(TAG, "create paginator:" + mSubName);
        DefaultPaginator<Submission> paginator = RedditManager.getInstance().getRedditClient()
                .subreddit(mSubName)
                .posts()
                .build();
        mPaginator = paginator;

        Listing<Submission> submissions = null;
        // catch java.net.SocketTimeoutException
        try {
            submissions = paginator.next();
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "load data timeout:" + paginator.getBaseUrl());
        }

        // update UI
        if (submissions != null) {
            mSubList.addAll(submissions);
            mMainHandler.post(() -> {
                mAdapter.notifyDataSetChanged();
            });
        }
    });
}

使用ViewModel和LiveData进行重构后Fragment中只监听数据变化并更新UI,获取数据及相关业务都放到了ViewModel中。


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    .....

    // init ViewModel and load data
    mSubredditPageViewModel = ViewModelProviders.of(this).get(SubredditPageViewModel.class);

    mSubredditPageViewModel.getPaginatorData().observe(this,(DefaultPaginator<Submission> paginator)->{
        mPaginator = paginator;
        // second load list data
        mSubredditPageViewModel.loadSubmissionList(mPaginator);
    });

    mSubredditPageViewModel.getSubmissionList().observe(this,((Listing<Submission> result)->{
        // third update UI
        if (result != null) {
            mSubList.addAll(result);
            mAdapter.notifyDataSetChanged();
        }
    }));

    // first create Paginator
    mSubredditPageViewModel.createPaginator(mSubName);

    return view;
}