项目简介

玩Android demo。用Jetpack MVVM开发架构、单Activity多Fragment项目设计,项目结构清晰,代码简洁优雅,追求最官方的实现方式。用到以下知识点:

LiveData、ViewModel、DataBinding(包括双向绑定、BindingAdapter的使用)、ViewBinding、coroutines(包含flow、suspend、livedata协程构造器、flow协程构造器的使用)、Hilt、Paging3(包含RemoteMediator、加载状态)、Room、Navigation(通过ViewModel共享数据)、Banner(kotlin简单实现)、TabLayout、BottomNavigationView、RecycleView(包含ListAdapter、ConcatAdapter、PagingDataAdapter的使用)、ViewPager2、Glide、Cookie、Retrofit2、启动页面、深色主题、沉浸式模式、Kotlin高阶函数。

项目截图(默认主题、深色主题)

项目参考demo

项目知识点

LiveData

ViewModel

ViewBinding

DataBinding

coroutines

  • 理解协程、LiveData 和 Flow

    • liveData 协程构造方法提供了一个协程代码块,这个块就是 LiveData 的作用域,当 LiveData 被观察的时候,里面的操作就会被执行,当 LiveData 不再被使用时,里面的操作就会取消。 而且该协程构造方法产生的是一个不可变的LiveData,可以直接暴露给对应的视图使用。而 emit() 方法则用来更新 LiveData 的数据。
    • 一个常见用例,比如当用户在 UI 中选中一些元素,然后将这些选中的内容显示出来。一个常见的做法是,把被选中的项目的 ID 保存在一个 MutableLiveData 里,然后运行 switchMap。现在在 switchMap 里,您也可以使用协程构造方法:
    1. private val itemId = MutableLiveData<String>()
    2. val result = itemId.switchMap {
    3. liveData { emit(fetchItem(it)) }
    4. }
  • Google 推荐在 MVVM 架构中使用 Kotlin Flow
  • 图解协程原理

Hilt

  • hilt 和 Koin

