一文读懂Android Kotlin的数据流

目录
  • 一、Android分层架构
  • 二、ViewModel + LiveData
    • 2.1 LiveData 特性
      • 观察者的回调永远发生在主线程
      • 仅持有单个且最新数据
      • 自动取消订阅
      • 提供「可读可写」和「仅可读」两种方式
      • 配合 DataBinding 实现「双向绑定」
    • 2.2 LiveData的缺陷
      • value 可以是 nullable 的
      • 传入正确的 lifecycleOwner
      • 粘性事件
      • 默认不防抖
      • transformation 工作在主线程
    • 2.3 LiveData 小结
  • 三、Flow
    • 3.1 简介
    • 3.2 基本概念
    • 3.3 StateFlow
      • 基本使用
    • 3.4 SharedFlow
      • SharedFlow基本概念
      • 基本使用
    • 3.5 冷流转热流
    • 3.6 StateFlow与SharedFlow对比
  • 四、总结

一、Android分层架构

不管是早期的MVC、MVP,还是最新的MVVM和MVI架构,这些框架一直解决的都是一个数据流的问题。一个良好的数据流框架,每一层的职责是单一的。例如,我们可以在表现层(Presentation Layer)的基础上添加一个领域层(Domain Layer) 来保存业务逻辑,使用数据层(Data Layer)对上层屏蔽数据来源(数据可能来自远程服务,可能是本地数据库)。

在Android中,一个典型的Android分层架构图如下:

其中,我们需要重点看下Presenter 和 ViewModel, Presenter 和 ViewModel向 View 提供数据的机制是不同的。

  • Presenter: Presenter通过持有 View 的引用并直接调用操作 View,以此向 View 提供和更新数据。
  • ViewModel:ViewModel 通过将可观察的数据暴露给观察者来向 View 提供和更新数据。

目前,官方提供的可观察的数据组件有LiveData、StateFlow和SharedFlow。可能大家对LiveData比较熟悉,配合ViewModel可以很方便的实现数据流的流转。不过,LiveData也有很多常见的缺陷,并且使用场景也比较固定,如果网上出现了KotlinFlow 替代 LiveData的声音。那么 Flow 真的会替代 LiveData吗?Flow 真的适合你的项目吗?看完下面的分析后,你定会有所收获。

二、ViewModel + LiveData

ViewModel的作用是将视图和逻辑进行分离,Activity或者Fragment只负责UI显示部分,网络请求或者数据库操作则有ViewModel负责。ViewModel旨在以注重生命周期的方式存储和管理界面相关的数据,让数据可在发生屏幕旋转等配置更改后继续留存。并且ViewModel不持有View层的实例,通过LiveData与Activity或者Fragment通讯,不需要担心潜在的内存泄漏问题。

而LiveData 则是一种可观察的数据存储器类,与常规的可观察类不同,LiveData 具有生命周期感知能力,它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保LiveData当数据源发生变化的时候,通知它的观察者更新UI界面。同时它只会通知处于Active状态的观察者更新界面,如果某个观察者的状态处于Paused或Destroyed时那么它将不会收到通知,所以不用担心内存泄漏问题。

下面是官方发布的架构组件库的生命周期的说明:

2.1 LiveData 特性

通过前面的介绍可以知道,LiveData 是 Android Jetpack Lifecycle 组件中的内容,具有生命周期感知能力。一句话概括就是:LiveData 是可感知生命周期的,可观察的,数据持有者。

特点如下:

  • 观察者的回调永远发生在主线程
  • 仅持有单个且最新的数据
  • 自动取消订阅
  • 提供「可读可写」和「仅可读」两个版本收缩权限
  • 配合 DataBinding 实现「双向绑定」

观察者的回调永远发生在主线程

因为LiveData 是被用来更新 UI的,因此 Observer 接口的 onChanged() 方法必须在主线程回调。

public interface Observer<T> {
    void onChanged(T t);
}

背后的道理也很简单,LiveData 的 setValue() 发生在主线程(非主线程调用会抛异常),而如果调用postValue()方法,则它的内部会切换到主线程调用 setValue()。

protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

可以看到,postValue()方法的内部调用了postToMainThread()实现线程的切换,之后遍历所有观察者的 onChanged() 方法。

仅持有单个且最新数据

