JetPack控件Paging(基于PagingWithNetworkSample)

前言:

开始前我们先回答几个问题

1.Jetpack是什么/怎么用?
2.android-sunflower-0.1.6是什么?

问题一:

  1. Jetpack是什么?
    *给出下图:*简单的概括为四大组件库,可以看到他提供的功能还是蛮实用的,没有多余的部分。
    是什么
  2. Jetpack怎么用?
    这个问题比较大一下说不清楚,我们从接下来的源码分析中,一步一步理解和掌握,有人会说既然做这么多年开发直接看文档不就能行了吗,你说的有道理,但是阅读外文文档学习确实不符合中国国情,这里就涉及到个人以及政治方面的原因了。也可以看到上面的模块之多,不是一日而语的,纸上读来终觉浅,绝知此事要躬行,我们需要慢慢来,我也是第一次在技术迁移上感受到这种焦虑感,Kotlin在Android开发当中的比重越来越大,还在使用JAVA的伙伴赶紧跟上脚步。

问题二:
长征第一步
地址:

  1. android-sunflower-0.1.6
  2. PagingWithNetworkSample

--------------------------------------进入正题---------------------------------------
衔接上篇
JetPack控件WorkManager(基于Mixin Messenger)
配置

    implementation "androidx.paging:paging-runtime:$paging_version"
    implementation "androidx.paging:paging-runtime-ktx:$paging_version"
    // optional - RxJava support
    implementation "androidx.paging:paging-rxjava2:$paging_version"

ListAdapter

注:android-sunflower-0.1.6
官方文档(很详细):https://developer.android.com/topic/libraries/architecture/paging?hl=zh-cn

说到LIstView就当然要说说Adapter(androidx.recyclerview.widget)

坐标:PlantAdapter

ListAdapter是 RecyclerView.Adapter的子类,特别的地方只是构造函数,要传一个 DiffUtil.ItemCallback

    protected ListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
        mHelper = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
                new AsyncDifferConfig.Builder<>(diffCallback).build());
    }

坐标:PlantDiffCallback

class PlantDiffCallback : DiffUtil.ItemCallback<Plant>() {
	//比较Item
    override fun areItemsTheSame(oldItem: Plant, newItem: Plant): Boolean {
        return oldItem.plantId == newItem.plantId
    }
    //比较Conten
    override fun areContentsTheSame(oldItem: Plant, newItem: Plant): Boolean {
        return oldItem == newItem
    }
}

坐标:AsyncListDiffer

submitList函数

mConfig.getDiffCallback().areItemsTheSame(oldItem, newItem)获取PlantDiffCallback 的重写返回一个result,这个result是 DiffUtil中自己维护的一个DiffResult,而DiffResult包含一个snakes集合

final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
                    @Override
                    public int getOldListSize() {
                        return oldList.size();
                    }

                    @Override
                    public int getNewListSize() {
                        return newList.size();
                    }

                    @Override
                    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                        T oldItem = oldList.get(oldItemPosition);
                        T newItem = newList.get(newItemPosition);
                        if (oldItem != null && newItem != null) {
                            return mConfig.getDiffCallback().areItemsTheSame(oldItem, newItem);
                        }
                        // If both items are null we consider them the same.
                        return oldItem == null && newItem == null;
                    }

                    @Override
                    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                        T oldItem = oldList.get(oldItemPosition);
                        T newItem = newList.get(newItemPosition);
                        if (oldItem != null && newItem != null) {
                            return mConfig.getDiffCallback().areContentsTheSame(oldItem, newItem);
                        }
                        if (oldItem == null && newItem == null) {
                            return true;
                        }
                        // There is an implementation bug if we reach this point. Per the docs, this
                        // method should only be invoked when areItemsTheSame returns true. That
                        // only occurs when both items are non-null or both are null and both of
                        // those cases are handled above.
                        throw new AssertionError();
                    }

                    @Nullable
                    @Override
                    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
                        T oldItem = oldList.get(oldItemPosition);
                        T newItem = newList.get(newItemPosition);
                        if (oldItem != null && newItem != null) {
                            return mConfig.getDiffCallback().getChangePayload(oldItem, newItem);
                        }
                        // There is an implementation bug if we reach this point. Per the docs, this
                        // method should only be invoked when areItemsTheSame returns true AND
                        // areContentsTheSame returns false. That only occurs when both items are
                        // non-null which is the only case handled above.
                        throw new AssertionError();
                    }
                });

                mMainThreadExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        if (mMaxScheduledGeneration == runGeneration) {
                            latchList(newList, result);
                        }
                    }
                });
                
		@SuppressWarnings("WeakerAccess") /* synthetic access */
		void latchList(@NonNull List<T> newList, @NonNull DiffUtil.DiffResult diffResult) {
				mList = newList;
				// notify last, after list is updated
				mReadOnlyList = Collections.unmodifiableList(newList);
				diffResult.dispatchUpdatesTo(mUpdateCallback);
		}

