国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

andriod開發之Activity的渲染機制

2019-10-22 18:14:34
字體:
來源:轉載
供稿:網友

一切從setContentView說起。安卓中最常用的代碼可能就是setContentView了,但大家有沒有想過這個方法的背后到底做了些什么?

public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main); }}

直接跳轉到Activity的源碼我們可以看到,Activity.setContentView實際上調用了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();}

我們繼續跟蹤PhoneWindow的源碼,可以發現最終layoutResID被inflate出來之后是成為了mDecor這個DecorView的子view。而DecorView實際上是一個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 實際上是mDecor的一個子view   mContentParent = generateLayout(mDecor);   ...  }  ...}protected DecorView generateDecor() {  return new DecorView(getContext(), -1);}private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {  ...}

這里的generateLayout比較重要,它實際上是根據window的各種屬性inflate出不同的layout掛到DecorView下面,而mContentParent是這個layout中的一個子ViewGroup。如果我們沒有對window的屬性進行設置就會使用默認的com.android.internal.R.layout.screen_simple這個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,這是個豎直的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>

我們可以用一張圖片來總結下Activity是如何管理布局的(這里假設DecorView里面添加了screen_simple這個布局):

andriod,Activity,渲染機制

Activity的布局是怎樣被系統渲染的

在上一節中我們已經知道了Activity是怎樣管理布局的。接著我們來看看Activity中的布局是如何渲染到系統的。

ActivityThread用于管理Activity的聲明周期,之后我會專門寫一篇文章來講它。我們直接看ActivityThread.handleResumeActivity方法:

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ... //performResumeActivity方法會調用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在手機上渲染了出來。

WindowManager.addView方法可以將一個view渲染到手機界面上。不知道大家有沒有做過類似懸浮球的應用,就是用WindowManager.addView去實現的。這里就不再展開了,大家有興趣的話可以自己去搜索一下。

為什么不能在子線程中操作view

我們都知道,在安卓中必須在ui線程中操作ui,不能在子線程中對view進行操作,否則或拋出CalledFromWrongThreadException異常。但是在子線程中操作view是不是真的就一定會出現異常呢?讓我們運行下面的代碼:

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(); }}

我們可以看到實際上在onCreate的時候直接啟動子線程去修改TextView的文字是可以正常運行的,且文字也是顯示正常的:

andriod,Activity,渲染機制

讓我們家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(); }}

運行之后就能看到熟悉的崩潰日志了:

02-28 22:36:48.550 3780 3817 E AndroidRuntime: FATAL EXCEPTION: Thread-502-28 22:36:48.550 3780 3817 E AndroidRuntime: Process: com.example.linjw.myapplication, PID: 378002-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方法中會調用ViewRootImpl.requestLayout,然后在ViewRootImpl.requestLayout里面會調用ViewRootImpl.checkThread去判斷當前線程和創建ViewRootImpl的線程是不是同一個線程。如果不是的話就拋出CalledFromWrongThreadException異常。

那ViewRootImpl又是在哪個線程中被創建的呢?還記得上一節中講到的ActivityThread.handleResumeActivity方法中將DecorView添加到WindowManager中嗎?WindowManager實際上是WindowManagerImpl實例:

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實際上是調到了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的線程中被創建的,我們都知道onResume是在主線程中被調用的,所以ViewRootImpl是在主線程中被調用的。所以只要在非主線程中調用ViewRootImpl.requestLayout就會拋出CalledFromWrongThreadException異常。

那回到最初的問題,為什么我們在onCreate的時候直接起子線程去修改TextView的文字,不會拋出CalledFromWrongThreadException異常?因為ViewRootImpl是在onResume中創建的,在onCreate的時候它就還沒有被創建,所以就不會拋出CalledFromWrongThreadException異常。

等到onResume的時候ViewRootImpl被創建,會進行第一次layout,這個時候才會檢查是否在主線程中操作ui。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 古交市| 巨鹿县| 乌鲁木齐市| 商都县| 乌拉特前旗| 舒兰市| 岢岚县| 亚东县| 神池县| 班戈县| 辽宁省| 中方县| 新蔡县| 景洪市| 合作市| 若尔盖县| 融水| 陇西县| 大冶市| 十堰市| 鄯善县| 汉中市| 广德县| 辛集市| 桂林市| 枣强县| 云和县| 临朐县| 东莞市| 芷江| 临泽县| 沙雅县| 安吉县| 敖汉旗| 本溪市| 台州市| 绥化市| 巨野县| 剑川县| 华蓥市| 弥勒县|