作为数据持有者,LiveData仅持有【单个且最新】的数据。单个且最新,意味着 LiveData 每次只能持有一个数据,如果有新数据则会覆盖上一个。并且,由于LiveData具备生命周期感知能力,所以观察者只会在活跃状态下(STARTED 到 RESUMED)才会接收到 LiveData 最新的数据,在非活跃状态下则不会收到。

自动取消订阅

可感知生命周期的重要优势就是可以自动取消订阅,这意味着开发者无需手动编写那些取消订阅的模板代码,降低了内存泄漏的可能性。背后的实现逻辑是在生命周期处于 DESTROYED 时,移除观察者。

@Override
public void onStateChanged(@NonNull LifecycleOwner source,
        @NonNull Lifecycle.Event event) {
    Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
    if (currentState == DESTROYED) {
        removeObserver(mObserver);
        return;
    }
    ... //省略其他代码
}

提供「可读可写」和「仅可读」两种方式

LiveData 提供了setValue() 和 postValue()两种方式来操作实体数据,而为了细化权限,LiveData又提供了mutable(MutableLiveData) 和 immutable(LiveData) 两个类,前者「可读可写」,后者则「仅可读」。

配合 DataBinding 实现「双向绑定」

LiveData 配合 DataBinding 可以实现更新数据自动驱动UI变化,如果使用「双向绑定」还能实现 UI 变化影响数据的变化功能。

2.2 LiveData的缺陷

正如前面说的,LiveData有自己的使用场景,只有满足使用场景才会最大限度的发挥它的功能,而下面这些则是在设计时将自带的一些缺陷:

  • value 可以是 nullable 的
  • 在 fragment 订阅时需要传入正确的 lifecycleOwner
  • 当 LiveData 持有的数据是「事件」时,可能会遇到「粘性事件」
  • LiveData 是不防抖的
  • LiveData 的 transformation 需要工作在主线程

value 可以是 nullable 的

由于LiveData的getValue() 是可空的,所以在使用时应该注意判空,否则容易出现空指针的报错。

@Nullable
public T getValue() {
    Object data = mData;
    if (data != NOT_SET) {
        return (T) data;
    }
    return null;
}

传入正确的 lifecycleOwner

Fragment 调用 LiveData的observe() 方法时传入 this 和 viewLifecycleOwner 的含义是不一样的。因为Fragment与Fragment中的View的生命周期并不一致,有时候我们需要的让observer感知Fragment中的View的生命周期而非Fragment。

粘性事件

粘性事件的定义是,发射的事件如果早于注册,那么注册之后依然可以接收到的事件,这一现象称为粘性事件。解决办法是:将事件作为状态的一部分,在事件被消费后,不再通知观察者。推荐两种解决方式:

  • KunMinX/UnPeek-LiveData
  • 使用kotlin 扩展函数和 typealias 封装解决「粘性」事件的 LiveData

默认不防抖

当setValue()/postValue() 传入相同的值且多次调用时,观察者的 onChanged() 也会被多次调用。不过,严格来讲,这也不算一个问题,我们只需要在调用 setValue()/postValue() 前判断一下 vlaue 与之前是否相同即可。

transformation 工作在主线程

有些时候,我们需要对从Repository 层得到的数据进行处理。例如,从数据库获得 User列表,我们需要根据 id 获取某个 User, 那么就需要用到MediatorLiveData 和 Transformatoins 来实现。

  • Transformations.map
  • Transformations.switchMap

并且,map 和 switchMap 内部均是使用 MediatorLiveData的addSource() 方法实现的,而该方法会在主线程调用,使用不当会有性能问题。

@MainThread
public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
    Source<S> e = new Source<>(source, onChanged);
    Source<?> existing = mSources.putIfAbsent(source, e);
    if (existing != null && existing.mObserver != onChanged) {
        throw new IllegalArgumentException(
                "This source was already added with the different observer");
    }
    if (existing != null) {
        return;
    }
    if (hasActiveObservers()) {
        e.plug();
    }
}

2.3 LiveData 小结

LiveData 是一种可观察的数据存储器类,与常规的可观察类不同,LiveData 具有生命周期感知能力,它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保LiveData当数据源发生变化的时候,通知它的观察者更新UI界面。同时它只会通知处于Active状态的观察者更新界面,如果某个观察者的状态处于Paused或Destroyed时那么它将不会收到通知,所以不用担心内存泄漏问题。