Paging

  • Paging 库 3.0.0正式版已发布,普天同庆!Paging 库可帮助您加载和显示来自本地存储或网络中更大的数据集中的数据页面。此方法可让您的应用更高效地利用网络带宽和系统资源。Paging 库的组件旨在契合推荐的 Android 应用架构,流畅集成其他 Jetpack 组件,并提供一流的 Kotlin 支持。

  • 官方文档

  • 官方demo:

  • Paging 库包含以下功能:

    • 分页数据的内存中缓存。该功能可确保您的应用在处理分页数据时高效利用系统资源。
    • 内置的请求重复信息删除功能,可确保您的应用高效利用网络带宽和系统资源。
    • 可配置的 RecyclerView 适配器,会在用户滚动到已加载数据的末尾时自动请求数据。
    • 对 Kotlin 协程和 Flow 以及 LiveData 和 RxJava 的一流支持。
    • 内置对错误处理功能的支持,包括刷新和重试功能。
  • Paging 组件及其在应用架构的集成:

  • 定义数据源 : 数据源的定义取决于您从哪里加载数据。您仅需实现 PagingSource 或者 PagingSource 与 RemoteMediator 的组合:

    • 如果您从单个源加载数据,例如网络本地数据文件内存缓存等(不只是网络和数据库,其他如文件也可以使用Paging),实现 PagingSource 即可,如果您使用了 Room,从 2.3.0-alpha 开始,它将默认为您实现 PagingSource。
    • 如果您从一个多层级数据源加载数据,就像带有本地数据库缓存的网络数据源那样。那么您需要实现 RemoteMediator 来合并两个数据源到一个本地数据库缓存的 PagingSource 中。
  • PagingSource :

    • PagingSource 可以定义一个分页数据的数据源,以及从该数据源获取数据的方式。
    • LoadParams:PagingSource 的 密封类(sealed),包含有关要执行的加载操作的信息,其中包括要加载的键和要加载的项数。作为load()函数的参数使用
    • LoadResult:PagingSource 的 密封类(sealed),包含加载操作的结果。LoadResult 是一个密封的类,根据 load() 调用是否成功。作为load()函数的返回值
    • getRefreshKey(): 该方法接受 PagingState 对象作为参数,并且当数据在初始加载后刷新或失效时,该方法会返回要传递给 load() 方法的键。在后续刷新数据时,Paging 库会自动调用此方法。
    • load(): 下图说明了load() 函数如何接收每次加载的键并为后续加载提供键:

    • 代码示例:
      1. // 自定义PagingSource类
      2. private const val ARTICLE_STARTING_PAGE_INDEX = 0
      3. class HomeArticlePagingSource(
      4. private val api: WanJetpackApi
      5. ) : PagingSource<Int, ApiArticle>() {
      6. override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ApiArticle> {
      7. val page = params.key ?: ARTICLE_STARTING_PAGE_INDEX
      8. return try {
      9. val response = api.getHomeArticle(page)
      10. val datas = response.data.datas
      11. LoadResult.Page(
      12. data = datas,
      13. prevKey = if (page == ARTICLE_STARTING_PAGE_INDEX) null else page - 1,
      14. nextKey = if (page == response.data.pageCount) null else page + 1,
      15. )
      16. } catch (exception: Exception) {
      17. LoadResult.Error(exception)
      18. }
      19. }
      20. override fun getRefreshKey(state: PagingState<Int, ApiArticle>): Int? {
      21. return null
      22. }
      23. }
  • PagingData :

    • 分页数据的容器被称为 PagingData,每次刷新数据时,都会创建一个 PagingData 的实例。如果要创建 PagingData 数据流,您需要创建一个 Pager 实例,并提供一个 PagingConfig 配置对象和一个可以告诉 Pager 如何获取您实现的 PagerSource 的实例的函数,以供 Pager 使用。
    • Pager 类提供的方法可显示来自 PagingSource 的 PagingData 对象的响应式流。Paging 库支持使用多种流类型,包括 Flow、LiveData 以及 RxJava 中的 Flowable 和 Observable 类型。
    • 通过 Pager().flow可以返回Flow<PagingData>。然后在ViewModel中.cachedIn(viewModelScope), cachedIn()运算符使数据流可共享,并使用提供的 CoroutineScope 缓存加载的数据
    • 代码示例: (注:Pager 的 remoteMediator 参数可选项, RemoteMediator 是重点)
      1. //Repository:
      2. fun getHomeArticle(): Flow<PagingData<ApiArticle>> {
      3. return Pager(
      4. config = PagingConfig(enablePlaceholders = false, pageSize = HOME_ARTICLE_PAGE_SIZE),
      5. pagingSourceFactory = { HomeArticlePagingSource(api) }
      6. ).flow
      7. }
      1. //ViewModel:
      2. fun getHomeArticle(): Flow<PagingData<ApiArticle>> {
      3. val newResult: Flow<PagingData<ApiArticle>> =
      4. repository.getHomeArticle().cachedIn(viewModelScope)
      5. currentArticleResult = newResult
      6. return newResult
      7. }
  • PagingDataAdapter :

    • 与定义 RecyclerView 列表 Adapter 时的通常做法相同:必须定义 onCreateViewHolder() 和 onBindViewHolder() 方法;指定 ViewHoler 和 DiffUtil.ItemCallback
    • Adapter 及 UI ( Activity、Fragment )中的相关代码略。
  • LoadType : 是个 enum 类,包含三种状态:REFRESH、PREPEND、APPEND。在 PagingSource 的 LoadParams 类中用到。

    • 官方介绍:Type of load a [PagingData] can trigger a [PagingSource] to perform.
    • REFRESH:[PagingData] content being refreshed, which can be a result of [PagingSource] invalidation, refresh that may contain content updates, or the initial load.
    • PREPEND:Load at the start of a [PagingData].
    • APPEND:Load at the end of a [PagingData].
  • LoadState : 是个 sealed(密封) 类。

    • 官方介绍:LoadState of a PagedList load - associated with a [LoadType].
    • [LoadState] of any [LoadType] may be observed for UI purposes by registering a listener via [androidx.paging.PagingDataAdapter.addLoadStateListener] or [androidx.paging.AsyncPagingDataDiffer.addLoadStateListener]
    • Paging 库通过 LoadState 对象公开可在界面中使用的加载状态。LoadState 根据当前的加载状态采用以下三种形式之一:
      • 如果没有正在执行的加载操作且没有错误,则 LoadState 为 LoadState.NotLoading 对象。
      • 如果有正在执行的加载操作,则 LoadState 为 LoadState.Loading 对象。
      • 如果出现错误,则 LoadState 为 LoadState.Error 对象。
  • 加载状态的三个场景:下拉刷新、上拉加载更多、首次进入页面中间的滚动条(及加载失败提醒)

  • 显示加载状态 : 可通过两种方法在界面中使用 LoadState:使用监听器,以及使用特殊的列表适配器在 RecyclerView 列表中直接显示加载状态。

    • 方法一、 使用监听器获取加载状态: 为了获取加载状态以用于界面中的一般用途,PagingDataAdapter 中提供了 addLoadStateListener()、loadStateFlow 两种方式。来自 loadStateFlow 或 addLoadStateListener() 的更新可确保与界面的更新保持同步。这意味着,如果您收到 NotLoading.Incomplete 的 LoadState,则可以确定加载已完成,并且界面也已相应更新。
      1. // addLoadStateListener 方式。
      2. articleAdapter.addLoadStateListener {
      3. when (it.refresh) {
      4. is LoadState.NotLoading -> {
      5. progressBar.visibility = View.INVISIBLE
      6. recyclerView.visibility = View.VISIBLE
      7. }
      8. is LoadState.Loading -> {
      9. progressBar.visibility = View.VISIBLE
      10. recyclerView.visibility = View.INVISIBLE
      11. }
      12. is LoadState.Error -> {
      13. val state = it.refresh as LoadState.Error
      14. progressBar.visibility = View.INVISIBLE
      15. Toast.makeText(this, "Load Error: ${state.error.message}", Toast.LENGTH_SHORT).show()
      16. }
      17. }
      18. }
      1. // loadStateFlow 方式
      2. // collectLatest 是个 suspend 函数,所以要在协程或者另一个 suspend 中调用
      3. lifecycleScope.launch {
      4. pagingAdapter.loadStateFlow.collectLatest {
      5. progressBar.isVisible = it.refresh is LoadState.Loading
      6. retry.isVisible = it.refresh !is LoadState.Loading
      7. errorMsg.isVisible = it.refresh is LoadState.Error
      8. }
      9. }
    • 方法二、 使用适配器呈现加载状态: Paging 库提供了另一个名为 LoadStateAdapter 的列表适配器,用于直接在显示的分页数据列表中呈现加载状态。其实该方法就是在PagingDataAdapter中把addLoadStateListener()和ConcatAdapter封装了一下
      • 首先,创建一个实现 LoadStateAdapter 的类,并定义 onCreateViewHolder() 和 onBindViewHolder() 方法:
        1. class LoadStateViewHolder(
        2. parent: ViewGroup,
        3. retry: () -> Unit
        4. ) : RecyclerView.ViewHolder(
        5. LayoutInflater.from(parent.context)
        6. .inflate(R.layout.load_state_item, parent, false)
        7. ) {
        8. private val binding = LoadStateItemBinding.bind(itemView)
        9. private val progressBar: ProgressBar = binding.progressBar
        10. private val errorMsg: TextView = binding.errorMsg
        11. private val retry: Button = binding.retryButton
        12. .also {
        13. it.setOnClickListener { retry() }
        14. }
        15. fun bind(loadState: LoadState) {
        16. if (loadState is LoadState.Error) {
        17. errorMsg.text = loadState.error.localizedMessage
        18. }
        19. progressBar.isVisible = loadState is LoadState.Loading
        20. retry.isVisible = loadState is LoadState.Error
        21. errorMsg.isVisible = loadState is LoadState.Error
        22. }
        23. }
        24. // Adapter that displays a loading spinner when
        25. // state = LoadState.Loading, and an error message and retry
        26. // button when state is LoadState.Error.
        27. class ExampleLoadStateAdapter(
        28. private val retry: () -> Unit
        29. ) : LoadStateAdapter<LoadStateViewHolder>() {
        30. override fun onCreateViewHolder(
        31. parent: ViewGroup,
        32. loadState: LoadState
        33. ) = LoadStateViewHolder(parent, retry)
        34. override fun onBindViewHolder(
        35. holder: LoadStateViewHolder,
        36. loadState: LoadState
        37. ) = holder.bind(loadState)
        38. }
      • 然后,从 PagingDataAdapter 对象调用 withLoadStateHeaderAndFooter() 方法:
        1. pagingAdapter
        2. .withLoadStateHeaderAndFooter(
        3. header = ExampleLoadStateAdapter(adapter::retry),
        4. footer = ExampleLoadStateAdapter(adapter::retry)
        5. )
      • 如果您只想让 RecyclerView 在页眉或页脚中显示加载状态,则可以调用 withLoadStateHeader() 或 withLoadStateFooter()。 关于withLoadStateHeaderAndFooter()、withLoadStateHeader() 和 withLoadStateFooter()的实现,通过源码发现,其实就是用的PagingDataAdapter.addLoadStateListener()方案,只不过是通过ConcatAdapter封装下。即:在PagingDataAdapter中把addLoadStateListener()和ConcatAdapter封装了一下,且返回值是ConcatAdapter
      • 注意:由于withLoadStateHeaderAndFooter()、withLoadStateHeader() 和 withLoadStateFooter()返回的是ConcatAdapter,所以如果已经用构造函数ConcatAdapter(firstAdapter, articleAdapter)的话,再用withLoadState···添加页眉页脚会失败,因为用withLoadState···返回的也是ConcatAdapter就有两个ConcatAdapter了。这个时候正确的做法是用withLoadState···创建ConcatAdapter,然后再用concatAdapter.addAdapter(0,firstAdapter)添加其它的adapter,且调用concatAdapter.addAdapter的位置在binding.articleList.adapter = concatAdapter前后都可以。
  • Pager : Pager().flow 把 PagingSource 转换为 PagingData。在Repository中用到

  • RemoteMediator : 在Pager()中用到。

    • 当您从一个多层级数据源加载数据时,应当实现一个 RemoteMediator。
    • 一般用法为从网络请求数据并存入数据库。每当数据库中没有数据可以被展示时,就会触发 load() 方法。基于 PagingStateLoadType,我们可以构造下一页的数据请求。
  • PagingConfig : 在Pager()中用到

  • PagingState : 在自定义 PagingSource 的 getRefreshKey()方法中用到,在自定义RemoteMediator的load()方法中也用到了。

    • 官方介绍:Snapshot state of Paging system including the loaded [pages], the last accessed [anchorPosition], and the [config] used.
  • 参考博客:目前Paging已经发布3.0正式版,下面这个博客是alpha版本的,但可以参考:

