前言

Jetpack 实战项目 PokemonGo(神奇宝贝)基于 MVVM 架构和 Repository 设计模式,PokemonGo 项目中用到的技术,都是之前写过的一系列文章里面涉及到的知识点:Paging3(network + db),Dagger-Hilt,App Startup,DataBinding,Room,Motionlayout,Kotlin Flow,Coil,JProgressView 等等。

项目 PokemonGo 已经上传到 GitHub: https://github.com/hi-dhl/PokemonGo,欢迎前去查看,动态效果图如下所示,如果动图无法查看,请点击这里查看 动态效果图 | 静态图

Jetpack 实战项目 PokemonGo 包含了以下功能:

  1. 自定义 RemoteMediator 实现 network + db 的混合使用 ( RemoteMediator 是 Paging3 当中重要成员 )
  2. 使用 Data Mapper 分离数据源 和 UI
  3. Kotlin Flow 结合 Retrofit2 + Room 的混合使用
  4. Kotlin Flow 与 LiveData 的使用
  5. 使用 Coil 加载图片
  6. 使用 ViewModel、LiveData、DataBinding 协同工作
  7. 使用 Motionlayout 做动画
  8. App Startup 与 Hilt 的使用
  9. 在 Flow 基础上封装成功或者失败处理

PokemonGo 涉及的技术:

  • Gradle Versions Plugin:检查依赖库是否存在最新版本
  • Kotlin + Coroutines + Flow:flow 是对 Kotlin 协程的扩展,让我们可以像运行同步代码一样运行异步代码
  • JetPack
    • Paging3(network + db):用到了 Paging3 中的 RemoteMediator 用来实现 network + db
    • Dagger-Hilt (2.28-alpha):依赖注入框架
    • App Startup:设置组件初始化顺序
    • DataBinding:以声明方式将可观察数据绑定到界面上
    • Room:在 SQLite 上提供了一个抽象层,流畅地访问 SQLite 数据库
    • LiveData:在底层数据库更改时通知视图
    • ViewModel:以注重生命周期的方式管理界面相关的数据
    • Andriod KTX:编写更简洁、惯用的 Kotlin 代码
  • 项目架构
    • MVVM 架构
    • Repository 设计模式
    • Data Mapper 数据映射
  • Retrofit2 & OkHttp3:用于请求网路数据
  • Coil:基于 Kotlin 开发的首个图片加载库
  • material-components-android:模块化和可定制的材料设计 UI 组件
  • Motionlayout :MotionLayout 是一种布局类型,可帮助您管理应用中的动画
  • Timber: 日志打印
  • JProgressView :一个小巧灵活可定制的进度条,支持图形:圆形、圆角矩形、矩形等等

以上技术栈对应之前写的技术文章:

  • Jetpack 最新成员 AndroidX App Startup 实践以及原理分析
  • Jetpack 成员 Paging3 实践以及源码分析(一)
  • Jetpack 新成员 Paging3 网络实践及原理分析(二)
  • Jetpack 新成员 Hilt 实践(一)启程过坑记
  • Jetpack 新成员 Hilt 实践之 App Startup(二)进阶篇
  • Jetpack 新成员 Hilt 与 Dagger 大不同(三)落地篇
  • 全方面分析 Hilt 和 Koin 性能
  • [译][2.4K Star] 放弃 Dagger 拥抱 Koin
  • 项目中封装 Kotlin + Android Databinding
  • 为数不多的人知道的 Kotlin 技巧以及 原理解析(一)
  • 为数不多的人知道的 Kotlin 技巧以及 原理解析(二)

如果之前对这些技术没有接触过,或者只是听说,对阅读本文没有什么影响,本文会对这些技术结合着项目 PokemonGo 来分析,为了文章的简洁性,本文不会细究技术细节,因为每个技术都需要花好几篇文章才能分析清楚,我会在后续的文章去详细分析。

如何检查依赖库最新版本

