1.MEF框架基础理解
2.Andorid进阶一:LeakCanary源码分析,码分从头到尾搞个明白
3.lifecycleScope 和viewModelScope
4.最全vue面试必问题(附题)
5.LiveData 面试题库、码分解答、码分源码分析
MEF框架基础理解
MEF框架基础揭秘:灵活扩展与高效管理MEF(Managed Extensibility Framework)是码分面向现代应用开发的强大工具,它以轻量级的码分方式支持无配置的扩展,为代码封装和依赖管理提供了革命性的码分c 源码加壳解决方案。作为IoC(Inversion of Control)理念的码分杰出实践者,如Unity和MAF(包括Prism组件),码分MEF的码分核心理念在于简化组件间的交互,实现高内聚低耦合。码分
首先,码分我们来看看IoC的码分基本概念。在软件设计中,码分依赖是码分指一个类如何与其他类协作。理想情况下,码分我们追求的是类与类之间的关系是松散的,这样可以降低耦合度,提高模块的复用性。而依赖倒置原则(DIP)要求高层模块不应直接依赖于底层实现,而是依赖于抽象,这样使得系统更具灵活性和可扩展性。 控制反转(IoC)是IoC的核心机制,它将组件间的依赖关系交由外部容器管理,例如在MVVM模式中,ViewModel无需直接操作View,而是通过IoC容器来实现两者之间的通信。这种设计模式让代码更加简洁,易于维护和测试。 MEF通过Export和Import特性,实现了组件的自动发现和管理。有三种主要的部件发现方式:AssemblyCatalog、DirectoryCatalog和DeploymentCatalog,它们分别扫描指定的程序集、目录和部署,发现符合约定的插件类。 在实践中,例如在Chapter8.AddFriend.AddFriendPlug这个插件示例中,一个类库被设计为插件,它继承自IPlug接口,包含了名称、唯一标识符以及相关的视图。这个插件的初始化和释放操作,如开关功能,打赏源码充值源码都是通过MEF的动态加载机制来实现的。 在MVVM架构中,MainViewModel作为核心视图模型,通过CompositionContainer来管理整个插件集合。这个容器就像是一个智能的模块管理器,根据需要动态加载并调用插件提供的功能,实现了组件的无缝集成和扩展。 想要深入了解MEF的源码和具体应用,你可以参考官方文档或查阅相关技术博客。在实践中,MEF的优雅设计和灵活扩展性无疑为现代应用开发提供了强大动力。Andorid进阶一:LeakCanary源码分析,从头到尾搞个明白
内存优化掌握了吗?知道如何定位内存问题吗?面试官和蔼地问有些拘谨的小张。小张回答道:“就是用LeakCanary检测一下泄漏,找到对应泄漏的地方,修改错误的代码,回收没回收的引用,优化生命周期线程的依赖关系。”“那你了解LeakCanary分析内存泄漏的原理吗?”面试官追问。“不好意思,平时没有注意过。”小张心想:面试怎么总问这个,我只是一个普通的程序员。
前言:
应用性能优化是开发中不可或缺的一环,而内存优化尤为重要。内存泄漏导致的内存溢出崩溃和内存抖动带来的卡顿不流畅,都在切实影响着用户体验。LeakCanary常用于定位内存泄漏问题,是时候深入理解它的工作机制了。
名词理解:
hprof:hprof文件是Java的内存快照文件,格式后缀为.hprof,在LeakCanary中用于内存分析。WeakReference:弱引用,当对象仅被weak reference指向,没有任何其他strong reference指向时,在GC运行时,这个对象就会被回收,不论当前内存空间是否足够。在LeakCanary中用于监测被回收的无用对象是否被释放。Curtains:Square的另一个开源框架,用于处理Android窗口的集中式API,在LeakCanary中用于监测window rootView在detach后的netty源码和spring源码内存泄漏。
目录:
本文将从以下几个方面进行分析:
一,怎么用?
查看官网文档可以看出,使用LeakCanary非常简单,只需添加相关依赖即可。debugImplementation只在debug模式的编译和最终的debug apk打包时有效。LeakCanary的初始化代码通过ContentProvider进行,会在AppWatcherInstaller类的oncreate方法中调用真正的初始化代码AppWatcher.manualInstall(application)。在AndroidManifest.xml中注册该provider,注册的ContentProvider会在application启动的时候自动回调oncreate方法。
二,官方阐述
安装LeakCanary后,它会通过4个步骤自动检测并报告内存泄漏:如果ObjectWatcher在等待5秒并运行垃圾收集后没有清除持有的弱引用,则被监视的对象被认为是保留的,并且可能会泄漏。LeakCanary会将其记录到Logcat中,并在泄漏列表展示中用Library Leak标签标记。LeakCanary附带一个已知泄漏的数据库,通过引用名称的模式匹配来识别泄漏,如Library Leaks。对于无法识别的泄漏,可以报告并自定义已知库泄漏的列表。
三,监测activity,fragment,rootView和viewmodel
初始化的代码关键在于AppWatcher作为Android平台使用ObjectWatcher封装的API中心,自动安装配置默认的监听。我们分析了四个默认监听的Watcher,包括ActivityWatcher,FragmentAndViewModelWatcher,RootViewWatcher和ServiceWatcher,分别用于监测activity,fragment,rootView和service的内存泄漏。
四,ObjectWatcher保留对象检查分析
LeakCanary通过ObjectWatcher监控内存泄漏,我们深入分析了其检查过程,包括创建弱引用,检查对应key对象的保留,以及内存快照转储和内存分析。
五,总结
本文全面分析了LeakCanary的实现原理,从安装、mc源码和迷你源码使用到内存泄漏的检测和分析,详细介绍了各个组件的作用和工作流程。通过深入理解LeakCanary,开发者可以更有效地定位和解决内存泄漏问题,优化应用性能。阅读源码不仅能深入了解LeakCanary的工作机制,还能学习到内存泄漏检测的通用方法和技巧。
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的使用及执行流程分析,揭示了其如何与生命周期绑定,避免内存泄漏,并在特定生命周期执行协程。
最全vue面试必问题(附题)
1. 请解释MVVM。
MVVM是Model-View-ViewModel的缩写,它将MVC中的Controller演变为ViewModel。Model层代表数据模型,View代表UI组件,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操作的是样式(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 来异步化数据变换。