Room

DataStore

App Startup

WorkManager

compose

Navigation

Preference

RecyclerView

滑动刷新

加载状态

  • 加载状态的几个场景:下拉刷新、上拉加载更多、底部的已加载全部内容、首次进入页面的加载状态(及加载失败提醒)
  • 下拉刷新、上拉加载更多:略
  • 首次进入页面的加载状态:
  • 底部的已加载全部内容:方案比较多,个人比较倾向下面两种方案
    • 方案一:通过withLoadStateFooter实现,和上拉加载更多用同一套布局,同一个adapter。【参考本demo】
    • 方案二:通过ConcatAdapter.addAdapter实现,专门显示加载更多

动画

  • Animation 动画: 下拉刷新场景通过属性动画实现
  • 官方文档
  • 属性动画
    • ValueAnimator: 属性动画的主计时引擎,它也可计算要添加动画效果的属性的值。它具有计算动画值所需的所有核心功能,同时包含每个动画的计时详情、有关动画是否重复播放的信息、用于接收更新事件的监听器以及设置待评估自定义类型的功能。为属性添加动画效果分为两个步骤:计算添加动画效果之后的值,以及对要添加动画效果的对象和属性设置这些值。ValueAnimator 不会执行第二个步骤,因此,您必须监听由 ValueAnimator 计算的值的更新情况,并使用您自己的逻辑修改要添加动画效果的对象。如需了解详情,请参阅使用 ValueAnimator 添加动画效果部分。
    • ObjectAnimator: ValueAnimator 的子类,用于设置目标对象和对象属性以添加动画效果。此类会在计算出动画的新值后相应地更新属性。在大多数情况下,您不妨使用 ObjectAnimator,因为它可以极大地简化对目标对象的值添加动画效果这一过程。不过,有时您需要直接使用 ValueAnimator,因为 ObjectAnimator 存在其他一些限制,例如要求目标对象具有特定的访问器方法。
    • AnimationSet: 此类提供一种将动画分组在一起的机制,以使它们彼此相对运行。您可以将动画设置为一起播放、按顺序播放或者在指定的延迟时间后播放。如需了解详情,请参阅使用 AnimatorSet 编排多个动画部分。
    • LayoutTransition:
    • LayoutAnimations:

