我已经开始在android中再次编程,因为在过去的4年中它已经发生了很大的变化,我有点困惑。我试图从我的ViewHolder中的RxJava和Flowable异步加载我的服务器数据。我正在使用第三方库来处理我在适配器MultiViewAdaptor中的视图。这是我的Binder中的代码
class FooBinder : ItemBinder<Foo, FooBinder.ViewHolder>() {
@Inject
protected lateinit var requestFavoriteUseCase: SendFooFavoriteUseCase
@Inject
protected lateinit var compositeDisposable: CompositeDisposable
@Inject
protected lateinit var retrieveFooFavoriteCountUseCase: RetrieveFooFavoriteCountUseCase
override fun create(inflater: LayoutInflater, parent: ViewGroup) =
ViewHolder(inflater.inflate(R.layout.item_foo_content, parent, false))
override fun bind(holder: ViewHolder, item: Foo) {
holder.itemView.foo_content_creator.text = with(item.creator) { "$firstName $lastName" }
GlideApp.with(holder.itemView)
.load(item.creator.avatar)
.dontAnimate()
.placeholder(R.drawable.ic_action_person)
.error(R.drawable.ic_action_person)
.into(holder.itemView.foo_content_profile)
retrieveFooFavoriteCountUseCase
.execute(item.id)
.applyComputationScheduler()
.subscribe { count ->
holder.itemView.foo_content_like_count.text = "$count"
}
.addTo(compositeDisposable)
}
inner class ViewHolder(view: View) : ViewHolder<Foo>(view) {
private var favorite = false
init {
view
.foo_content_button_layout
.clicks()
.flatMap {
favorite = !favorite
requestFavoriteUseCase.execute(FavoriteVM(item.id, favorite)).toObservable()
}
.subscribe {
view.foo_content_like_count.text = it.toString()
var res = R.drawable.ic_action_like_default
if (favorite) {
res = R.drawable.ic_action_like_enabled
}
view.foo_content_like_icon.setImageResource(res)
}
.addTo(compositeDisposable)
}
}
}
如你所见,我必须在bind方法中调用服务器,这是不理想的,并且每次用户滚动时都会调用它,在调用Activity并调用compositeDisposable之前我无法取消调用它们,这意味着我将为一个视图设置多个一次性:(
我的问题是,如何在ViewHolder中使用observable并在屏幕上没有显示时停止提供它?
你能做的最好的事情是:
onViewAttachedToWindow
和onViewDetachedToWindow
来控制您的订阅生命周期(订阅/处置)。onBindViewHolder
上创建/检索您的observableonViewDetachedToWindow
以确保您没有泄漏任何内容(例如片段,在onDestoryView
中调用list.adapter = null
)delaySubscription
(例如400毫秒)添加到您的observables以减少滞后,同时快速滚动(当列表在动画上滑动时,您不想订阅任何内容)第5点可能有点令人困惑,因此有一些资源更广泛地解释了这一点:
因此,基本上同步发射意味着在订阅方法返回之前,在订阅observable时将发出该项。例如,这种行为可能是通过在缓存之后不修改订阅发射线程的链中的某种缓存(如replay(1),startWith()等)来实现的。
例:
Observable.create(2)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.startWith(0)
.subscribe(println(it))
println("after subscribe")
假设我们订阅主线程。在那种情况下,我们打印:
0
after subscribe
2
因此,正如您所看到的,即使两个项目都将在主线程上发出,其中一个将在订阅时立即到达,而第二个将在looper上发布。
bind
方法(最初是onBindViewHolder
)每当视图尝试向用户显示时调用。 (实际上,当用户滚动到底部并向上时,此方法再次调用)
因此,就生命周期问题或性能而言,在onBindViewHolder上调用网络进程是个坏主意。
我建议用这种方法实现这一目标。
item.id
相关的所有数据。onBindViewHolder
中,holder.itemView.foo_content_like_count.text = item.count
订阅了retrieveFooFavoriteCountUseCase。示例代码,它可能无法正常工作。我只是说它是如何工作的。)
val items = ...
retrieveFooFavoriteCountUseCase
.execute(items.map { it.id }) // 1)
.applyComputationScheduler()
.subscribe { counts ->
for ((index, count) in counts.indexed()) { // 2)
items[index] = items[index].apply { this.count = count } // 3)
}
// TODO: initialize adapter with items
}.addTo(compositeDisposable)
1)你应该用List实现execute方法吗?我不知道Foo的具体类型。
2)计数包含您需要的计数列表。在这个示例代码中,我使用for-each来匹配数据。但你可以使用自己的匹配代码。
3)与上面相同,您可以使用自己的匹配代码。
因此,订阅将在整个过程中仅被调用一次。