欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

andriod開發(fā)之Activity的渲染機制

 更新時間:2018年03月01日 08:43:58   作者:林嘉偉  
本文給大家分享的是在andriod開發(fā)中經(jīng)常需要用到的Activity的渲染機制的詳細(xì)說明,主要是通過實例給大家講解Activity是如何畫到屏幕上的,希望大家能夠喜歡

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

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

直接跳轉(zhuǎn)到Activity的源碼我們可以看到,Activity.setContentView實際上調(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這個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比較重要,它實際上是根據(jù)window的各種屬性inflate出不同的layout掛到DecorView下面,而mContentParent是這個layout中的一個子ViewGroup。如果我們沒有對window的屬性進(jìn)行設(shè)置就會使用默認(rèn)的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>

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

 

Activity的布局是怎樣被系統(tǒng)渲染的

在上一節(jié)中我們已經(jīng)知道了Activity是怎樣管理布局的。接著我們來看看Activity中的布局是如何渲染到系統(tǒng)的。

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

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

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

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

我們都知道,在安卓中必須在ui線程中操作ui,不能在子線程中對view進(jìn)行操作,否則或拋出CalledFromWrongThreadException異常。但是在子線程中操作view是不是真的就一定會出現(xià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();
 }
}

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

讓我們家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-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方法中會調(diào)用ViewRootImpl.requestLayout,然后在ViewRootImpl.requestLayout里面會調(diào)用ViewRootImpl.checkThread去判斷當(dāng)前線程和創(chuàng)建ViewRootImpl的線程是不是同一個線程。如果不是的話就拋出CalledFromWrongThreadException異常。

那ViewRootImpl又是在哪個線程中被創(chuàng)建的呢?還記得上一節(jié)中講到的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實際上是調(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就會拋出CalledFromWrongThreadException異常。

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

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

相關(guān)文章

最新評論