天天看點

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