同时,LiveData 专注单一功能,因此它的一些方法使用上是有局限性的,并且需要配合 ViewModel 使用才能显示其价值。

三、Flow

3.1 简介

Flow是Google官方提供的一套基于kotlin协程的响应式编程模型,它与RxJava的使用类似,但相比之下Flow使用起来更简单,另外Flow作用在协程内,可以与协程的生命周期绑定,当协程取消时,Flow也会被取消,避免了内存泄漏风险。

协程是轻量级的线程,本质上协程、线程都是服务于并发场景下,其中协程是协作式任务,线程是抢占式任务。默认协程用来处理实时性不高的数据,请求到结果后整个协程就结束了。比如,有下面一个例子:

其中,红框中需要展示的内容实时性不高,而需要交互的,比如转发和点赞属于实时性很高的数据需要定时刷新。对于实时性不高的场景,直接使用 Kotlin 的协程处理即可,比如。

suspend fun loadData(): Data

uiScope.launch {
  val data = loadData()
  updateUI(data)
}

而对于实时性要求较高的场景,上面的方式就不起作用了,此时需要用到Kotlin提供的Flow数据流。

fun dataStream(): Flow<Data>uiScope.launch {
  dataStream().collect { data ->
    updateUI(data)
  }
}

3.2 基本概念

Kotlin的数据流主要由三个成员组成,分别是生产者、消费者和中介。 生产者:生成添加到数据流中的数据,可以配合得协程使用,使用异步方式生成数据。 中介(可选):可以修改发送到数据流的值,或修正数据流本身。 消费者:使用方则使用数据流中的值。

其中,中介可以对数据流中的数据进行更改,甚至可以更改数据流本身,他们的架构示意图如下。

在Kotlin中,Flow 是一种冷流,不过有一种特殊的Flow( StateFlow/SharedFlow) 是热流。什么是冷流,他和热流又有什么关系呢?

冷流:只有订阅者订阅时,才开始执行发射数据流的代码。并且冷流和订阅者只能是一对一的关系,当有多个不同的订阅者时,消息是重新完整发送的。也就是说对冷流而言,有多个订阅者的时候,他们各自的事件是独立的。 热流:无论有没有订阅者订阅,事件始终都会发生。当 热流有多个订阅者时,热流与订阅者们的关系是一对多的关系,可以与多个订阅者共享信息。

3.3 StateFlow

前面说过,冷流和订阅者只能是一对一的关系,当我们要实现一个流多个订阅者的场景时,就需要使用热流了。

StateFlow 是一个状态容器式可观察数据流,可以向其收集器发出当前状态更新和新状态更新。可以通过其 value 属性读取当前状态值,如需更新状态并将其发送到数据流,那么就需要使用MutableStateFlow。

基本使用

在Android 中,StateFlow 非常适合需要让可变状态保持可观察的类。由于StateFlow并不是系统API,所以使用前需要添加依赖:

dependencies {
    ...  //省略其他

    implementation "androidx.activity:activity-ktx:1.3.1"
    implementation "androidx.fragment:fragment-ktx:1.4.1"
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
}

接着,我们需要创建一个ViewModel,比如:

class StateFlowViewModel: ViewModel() {
    val data = MutableStateFlow<Int>(0)
    fun add(v: View) {
        data.value++
    }
    fun del(v: View) {
        data.value--
    }
}

可以看到,我们使用MutableStateFlow包裹需要操作的数据,并添加了add()和del()两个方法。然后,我们再编写一段测试代码实现数据的修改,并自动刷新数据。

class StateFlowActivity : AppCompatActivity() {
    private val viewModel by viewModels<StateFlowViewModel>()
    private val mBinding : ActivityStateFlowBinding by lazy {
        ActivityStateFlowBinding.inflate(layoutInflater)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        initFlow()
    }
    private fun initFlow() {
        mBinding.apply {
            btnAdd.setOnClickListener {
                viewModel.add(it)
            }
            btnDel.setOnClickListener {
                viewModel.del(it)
            }
        }
    }

}