ViewPager2

  • ViewPager2 库
  • 官方文档
  • 官方demo
    • 官方demo中的ViewPager2 with a Preview of Next/Prev Page 相当于Banner中类似的场景
    • 官方demo中的ViewPager2 with a Nested RecyclerViews 场景很好,提供了解决嵌套滑动的方案
  • ViewPager2 底层使用 RecycleView 实现的,所以这里不再使用 PagerAdapter 而是使用了 RecyclerView.Adapter
  • 对应的fragment用的是 FragmentStateAdapter,而不是 FragmentStatePagerAdapter、FragmentPagerAdapter之类的

Banner

  • Banner:其实就是 ViewPager 的应用
  • 三方库:
  • 自己实现方案:
    • 让Banner和RecyclerView分开: 通过NestedScrollView里包裹ViewPager2和RecyclerView的话,会有滑动卡顿的问题,即使加上android:nestedScrollingEnabled="false"属性,除非再加上setHasFixedSize(true),但是还会有其他的问题:加上setHasFixedSize(true)后,界面的数据只显示一页了。故此方案暂时行不通了。本方案相关代码
      1. binding.articleList.setHasFixedSize(true)
      2. binding.articleList.isNestedScrollingEnabled = false
    • 让Banner成为RecyclerView的一部分:
      • 如果Banner在顶部:banner在顶部的话,就做header
      • 如果Banner在中间:在中间的话,就type,或者对adapter做一个扩展,做一个可以在中间插入的类似header。毕竟type的话,写起来也蛮麻烦的
    • 通过 ConcatAdapter 实现:
      • 本demo就是用的该方案,demo中通过HomeFirstAdapter添加RecyclerView的ConcatAdapter中,通过HomeBannerAdapter实现ViewPager2的adapter。
      • 通过上述的方式加上ViewPager2之后,ViewPager2没有影响RecyclerView的功能,RecyclerView上下滑动流畅;但是ViewPager2不能滑动,因为事件被RecyclerView拦截了。故需新增自定义布局 NestedFrameLayout 嵌套在ViewPager2之上,在 NestedFrameLayout 去处理父类的事件分发,即当左右滑动 NestedFrameLayout 时,执行 NestedFrameLayout 的parent.requestDisallowInterceptTouchEvent(true)方法,让ViewPager2消费事件。
    • 通过 MultiTypeAdapter 实现:暂时没有验证
    • 工行融e购实现方案:首页除了viewpager功能都放在AppBarLayout里面,但是这样TabLayout可能就要和融e购一样放在下面了,不是想要的。用ConcatAdapter也可以实现工行融e购的首页效果。
    • 京东首页实现方案:自定义控件实现。用ConcatAdapter也能实现京东首页效果

