1.听说这套框架可以搞定 Android MVI
2.WPF MVVM实例一
3.最全vue面试必问题(附题)
4.LiveData 面试题库、码详解答、码详源码分析
5.lifecycleScope 和viewModelScope
6.Jetpackå¦ä¹ ä¹----ViewModel
听说这套框架可以搞定 Android MVI
前言
没有最完美的码详架构,只有最合适的码详架构。
Android应用架构变迁:MVC、码详MVP、码详易考宝典源码MVVM、码详MVI。码详
关于这四种架构的码详概念、逻辑、码详实现方式与优劣,码详技术社区内优质文章不胜枚举,码详此处不再赘述。码详
今天重点介绍如何利用Airbnb开源框架Mavericks快速实践MVI架构。码详
主要弄清楚下面几个问题:
Mavericks(MvRx): Android on Autopilot
Mavericks是码详Aribnb开源的一款功能强大且易于学习的AndroidMVI框架。Mavericks以AndroidJetpack和KotlinCoroutines为基础搭建上层逻辑,在技术先进性和可持续方面毋庸置疑。至于框架实用性,相信接受了Airbnb、Tonal等大型APP长时间检验的Mavericks,不会让开发者失望。
核心概念
一个简单的计数界面只需要下面几行代码,既清晰又简洁。
实践
需求:利用WanAndroidAPI[1]实现搜索热词的列表展示(支持下拉刷新)
接口:/hotkey/json
1. 依赖2. 初始化
Application内onCreate()函数中执行初始化。
3. MavericksState
定义MainState并添加两个属性:
4. MavericksViewModel
定义MainViewModel管理MainState,实现获取搜索热词函数。
5. MavericksView
创建MainFragment并实现MavericksView接口用于展示搜索热词列表,用户可以下拉刷新请求新数据。
6. 效果源码
Talk is cheap,Show me the code。
/onlyloveyd/AndroidSamples
划重点
1. Async异步处理密封类,有四个子类:Uninitialized、Loading、Success、Fail,分别代表异步处理的4种状态。
2. onAsync异步属性状态变化监听
3. retainValue加载过程中或者加载失败后显示的数据。
示例中,生日备忘源码我们将getHotKeys()函数内的retainValue去掉,界面更新数据时会有明显的闪动。
4. 监听模式:DeliveryMode
5. 状态监听防止崩溃
为了防止回调时界面已经被销毁而导致程序奔溃,采用launchWhenStarted防御策略。
总结
简单好用,是选轮子的基本标准。
上手Mavericks后,感觉代码层次清晰,集成方便,简单易用,符合好轮子的标准。
对于鄙人这种不太爱自己折腾框架的躺平者而言,简直福音。下一步准备把之前写的WanAndroidClient用Mavericks改写下。
WPF MVVM实例一
1. 新建WPF应用程序 "WPFMVVMExample".
2. Model实现
创建 "StudentModel" 类于 "Model" 文件夹,实现 INotifyPropertyChanged 接口,以支持属性值更改的通知.
3. ViewModel实现
在 "ViewModel" 文件夹内新建 "StudentViewModel" 类,定义 DelegateCommand 类实现 ICommand 接口. DelegateCommand 可与 Button 的 Command 属性绑定,实现命令的执行与可用性指示.
4. MainWindow.xaml实现
设计 "MainWindow.xaml" 界面,包含 "显示" 按钮等元素,并用 xaml 代码描述界面布局.
5. 运行程序
执行程序,点击 "显示" 按钮,数据自动绑定至界面显示.
6. 说明
在 WPF 中,MVVM 设计模式降低 UI 与逻辑代码耦合,易于界面更新. 使用数据绑定,数据变化自动通知界面,无需直接操作界面元素.
MVVM 结构将界面(View)、逻辑处理(ViewModel)与业务模型(Model)分离,View 通过 DataContext 绑定 ViewModel,ViewModel 通过 Model 获取数据和命令执行.
项目源码下载链接:百度网盘 - pan.baidu.com/s/BIKyd...
提取码:h1iw
技术群加入:添加微信 "mm",备注 "加群",获取技术支持与交流.
最全vue面试必问题(附题)
1. 请解释MVVM。
MVVM是Model-View-ViewModel的缩写,它将MVC中的Controller演变为ViewModel。Model层代表数据模型,View代表UI组件,Profit Fx 源码ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。
2. 请说明Vue的生命周期。
beforeCreate是new Vue()之后触发的第一个钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问。created在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom。beforeMount发生在挂载之前,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。mounted在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作。beforeUpdate发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。updated发生在更新完成之后,当前阶段组件Dom已完成更新。上门遛狗源码要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。beforeDestroy发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。destroyed发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
3. 你的接口请求一般放在哪个生命周期中?
接口请求一般放在mounted中,但需要注意的是服务端渲染时不支持mounted,需要放到created中。
4. 再说一下Computed和Watch。
Computed本质是一个具备缓存的watcher,依赖的属性发生变化就会更新视图。适用于计算比较消耗性能的计算场景。当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理。Watch没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。当我们需要深度监听对象中的属性时,可以打开deep:true选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话可以使用字符串形式监听,如果没有写到组件中,不要忘记使用unWatch手动注销哦。
5. 请说明v-if和v-show的区别。
当条件不成立时,v-if不会渲染DOM元素,v-show操作的仿WAP源码是样式(display),切换当前DOM的显示和隐藏。
6. Vue模版编译原理知道吗,能简单说一下吗?
简单说,Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段:生成AST树、优化codegen。首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。编译的最后一步是将优化后的AST树转换为可执行的代码。
7. Vue2.x和Vue3.x渲染器的diff算法分别说一下。
同级比较,再比较子节点。先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)。比较都有子节点的情况(核心diff)。递归比较子节点。正常Diff两个树的时间复杂度是O(n^3),但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n^3) -> O(n),只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。Vue3.x借鉴了 ivi算法和 inferno算法 在创建VNode时就确定其类型,以及在 mount/patch 的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。(实际的实现可以结合Vue3.x源码看。) 该算法中还运用了动态规划的思想求解最长递归子序列。
8. 再说一下虚拟Dom以及key属性的作用。
由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因。Vue2的Virtual DOM借鉴了开源库snabbdom的实现。Virtual DOM本质就是用一个原生的JS对象去描述一个DOM节点。是对真实DOM的一层抽象。(也就是源码中的VNode类,它定义在src/core/vdom/vnode.js中。) VirtualDOM映射到真实DOM要经历VNode的create、diff、patch等阶段。key的作用是尽可能的复用 DOM 元素。新旧 children 中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key也就是children中节点的唯一标识。
9. Vue2.x组件通信有哪些方式?
父子组件通信:父->子props,子->父 $on、$emit 获取父子组件实例 $parent、$children Ref 获取实例的方式调用组件的属性或者方法 Provide、inject 官方不推荐使用,但是写组件库时很常用 兄弟组件通信 Event Bus 实现跨组件通信 Vue.prototype.$bus = new Vue Vuex 跨级组件通信 Vuex $attrs、$listeners Provide、inject
. v-model是如何实现双向绑定的?
v-model是用来在表单控件或者组件上创建双向绑定的。
他的本质是v-bind和v-on的语法糖。
在一个组件上使用v-model,默认会为组件绑定名为value的prop和名为input的事件。
. 怎样理解Vue的单向数据流?所有的prop都使得其父子prop之间形成了一个单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的prop都将刷新为最新的值。这意味着你不应该在一个子组件内部改变prop。如果你这样做了,Vue会在浏览器的控制台中发出警告。子组件想修改时,只能通过$emit派发一个自定义事件,父组件接收到后,由父组件修改。
有两种常见的试图改变一个prop的情形:
这个prop用来传递一个初始值;这个子组件接下来希望将其作为一个本地的prop数据来使用。在这种情况下,最好定义一个本地的data属性并将这个prop用作其初始值:
这个prop以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个prop的值来定义一个计算属性。
. hash路由和history路由实现原理说一下。
location.hash的值实际就是URL中#后面的东西。history实际采用了HTML5中提供的API来实现,主要有history.pushState()和history.replaceState()。
LiveData 面试题库、解答、源码分析
LivaData 的面试题库与解答、源码分析 作者:唐子玄1. LiveData 如何感知生命周期的变化?
LiveData 在常规的观察者模式上附加了条件,若生命周期未达标,即使数据发生变化也不通知观察者。这通过 Lifecycle 实现,Lifecycle 是生命周期对应的类,提供了添加/移除生命周期观察者的方法,并定义了全部生命周期的状态及对应事件。要观察生命周期,需要实现 LifecycleEventObserver 接口,并注册给 Lifecycle。除了生命周期观察者外,还有数据观察者,数据观察者会与 LifecycleOwner 进行绑定。2. LiveData 是如何避免内存泄漏的?
内存泄漏是因为长生命周期的对象持有了短生命周期对象。在观察 LiveData 数据的代码中,Observer 作为界面的匿名内部类,它会持有界面的引用,同时 Observer 被 LiveData 持有,LivData 被 ViewModel 持有,而 ViewModel 的生命周期比 Activity 长。最终的持有链导致内存泄漏。LiveData 帮助避免内存泄漏,在内部 Observer 会被包装成 LifecycleBoundObserver,这实现了生命周期感知能力,同时它还持有了数据观察者,具备了数据观察能力。3. LiveData 是粘性的吗?若是,它是怎么做到的?
是的,LiveData 是粘性的。数据是持久的,意味着它不会因被消费而消失。当 LiveData 值更新时,会通知所有观察者。这一过程通过一个 Map 结构保存了所有观察者,并通过遍历 Map 并逐个调用 considerNotify() 方法实现。观察者会被包装在 LifecycleBoundObserver 中,它具备了生命周期感知能力,同时持有了数据观察者。当组件生命周期发生变化时,会尝试将最新值分发给该数据观察者。4. 粘性的 LiveData 会造成什么问题?怎么解决?
粘性的 LiveData 可能导致数据重复消费或消费逻辑混乱。解决方案包括使用带消费记录的值、带有最新版本号的观察者、SingleLiveEvent 等。其中,使用 SingleLiveEvent 可以根据数据的分类(暂态数据或非暂态数据)来选择性地利用或避免粘性。5. 什么情况下 LiveData 会丢失数据?
在高频数据更新的场景下使用 LiveData.postValue() 时,如果在这次调用和下次调用之间再次调用 postValue(),则会导致数据丢失,因为值先被缓存,再向主线程抛出分发值的任务。这与 LiveData 的设计和更新机制有关。6. 在 Fragment 中使用 LiveData 需注意些什么?
在 Fragment 中使用 LiveData 时,应当使用 viewLifecycleOwner 而非 this。避免因生命周期不一致导致的额外订阅者问题。使用 SingleLiveEvent 可以解决数据重复消费问题。7. 如何变换 LiveData 数据及注意事项?
androidx.lifecycle.Transformations 提供了变换 LiveData 数据的方法,如 map()。需要注意数据变换操作应避免阻塞主线程,可使用 CoroutineLiveData 来异步化数据变换。lifecycleScope 和viewModelScope
前序:
通过《ViewModel中的简易协程:viewModelScope》的文章,联想到了lifecycleScope的使用。
LifecycleScope,即具有生命周期的协程,是LifecycleOwner的扩展属性,与生命周期绑定,并在LifecycleOwner销毁时自动取消。
引入使用:LifecycleScope作为Lifecycle的扩展属性,与LifecycleOwner绑定。在示例中,lifecycleScope默认主线程,可通过withContext指定线程。
whenResumed与launchWhenResumed在执行时机上相似,关键区别在于它们在生命周期不同状态下的行为。
lifecycleScope的源码分析揭示了它如何避免内存泄漏。lifecycleScope继承自LifecycleCoroutineScope,后者的register方法添加了LifecycleEventObserver监听,当生命周期状态变为destroyed时,监听被移除,协程取消。
源码中的小技巧指出,当继承对象与返回对象不一致时,返回对象通常是继承对象的子类。这解释了lifecycleScope的生命周期管理。
在其他开发场景中,可以借鉴源码中的监听机制来实现资源回收,避免内存泄漏。
关于如何在特定生命周期执行协程,以lifecycleScope.launchWhenResumed为例,涉及LifecycleController和LifecycleEventObserver的使用。
当调用whenResumed并传入具体生命周期状态时,创建LifecycleController并初始化监听。在回调中,当生命周期状态大于传入状态时,执行调度队列,开始协程执行。
关于获取当前生命周期状态,涉及到Lifecycle相关知识。在不同组件(如Activity或Fragment)中,通过ComponentActivity的实现来派发生命周期状态。
验证分析通过代码测试和源码调试,证实了以上流程的正确性。
总结:lifecycleScope的使用及执行流程分析,揭示了其如何与生命周期绑定,避免内存泄漏,并在特定生命周期执行协程。
Jetpackå¦ä¹ ä¹----ViewModel
å®æ¹å¦ä¹ ææ¡£ViewModelå°±æ¯åå¨é¡µé¢ç¸å ³çæ°æ®ï¼å¹¶å°è¿äºæ°æ®åActivityãFragmentçæçå½å¨æç¸å ³çç»ä»¶ç¸å ³èï¼èµäºæ°æ®çå½å¨æã
ç¹ç¹ï¼
ViewModelççå½å¨æ
å¨viewModel对象å建æ¶å¼å§ï¼ä¸ç´å°ä»æå ³èççé¢æ§å¶å¨éæ¯æ¶æéæ¯ï¼è¿å°±è¯´æäºå³ä½¿åçäºæ¨ªç«å±åæ¢ï¼çé¢ç¸å ³çæ°æ®ä¹æ¯ä¸ç´åå¨å¹¶ä¸ä¸å横ç«å±åæ¢çå½±åã
é常æ们æ¯å¨ActvityçonCreate()æ¹æ³ä¸æ¥å建ViewModel对象ï¼è¯¥ViewModel对象ä¼ä¸ç´å¨å åä¸ï¼ç´å°è¿ä¸ªActivityéæ¯æ¶æéæ¾èµæºã
ä»ä¸é¢ViewModelçå·¥ä½åçå¯ä»¥å¾ç¥ï¼
1ãViewModel ä¸æ¦å建好äºï¼å°±ä¼ä¸ç´ä¿åå°å½åçé¢æ§å¶å¨ï¼Activity ãFragmentçï¼éæ¯æ¶æä¼éæ¾èµæºï¼
2ãä¸åççé¢æ§å¶å¨ï¼ViewModel ç对象æ¶åå¨ä¸åçHashmapä¸çï¼ä»ä»¬ä¹æ¯ä¸åç对象ï¼å±é¨åä¾ï¼
3ãè¦åå°å ¨å±åä¾ViewModel对象ï¼å¯ä»¥å°ViewModelæ¾å°Applicationä¸å»ï¼
æ¥ä¸æ¥ä»æºç è§åº¦æ¥åæä¸ä¸åçï¼
å¨æ建Activityç对象æ¶ï¼å¨å ¶ç¶ç±»ComponentActivity.javaä¸å®ç°äºæ¥å£ViewModelStoreOwnerï¼å¨å ¶å®ç°æ¹æ³ä¸çæViewModelStore对象
å¨çé¢æ§å¶å¨çæé å½æ°ä¸ï¼å°±æ·»å äºå¯¹çå½å¨æçè§å¯è ï¼èå½è§å¯è æ¶å°å½åççé¢æ§å¶å¨ççå½å¨ææ¯Lifecycle.Event.ON_DESTROYæ¶ï¼å°±ä¼å°mViewModelStore对象mapä¸ææä¿åçviewModelæ¸ çæï¼è¿æ ·æ¥è¾¾å°éæ¾èµæºã
è¿éåªå¤çäºON_DESTROYççå½å¨æç¶æï¼é£ä¹ä¹å°±è¯´æäºå¨ViewModel对象å®ä¾å建æååï¼ä¸ç®¡çé¢æ§å¶å¨ï¼å¦Activityï¼ççå½å¨æï¼é¤ON_DESTROYå¤ï¼å¦ä½åçååï¼ViewModelé½ä¸ä¼è¢«æ¸ çæã
ä»è¿éçåºæ¥ViewModel对åºkeyçå¯ä¸æ§
ViewModelå·¥ä½åççæ ¸å¿ææ¯ç¹ï¼
è§å¯è 模å¼ãå·¥ç¨æ¨¡å¼ãåå°ãHashmapæ°æ®ç»æ
ViewModelå¨MVVMæ¶æ模åä¸ï¼ä¸DataBindingç»å使ç¨ï¼ä¼è®©ä½ æèµ·é£çæè§ãåç»ä¼è¿ä¸æ¥å 深使ç¨ãæ¬ç¯ä» 以å¦ä¼ä½¿ç¨ãäºè§£åç为éç¹ã
2024-11-30 12:21
2024-11-30 11:45
2024-11-30 11:02
2024-11-30 10:33
2024-11-30 10:07
2024-11-30 10:03