上面代码中涉及到的布局代码如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="stateFlowViewModel"
            type="com.xzh.demo.flow.StateFlowViewModel" />
    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="200dp"
            android:layout_marginTop="30dp"
            android:text="@{String.valueOf(stateFlowViewModel.data)}"
            android:textSize="24sp" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/btn_add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|start"
            android:layout_marginStart="10dp"
            android:layout_marginBottom="10dp"
            android:contentDescription="start"
            android:src="@android:drawable/ic_input_add" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/btn_del"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_marginEnd="10dp"
            android:layout_marginBottom="10dp"
            android:contentDescription="cancel"
            android:src="@android:drawable/ic_menu_close_clear_cancel" />
    </FrameLayout>
</layout>

上面代码中,我们使用了DataBing写法,因此不需要再手动的绑定数据和刷新数据。

3.4 SharedFlow

SharedFlow基本概念

SharedFlow提供了SharedFlow 与 MutableSharedFlow两个版本,平时使用较多的是MutableSharedFlow。它们的区别是,SharedFlow可以保留历史数据,MutableSharedFlow 没有起始值,发送数据时需要调用 emit()/tryEmit() 方法。

首先,我们来看看SharedFlow的构造函数:

public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T>

可以看到,MutableSharedFlow需要三个参数:

  • replay:表示当新的订阅者Collect时,发送几个已经发送过的数据给它,默认为0,即默认新订阅者不会获取以前的数据
  • extraBufferCapacity:表示减去replay,MutableSharedFlow还缓存多少数据,默认为0
  • onBufferOverflow:表示缓存策略,即缓冲区满了之后Flow如何处理,默认为挂起。除此之外,还支持DROP_OLDEST 和DROP_LATEST 。
 //ViewModel
val sharedFlow=MutableSharedFlow<String>()
viewModelScope.launch{
      sharedFlow.emit("Hello")
      sharedFlow.emit("SharedFlow")
}

//Activity
lifecycleScope.launch{
    viewMode.sharedFlow.collect {
       print(it)
    }
}

基本使用

SharedFlow并不是系统API,所以使用前需要添加依赖:

dependencies {
    ...  //省略其他

    implementation "androidx.activity:activity-ktx:1.3.1"
    implementation "androidx.fragment:fragment-ktx:1.4.1"
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
}

接下来,我们创建一个SharedFlow,由于需要一对多的进行通知,所以我们MutableSharedFlow,然后重写postEvent()方法,

代码如下:

object LocalEventBus  {
    private val events= MutableSharedFlow< Event>()
    suspend fun postEvent(event: Event){
        events.emit(event)
    }
}
data class Event(val timestamp:Long)

接下来,我们再创建一个ViewModel,里面添加startRefresh()和cancelRefresh()两个方法,

如下:

class SharedViewModel: ViewModel() {
    private lateinit var job: Job

    fun startRefresh(){
        job=viewModelScope.launch (Dispatchers.IO){
            while (true){
                LocalEventBus.postEvent(Event(System.currentTimeMillis()))
            }
        }
    }

    fun cancelRefresh(){
        job.cancel()
    }
}

前面说过,一个典型的Flow是由三部分构成的。所以,此处我们先新建一个用于数据消费的Fragment

代码如下:

class FlowFragment: Fragment() {
    private val mBinding : FragmentFlowBinding by lazy {
        FragmentFlowBinding.inflate(layoutInflater)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return mBinding.root
    }
    override fun onStart() {
        super.onStart()
        lifecycleScope.launchWhenCreated {
            LocalEventBus.events.collect {
                mBinding.tvShow.text=" ${it.timestamp}"
            }
        }
    }
}

FlowFragment的主要作用就是接收LocalEventBus的数据,并显示到视图上。接下来,我们还需要创建一个数据的生产者,为了简单,我们只在生产者页面中开启协程,

代码如下:

class FlowActivity : AppCompatActivity() {
    private val viewModel by viewModels<SharedViewModel>()
    private val mBinding : ActivityFlowBinding by lazy {
        ActivityFlowBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        initFlow()
    }

    private fun initFlow() {
        mBinding.apply {
            btnStart.setOnClickListener {
                viewModel.startRefresh()
            }
            btnStop.setOnClickListener {
                viewModel.cancelRefresh()
            }
        }
    }
}

