文章目录
前言:
开始前我们先回答几个问题
1.Jetpack是什么/怎么用?
2.android-sunflower-0.1.6是什么?
问题一:
- Jetpack是什么?
*给出下图:*简单的概括为四大组件库,可以看到他提供的功能还是蛮实用的,没有多余的部分。
- Jetpack怎么用?
这个问题比较大一下说不清楚,我们从接下来的源码分析中,一步一步理解和掌握,有人会说既然做这么多年开发直接看文档不就能行了吗,你说的有道理,但是阅读外文文档学习确实不符合中国国情,这里就涉及到个人以及政治方面的原因了。也可以看到上面的模块之多,不是一日而语的,纸上读来终觉浅,绝知此事要躬行,我们需要慢慢来,我也是第一次在技术迁移上感受到这种焦虑感,Kotlin在Android开发当中的比重越来越大,还在使用JAVA的伙伴赶紧跟上脚步。
问题二:
长征第一步
地址:
--------------------------------------进入正题---------------------------------------
衔接上篇
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
调用流程一览
AsyncLIstDiffer和ListAdapter的关系 : 内部成员 mHelper,调用ListAdapter的submitList即调用AsyncLIstDiffer的submitList方法
介绍完ListAdapt的刷新机制,我们再来看看数据的获取和绑定
Paging
注:PagingWithNetworkSample
参考:paging gradle配置
google的sample充满了设计模式之美,奈何看着太费劲,笔者这里简化一下,尽量将代码放在一个类里面帮助理解。
DataSource 有以下几个类,每个类有特定的功能,具体可参考https://www.loongwind.com/archives/367.html
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这样做更像是一次炫技,或者说一次刻意技术革新的优胜略汰。。。