You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

16 KiB

34 | Kotlin与Jetpack简直是天生一对

你好我是朱涛。今天我们来聊聊Android的Jetpack。

在我看来Kotlin和Jetpack它们两个简直就是天生一对。作为Android开发者如果只用Kotlin不用Jetpack我们其实很难在Android平台充分发挥Kotlin的语言优势。而如果我们只用Jetpack而不用Kotlin那么我们将只能用到Jetpack的小部分功能。毕竟Jetpack当中有很多API和库是专门为Kotlin提供的。

经过前面课程内容的学习相信现在你已经对Kotlin十分熟悉了那么接下来就让我们来看看Jetpack吧这节课里我会为你介绍Jetpack核心库的基本概念、简单用法以及它跟Kotlin之间的关系从而也为我们下节课的实战项目打下基础。

Jetpack简介

Jetpack它有“喷气式背包”的意思。对于我们开发者来说它其实就是Google官方为我们提供的一套开发套件专门用来帮助Android开发者提升开发效率、提升应用稳定性的。

Android Jetpack最初的宣传图标就是“穿着喷气式背包的Android机器人”。大概意思就是有了JetpackAndroid就能“起飞了”。这当然只是一种夸张的比喻不过从我实际的开发体验来说Jetpack确实可以给Android开发者带来极大的好处尤其是当Jetpack与Kotlin结合到一起的情况下。

我们先来了解下KTX。

KTX

KTX是Jetpack当中最特殊的一类库它是由Kotlin编写的同时也仅为Kotlin开发者服务使用Java语言的Android开发者是用不了的。KTX它的作用其实是对当前Android生态当中的API进行额外补充。它依托Kotlin的扩展能力为Android原有API增加新的扩展函数、扩展属性、高阶函数、命名参数、参数默认值、协程支持。

如果我们想要使用KTX的核心功能我们需要单独进行依赖

// 代码段1

dependencies {
    implementation "androidx.core:core-ktx:1.7.0"
}

让我们来看一个关于SharedPreference的简单例子如果我们使用Java我们大概率是需要写一堆模板代码的类似这样

// 代码段2

SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(SP_KEY_RESPONSE, response);

editor.commit();
editor.apply();

不过如果我们有了KTX那么代码就会变得极其简单

// 代码段3

preference.edit { putBoolean("key", value) }

上面的这个edit()方法其实是一个高阶函数它是由KTX提供的如果你去看它的源代码会发现它其实就是一个扩展出来的高阶函数

// 代码段4

inline fun SharedPreferences.edit(
        commit: Boolean = false,
        action: SharedPreferences.Editor.() -> Unit
) {
    val editor = edit()
    action(editor)
    if (commit) {
        editor.commit()
    } else {
        editor.apply()
    }
}

可以看到KTX其实就是将一些常见的模板代码封装了起来然后以扩展函数的形式提供给开发者。虽然它自身的原理很简单但是却可以大大提升开发者的效率。

KTX除了能够扩展Android SDK的API以外它还可以扩展Jetpack当中其他的库比如说LiveData、Room等等。接下来我们就来看看Jetpack当中比较核心的库Lifecycle。

Lifecycle

Lifecycle其实就是Android的生命周期组件。在整个Jetpack组件当中的地位非常特殊是必学的组件。举个例子其他的组件比如WorkManager如果我们实际工作中用不上那么我们不去学它是不会有什么问题的。Lifecycle不一样只要我们是做Android开发的我们就绕不开Lifecycle。Activity里面有LifecycleFragment里面也有LiveData里面也有

ViewModel底层也用到了Lifecycle使用协程也离不开Lifecycle。

那么Lifecycle到底是什么呢我们平时提到生命周期往往都是说的Activity、Fragment而它们两者之间却有一个很大的问题生命周期函数不一致

Activity的生命周期我们肯定心里有数不过Fragment生命周期函数比Activity多了几个onCreateView、onViewCreated、onViewStateRestore、onDestoryView。最重要的是Fragment生命周期、回调函数、Fragment内部View的生命周期它们三者之间还有很复杂的对应关系。换句话说Fragment的生命周期函数要比Activity复杂一些。

加之Activity和Fragment结合的情况下它们的生命周期行为在不同版本的Android系统上行为可能还会不一致。这在某些边界条件下还会引发一些难以排查的bug进一步增加我们Android程序员的维护成本。

在计算机世界里大部分问题都可以通过增加一个抽象层来解决。Android团队的做法就是推出了Lifecycle这个架构组件用它来统一Activity、Fragment的生命周期行为。

有了LifeCycle以后我们开发者就可以面向Lifecycle编程。比如说我们希望实现一个通用的地理位置监听的Manager就可以这样来做

// 代码段5

// 不关心调用方是Activity还是Fragment
class LocationManager(
    private val context: Context,
    private val callback: (Location) -> Unit
): DefaultLifecycleObserver {

    override fun onStart(owner: LifecycleOwner) {
        start()
    }

    override fun onStop(owner: LifecycleOwner) {
        stop()
    }

    private fun start() {
        // 使用高德之类的 SDK 请求地理位置
    }

    private fun stop() {
        // 停止
    }
}

class LifecycleExampleActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_life_cycle_example)

        val locationManager = LocationManager(this) {
            // 展示地理位置信息
        }
        lifecycle.addObserver(locationManager)
    }
}

上面代码的LocationManager只需要实现DefaultLifecycleObserver这个接口即可外部是在Activity还是在Fragment当中使用根本不必关心。

Lifecycle与协程

通过前面课程的学习我们知道协程其实也是有生命周期的。也就是说Android和Kotlin协程都是有生命周期的。这就意味着当我们在Android当中使用协程的时候就要格外小心。