其中,FlowActivity代码中涉及的布局如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
    </data>
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".fragment.SharedFragment">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <fragment
                android:name="com.xzh.demo.FlowFragment"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1" />
        </LinearLayout>

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/btn_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|start"
            android:layout_marginStart="10dp"
            android:layout_marginBottom="10dp"
            android:src="@android:drawable/ic_input_add"
            android:contentDescription="start" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/btn_stop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_marginEnd="10dp"
            android:layout_marginBottom="10dp"
            android:src="@android:drawable/ic_menu_close_clear_cancel"
            android:contentDescription="cancel" />
    </FrameLayout>
</layout>

最后,当我们运行上面的代码时,就会在FlowFragment的页面上显示当前的时间戳,并且页面的数据会自动进行刷新。

3.5 冷流转热流

前文说过,Kotlin的Flow是一种冷流,而StateFlow/SharedFlow则属于热流。那么有人会问:怎么将冷流转化为热流呢?答案就是kotlin提供的shareIn()和stateIn()两个方法。

首先,来看一下StateFlow的shareIn的定义:

public fun <T> Flow<T>.stateIn(
    scope: CoroutineScope,
    started: SharingStarted,
    initialValue: T
): StateFlow<T>

shareIn方法将流转换为SharedFlow,需要三个参数,我们重点看一下started参数,表示流启动的条件,支持三种:

  • SharingStarted.Eagerly:无论当前有没有订阅者,流都会启动,订阅者只能接收到replay个缓冲区的值。
  • SharingStarted.Lazily:当有第一个订阅者时,流才会开始,后面的订阅者只能接收到replay个缓冲区的值,当没有订阅者时流还是活跃的。
  • SharingStarted.WhileSubscribed:只有满足特定的条件时才会启动。

接下来,我们在看一下SharedFlow的shareIn的定义:

public fun <T> Flow<T>.shareIn(
    scope: CoroutineScope,
    started: SharingStarted,
    replay: Int = 0
): SharedFlow<T> 

此处,我们重点看下replay参数,该参数表示转换为SharedFlow之后,当有新的订阅者的时候发送缓存中值的个数。

3.6 StateFlow与SharedFlow对比

从前文的介绍可以知道,StateFlow与SharedFlow都是热流,都是为了满足流的多个订阅者的使用场景的,一时间让人有些傻傻分不清,那StateFlow与SharedFlow究竟有什么区别呢?总结起来,大概有以下几点:

  • SharedFlow配置更为灵活,支持配置replay、缓冲区大小等,StateFlow是SharedFlow的特殊化版本,replay固定为1,缓冲区大小默认为0。
  • StateFlow与LiveData类似,支持通过myFlow.value获取当前状态,如果有这个需求,必须使用StateFlow。
  • SharedFlow支持发出和收集重复值,而StateFlow当value重复时,不会回调collect给新的订阅者,StateFlow只会重播当前最新值,SharedFlow可配置重播元素个数(默认为0,即不重播)。

从上面的描述可以看出,StateFlow为我们做了一些默认的配置,而SharedFlow泽添加了一些默认约束。总的来说,SharedFlow相比StateFlow更灵活。

四、总结

目前,官方提供的可观察的数据组件有LiveData、StateFlow和SharedFlow。LiveData是Android早期的数据流组件,具有生命周期感知能力,需要配合ViewModel才能实现它的价值。不过,LiveData也有很多使用场景缺陷,常见的有粘性事件、不支持防抖等。

于是,Kotlin在1.4.0版本,陆续推出了StateFlow与SharedFlow两个组件,StateFlow与SharedFlow都是热流,都是为了满足流的多个订阅者的使用场景,不过它们也有微妙的区别,具体参考前面内容的说明。

