ITPub博客

首页 > 移动开发 > ANDROID > 从findViewById失效的问题到深入setContentView

从findViewById失效的问题到深入setContentView

原创 ANDROID 作者:山有木xi 时间:2024-01-31 20:51:38 0 删除 编辑

最近有几个老项目需要更新 , 在接手时发现项目中出现布局的点按操作无效以及很多的崩溃问题 , 简单排查以后发现接手时项目中混用了 ViewBinding 和 findViewById , 而出现问题的地方都是 findViewById 。

最开始想到的也是更新中目的之一 , 升级 Target  30 , 当时还在想难道是 Target  30 不给用 findViewById 了 ? 把步骤改为 ViewBinding 后正常执行 , 好像很像我的猜测 , 但是这不符合逻辑啊 , ViewBinding 的底层实际上还是 findViewById 。

继续看代码 , 发现项目的 Activity 全部继承了一个基类 Activity , 问题就出现在这里 , 先上代码

abstract class InitActivity : SimpleActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(getLayout())
       initTitlebar()
       initData()
       initListener()
   }
   protected abstract fun getLayout(): Int
   protected abstract fun initTitlebar()
   protected abstract fun initListener()
   protected abstract fun initData()
}

这个类很好理解 , 统一了 Activity 常用的方法 , 只要继承后实现即可 , 同时最重要的 , 在这个基类中实现了 setContentView , 通过 getLayout 获取布局 i d , 而我们要使用 ViewBinding , 需要在 setContentView 中注册 ViewBinding

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(binding.root)
}

也就是说 , 每个 Activity 都执行了两次 setContentView , 并且由于常用的初始化方法都是在基类中调用的 , 所有的 findViewById 都是基于第一次设置在 setContentView 的布局 , 当第二次设置 setContentView (binding.root) 后 , 第一次的布局就失效了 , 所以导致了遇到的问题 。

那么为什么 , 第二次调用 setContentView 会出现这样的问题 , setContentView 到底经历了什么 , 这就跟 View 的渲染流程有关了

先简单说明 View 的渲染 :

整个流程主要从 ActivityThread 类开始,途经 PhoneWindow 、 DecorView 、 LayoutInflater 、等类 。
首先是创建 activity ,创建 widow, 创建 decorview, 然后是调用 setContent 时候,创建 View ,然后解析生成 View 。

可以看到在 setContent 的时候 创建 View ,然后解析生成 View 。

来看看 setContent 经历了什么

首先 ,实现了三个重载的setContentView 方法, getDelegate() 方法负责创建 Activity 的代理类实例,然后调用 setContentView 方法添加显示的视图, Activity 通过代理模式添加要显示的视图。

在 getDelegate() 中负责创建 Activity 代理 AppCompatDelegate 类实例

再来到 AppCompatDelegateImpl 中的   setContentView 方法看看

其中 ensureSubDecor 的核心代码如下

而 createSubDecor 就很长了 , 一张图都放不下 , 在这个方法中主要干了三件事

1 、 this.mWindow.getDecorView(); 创建 Decorview, 并为它加载一个布局文件,找到这个布局文件中 R.id.content 的容器,赋值给 mContentParent 。这样我们就准备好了一个 DecorView 和其布局中 id 为 R.id.content 的容器。

AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback) {
        ......
       this.mWindow = window;
  // mWindow 的初始化是在AppCompatDelegateImpl构造函数里
        .....
       
    }
 // 想要知道mWindow是啥就要找到AppCompatDelegateImpl(context,window,callback),那么这个构造函数初始化的时候传//入的window是啥,还记得最开始我们从setContentView说起
       
  protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
}
//往下传递
public class AppCompatActivity extends ... {
 
     public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
     }
 
//getDelegate().setContentView(layoutResID);先找getDelegate()
  //getDelegate()也在AppCompatActivity 中
                
 @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
}
    // getDelegate() = AppCompatDelegate.create(this, this);
public abstract class AppCompatDelegate {
  public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
       // 在这里初始化的,activity就是AppCompatActivity ,window就是activity.getWindow()
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
    }   
}
 
//window就是AppCompatActivity.getWindow(),但是AppCompatActivity中没有getWindow()方法,getWindow()是在其父类Activity中实现
public class Activity extends ... ... {
 
       private Window mWindow;
        
      final void attach(Context context, ......) {
        attachBaseContext(context);
 
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        
        ......
       }
 
      public @Nullable Window getWindow() {
            return mWindow;
       }
}

2 、 给ViewGroup subDecor 根据主题、 style 选择合适布局文件并加载到 subDecor 中:

 ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);  
 
        ViewGroup windowContentView =  (ViewGroup)this.mWindow.findViewById(R.id.content);
// 这里就是上一步里面那个布局文件的R.id.content 容器  
 
  windowContentView.setId(View.NO_ID);  
// 把windowContentView的id设置为View.NO_ID 即  -1
 
        contentView.setId(android.R.id.content);
// 把contentView 的id设置为R.id.content

这样我们准备好了subDecor 和其布局中 id 为 action_bar_activity_content 的容器,并把这个容器的 id 改成 R.id.content

3、 this.mWindow.setContentView(subDecor); 将第 2 步的 subDecor 添加到 第 1 步准备好的 DecorView 的容器 mContentParent 中。

            @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
 
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
 
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
           
//还记得 第一步 的时候准备好的mContentParent,现在就是把subDecor加载到其中
            
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

也就是每次调用 set ContentView 都会修改整个 activity 的容器中的布局



来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69917874/viewspace-3005923/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
Oracle OCA(Java),IBM高级工程师认证,中国软件行业人才(高级c语言),高级Android工程师,对数据库并发与性能调优也有一定了解

注册时间:2019-04-25

  • 博文量
    147
  • 访问量
    390775