赞
踩
开始前我们先回答几个问题
1.Jetpack是什么/怎么用?
2.android-sunflower-0.1.6是什么?
问题一:
问题二:
长征第一步
地址:
--------------------------------------进入正题---------------------------------------
衔接上篇
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"
注:android-sunflower-0.1.6
官方文档(很详细):https://developer.android.com/topic/libraries/architecture/paging?hl=zh-cn
说到LIstView就当然要说说Adapter(androidx.recyclerview.widget)
ListAdapter是 RecyclerView.Adapter的子类,特别的地方只是构造函数,要传一个 DiffUtil.ItemCallback
protected ListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mHelper = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
new AsyncDifferConfig.Builder<>(diffCallback).build());
}
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
}
}
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的,那么他是怎么更新的呢?
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
AsyncLIstDiffer和ListAdapter的关系 : 内部成员 mHelper,调用ListAdapter的submitList即调用AsyncLIstDiffer的submitList方法
介绍完ListAdapt的刷新机制,我们再来看看数据的获取和绑定
注:PagingWithNetworkSample
参考:paging gradle配置
google的sample充满了设计模式之美,奈何看着太费劲,笔者这里简化一下,尽量将代码放在一个类里面帮助理解。
DataSource 有以下几个类,每个类有特定的功能,具体可参考https://www.loongwind.com/archives/367.html
基类 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)
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>) { } }
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复杂应用
private fun initAdapter() {
val adapter = PostsAdapter( GlideApp.with(this)) {
model.retry()
}
list.adapter = adapter
model.posts.observe(this, Observer<PagedList<RedditPost>> {
adapter.submitList(it)
})
}
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这样做更像是一次炫技,或者说一次刻意技术革新的优胜略汰。。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。