到此这篇关于一文读懂Android Kotlin的数据流的文章就介绍到这了,更多相关Android Kotlin内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android使用ViewBinding的详细步骤(Kotlin简易版)

    ViewBinding 是什么 2020年的3月份 巨佬 JakeWharton 开源的 butterknife 被官宣 停止维护,在github 上 说明 Attention: This tool is now deprecated. Please switch to view binding. Existing versions will continue to work, obviously, but only critical bug fixes for integration with

  • Android使用kotlin实现多行文本上下滚动播放

    最近在项目中用到了上下滚动展示条目内容,就使用kotlin简单编写实现了一下该功能. 使用kotlin实现viewflipper展示textview的上下滚动播放 其中包含了kotlin的一些简单的使用 - 首先是在布局文件中如下代码: <ViewFlipper         android:id="@+id/viewFlipper"         android:layout_width="match_parent"         android:la

  • Kotlin实现Android系统悬浮窗详解

    目录 Android 弹窗浅谈 系统悬浮窗具体实现 权限申请 代码设计 具体实现 FloatWindowService 类 FloatWindowManager 类 FloatWindowManager 类代码 FloatLayout 类及其 Layout HomeKeyObserverReceiver 类 FloatWindowUtils 类 总结 Android 弹窗浅谈 我们知道 Android 弹窗中,有一类弹窗会在应用之外也显示,这是因为他被申明成了系统弹窗,除此之外还有2类弹窗分别是

  • Android使用Kotlin API实践WorkManager

    WorkManager 提供了一系列 API 可以更加便捷地规划异步任务,即使在应用被关闭之后或者设备重启之后,仍然需要保证立即执行的或者推迟执行的任务被正常处理.对于 Kotlin 开发者,WorkManager 为协程提供了最佳的支持.在本文中,我将通过实践 WorkManager codelab 为大家展示 WorkManager 中与协程相关的基本操作.那么让我们开始吧! WorkManager 基础 当您需要某个任务保持运行状态,即使用户切换到别的界面或者用户将应用切换到后台,甚至设备

  • Android Kotlin全面详细类使用语法学习指南

    目录 前言 1. 类的声明 & 实例化 2. 构造函数 2.1 主构造函数 2.2 次构造函数 3. 类的属性 4. 可见性修饰符 5. 继承 & 重写 6. 特殊类 6.1 嵌套类(内部类) 6.2 接口 6.3 数据类 6.4 枚举类 总结 前言 Kotlin被Google官方认为是Android开发的一级编程语言 今天,我将主要讲解kotlin中的类的所有知识,主要内容包括如下: 1. 类的声明 & 实例化 // 格式 class 类名(参数名1:参数类型,参数名2:参数类型

  • Android开发Kotlin实现圆弧计步器示例详解

    目录 效果图 定义控件的样式 自定义StepView 绘制文本坐标 Android获取中线到基线距离 效果图 定义控件的样式 看完效果后,我们先定义控件的样式 <!-- 自定义View的名字 StepView --> <!-- name 属性名称 format 格式 string 文字 color 颜色 dimension 字体大小 integer 数字 reference 资源或者颜色 --> <declare-styleable name="StepView&q

  • Android Kotlin使用SQLite案例详解

    Kotlin使用SQLite 首先确定我们的目标,SQLite只是一种工具,我们需要掌握就是增删改查就可以,我们真正需要动脑的还是项目中的业务逻辑.我这篇文章写得比较适合新手,没用过SQLite的同学. 前期准备工作 新建一个类MyDataBaseHelper继承自SQLiteOpenHelper,代码如下: class MyDatabaseHelper(var context: Context, name: String, version: Int) : SQLiteOpenHelper(co

  • Android自定义View实现九宫格图形解锁(Kotlin版)

    本文实例为大家分享了Android自定义View实现九宫格图形解锁的具体代码,供大家参考,具体内容如下 效果: 代码: package com.example.kotlin_test import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.util.AttributeSet imp

  • Kotlin-Android之Activity使用详解

    目录 Activity中Toast的使用 Activity中不使用findViewById()获取控件ID Activity中使用菜单Menu Activity中intent的使用 intent显式 intent隐式 intent数据的传递 传递数据 返回数据 扩展 参考 Activity中Toast的使用 Toast.makeText(this,"ADD",Toast.LENGTH_SHORT).show() // Toast.makeText(Activity,提醒字符,lengt

  • 一文读懂Android Kotlin的数据流

    目录 一.Android分层架构 二.ViewModel + LiveData 2.1 LiveData 特性 观察者的回调永远发生在主线程 仅持有单个且最新数据 自动取消订阅 提供「可读可写」和「仅可读」两种方式 配合 DataBinding 实现「双向绑定」 2.2 LiveData的缺陷 value 可以是 nullable 的 传入正确的 lifecycleOwner 粘性事件 默认不防抖 transformation 工作在主线程 2.3 LiveData 小结 三.Flow 3.1

  • 一文读懂ava中的Volatile关键字使用

    在本文中,我们会介绍java中的一个关键字volatile. volatile的中文意思是易挥发的,不稳定的.那么在java中使用是什么意思呢? 我们知道,在java中,每个线程都会有个自己的内存空间,我们称之为working memory.这个空间会缓存一些变量的信息,从而提升程序的性能.当执行完某个操作之后,thread会将更新后的变量更新到主缓存中,以供其他线程读写. 因为变量存在working memory和main memory两个地方,那么就有可能出现不一致的情况. 那么我们就可以使

  • 一文读懂JAVA中HttpURLConnection的用法

    针对JDK中的URLConnection连接Servlet的问题,网上有虽然有所涉及,但是只是说明了某一个或几个问题,是以FAQ的方式来解决的,而且比较零散,现在对这个类的使用就本人在项目中的使用经验做如下总结: 1:> URL请求的类别: 分为二类,GET与POST请求.二者的区别在于: a:) get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet, b:) post与get的不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内. 2:>

  • 一文读懂c++之static关键字

    一.静态变量 与C语言一样,可以使用static说明自动变量.根据定义的位置不同,分为静态全局变量和静态局部变量. 全局变量是指在所有花括号之外声明的变量,其作用域范围是全局可见的,即在整个项目文件内都有效.使用static修饰的全局变量是静态全局变量,其作用域有所限制,仅在定义该变量的源文件内有效,项目中的其他源文件中不能使用它. 块内定义的变量是局部变量,从定义之处开始到本块结束处为止是局部变量的作用域.使用static修饰的局部变量是静态局部变量,即定义在块中的静态变量.静态局部变量具有局

  • 一文读懂Java Iterator(迭代器)

    Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList和HashSet等集合. Iterator 是 Java 迭代器最简单的实现,ListIterator 是 Collection API 中的接口, 它扩展了 Iterator 接口. 迭代器 it 的两个基本操作是 next .hasNext 和 remove. 调用 it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态. 调用 it.hasNext() 用于检测集合中是否

  • 一文读懂vue动态属性数据绑定(v-bind指令)

    v-bind的基本用法 一.本节说明 前面的章节我们学习了如何向页面html标签进行插值操作,那么如果我们想动态改变html标签的属性,该怎么办呢? 这就是我们这节开始要讲的内容v-bind. 二. 怎么做 ":"为v-bind的简写形式,也可称为语法糖 三. 效果 四. 深入 在上图中将a标签的href属性值设置为toutiao,VUE实例将自动去data里面寻找toutiao属性进行值绑定. 不只是a标签,所有的html标签属性都可以通过v-bind进行值绑定,然后通过改变数据动态

  • 一文读懂c++11 Lambda表达式

    1.简介 1.1定义 C++11新增了很多特性,Lambda表达式(Lambda expression)就是其中之一,很多语言都提供了 Lambda 表达式,如 Python,Java ,C#等.本质上, Lambda 表达式是一个可调用的代码单元[1]^{[1]}[1].实际上是一个闭包(closure),类似于一个匿名函数,拥有捕获所在作用域中变量的能力,能够将函数做为对象一样使用,通常用来实现回调函数.代理等功能.Lambda表达式是函数式编程的基础,C++11引入了Lambda则弥补了C

  • 一文读懂Python 枚举

    enum 是一组绑定到唯一常数值的符号名称,并且具备可迭代性和可比较性的特性.我们可以使用 enum 创建具有良好定义的标识符,而不是直接使用魔法字符串或整数,也便于开发工程师的代码维护. 创建枚举 我们可以使用 class 语法创建一个枚举类型,方便我们进行读写,另外,根据函数 API 的描述定义,我们可以创建一个 enum 的子类,如下: from enum import Enum class HttpStatus(Enum): OK = 200 BAD_REQUEST = 400 FORB

  • 一文读懂python Scrapy爬虫框架

    Scrapy是什么? 先看官网上的说明,http://scrapy-chs.readthedocs.io/zh_CN/latest/intro/overview.html Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架.可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中. 其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫. S

  • 一文读懂C++ 虚函数 virtual

    探讨 C++ 虚函数 virtual 有无虚函数的对比 C++ 中的虚函数用于解决动态多态问题,虚函数的作用是允许在派生类中重新定义与积累同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数. 首先写两个简单的类,类 B 继承自类 A,即 A 是基类,B 是派生类. class A{ public: void print(){ cout << "A" << endl; } }; class B : public A { public: void

随机推荐