• Android Hook View的创建流程


    前言

    前面我们对setContentView的源码进行了深入的分析Android最全的setContentView源码分析,那了解了View的创建流程,我们可以做些什么呢?答案就是我们可以通过拦截View的创建流程去解析View对应的属性(如textColor、src、background等),然后进行APP的换肤!

    具体拦截实现

    前面我们分析过View的创建流程会交给Factory2.onCreateView()方法去实现,那我们就通过实现 LayoutInflater.factory2接口进行View的创建拦截!

    • 实现LayoutInflater.factory2接口
    class BaseSkinActivity : BaseActivity(), LayoutInflater.Factory2
    
    • 1
    • onCreate(savedInstanceState: Bundle?)中设置LayoutInflater.factory2
        override fun onCreate(savedInstanceState: Bundle?) {
           val layoutInflater = LayoutInflater.from(this)
           layoutInflater.factory2 = this  //当前activity实现 LayoutInflater.factory2接口
           super.onCreate(savedInstanceState)
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 在factory2接口方法中进行View的创建拦截处理
     override fun onCreateView(
           parent: View?,
           name: String,
           context: Context,
           attrs: AttributeSet
       ): View? {
           val view = createView(parent, name, context, attrs)
           if (view != null) {
              	//在这里可以通过attrs解析View的属性和值,进行对应的换肤操作
           }
    
           return view
       }
    
       private fun createView(
           parent: View?,
           name: String,
           context: Context,
           attrs: AttributeSet
       ): View {
       	//这里的实现参考系统中的AppCompatDelegateImpl.createView方法,直接抄
           if (appCompatViewInflater == null) {
               appCompatViewInflater = AppCompatViewInflater()
           }
           var inheritContext = false
           if (IS_PRE_LOLLIPOP) {
               if (mLayoutIncludeDetector == null) {
                   mLayoutIncludeDetector = LayoutIncludeDetector()
               }
               inheritContext = if (mLayoutIncludeDetector!!.detect(attrs)) {
                   true
               } else {
                   if (attrs is XmlPullParser // If we have a XmlPullParser, we can detect where we are in the layout
                   ) (attrs as XmlPullParser).depth > 1 // Otherwise we have to use the old heuristic
                   else shouldInheritContext(parent as ViewParent?)
               }
           }
    
           return appCompatViewInflater!!.createView(
               parent, name, context, attrs, inheritContext,
               IS_PRE_LOLLIPOP,  /* Only read android:theme pre-L (L+ handles this anyway) */
               true,  /* Read read app:theme as a fallback at all times for legacy reasons */
               VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
           )
       }
    
    
       private fun shouldInheritContext(parent: ViewParent?): Boolean {
           var parent: ViewParent? = parent
           val windowDecor: View = window.decorView
           while (true) {
               if (parent == null) {
                   return true
               } else if (parent === windowDecor || parent !is View
                   || ViewCompat.isAttachedToWindow((parent as View?)!!)
               ) {
                   return false
               }
               parent = parent.getParent()
           }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    其中AppCompatViewInflater我们无法调用使用系统对应的createView方法,最简单的方式是我们直接复制一份相同的AppCompatViewInflater类,去设置createViewpublic方法即可,createView方法的实现完全可以参考系统中的AppCompatDelegateImpl.createView方法~~

    总结

    这里只是简单的提供了APP插件换肤的思路,实际开发中插件换肤需要考虑的问题是比较多的,如多套皮肤的管理、自定义View的处理等等问题,但插件换肤的核心就是Hook View的创建流程,从而替换View的属性,说到底还是对源码的深刻理解。

    结语

    如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

  • 相关阅读:
    【PyTorch 攻略 (3/7)】线性组件、激活函数
    JAVA枚举
    Docker的私有仓库部署——Harbor
    linux 中jenkins启动重启停止命令 改端口
    1000粉!使用Three.js实现一个创意纪念页面 🏆
    毕业设计-机器视觉深度学习的视频去水印算法
    为什么 wireguard-go 高尚而 boringtun 孬种
    Ubuntu下命令行解析
    std::initializer_list详解
    恭喜磊哥喜提n+1
  • 原文地址:https://blog.csdn.net/a734474820/article/details/127999719