在之前的文章 再见吧 buildSrc, 拥抱 Composing builds 提升 Android 编译速度 分析过,到目前为止大概管理 Gradle 依赖提供了 4 种不同方法:

  • 手动管理 :在每个 module 中定义插件依赖库,每次升级依赖库时都需要手动更改(不建议使用)
  • 使用 ext 的方式管理插件依赖库 :这是 Google 推荐管理依赖的方法 Android官方文档
  • Kotlin + buildSrc:自动补全和单击跳转,依赖更新时 将重新 构建整个项目
  • Composing builds:自动补全和单击跳转,依赖更新时 不会重新 构建整个项目

新版的 AndroidStudio 只支持 ext 的方式手动方式管理 检查依赖库是否存在最新版本,不支持 buildSrc、gradle-wrapper 版本的检查。

满足不了 PokemonGo 项目的需求,在 PokemonGo 项目中采用 buildSrc 方式去管理所有依赖库,因为 PokemonGo 项目采用单模块结构,而且支持 自动补全单击跳转 很方便,所这里用到了 Gradle Versions Plugin 插件去检查依赖库的最新版本,检查结果如下所示:

The following dependencies have later release versions:- androidx.swiperefreshlayout:swiperefreshlayout [1.0.0 -> 1.1.0]https://developer.android.com/jetpack/androidx- com.squareup.okhttp3:logging-interceptor [3.9.0 -> 4.7.2]https://square.github.io/okhttp/- junit:junit [4.12 -> 4.13]http://junit.org- org.koin:koin-android [2.1.5 -> 2.1.6]- org.koin:koin-androidx-viewmodel [2.1.5 -> 2.1.6]- org.koin:koin-core [2.1.5 -> 2.1.6]Gradle release-candidate updates:- Gradle: [6.1.1 -> 6.5.1]

会列出所有需要更新的依赖库的最新版本,并且 Gradle Versions Plugin 比 AndroidStudio 所支持的更加全面:

  • 支持手动方式管理依赖库最新版本检查
  • 支持 ext 的方式管理依赖库最新版本检查
  • 支持 buildSrc 方式管理依赖库最新版本检查
  • 支持 gradle-wrapper 最新版本检查
  • 支持多模块的依赖库最新版本检查

那么如何使用呢?只需要三步

  • 1.将 PokemonGo 项目根目录 checkVersions.gradle 文件拷贝到你的项目根目录下面

  • 2.在项目的根目录 build.gradle 文件夹内添加以下代码

    apply from: './checkVersions.gradle'
    buildscript {repositories {google()jcenter()}dependencies {classpath "com.github.ben-manes:gradle-versions-plugin:0.28.0"}
    }
    
  • 3.添加完成之后,在根目录下执行以下命令。

    ./gradlew dependencyUpdates
    

    会在当前目录下生成 build/dependencyUpdates/report.txt 文件。

MVVM 架构

Jetpack 实战项目 PokemonGo 基于 MVVM 架构和 Repository 设计模式,如今几乎所有的 Android 开发者至少都听过 MVVM 架构,在谷歌 Android 团队宣布了 Jetpack 的视图模型之后,它已经成为了现代 Android 开发模式最流行的架构之一,如下图所示:

MVVM 有助于将应用程序的业务逻辑与 UI 完全分开。 如果业务逻辑与 UI 逻辑之间的联系非常紧密,那么维护将很困难,由于很难重用业务逻辑,因此编写单元测试代码非常困难,一堆重复的代码和复杂的逻辑。

Jetpack 的视图模型的 MVVM 架构由 View + DataBinding + ViewModel + Model 组成。

DataBinding

DataBinding(数据绑定)实际上是 XML 布局中的另一个视图结构层次,视图 (XML) 通过数据绑定层不断地与 ViewModel 交互。

我们来看一个例子,首页上有个 RecyclerView 用来展示神奇宝贝数据(名字、图片、点击事件等等),每一个 item 对应一个 ViewHolder,来看一下 ViewHolder 的实现。

class PokemonViewModel(view: View) : DataBindingViewHolder<PokemonListModel>(view) {private val mBinding: RecycleItemPokemonBinding by viewHolderBinding(view)override fun bindData(data: PokemonListModel, position: Int) {mBinding.apply {pokemon = dataexecutePendingBindings()}}}