可以发现没有我熟悉的notifyDataSetChanged或者setAdapter的,那么他是怎么更新的呢?

DiffUtil

dispatchUpdatesTo函数

batchingCallback是ListUpdateCallback的实现类,在回到我们之前的 坐标:ListAdapter构造函数的AdapterListUpdateCallback原来notifyDataSetChanged在这里啊

if (endX < posOld) {
	dispatchRemovals(postponedUpdates, batchingCallback, endX, posOld - endX, endX);
}

if (endY < posNew) {
   dispatchAdditions(postponedUpdates, batchingCallback, endX, posNew - endY,
           endY);
}

也就是说submitList 等同于 notifyDataSetChanged

调用流程一览

ListAdapter
AsyncLIstDiffer和ListAdapter的关系 : 内部成员 mHelper,调用ListAdapter的submitList即调用AsyncLIstDiffer的submitList方法
helper
介绍完ListAdapt的刷新机制,我们再来看看数据的获取和绑定

Paging

注:PagingWithNetworkSample
参考:paging gradle配置
google的sample充满了设计模式之美,奈何看着太费劲,笔者这里简化一下,尽量将代码放在一个类里面帮助理解。

DataSource 有以下几个类,每个类有特定的功能,具体可参考https://www.loongwind.com/archives/367.html
DataSource

DataSource 方式加载

基类 RedditPostRepository

interface RedditPostRepository {
    fun postsOfSubreddit(subReddit: String, pageSize: Int): Listing<RedditPost>
}

抽象 Listing

data class Listing<T>(
        // the LiveData of paged lists for the UI to observe
        val pagedList: LiveData<PagedList<T>>,
        // represents the network request status to show to the user
        val networkState: LiveData<NetworkState>,
        // represents the refresh status to show to the user. Separate from networkState, this
        // value is importantly only when refresh is requested.
        val refreshState: LiveData<NetworkState>,
        // refreshes the whole data and fetches it from scratch.
        val refresh: () -> Unit,
        // retries any failed requests.
        val retry: () -> Unit)

SubRedditViewModel 初始化

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations.map
import androidx.lifecycle.Transformations.switchMap
import androidx.lifecycle.ViewModel

// thread pool used for network requests
@Suppress("PrivatePropertyName")
private val NETWORK_IO = Executors.newFixedThreadPool(5)

private val api by lazy {
    RedditApi.create()
}

private val model: SubRedditViewModel by lazy {
        ViewModelProviders.of(this, object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                val repo = object : RedditPostRepository {
                    @MainThread
                    override fun postsOfSubreddit(subReddit: String, pageSize: Int): Listing<RedditPost> {

                        val sourceFactory = object : DataSource.Factory<String, RedditPost>() {
                            val sourceLiveData = MutableLiveData<PageKeyedSubredditDataSource>()
                            override fun create(): DataSource<String, RedditPost> {
                                return PageKeyedSubredditDataSource(api, subReddit, NETWORK_IO).also {
                                    sourceLiveData.postValue(it)
                                }
                            }
                        }

                        val livePagedList = LivePagedListBuilder(sourceFactory, Config(pageSize))
                                .setFetchExecutor(NETWORK_IO)
                                .build()

                        val sourceLiveData = sourceFactory.sourceLiveData

                        val refreshState = Transformations.switchMap(sourceLiveData) {
                            it.initialLoad
                        }
                        return Listing(
                                pagedList = livePagedList,
                                networkState = Transformations.switchMap(sourceLiveData) {
                                    it.networkState
                                },
                                retry = {
                                    sourceLiveData.value?.retryAllFailed()
                                },
                                refresh = {
                                    sourceLiveData.value?.invalidate()
                                },
                                refreshState = refreshState
                        )
                    }
                }
                @Suppress("UNCHECKED_CAST")
                return SubRedditViewModel(repo) as T
            }
        })[SubRedditViewModel::class.java]
    }

PageKeyedDataSource已经定义好 loadInitial loadAfter loadBefore三个函数了,顾名思义,我们进行相关的操作获取数据即可,关于如何获取我们新版Retrofit2.0篇中讨论