作为Android开发者你一定知道内存泄漏的概念当内存变量的生命周期大于Android生命周期的时候我们就认为内存发生泄漏了。类似的当协程的生命周期大于Android生命周期的时候协程也就发生泄漏了

这一点Android官方早就帮我们考虑到了。Lifecycle还可以跟我们前面提到的KTX结合到一起进一步为Kotlin协程提供支持。

图片

在Activity、Fragment当中KTX还提供了对应的lifecycleScope它本质上就是一个与生命周期绑定的协程作用域。

// 代码段6

// 1
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    // 2
    get() = lifecycle.coroutineScope

public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
    internal abstract val lifecycle: Lifecycle

    public fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenCreated(block)
    }

    public fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenStarted(block)
    }

    public fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenResumed(block)
    }
}

在Android当中Activity和Fragment都会实现LifecycleOwner这个接口代表它们都是拥有生命周期的组件。注释1处这里使用了Kotlin的扩展属性为LifecycleOwner扩展了lifecycleScope。它的类型是LifecycleCoroutineScope而它其实就是CoroutineScope的实现类。

lifecycleScope这个属性的具体实现其实是通过注释2处的自定义getter()实现的也就是Lifecycle.coroutineScope。

// 代码段7

public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            // 1
            val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            // 2
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            //3
            if (mInternalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }

可以看到Lifecycle.coroutineScope仍然是一个扩展属性。它的逻辑其实也很简单主要是分为了三个步骤

  • 第一步检查是否存在缓存的CoroutineScope如果存在那就直接返回即可。
  • 第二步如果不存在缓存那就创建一个新的协程作用域。在创建的作用域的时候用到了两个我们熟悉的概念SupervisorJob、Dispatchers.Main它们都是协程上下文的元素前者是用来隔离协程异常传播的,后者是指定协程执行线程的
  • 第三步更新缓存并且调用register()绑定scope与Lifecycle的关系最后返回。

接下来我们打破砂锅问到底看看register()的具体逻辑是什么:

// 代码段8

internal class LifecycleCoroutineScopeImpl(
    override val lifecycle: Lifecycle,
    override val coroutineContext: CoroutineContext
    // 2
) : LifecycleCoroutineScope(), LifecycleEventObserver {
    init {
        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
            coroutineContext.cancel()
        }
    }

    // 1
    fun register() {
        launch(Dispatchers.Main.immediate) {
            if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
                lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
            } else {
                coroutineContext.cancel()
            }
        }
    }

    // 3
    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
            lifecycle.removeObserver(this)
            coroutineContext.cancel()
        }
    }
}

public interface LifecycleEventObserver extends LifecycleObserver {
    void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event);
}

上面的代码一共有三个注释,我们一个个来看:

  • 注释1register()可以看到它的逻辑其实很简单主要就是调用了addObserver()将自身作为观察者传了进去。之所以可以这么做还是因为注释2处的LifecycleEventObserver。
  • 注释2LifecycleEventObserver它其实就是一个SAM接口每当LifeCycleOwner的生命周期发生变化的时候这个onStateChanged()方法就会被调用。而这个方法的具体实现则在注释3处。
  • 注释3这里的逻辑也很简单当LifeCycleOwner对应的Activity、Fragment被销毁以后就会调用removeObserver(this)移除观察者最后就是最关键的coroutineContext.cancel(),取消整个作用域里所有的协程任务。

这样一来就能保证LifeCycle与协程的生命周期完全一致了也就不会出现协程泄漏的问题了。

小结

这节课我们主要了解了Android当中的Jetpack它是Android官方提供给开发者的一个开发套件可以帮助我们开发者提升开发效率。Jetpack当中其实有几十个库在这节课里我们是着重讲解了其中的KTX与LifeCycle。

  • KTX主要是依托Kotlin的扩展能力为Android原有API增加新的扩展函数、扩展属性、高阶函数、命名参数、参数默认值、协程支持。
  • Lifecycle其实就是Android的生命周期组件。它统一封装了Activity、Fragment等Android生命周期的组件。让我们开发者可以只关注LifeCycle的生命周期而不用在意其他细节。
  • KTX还为LifeCycle增加了协程支持也就是lifecycleScope。在它的底层这个协程作用域和宿主的生命周期进行了绑定。当宿主被销毁以后它可以确保lifecycleScope当中的协程任务也跟着被取消。

所以对于Android开发者来说Kotlin和Jetpack是一个“你中有我我中有你”的关系我们把它们称为“天生一对”一点儿也不为过。

思考题

在Android中使用协程的时候除了lifecycleScope以外我们还经常会使用ViewModel的viewModelScope。你能结合前面协程篇、源码篇的知识点分析出viewModelScope的实现原理吗

class MyViewModel : ViewModel() {
    private val _persons: MutableLiveData<List<Person>> = MutableLiveData()
    val persons: LiveData<List<Person>> = _persons

    fun loadPersons() {
        viewModelScope.launch {
            delay(500L)
            _persons.value = listOf(Person("Tom"), Person("Jack"))
        }
    }
}

// 扩展属性
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            // 实现类
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

// 实现了Closeable的CoroutineScope
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        // 取消
        coroutineContext.cancel()
    }
}

public abstract class ViewModel {

    @Nullable
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    private volatile boolean mCleared = false;


    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }

    @MainThread
    final void clear() {
        mCleared = true;

        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // 调用scope的close()
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }

    // scope暂存起来
    @SuppressWarnings("unchecked")
    <T> T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {

            closeWithRuntimeException(result);
        }
        return result;
    }

    private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}