中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

andriod開發之Activity渲染機制的示例分析

發布時間:2021-08-21 14:10:50 來源:億速云 閱讀:102 作者:小新 欄目:移動開發

這篇文章將為大家詳細講解有關andriod開發之Activity渲染機制的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

一切從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-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方法中會調用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。

關于“andriod開發之Activity渲染機制的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

乌恰县| 凯里市| 星座| 红桥区| 青海省| 峨山| 黄陵县| 云阳县| 余干县| 塘沽区| 天等县| 大同市| 乐业县| 丰宁| 黄浦区| 桂东县| 景泰县| 泰安市| 平昌县| 府谷县| 琼中| 昌平区| 宣化县| 勃利县| 白城市| 清丰县| 上杭县| 昔阳县| 林周县| 信宜市| 武安市| 观塘区| 博罗县| 迁西县| 祁阳县| 拜泉县| 南部县| 交城县| 田林县| 夏河县| 阿尔山市|