class PageKeyedSubredditDataSource(
private val redditApi: RedditApi,
private val subredditName: String,
private val retryExecutor: Executor) : PageKeyedDataSource<String, RedditPost>() {
	private var retry: (() -> Any)? = null
	
	val networkState = MutableLiveData<NetworkState>()
	
	val initialLoad = MutableLiveData<NetworkState>()
	
	fun retryAllFailed() {
	    val prevRetry = retry
	    retry = null
	    prevRetry?.let {
	        retryExecutor.execute {
	            it.invoke()
	        }
	    }
	}
    
	override fun loadBefore(
		      params: PageKeyedDataSource.LoadParams<String>,
		        callback: PageKeyedDataSource.LoadCallback<String, RedditPost>) {
		    // ignored, since we only ever append to our initial load
	}
		
	override fun loadAfter(params: PageKeyedDataSource.LoadParams<String>, callback: PageKeyedDataSource.LoadCallback<String, RedditPost>) {
		
		
	}
		
	override fun loadInitial(
		        params: PageKeyedDataSource.LoadInitialParams<String>,
		        callback: PageKeyedDataSource.LoadInitialCallback<String, RedditPost>) {
	}
}

SubRedditViewModel 定义

class SubRedditViewModel(private val repository: RedditPostRepository) : ViewModel() {
	 private val subredditName = MutableLiveData<String>()
	    private val repoResult = map(subredditName) {
	        repository.postsOfSubreddit(it, 30)
	    }
	    val posts = switchMap(repoResult, { it.pagedList })!!
	    val networkState = switchMap(repoResult, { it.networkState })!!
	    val refreshState = switchMap(repoResult, { it.refreshState })!!
	    f
	    un refresh() {
	        repoResult.value?.refresh?.invoke()
	    }
	
	    fun showSubreddit(subreddit: String): Boolean {
	        if (subredditName.value == subreddit) {
	            return false
	        }
	        subredditName.value = subreddit
	        return true
	    }
	
	    fun retry() {
	        val listing = repoResult?.value
	        listing?.retry?.invoke()
	    }
	
	    fun currentSubreddit(): String? = subredditName.value
	}
}

可以发现最终SubRedditViewModel 是获取到了 Listing的一个实例,然后各种调用。
对于Transformations 的switchMap 和 map不理解请参考
Jetpack Transformation复杂应用

关联ListApater

  private fun initAdapter() {
        val adapter = PostsAdapter( GlideApp.with(this)) {
            model.retry()
        }
        list.adapter = adapter
        model.posts.observe(this, Observer<PagedList<RedditPost>> {
            adapter.submitList(it)
        })
    }

PagedList.BoundaryCallback方式加载

 private val db by lazy {
        Room.databaseBuilder(applicationContext, RedditDb::class.java,
                "reddit.db")
                .fallbackToDestructiveMigration()
                .build()
    }
private val model2: SubRedditViewModel by lazy {
        ViewModelProviders.of(this, object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                val repo = object : RedditPostRepository {
                    @MainThread
                    override fun postsOfSubreddit(subReddit: String, pageSize: Int): Listing<RedditPost> {

                        val boundaryCallback = object : PagedList.BoundaryCallback<RedditPost>() {
                            /**
                             * Database returned 0 items. We should query the backend for more items.
                             */
                            @MainThread
                            override fun onZeroItemsLoaded() {

                            }

                            @MainThread
                            override fun onItemAtEndLoaded(itemAtEnd: RedditPost) {

                            }
                         
                            override fun onItemAtFrontLoaded(itemAtFront: RedditPost) {
                                // ignored, since we only ever append to what's in the DB
                            }

                        }
                        val refreshTrigger = MutableLiveData<Unit>()
                        val refreshState = Transformations.switchMap(refreshTrigger) {
                            MutableLiveData<NetworkState>()
                        }

                        // We use toLiveData Kotlin extension function here, you could also use LivePagedListBuilder
                        val livePagedList = db.posts().postsBySubreddit(subReddit).toLiveData(
                                pageSize = pageSize,
                                boundaryCallback = boundaryCallback)

                        return Listing(
                                pagedList = livePagedList,
                                networkState = MutableLiveData<NetworkState>(),
                                retry =
                                {
                                },
                                refresh =
                                {
                                    refreshTrigger.value = null
                                },
                                refreshState = refreshState
                        )
                    }
                }
                @Suppress("UNCHECKED_CAST")
                return SubRedditViewModel(repo) as T
            }
        })[SubRedditViewModel::class.java]
    }

加载过程全权被BoundaryCallback和DataSource代理了,同时动态刷新ListAdapter.所以看的时候大家难免会有点不适应,目前来看Jetpack相关资料比较少,且质量不高,对深入学习有一定阻碍,可以看出Jetpack无论是资料还Google Demo,已经全面使用kotlin语言编写,这样也进一步的加大了学习的难度,感觉Google这样做更像是一次炫技,或者说一次刻意技术革新的优胜略汰。。。

猜你喜欢

转载自blog.csdn.net/qq_20330595/article/details/87967304