NestedScrollView

  • 直接在 NestedScrollView 中放入 ViewPager2 和 RecyclerView 时,会出现滑动卡顿。解决方案参考
  • NestedScrollView
  • 事件冲突的原因:Android 的事件分发机制中,只要有一个控件消费了事件,其他控件就没办法再接收到这个事件了。因此,当有嵌套滑动场景时,我们都需要自己手动解决事件冲突。而在 Android 5.0 Lollipop 之后,Google 官方通过 嵌套滑动机制 解决了传统 Android 事件分发无法共享事件这个问题。
  • 嵌套滑动机制:嵌套滑动机制 的基本原理可以认为是事件共享,即当子控件接收到滑动事件,准备要滑动时,会先通知父控件(startNestedScroll);然后在滑动之前,会先询问父控件是否要滑动(dispatchNestedPreScroll);如果父控件响应该事件进行了滑动,那么就会通知子控件它具体消耗了多少滑动距离;然后交由子控件处理剩余的滑动距离;最后子控件滑动结束后,如果滑动距离还有剩余,就会再问一下父控件是否需要在继续滑动剩下的距离(dispatchNestedScroll)...

TabLayout

BottomNavigationView

Constraint Layout

Glide

  • 和Coil对比,建议换成Coil加载图片

Cookie

  • CookieManager
  • 本demo中,和收藏相关都需要登录操作,建议登录将返回的cookie(其中包含账号、密码)持久化到本地即可。