正如你所看到的,由于使用了数据绑定,ViewHolder 里面的代码变的非常简单,可能这个例子不够明显,我们来看一个劲爆的,点击首页每一个 item 会跳转到详情页面,详情页面如下图所示:

详情页面(DetailActivity)展示了神奇宝贝的详细数据,先查询数据库,如果没有找到,读取网路数据然后保存到数据库,由于使用了数据绑定,代码变得非常简单,如下所示:

class DetailActivity : DataBindingAppCompatActivity() {private val mBindingActivity: ActivityDetailsBinding by binding(R.layout.activity_details)private val mViewModel: DetailViewModel by viewModels()lateinit var mPokemonModel: PokemonListModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mBindingActivity.apply {mPokemonModel = requireNotNull(intent.getParcelableExtra(KEY_LIST_MODEL))pokemonListModel = mPokemonModellifecycleOwner = this@DetailActivityviewModel = mViewModel.apply {fectchPokemonInfo(mPokemonModel.name).observe(this@DetailActivity, Observer {})}}}
}

正如你所见 DetailActivity 代码变得非常简单,如果以后我们想要改变网络的 URL、Model、获取或保存数据的方式等等,我们不需要改变 DetailActivity 中的任何代码。

更多关于 DataBinding 的使用请参考我另外一个仓库 JDataBinding:目前已经封装了一系列的组件包含 DataBindingActivity、DataBindingAppCompatActivity、DataBindingFragmentActivity、DataBindingFragment、DataBindingDialog、DataBindingListAdapter、DataBindingViewHolder 等等。

ViewModel

ViewModel 是 MVVM 架构中非常重要的设计,它在 activities 或 fragments 和业务逻辑中起到了非常重要的作用,它不依赖于 UI 组件,使得单元测试更加容易,ViewModel 以生命周期的方式管理界面相关的数据,直到 Activity 被销毁。

LiveData 与 ViewModel 具有很好的协同作用,LiveData 持有从数据源获取到的数据,并且它可以被 DataBinding 组件观察,当 Activity 被销毁时,它将被取消订阅。

而详情页面(DetailActivity) 代码之所以能这么简单得益于 ViewModel、LiveData、DataBinding 协同工作, 我们来看一下 ViewModel 代码。

