Android-ViewModel原理解析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android-ViewModel原理解析相关的知识,希望对你有一定的参考价值。
参考技术A在这四个方法中,其实唯一的区别就是要不要传Factory,当没有传自定义的Factory的时候,则会传入默认的Factory,我们看ViewModelProvider构造器的源码和部分of方法的源码:
在ViewModelProvider中需要传入一个VieModelStore对象,这个对象是由ViewModelStoreOwner来提供的,而在Activity或者Fragment中,是由Activity和Fragment来提供的,因为ViewModelStoreOwner是一个接口,而AppCompatActivity的祖父ComponentActivity和Fragment均实现了ViewModelStoreOwner接口。
但是ViewModelProviders在新的lifecycle-extensions库中,已经是属于被弃用的。新版的API直接使用ViewModelProvider,而不是ViewModelProviders。
比如:
可以如下的方式在baseActivity中添加,由子类Activity调用:
创建ViewModel对象,首先就需要先初始化一个ViewModelProvider对象
可以看出,ViewModelProvider构造函数其实最终都是需要两个参数,一个是ViewModelStoreOwner对象,一个是Factory。而ViewModelStoreOwner其实就是用来获取一个ViewModelStore对象来保存ViewModel对象的。而Factory就是用来创建ViewModel对象的。
这个接口的主要实现的作用就是返回一个ViewModelStore对象。在android中,Activity和Fragment都会实现该接口,并且实现getViewModelStore()方法。
比如Activity就是在FragmentActivity的父类ComponentActivity中实现了ViewModelStoreOwner接口
Fragment的ViewModelStore其实是由FragmentManager进行管理获取
每个FragmentActivity都会有一个自己的FragmentManager对象,所以每个FragmentManagerViewModel对象,管理的是一个FragmentActivity中的所有的Fragment对应的ViewModel。具体看FragmentManagerViewModel的getViewModelStore方法
从这里可以看出,每个Fragment都会有自己的ViewModelStore对象,而ViewModelStore对象,是根据每个Fragment的唯一标识进行创建的。
ViewModelStore类对象,是每个Activity或者Fragment都有一个,目的是用于保存该页面的ViewModel对象,方便ViewModel的管理
从ViewModelProvider的get方法中,可以看出,get方法传入的是一个ViewModel.class的Class类型,然后通过这个类型,得到ViewModel的规范名称。将ViewModel对象缓存在ViewModelStore中的HashMap中。而ViewModel的创建,其实是通过ViewModelProvider.Factory来实现的
ViewModelProviders的of方法,用于返回一个ViewModelProvider对象
从这里我们可以看到,如果传入的Activity或者Fragment有getDefaultViewModelProviderFactory方法实现,而factory为null的时候,则会通过getDefaultViewModelProviderFactory创建对应的Factory,而如果没有getDefaultViewModelProviderFactory的实现,那么就会调用NewInstanceFactory来创建对应的Factory,而NewInstanceFactory其实就是创建AndroidViewModelFactory对象。
最终ViewModel对象,其实就是通过AndroidViewModelFactory的create的方法实现来创建。一般就是通过Class.newInstance或者Class.getConstructor来创建对象。
而ViewModelProvider的第一个参数,其实最终传入的是ViewModelStore对象,这个对象内部是通过一个HashMap来保存ViewModel对象
而新版的源码,ViewModelStore对象是通过Fragment和FragmentActivity对象的getViewModelStore方法来获取,而原先的HolderFragment的功能都移植到了Fragment中
HolderFragment通过设置setRetainInstance(true),使得自身能够不受到屏幕旋转等configuration
changes影响而存活,直到依附的Activity正常结束。
因为HolderFragment的生命周期,ViewModelStore对象保存在HolderFragment中,而ViewModel又存储在ViewModelStore中,这就是为什么我们说ViewModel类能够让数据在屏幕旋转等配置信息改变导致UI重建的情况下不被销毁。
ViewModelProvider的get方法:
在ViewModel中,有两种Factory,Factory是的类型是由ViewModelProvider在初始化的时候创建的,所以是由ViewModelProvider决定Factory的类型。在ViewModelProvider中,有两种Factory,一种是默认的Factory,默认的Factory是通过在ComponentActivity或者Fragment中实现HasDefaultViewModelProviderFactory接口,然后在getDefaultViewModelProviderFactory()方法中初始化一个SavedStateViewModelFactory对象;另一种Factory则是NewInstanceFactory,这种是通过NewInstanceFactory.getInstance()的单例方式获取。
其实就是通过ViewModel的Class对象,然后通过反射创建ViewModel对象,然后保存到ViewModelStore中的Map集合中
从ViewModelProvider的get方法可以看出,在ViewModelProvider的get方法中会根据Factory的类型,进行不同方法的调用。SavedStateViewModelFactory是实现了ViewModelProvider.KeyedFactory接口的,所以在创建ViewModel的时候,调用的是SavedStateViewModelFactory的create方法。
AndroidViewModel和ViewModel的构造器参数Class
ViewModel保存和恢复数据
ComponentActivity和Fragment都将数据的保存和恢复逻辑转发给了SavedStateRegistryController。在在onCreate方法里通过调用performRestore来恢复数据,在onSaveInstanceState方法里通过调用performSave来保存数据。而SavedStateRegistryController中的SavedStateRegistry对象,就是实际进行数据的保存和恢复的,在SavedStateRegistry通过唯一的key获取到一个SavedStateProvider,而SavedStateProvider其实就是返回需要保存的数据,将对应的需要缓存的数据一一返回,然后保存在系统缓存时的回调到onSaveInstanceState的方法参数Bundle中进行保存。
SavedStateRegistry.performSave()
该方法是由ComponentActivity的onSaveInstanceState方法触发调用SavedStateRegistryController的performSave,进而调用的
在SavedStateRegistry恢复数据的时候,会把恢复后的数据都交给SavedStateHandle。希望保留的数据,可以通过两种方式向mRegular保存数据。
在ComponentActivity恢复数据的时候,会通过SavedStateRegistryController.performSave在Activity的onSaveInstanceState方法中进行数据的保存,然后在ComponentActivity的onCreate方法中,通过调用SavedStateRegistryController.performRestore方法进行数据的恢复,这些恢复的数据都会保存在SavedStateHandleController对象中的SavedStateHandle属性中,然后在Activity重新创建的时候,会通过反射创建对应的ViewModel对象的时候,将SavedStateHandleController中的SavedStateHandle赋值给对应的ViewModel进行数据恢复。
这块的源码分析可以参考:
从源码看 Jetpack(7)-SavedStateHandle源码详解
这里其实就是直接使用Class的newInstance直接创建对象。Activity和Fragment一般都是使用SavedStateViewModelFactory创建ViewModel对象。
ViewModel的销毁,要分为Activity和Fragment两部分。
首先看下ViewModel在销毁的时候做的事情
而ViewModel的clear()方法的调用,是在ViewModelStore中
Activity的销毁,是通过Lifecycle监听生命周期回调,当生命周期执行到onDestroy的时候,调用ViewModelStore的clear()方法进行ViewModel的销毁。
看ComponentActivity中构造器中的实现:
Fragment的生命周期管理,如下:
Fragment的生命周期,首先会依次增大,然后在从onResume变成onPause的时候,就开始状态码减小。即先升再降的一个状态变化。在当前状态码变成CREATED的时候,就会执行onDestroy。即调用
FragmentStateManager.destroy
在这里就会调用nonConfig.clearNonConfigState方法,nonConfig其实就是FragmentManagerViewModel对象。
FragmentManagerViewModel.clearNonConfigState
按照上面的逻辑,在Activity重建时会执行destory生命周期事件,那么为什么ViewModel没有销毁呢?
其实就是在屏幕旋转的时候,AMS通过Binder回调Activity的retainNonConfigurationInstances()方法,这个时候就会进行数据的保存,保存到一个NonConfigurationInstances对象;而在屏幕翻转结束之后,会再一次调用ViewModelProvider的构造函数,此时就会调用owner.getViewModelStore(),接着就会调用getLastNonConfigurationInstance(),这里就会通过Activity中的NonConfigurationInstances对象取出保存的ViewModelStore对象。
所以数据保存就是通过retainNonConfigurationInstances()方法保存在NonConfigurationInstances对象,而再一次使用取出ViewModel的数据的时候,就是从nc对象中取出ViewModelStore对象,而ViewModelStore对象保存有ViewModel集合
通过对ComponentActivity的getViewModelStore()方法进行分析。可以找到这个问题的答案。
当mViewModelStore为null的时候,会从NonConfigurationInstances中获取ViewModelStore对象。
其实在ComponentActivity和Activity中都会有一个NonConfigurationInstances类,而Activity中的NonConfigurationInstances类结构如下:
这里的Object activity其实就是保存的ComponentActivity中的NonConfigurationInstances类对象,看Activity的下面的方法:
activity这个Object对象,其实是通过onRetainNonConfigurationInstance()方法返回值赋值,而onRetainNonConfigurationInstance()方法的实现是在ComponentActivity中。
看ComponentActivity中的下面方法:
因为这里会在ComponentActivity中的NonConfigurationInstances类对象中保存ViewModelStore对象,所以这也是Activity重建时不会销毁ViewModel的原因。
onRetainNonConfigurationInstance()方法除了被Activity的retainNonConfigurationInstances()调用以外,还会被LocalActivityManager的dispatchRetainNonConfigurationInstance()方法调用
在分析ViewModel的销毁过程时,我们看到Activity与Fragment存储VieModel是分离的,那么同一个Activity下的Fragment是如何共享ViewModel的呢?
其实共享的是Activity的ViewModel。
而具体的实现逻辑,其实就是在FragmentViewModelLazy.kt中的:
在Fragment中可以直接调用,这是一个Fragment的扩展函数,通过实现requireActivity().viewModelStore,获取到了Activity的ViewModelStore对象后,这样就可以实现了Fragment共用Activity的ViewModel,从而实现了Fragment之间共享ViewModel。
Fragment之间共享ViewModel,需要引入
【转载】AlphaGo原理解析
参考技术A 这些天都在没日没夜地关注一个话题,谷歌人工智能程序AlphaGo(国内网友亲切地称为“阿尔法狗”)以5:0击败欧洲职业围棋冠军樊麾二段,并在和世界冠军的比赛中2:0领先。
什么!!
19年前计算机击败国际象棋冠军卡斯帕罗夫的情景还历历在目,现在计算机又要来攻克围棋了吗!?
虚竹在天龙八部里自填一子,无意中以“自杀”破解“珍笼”棋局,逍遥子方才亲传掌门之位。难道以后“阿尔法狗”要出任逍遥派掌门了?
1933年,东渡日本19岁的吴清源迎战当时的日本棋坛霸主、已经60岁的本因坊秀哉,开局三招即是日本人从未见过的三三、星、天元布阵,快速进击逼得对方连连暂停“打卦”和弟子商量应对之策。随后以“新布局”开创棋坛新纪元。难道阿尔法狗会再造一个“新新布局”?
作为一个关心人工智能和人类命运的理科生,近些天刷了好些报道,记者们说“阿尔法狗是个‘价值神经网络’和‘策略神经网’络综合蒙特卡洛搜索树的程序”,但我觉得光知道这些概念是不够的。我想看看“阿尔法狗”的庐山真面目。
准备好棋盘和脑容量,一起来探索吧?
围棋棋盘是19x19路,所以一共是361个交叉点,每个交叉点有三种状态,可以用1表示黑子,-1表示白字,0表示无子,考虑到每个位置还可能有落子的时间、这个位置的气等其他信息,我们可以用一个361 * n维的向量来表示一个棋盘的状态。我们把一个棋盘状态向量记为s。
当状态s下,我们暂时不考虑无法落子的地方,可供下一步落子的空间也是361个。我们把下一步的落子的行动也用361维的向量来表示,记为a。
这样,设计一个围棋人工智能的程序,就转换成为了,任意给定一个s状态,寻找最好的应对策略a,让你的程序按照这个策略走,最后获得棋盘上最大的地盘。
如果你想要设计一个特别牛逼惊世骇俗的围棋程序,你会从哪里开始呢?对于在谷歌DeepMind工作的黄士杰和他的小伙伴而言,第一招是:
蒙特卡洛搜索树(Monte-Carlo Tree Search)是一种“大智若愚”的方法。面对一个空白棋盘S0,黄士杰的老师Coulum最初对围棋一无所知,便假设所有落子方法分值都相等,设为1。然后扔了一个骰子,从361种落子方法中随机选择一个走法a0。Coulum想象自己落子之后,棋盘状态变成S1,然后继续假设对手也和自己一样二逼,对方也扔了一个筛子,随便瞎走了一步,这时棋盘状态变成S2,于是这两个二逼青年一直扔骰子下棋,一路走到Sn,最后肯定也能分出一个胜负r,赢了就r记为1,输了则为0,假设这第一次r=1。这样Coulum便算是在心中模拟了完整的一盘围棋。
Coulum心想,这样随机扔骰子也能赢?运气不错啊,那把刚才那个落子方法(S0,a0)记下来,分值提高一些:
我刚才从(S0, a0)开始模拟赢了一次,r=1,那么新分数=2,除了第一步,后面几步运气也不错,那我把这些随机出的局面所对应落子方法(Si,ai)的分数都设为2吧。然后Coulum开始做第二次模拟,这次扔骰子的时候Coulum对围棋已经不是一无所知了,但也知道的不是太多,所以这次除(S0, a0)的分值是2之外,其他落子方法的分数还是1。再次选择a0的概率要比其他方法高一点点。
那位假想中的二逼对手也用同样的方法更新了自己的新分数,他会选择一个a1作为应对。如法炮制,Coulum又和想象中的对手又下了一盘稍微不那么二逼的棋,结果他又赢了,Coulum于是继续调整他的模拟路径上相应的分数,把它们都+1。随着想象中的棋局下得越来越多,那些看起来不错的落子方案的分数就会越来越高,而这些落子方案越是有前途,就会被更多的选中进行推演,于是最有“前途”的落子方法就会“涌现”出来。
最后,Coulum在想象中下完10万盘棋之后,选择他推演过次数最多的那个方案落子,而这时,Coulum才真正下了第一步棋。
蒙特卡洛搜索树华丽转身为相当深刻的方法,可以看到它有两个很有意思的特点:
1)没有任何人工的feature,完全依靠规则本身,通过不断想象自对弈来提高能力。这和深蓝战胜卡斯帕罗夫完全不同,深蓝包含了很多人工设计的规则。MCTS靠的是一种类似遗传算法的自我进化,让靠谱的方法自我涌现出来。让我想起了卡尔文在《大脑如何思维》中说的思维的达尔文主义[6]。
2)MCTS可以连续运行,在对手思考对策的同时自己也可以思考对策。Coulum下完第一步之后,完全不必要停下,可以继续进行想象中的对弈,直到对手落子。Coulum随后从对手落子之后的状态开始计算,但是之前的想象中的对弈完全可以保留,因为对手的落子完全可能出现在之前想象中的对弈中,所以之前的计算是有用的。这就像人在进行对弈的时候,可以不断思考,不会因为等待对手行动而中断。这一点Coulum的程序非常像人,酷毙了。
但黄士杰很快意识到他老师的程序仍然有局限:初始策略太简单。我们需要更高效地扔骰子。
如何更高效的扔骰子呢?
用P_human()来扔。
如果某一步被随机到很多次,就应该主要依据模拟得到的概率而非P_human。
所以P_human的初始分会被打个折扣:
这样就既可以用P_human快速定位比较好的落子方案,又给了其他位置一定的概率。看起来很美,然后实际操作中却发现:“然并卵”。因为,P_human()计算太慢了。
一次P_human()计算需要3ms,相对于原来随机扔骰子不到1us,慢了3000倍。如果不能快速模拟对局,就找不到妙招,棋力就不能提高。所以,黄士杰训练了一个简化版的P_human_fast(),把神经网络层数、输入特征都减少,耗时下降到了2us,基本满足了要求。先以P_human()来开局,走前面大概20多步,后面再使用P_human_fast()快速走到最后。兼顾了准确度和效率。
这样便综合了深度神经网络和MCTS两种方案,此时黄士杰的围棋程序已经可以战胜所有其他电脑,虽然距离人类职业选手仍有不小的差距,但他在2015年那篇论文的最后部分信心满满的表示:“我们围棋软件所使用的神经网络和蒙特卡洛方法都可以随着训练集的增长和计算力的加强(比如增加CPU数)而同步增强,我们正前进在正确的道路上。”
看样子,下一步的突破很快就将到来。同年2月,黄士杰在Deepmind的同事在顶级学术期刊nature上发表了“用神经网络打游戏”的文章[2]。这篇神作,为进一步提高MCTS的棋力,指明了前进的新方向:
红白机很多人小时候都玩过,你能都打通吗?黄士杰的同事通过“强化学习”方法训练的程序在类似红白机的游戏机上打通了200多个游戏,大多数得分都比人类还好。
“强化学习”是一类机器学习方法,Agent通过和环境s的交互,选择下一步的动作a,这个动作会影响环境s,给Agent一个reward,Agent然后继续和环境交互。游戏结束的时候,Agent得到一个最后总分r。这时我们把之前的环境状态s、动作a匹配起来就得到了一系列<s,a>,设定目标为最后的总得分r,我们可以训练一个神经网络去拟合在状态s下,做动作a的总得分。下一次玩游戏的时候,我们就可以根据当前状态s,去选择最后总得分最大的动作a。通过不断玩游戏,我们对<s,a>下总得分的估计就会越来越准确,游戏也玩儿得越来越好。
打砖块游戏有一个秘诀:把球打到墙的后面去,球就会自己反弹得分。强化学习的程序在玩了600盘以后,学到这个秘诀:球快要把墙打穿的时候评价函数v的分值就会急剧上升。
机器学习的开山鼻祖Samuel早在1967年就用自对弈的方法来学习国际跳棋[7],而之前的蒙特卡洛搜索树也是一个自对弈的过程。但是现在黄士杰不仅有一个从人类对弈中学习出的P_human这样一个高起点,而且有一个神经网络可以从对弈样本中学习,有理由相信这次会有更好的结果。
黄士杰准备在MCTS框架之上融合局面评估函数v()。这次还是用P_human作为初始分开局,每局选择分数最高的方案落子,下到第L步之后,改用P_human_fast把剩下的棋局走完,同时调用v(SL),评估局面的获胜概率。然后按照如下规则更新整个树的分数:
前两项和原来一样,如果待更新的节点就是叶子节点,那局面评估分就是v(SL)。如果是待更新的节点是上级节点,局面评估分是该节点所有叶子节点v()的平均值。
如果v()表示大局观,“P_human_fast模拟对局”表示快速验算,那么上面的方法就是大局观和快速模拟验算并重。如果你不服,非要做一个0.5: 0.5之外的权重,黄士杰团队已经实验了目前的程序对阵其他权重有95%的胜率。
以上,便是阿尔法狗的庐山真面目。
上图演示了阿尔法狗和樊麾对弈时的计算过程,阿尔法狗执黑,红圈是阿尔法狗实际落子的地方。1、2、3和后面的数字表示他想象中的之后双方下一步落子的地方。白色方框是樊麾的实际落子。在复盘时,樊麾觉得位置1的走法更好。
深度学习、蒙特卡洛搜索树,自我进化三招齐出,所有其他围棋ai都毫无还手之力。99%的胜率不说,“阿尔法狗”还可以在让四子的情况下以77%的胜率击败crazystone。“阿尔法狗”利用超过170个GPU,粗略估算超过800万核并行计算,不仅有前期训练过程中模仿人类,自我对弈不断进化,还有实战时的模拟对局可以实时进化,已经把现有方法发挥到了极限,是目前人工智能领域绝对的巅峰之作。
围棋是NP-hard问题,如果用一个原子来存储围棋可能的状态,把全宇宙的原子加起来都不够储存所有的状态。于是我们把这样的问题转换为寻找一个函数P,当状态为S时,计算最优的落子方案a = P(s)。我们看到,无论是“狂拽酷炫”的深度学习,还是“大智若愚”的MCTS,都是对P(s)的越来越精确的估计,但即使引入了“左右互搏”来强化学习,黄士杰和团队仍然做了大量的细节工作。所以只有一步一个脚印,面对挑战不断拆解,用耐心与细心,还有辛勤的汗水,才能取得一点又一点的进步,而这些进步积累在一起,终于让计算机达到并超过了人类职业选手的水平。
以上是关于Android-ViewModel原理解析的主要内容,如果未能解决你的问题,请参考以下文章
Android - ViewModel、LiveData、Room 和 Retrofit 以及协程放在 kotlin 中