從findViewById失效的問題到深入setContentView
最近有幾個老專案需要更新 , 在接手時發現專案中出現佈局的點按操作無效以及很多的崩潰問題 , 簡單排查以後發現接手時專案中混用了 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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- android LayoutInflater、setContentView、findviewbyid 區分解析AndroidView
- Jib構建映象問題從定位到深入分析
- Mysql跨庫主從熱備失效問題MySql
- css失效問題CSS
- MyBatis order by失效問題MyBatis
- 外掛失效問題
- JDBC連線MySQL失效的問題JDBCMySql
- 再談量化策略失效的問題
- 深入探索智慧問答:從檢索到生成的技術之旅
- 聊一聊MySQL索引失效的問題MySql索引
- C++vector迭代器失效的問題C++
- session跟蹤失效的問題和原因Session
- Android 從SetContentView()談起AndroidView
- 探究 position-sticky 失效問題
- kubernetesgraceperiod失效問題排查
- .gitignore 失效問題解決Git
- click事件在蘋果手機失效的問題事件蘋果
- iptables 重啟後ftp 策略失效的問題FTP
- View.findViewById()和Activity.findViewById()區別View
- spring data mongo @Field 失效問題SpringGo
- IOS margin-bottom失效問題iOS
- 如何解決快取失效問題快取
- 從八皇后問題到回溯演算法演算法
- 從網路鏈路到跨域問題跨域
- 關於 Laravel mix 導致 Bootstrap 失效的問題Laravelboot
- transition-group在table表格中失效的問題
- Zabbix中Orabbix監控失效的問題及分析
- Windows 10 LTSC中個人版OneDrive失效的問題Windows
- 【React深入】從Mixin到HOC再到HookReactHook
- JavaScript深入之從原型到原型鏈JavaScript原型
- JavaScript 深入之從原型到原型鏈JavaScript原型
- 九個問題從入門到熟悉HTTPSHTTP
- 日常問題 頁面跳轉 $_SESSION 失效Session
- 從問題入手,深入瞭解JavaScript中原型與原型鏈JavaScript原型
- 從原始碼角度剖析 setContentView() 背後的機制原始碼View
- Activity 中的方法 findViewById(int) 和 AppCompatActivity 中的方法 findViewById(int) 都匹配...ViewAPP
- 從Google Sheets內容的匯出,到字串換行問題Go字串
- 從Access轉到MySql以後遇到的關於null問題MySqlNull