class DetailViewModel @ViewModelInject constructor(val polemonRepository: Repository
) : ViewModel() {private val _pokemon = MutableLiveData<PokemonInfoModel>()val pokemon: LiveData<PokemonInfoModel> = _pokemon@OptIn(ExperimentalCoroutinesApi::class)fun fectchPokemonInfo(name: String) = liveData<PokemonInfoModel> {polemonRepository.featchPokemonInfo(name).collectLatest {_pokemon.postValue(it)emit(it)}.......// 省略部分代码,}}

activity_details.xml 代码

<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><data><variablename="viewModel"type="com.hi.dhl.pokemon.ui.detail.DetailViewModel" /></data>......<androidx.appcompat.widget.AppCompatTextViewandroid:id="@+id/weight"android:text="@{viewModel.pokemon.getWeightString}"/>......</layout>

这是获取神奇宝贝的详细信息,通过 DataBinding 以声明方式将数据(神奇宝贝的体重)绑定到界面上,更多使用参考项目中的代码。

Repository

Repository 设计模式是最流行、应用最广泛的设计模式之一,在 Repository 层获取网络数据,并将数据存储到数据库中,在这一层中有两个非常重要的成员 Paging3 库中的 RemoteMediator 和 Data Mappers。

RemoteMediator

在之前的文章 Jetpack 成员 Paging3 实践以及源码分析(一) 和 Jetpack 新成员 Paging3 网络实践及原理分析(二) 分别分析了使用 Paging3 访问 数据库网络,但是遗漏了 RemoteMediator 类的使用,RemoteMediator 是 Paging3 当中一个非常重要的成员,用于实现 数据库网络 访问,所以这里是对之前的文章一个补充。

RemoteMediator 很重要,需要单独花一篇文章去分析,为了节省篇幅,在这里不会详细的去分析它,如果对 RemoteMediator 不太理解没有关系,我会在后续的文章里面详细的分析它。

项目中网络访问用的是 Retrofit2 & OkHttp3 用来请求网络数据,使用 Room 作为数据库存储,将获得的数据保存到数据库中,Room 在 SQLite 上提供了一个抽象层,流畅地访问 SQLite 数据库,同时拥有了 SQLite 全部功能,在编译的时候进行错误检查。

@OptIn(ExperimentalPagingApi::class)
class PokemonRemoteMediator(val api: PokemonService,val db: AppDataBase
) : RemoteMediator<Int, PokemonEntity>() {val mPageKey = 0override suspend fun load(loadType: LoadType,state: PagingState<Int, PokemonEntity>): MediatorResult {try {......val pageKey = when (loadType) {// 首次访问 或者调用 PagingDataAdapter.refresh()LoadType.REFRESH -> null// 在当前加载的数据集的开头加载数据时LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)// 在当前数据集末尾添加数据LoadType.APPEND -> {......if (remoteKey == null || remoteKey.nextKey == null) {return MediatorResult.Success(endOfPaginationReached = true)}remoteKey.nextKey}}......// 使用 Retrofit2 获取网络数据val page = pageKey ?: 0val result = api.fetchPokemonList(state.config.pageSize,page * state.config.pageSize).results.......db.withTransaction {if (loadType == LoadType.REFRESH) { // 当首次加载,或者下拉刷新的时候,清空当前数据 }......// 存储获取到的数据remoteKeysDao.insertAll(entity)pokemonDao.insertPokemon(item)}return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)} catch (e: IOException) {return MediatorResult.Error(e)} catch (e: HttpException) {return MediatorResult.Error(e)}}
}

注意:使用了 @OptIn(ExperimentalPagingApi::class) 需要在 App 模块 build.gradle 文件内添加以下代码。

android {kotlinOptions {freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]}
}

RemoteMediator 的实现类 PokemonRemoteMediator 中的核心部分是关于参数 LoadType 的判断。

  • LoadType.REFRESH首次访问 或者调用 PagingDataAdapter.refresh() 触发,这里不需要做任何操作,返回 null 就可以
  • LoadType.PREPEND:在当前列表头部添加数据的时候时触发,实际在项目中基本很少会用到直接返回 MediatorResult.Success(endOfPaginationReached = true) ,参数 endOfPaginationReached 表示没有数据了不在加载
  • LoadType.APPEND:下拉加载更多时触发,这里获取下一页的 key, 如果 key 不存在,表示已经没有更多数据,直接返回 MediatorResult.Success(endOfPaginationReached = true) 不会在进行网络和数据库的访问

接下来的逻辑和之前请求网络数据的逻辑没有什么区别了,使用 Retrofit2 获取网络数据,然后使用 Room 将数据保存到数据库中。

接下来聊一下 Repository 中另外一个重要的成员 Data Mapper,在项目中起到了非常的重要,在一个快速开发的项目中,为了越快完成第一个版本交付,下意识的将数据源和 UI 绑定到一起,当业务逐渐增多,数据源变化了,上层也要一起变化,导致后期的重构工作量很大,核心的原因耦合性太强了。

Data Mapper(个人建议)

Data Mapper 的意识非常重要,在项目中起到了非常的重要,关于 Data Mappers 在 Repository 中的重要性可以看一下国外大神写的这篇文章 The “Real” Repository Pattern in Android 在 Medium 上获得了 4.9K 的赞。

使用 Data Mapper 分离数据源的 Model 和 页面显示的 Model,不要因为数据源的增加、修改或者删除,导致上层页面也要跟着一起修改,换句话说使用 Data Mapper 做一个中间转换,如下图所示,来源于网络:

使用 Data Mapper(数据映射)优点如下:

  • 数据源的更改不会影响上层的业务
  • 糟糕的后端实现不会影响上层的业务 ( 想象一下,如果你被迫执行2个网络请求,因为后端不能在一个请求中提供你需要的所有信息,你会让这个问题影响你的整个代码吗? )
  • Data Mapper 便于做单元测试,确保不会因为数据源的变化,而影响上层的业务

如果在一个大型项目中直接使用 Data Mapper 会有适得其反的效果,所以需要结合设计模式来完善,这不在本文讨论范围之内,其实在这里我想表达是,不要因为快速实现某个功能,下意识的将数据源的 model 和 UI 绑定在一起。

Data Mappe 实现方式有很多种,可以手动实现,也可以通过引入第三方框架,其中有名框架 modelmapper,在 PokemonGo 项目中是手动实现的。

Koltin Flow

停止使用 RxJava,尝试一下 Flow,不仅简单而且功能很强大,Retrofit2 和 Room 也都提供了对应的支持。

Flow 库是在 Kotlin Coroutines 1.3.2 发布之后新增的库,也叫做异步流,类似 RxJava 的 Observable,在 PokemonGo 项目中也用到了 Flow。

override suspend fun featchPokemonInfo(name: String): Flow<PokemonInfoModel> {return flow {val pokemonDao = db.pokemonInfoDao()var infoModel = pokemonDao.getPokemon(name)// 查询数据库是否存在,如果不存在请求网络if (infoModel == null) {// 网络请求val netWorkPokemonInfo = api.fetchPokemonInfo(name)......pokemonDao.insertPokemon(infoModel) // 插入更新数据库}val model = mapper2InfoModel.map(infoModel) // 数据转换emit(model)}.flowOn(Dispatchers.IO)
}

在这里做了三件事:

  • 查询数据库是否存在,如果不存在请求网络
  • 请求网络获取数据,更新数据库
  • 将数据源的 Model 转换为页面显示的 Model

依赖注入

Hilt、Dagger、Koin 等等都是依赖注入库,使用依赖注入库有以下优点:

  • 依赖注入库会自动释放不再使用的对象,减少资源的过度使用。
  • 在配置 scopes 范围内,可重用依赖项和创建的实例,提高代码的可重用性,减少了很多模板代码。
  • 代码变得更具可读性。
  • 易于构建对象。
  • 编写低耦合代码,更容易测试。

在 PokemonGo 项目中使用的是 Hilt,Hilt 是在 Dagger 基础上进行开发的,减少了在项目中进行手动依赖,Hilt 集成了 Jetpack 库和 Android 框架类,并删除了大部分模板代码,让开发者只需要关注如何进行绑定,同时 Hilt 也继承了 Dagger 优点,编译时正确性、运行时性能、并且得到了 Android Studio 的支持,来看一下 Hilt 与 Room 在一起使用的例子。

@Module
@InstallIn(ApplicationComponent::class)
object RoomModule {/*** @Provides 常用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。* @Singleton 提供单例*/@Provides@Singletonfun provideAppDataBase(application: Application): AppDataBase {return Room.databaseBuilder(application, AppDataBase::class.java, "dhl.db").fallbackToDestructiveMigration().allowMainThreadQueries().build()}@Singleton@Providesfun provideTasksRepository(db: AppDataBase): Repository {return PokemonFactory.makePokemonRepository(db)}
}

这里需要用到 @Module 注解,使用 @Module 注解的普通类,在其内部提供 Room 的实例,更多使用可以查看 PokemonGo 项目。

小巧灵活的进度条

神奇宝贝详情页的进度条使用的是 JProgressView :一个小巧灵活可定制的进度条,支持图形:圆形、圆角矩形、矩形等等,效果如下图所示:

起源于当时想用一个现成的库,但是在网上找了很多,没有一个合适自己的,要不大而全,要不作者好久没更新了,要不不兼容 DataBinding,于是乎就自己封装了一个小巧灵活的进度条,项目长期维护并持续更新,如果有更好的建议欢迎告知我,JProgressView 使用非常的简单,根据自己的需求去配置即可。

<com.hi.dhl.jprogressview.JProgressViewandroid:layout_width="match_parent"android:layout_height="18dp"android:layout_below="@+id/exp"android:translationZ="100dp"app:maxProgressValue="@{viewModel.pokemon.maxExp}"app:progressValue="@{viewModel.pokemon.exp}"app:progress_animate_duration="@integer/progress_animate_duration"app:progress_color="@color/color_progress_4"app:progress_color_background="@color/color_progress_bg"app:progress_paint_bg_width="@dimen/circle_stroke_width"app:progress_paint_value_width="@dimen/circle_stroke_width"app:progress_text_color="@android:color/black"app:progress_text_size="@dimen/text_size_12sp"app:progress_type="@integer/porgress_tpye_round_rect" />
名称 值类型 默认值 备注
progress_type integer 圆形:1 矩形:0;矩形:0;矩形:0
progress_animate_duration integer 2000 动画运行时间
progress_color color Color.GRAY 当前进度颜色
progress_color_background color Color.GRAY 进度条背景颜色
progress_paint_bg_width dimen 10 进度条背景画笔的宽度
progress_paint_value_width dimen 10 当前进度画笔的宽度
progress_text_color color Color.BLUE 进度条上的文字的颜色
progress_text_size dimen sp2Px(20f) 进度条上的文字的大小
progress_text_visible boolean 默认不显示:false 是否显示文字
progress_value integer 0 当前进度
progress_value_max integer 100 当前进度条的最大值

更多关于进度条的使用,查看 JProgressView 仓库,全文到这里就结束了,为了节省篇幅,很多在之前系列文章里面分析过的,这里不在详细分析了,更多技术细节会在后续的系列文章中分析。

正在建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,目前已经包含了 App Startup、Paging3、Hilt 等等,正在逐渐增加其他 Jetpack 新成员,仓库持续更新,可以前去查看:AndroidX-Jetpack-Practice, 如果这个仓库对你有帮助,请仓库右上角帮我点个赞。

结语

致力于分享一系列 Android 系统源码、逆向分析、算法、翻译、Jetpack 源码相关的文章,正在努力写出更好的文章,如果这篇文章对你有帮助给个 star,文章中有什么没有写明白的地方,或者有什么更好的建议欢迎留言,欢迎一起来学习,在技术的道路上一起前进。

Android10-Source-Analysis

正在写一系列的 Android 10 源码分析的文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,如果你同我一样喜欢研究 Android 源码,可以关注我 GitHub 上的 Android10-Source-Analysis。

Leetcode-Solutions-with-Java-And-Kotlin

由于 LeetCode 的题库庞大,每个分类都能筛选出数百道题,由于每个人的精力有限,不可能刷完所有题目,因此我按照经典类型题目去分类、和题目的难易程度去排序。

  • 数据结构: 数组、栈、队列、字符串、链表、树……
  • 算法: 查找算法、搜索算法、位运算、排序、数学、……

每道题目都会用 Java 和 kotlin 去实现,并且每道题目都有解题思路,如果你同我一样喜欢算法、LeetCode,可以关注我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-And-Kotlin。

Technical-Article-Translation

目前正在整理和翻译一系列精选国外的技术文章,不仅仅是翻译,很多优秀的英文技术文章提供了很好思路和方法,每篇文章都会有译者思考部分,对原文的更加深入的解读,可以关注我 GitHub 上的 Technical-Article-Translation。

神奇宝贝 眼前一亮的 Jetpack + MVVM 极简实战相关推荐

  1. Django Web 开发极简实战

    课程介绍 本课程是一个系列基础教程,目标是带领读者上手实战 Django Web 开发,课程以 Django 1.10 为基础,通过一个在线视频网站的构建,实战化的介绍 Django Web 开发中涉 ...

  2. TDengine极简实战:从采集到入库,从前端到后端,体验物联网设备数据流转

    作者:牛晓青 背景 我们的项目涉及物联网相关业务,由于一开始的年少无知,传感器数据采用了 MySQL 进行存储,经过近两年的数据累积,目前几个核心表单表数据已过亿,虽然通过索引优化. SQL 优化以及 ...

  3. .NET Core实战项目之CMS 第八章 设计篇-内容管理极简设计全过程

    写在前面 上一篇文章.NET Core实战项目之CMS 第七章 设计篇-用户权限极简设计全过程中我带着大家进行了权限部分的极简设计,也仅仅是一个基本的权限设计.不过你完全可以基于这套权限系统设计你的更 ...

  4. .NET Core实战项目之CMS 第七章 设计篇-用户权限极简设计全过程

    写在前面 这篇我们对用户权限进行极简设计并保留其扩展性.首先很感谢大家的阅读,前面六章我带着大家快速入门了ASP.NET Core.ASP.NET Core的启动过程源码解析及配置文件的加载过程源码解 ...

  5. MVVM架构之自动增删改的极简RecycleView的实现

    先上个源代码的链接:github.com/whenSunSet/- RecycleView是Google替代ListView的一种方案,其有着很高的解耦度,让许多开发者抛弃了以往的ListView,那 ...

  6. 【2022·深度强化学习课程】深度强化学习极简入门与Pytorch实战

    课程名称:深度强化学习极简入门与Pytorch实战 课程内容:强化学习基础理论,Python和深度学习编程基础.深度强化学习理论与编程实战 课程地址:https://edu.csdn.net/cour ...

  7. Serverless 实战 —— 基于 Serverless 的 VuePress 极简静态网站

    基于 Serverless 的 VuePress 极简静态网站 作者: Aceyclee 之前用过 Docsify + Serverless Framework 快速创建个人博客系统,虽然 docsi ...

  8. 30个Python常用极简代码,拿走就用

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 作者丨Fatos Morina 来源丨Python 技术 编辑丨极市 ...

  9. 30 段极简 Python 代码:这些小技巧你都 Get 了么?

    选自 | towardsdatascienc 编译 | 机器之心 学 Python 怎样才最快,当然是实战各种小项目,只有自己去想与写,才记得住规则.本文是 30 个极简任务,初学者可以尝试着自己实现 ...

最新文章

  1. Appium使用のhelloworld
  2. vetur插件_6款让开发效率“起飞的”VS code插件,哪个才是你的最爱
  3. 四十九、IQ 与测试评分案例
  4. Dubbo(六)使用SpringBoot搭建dubbo服务提供者工程
  5. Laravel测试驱动开发--功能测试
  6. python编译exe运行慢_Python运行速度慢你知道这是为什么吗?
  7. eclipse和idea开发servlet的区别
  8. Entity Framework第三篇IQueryable和list本地集合
  9. 报错ValueError: check_hostname requires server_hostname
  10. 一句“哭什么哭”,说得好
  11. 计算机绘图cad期末考试试题,20年广东理工学院成人高考期末考试 计算机绘图(AutoCAD) 复习资料.pdf...
  12. 【Power Automate】在power automate中使用SharePoint rest api(Send an http request to SharePoint)获取列表数据
  13. QCustomplot 实现鼠标追踪定位线以及坐标
  14. 山西最新五大姓氏排名发布,排名第一的是王,第二的竟是……
  15. Android Studio插件GsonFormat快速实现JavaBean
  16. VBE开源插件Rubberduck
  17. html标签 lt heavy gt,HTML Purifier:转换&lt; body&gt;到&lt; div&gt;
  18. 企业管理 史玉柱:公司只有三个人可以谈战略,其他人抓好执行
  19. Base64编解码原理并用Java手工实现Base64编解码
  20. Eclipse安装、激活、配置最新版JRebel

热门文章

  1. idea远程调试jar包
  2. 广播和多播(组播)的区别
  3. XP 修复 ubuntu 启动
  4. mysql注入之limit 注入
  5. Windows7 64bit 使用局域网上的共享打印
  6. 分享166个HTML医疗保健模板,总有一款适合您
  7. PHP 单例模式如何实现,PHP 单例模式的实现
  8. Android P版本startService Crash问题,深入了解StartService流程
  9. java 字符流读取_Java 字符流读写文件
  10. php去除数组中重复的元素