WebView

  • WebView 库
  • 官方文档
  • 官方demo
  • 本demo中跳转到WebFragment是通过 Bundle 传递参数,没有用通过 Navigation 的 Safe Args 导航实现
  • 本demo中的WebView适配了深色主题。

启动界面

  • 方案:通过windowSplashscreenContent属性或者SplashActivity界面

    • 注意 windowSplashscreenContent属性是在Android8.0(v26)上才有的,如果在之前的版本上适配启动界面,应该新增个Activity,即 SplashActivity。
  • 冷启动、热启动
  • Splash Screen:展示品牌Logo或Slogan
    • 如果只是单纯的显示个界面,只需要在themes里设置@color/jetpack_green_500即可。
  • Advertisement Screen:展示节日活动或日常广告
  • Guide Screen:演示重点功能,一般只展示一次
  • 参考博客:Android 12上全新的应用启动API,适配一下?

样式系统、沉浸式(在Android6.0、8.1、10、11上已经适配,详见demo

触摸手势

项目地址:https://github.com/lelelongwang/WanJetpack

WanJetpack项目:用Jetpack实现玩Android,追求最官方的实现方式的更多相关文章

  1. 用Flutter开发的跨平台项目,完美运行在Android和IOS上,Material简洁风格,包括启动页、引导页、注册、登录、首页、体系、公众号、导航、项目,还有漂亮的妹子图库,运行极度流畅,结构清晰,代码规范,值得拥有

    Flutter学习资源汇总持续更新中...... Flutter官方网站 Flutter中文网 wendux的Flutter实战 Flutter官方exampleflutter_gallery 阿里巴 ...

  2. 原生Android App项目调用Untiy导出的Android项目

    背景:采用Google VR SDK for Unity 开发3D场景功能,然后导出Android项目,合并到一个Android App里面,供其它Activity调用. 用Google VR for ...

  3. 写一些有关android的东西吧,那时候玩android时候的一些笔记

    写一些有关android的东西吧,那时候玩android时候的一些笔记

  4. MAC安裝《Genymotion Android模擬器》大玩Android APP (神魔之塔)

    链接地址:http://www.minwt.com/mac/10083.html/comment-page-2 MAC» 智慧型裝罝» Android | 2014/02/12 Android是一個開 ...

  5. Web项目也能一键打包Android、IOS

    随着移动互联网的不断发展,智能手机配置的不断提高,越来越多的年轻人基本都在使用手机,如微信.支付宝等等.已基本成为一种习惯,坐电梯也好.吃饭也好.开车也好,基本都捧着一个手机在那按来按去,开车就不建议 ...

  6. Android Studio安装更新终极解决方式

    之前写过一篇Android SDK无法更新的博文,其实该方式对Android Studio同样有效,大伙可以下载网盘中分享的小软件,若搜索到通道后提示需要更细,也可以选择更新.参考:http://bl ...

  7. Android 连接 SQL Server (jtds方式)——上

    本文将介绍开发Android程序,连接SQL Server,通过第三方包jtds的方式. 如果你有同样的需求,请跟着做一遍,博主将以最详细的方式,进行介绍. 首先说明,Java.Android连接SQ ...

  8. uni-app&H5&Android混合开发三 || uni-app调用Android原生方法的三种方式

    前言: 关于H5的调用Android原生方法的方式有很多,在该片文章中我主要简单介绍三种与Android原生方法交互的方式. 一.H5+方法调用android原生方法 H5+ Android开发规范官 ...

  9. Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (一) —— 总览

    Android数据的四种存储方式SharedPreferences.SQLite.Content Provider和File (一) —— 总览   作为一个完成的应用程序,数据存储操作是必不可少的. ...

随机推荐

  1. 永恒之蓝MS17-010漏洞复现

    永恒之蓝MS17-010漏洞复现 1.漏洞描述: 起因: 永恒之蓝(Eternalblue)是指2017年4月14日晚,黑客团体Shadow Brokers(影子经纪人)公布一大批网络攻击工具,其中包 ...

  2. 深入学习Netty(2)——传统NIO编程

    前言 学习Netty编程,避免不了从了解Java 的NIO编程开始,这样才能通过比较让我们对Netty有更深的了解,才能知道Netty大大的好处.传统的NIO编程code起来比较麻烦,甚至有遗留Bug ...

  3. Pptx的多路径形状转为WPF的Path

    本文是将演示如何解析pptx文件的多路径的形状转换到WPF,绘制多个Shape的Path Shape Path 这是Pptx的[标注:弯曲曲线(无边框)]形状的OpenXml定义部分: <cal ...

  4. kotlin gradle 生成jni头文件

    目录 问题 解决方法 使用方法 代码 gradle task位置截图 问题 最近在用kotlin写jni,但是生成头文件的时候遇到了些问题. 首先 javah 在java >= 1.9 就被取消 ...

  5. 0、springboot

    在线新建springboot项目 https://start.spring.io/ 参考地址 https://github.com/battcn/spring-boot2-learning 博客 ht ...

  6. cordova自定义插件开发流程

    cordova自定义插件开发:1.cordova安装:npm install -g cordova2.plugman安装:npm install -g plugman3.cordova创建工程:cor ...

  7. I-Identical Day[题解]

    原题目地址(牛客) Identical Day 题目大意 给定一个长度为 \(n\) 的 \(01\) 串,对于每段长度为 \(l\) 的连续的 \(1\) ,其权值为 \(\frac{l\times ...

  8. 日常学习-001-Get和Post的区别

    首先说明参考链接:https://mp.weixin.qq.com/s/W68JzNIoUpm9hyXinOzkMw 以下为个人观后总结. 初级理解: GET和POST的区别 1.get传送的参数长度 ...

  9. UFT对于PDF 文档的操作方法 VBS代码

    1.首先需要安装Adobe Acrobat,而不是Adobe Reader 2.理解AcroExch.App .AcroExch.AVDoc.AcroExch.PODoc App 主要管理应用级别的对 ...

  10. python exec()函数

    ''' 函数的作用: 动态执行python代码.也就是说exec可以执行复杂的python代码,而不像eval函数那样只能计算一个表达式的值. exec(source, globals=None, l ...