andriod開發(fā)之Activity的渲染機(jī)制
一切從setContentView說起。安卓中最常用的代碼可能就是setContentView了,但大家有沒有想過這個(gè)方法的背后到底做了些什么?
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
直接跳轉(zhuǎn)到Activity的源碼我們可以看到,Activity.setContentView實(shí)際上調(diào)用了PhoneWindow.setContentView:
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window) { ... mWindow = new PhoneWindow(this, window); ... } public Window getWindow() { return mWindow; } public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
我們繼續(xù)跟蹤PhoneWindow的源碼,可以發(fā)現(xiàn)最終layoutResID被inflate出來之后是成為了mDecor這個(gè)DecorView的子view。而DecorView實(shí)際上是一個(gè)FrameLayout:
public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); ... } if (mContentParent == null) { //mContentParent 實(shí)際上是mDecor的一個(gè)子view mContentParent = generateLayout(mDecor); ... } ... } protected DecorView generateDecor() { return new DecorView(getContext(), -1); } private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { ... }
這里的generateLayout比較重要,它實(shí)際上是根據(jù)window的各種屬性inflate出不同的layout掛到DecorView下面,而mContentParent是這個(gè)layout中的一個(gè)子ViewGroup。如果我們沒有對(duì)window的屬性進(jìn)行設(shè)置就會(huì)使用默認(rèn)的com.android.internal.R.layout.screen_simple這個(gè)layout:
protected ViewGroup generateLayout(DecorView decor) { ... if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { ... layoutResource = com.android.internal.R.layout.screen_title_icons; ... } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { layoutResource = com.android.internal.R.layout.screen_progress; } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { ... layoutResource = com.android.internal.R.layout.screen_custom_title; ... } ... else{ layoutResource = com.android.internal.R.layout.screen_simple; } ... View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); ... return contentParent; }
我們可以在AndroidSdk根目錄/platforms/android-19/data/res/layout/下面找到這些layout xml,例如screen_simple,這是個(gè)豎直的LinearLayout,由上方的ActionBar和下方的content FrameLayout組成。它就是我們最常見的帶ActionBar的activity樣式:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical"> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>
我們可以用一張圖片來總結(jié)下Activity是如何管理布局的(這里假設(shè)DecorView里面添加了screen_simple這個(gè)布局):
Activity的布局是怎樣被系統(tǒng)渲染的
在上一節(jié)中我們已經(jīng)知道了Activity是怎樣管理布局的。接著我們來看看Activity中的布局是如何渲染到系統(tǒng)的。
ActivityThread用于管理Activity的聲明周期,之后我會(huì)專門寫一篇文章來講它。我們直接看ActivityThread.handleResumeActivity方法:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ... //performResumeActivity方法會(huì)調(diào)用Activity.onResume ActivityClientRecord r = performResumeActivity(token, clearHide); ... r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; wm.addView(decor, l); } ... }
可以看到它在Activity.onResume之后從Activity中獲取了Window,然后又從window中獲取了DecorView。最后使用WindowManager.addView將DecorView添加到了WindowManager中。這樣就將DecorView在手機(jī)上渲染了出來。
WindowManager.addView方法可以將一個(gè)view渲染到手機(jī)界面上。不知道大家有沒有做過類似懸浮球的應(yīng)用,就是用WindowManager.addView去實(shí)現(xiàn)的。這里就不再展開了,大家有興趣的話可以自己去搜索一下。
為什么不能在子線程中操作view
我們都知道,在安卓中必須在ui線程中操作ui,不能在子線程中對(duì)view進(jìn)行操作,否則或拋出CalledFromWrongThreadException異常。但是在子線程中操作view是不是真的就一定會(huì)出現(xiàn)異常呢?讓我們運(yùn)行下面的代碼:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { ((TextView)findViewById(R.id.textView)).setText("子線程中操作view"); } }).start(); } }
我們可以看到實(shí)際上在onCreate的時(shí)候直接啟動(dòng)子線程去修改TextView的文字是可以正常運(yùn)行的,且文字也是顯示正常的:
讓我們家1秒的延遲再試一下:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } ((TextView)findViewById(R.id.textView)).setText("子線程中操作view"); } }).start(); } }
運(yùn)行之后就能看到熟悉的崩潰日志了:
02-28 22:36:48.550 3780 3817 E AndroidRuntime: FATAL EXCEPTION: Thread-5 02-28 22:36:48.550 3780 3817 E AndroidRuntime: Process: com.example.linjw.myapplication, PID: 3780 02-28 22:36:48.550 3780 3817 E AndroidRuntime: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6987) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1104) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.view.View.requestLayout(View.java:19807) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.view.View.requestLayout(View.java:19807) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.view.View.requestLayout(View.java:19807) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.view.View.requestLayout(View.java:19807) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.support.constraint.ConstraintLayout.requestLayout(ConstraintLayout.java:874) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.view.View.requestLayout(View.java:19807) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.widget.TextView.checkForRelayout(TextView.java:7375) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.widget.TextView.setText(TextView.java:4487) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.widget.TextView.setText(TextView.java:4344) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at android.widget.TextView.setText(TextView.java:4319) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at com.example.linjw.myapplication.MainActivity$1.run(MainActivity.java:20) 02-28 22:36:48.550 3780 3817 E AndroidRuntime: at java.lang.Thread.run(Thread.java:760)
為什么延遲1秒之后就能看到異常被拋出了呢?本著尋根問底的精神,我們直接扣ViewRootImpl的源碼看看CalledFromWrongThreadException異常是怎么被拋出的:
public ViewRootImpl(Context context, Display display) { ... mThread = Thread.currentThread(); ... } void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
在View.requestLayout方法中會(huì)調(diào)用ViewRootImpl.requestLayout,然后在ViewRootImpl.requestLayout里面會(huì)調(diào)用ViewRootImpl.checkThread去判斷當(dāng)前線程和創(chuàng)建ViewRootImpl的線程是不是同一個(gè)線程。如果不是的話就拋出CalledFromWrongThreadException異常。
那ViewRootImpl又是在哪個(gè)線程中被創(chuàng)建的呢?還記得上一節(jié)中講到的ActivityThread.handleResumeActivity方法中將DecorView添加到WindowManager中嗎?WindowManager實(shí)際上是WindowManagerImpl實(shí)例:
public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); ... public void addView(View view, ViewGroup.LayoutParams params) { mGlobal.addView(view, params, mDisplay, mParentWindow); } ... }
我們可以看到WindowManagerImpl.addView實(shí)際上是調(diào)到了WindowManagerGlobal.addView:
public final class WindowManagerGlobal { public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... ViewRootImpl root; ... root = new ViewRootImpl(view.getContext(), display); ... } }
所以ViewRootImpl是在handleResumeActivity的線程中被創(chuàng)建的,我們都知道onResume是在主線程中被調(diào)用的,所以ViewRootImpl是在主線程中被調(diào)用的。所以只要在非主線程中調(diào)用ViewRootImpl.requestLayout就會(huì)拋出CalledFromWrongThreadException異常。
那回到最初的問題,為什么我們?cè)趏nCreate的時(shí)候直接起子線程去修改TextView的文字,不會(huì)拋出CalledFromWrongThreadException異常?因?yàn)閂iewRootImpl是在onResume中創(chuàng)建的,在onCreate的時(shí)候它就還沒有被創(chuàng)建,所以就不會(huì)拋出CalledFromWrongThreadException異常。
等到onResume的時(shí)候ViewRootImpl被創(chuàng)建,會(huì)進(jìn)行第一次layout,這個(gè)時(shí)候才會(huì)檢查是否在主線程中操作ui。
- Android的Activity跳轉(zhuǎn)動(dòng)畫各種效果整理
- android PopupWindow 和 Activity彈出窗口實(shí)現(xiàn)方式
- android的activity跳轉(zhuǎn)到另一個(gè)activity
- Android基礎(chǔ)之Fragment與Activity交互詳解
- Android Activity之間傳遞圖片(Bitmap)的方法
- Android筆記之:App應(yīng)用之啟動(dòng)界面SplashActivity的使用
- Activity透明/半透明效果的設(shè)置transparent(兩種實(shí)現(xiàn)方法)
- android獲取當(dāng)前運(yùn)行Activity名字的方法
- Android Activity切換(跳轉(zhuǎn))時(shí)出現(xiàn)黑屏的解決方法 分享
- Android實(shí)現(xiàn)Activity界面切換添加動(dòng)畫特效的方法
相關(guān)文章
Android studio實(shí)現(xiàn)菜單操作
這篇文章主要為大家詳細(xì)介紹了Android studio實(shí)現(xiàn)菜單操作,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10Android自定義控件開發(fā)實(shí)戰(zhàn)之實(shí)現(xiàn)ListView下拉刷新實(shí)例代碼
這篇文章主要介紹了Android自定義控件開發(fā)實(shí)戰(zhàn)之實(shí)現(xiàn)ListView下拉刷新實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-04-04HttpURLConnection和okHttp兩種獲取網(wǎng)絡(luò)數(shù)據(jù)的實(shí)現(xiàn)方法
下面小編就為大家分享一篇HttpURLConnection和okHttp兩種獲取網(wǎng)絡(luò)數(shù)據(jù)的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01AndroidStudio 配置 AspectJ 環(huán)境實(shí)現(xiàn)AOP的方法
本篇文章主要介紹了AndroidStudio 配置 AspectJ 環(huán)境實(shí)現(xiàn)AOP的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02android教程使用webview訪問https的url處理sslerror示例
這篇文章主要介紹了android教程使用webview訪問https的url處理sslerror示例,大家參考使用吧2014-01-01Android使用手勢(shì)監(jiān)聽器GestureDetector遇到的不響應(yīng)問題
這篇文章主要介紹了Android使用手勢(shì)監(jiān)聽器GestureDetector遇到的不響應(yīng)問題,具有很好的參考價(jià)值,對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08