andriod 개발 의 Activity 렌 더 링 메커니즘

12779 단어 andriodActivity
모든 것 은 set ContentView 에서 시작한다.안 드 로 이 드 에서 가장 자주 사용 하 는 코드 는 아마도 set ContentView 일 것 이다.그러나 여러분 은 이 방법의 배후 에서 도대체 무엇 을 했 는 지 생각해 본 적 이 있 습 니까?

public class MainActivity extends Activity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
 }
}
Activity 의 원본 코드 로 바로 이동 하면 Activity.set ContentView 가 실제로 Phone Window.set ContentView 를 호출 한 것 을 볼 수 있 습 니 다.

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();
}
우 리 는 Phone Window 의 소스 코드 를 계속 추적 하면 최종 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 에 따라 다른 layot 를 DecorView 아래 에 걸 고 mContentParent 는 이 layot 의 키 ViewGroup 입 니 다.window 의 속성 을 설정 하지 않 으 면 기본 com.android.internal.R.layot.screen 을 사용 합 니 다.simple 이 레이아웃:

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/아래 에서 이 layot xml 를 찾 을 수 있 습 니 다.예 를 들 어 screenSimple,이것 은 수직 적 인 LinearLayout 로 위의 Action Bar 와 아래 의 content FrameLayout 로 구성 되 어 있 습 니 다.이것 은 바로 우리 가 가장 흔히 볼 수 있 는 Action Bar 를 가 진 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 이 레이아웃):
 
Activity 의 레이아웃 은 어떻게 시스템 에 렌 더 링 됩 니까?
지난 절 에서 우 리 는 Activity 가 레이아웃 을 어떻게 관리 하 는 지 이미 알 게 되 었 다.이어서 Activity 의 레이아웃 이 시스템 에 어떻게 렌 더 링 되 는 지 살 펴 보 자.
Activity Thread 는 Activity 의 성명 주 기 를 관리 하 는 데 사 용 됩 니 다.그 후에 저 는 전문 적 으로 글 을 써 서 이야기 하 겠 습 니 다.Activity Thread.handle ResumeActivity 방법 을 직접 보 겠 습 니 다.

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 를 가 져 온 것 을 볼 수 있 습 니 다.마지막 으로 Window Manager.addView 를 사용 하여 DecorView 를 Window Manager 에 추가 하 였 습 니 다.이렇게 해서 DecorView 를 휴대 전화 에 렌 더 링 했다.
Window Manager.addView 방법 은 하나의 view 를 모 바 일 인터페이스 에 렌 더 링 할 수 있 습 니 다.여러분 이 부유 구 와 유사 한 응용 을 해 본 적 이 있 는 지 모 르 겠 지만,바로 Window Manager.addView 로 이 루어 진 것 입 니 다.여 기 는 더 이상 펼 쳐 지지 않 습 니 다.관심 이 있 으 시 면 직접 찾 아 보 세 요.
왜 하위 스 레 드 에서 view 를 조작 할 수 없 습 니까?
안 드 로 이 드 에서 ui 스 레 드 에서 ui 를 조작 해 야 한 다 는 것 을 잘 알 고 있 습 니 다.하위 스 레 드 에서 view 를 조작 할 수 없습니다.그렇지 않 으 면 Called FromWrong ThreadException 이상 을 던 질 수 있 습 니 다.그런데 서브 스 레 드 에서 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 의 문 자 를 수정 하면 정상적으로 실 행 될 수 있 고 문자 도 정상 적 인 것 을 볼 수 있 습 니 다.

우리 집의 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 의 소스 코드 를 직접 잠 그 고 Called From Wrong ThreadException 이상 이 어떻게 던 져 졌 는 지 살 펴 보 겠 습 니 다.

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 을 만 드 는 스 레 드 가 같은 스 레 드 인지 아 닌 지 판단 합 니 다.아니면 Called From Wrong ThreadException 이상 을 던 져.
그럼 ViewRootImpl 은 또 어느 스 레 드 에서 만 들 어 졌 습 니까?지난 절 에서 말 한 Activity Thread.handle ResumeActivity 방법 에서 DecorView 를 Window Manager 에 추가 한 것 을 기억 하 십 니까?Window Manager 는 실제로 Window Manager Impl 인 스 턴 스 입 니 다.

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);
 }
 ...
}
Window Manager Impl.addView 가 실제로 Window Manager Global.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 은 handle ResumeActivity 의 스 레 드 에서 만 들 어 졌 습 니 다.우 리 는 모두 onResume 이 메 인 스 레 드 에서 호출 되 었 다 는 것 을 알 고 있 기 때문에 ViewRootImpl 은 메 인 스 레 드 에서 호출 되 었 습 니 다.따라서 비 메 인 스 레 드 에서 ViewRootImpl.requestLayout 를 호출 하면 Called FromWrong ThreadException 이상 을 던 집 니 다.
그러면 최초의 문제 로 돌아 갑 니 다.왜 우 리 는 onCreate 에서 하위 스 레 드 를 직접 시작 하여 TextView 의 문 자 를 수정 합 니까?Called From Wrong ThreadException 이상 을 던 지지 않 습 니까?ViewRootImpl 은 onResume 에서 만 들 어 졌 기 때문에 onCreate 에서 만 들 지 않 았 기 때문에 Called From Wrong ThreadException 이상 을 던 지지 않 습 니 다.
onResume 을 기 다 렸 을 때 ViewRootImpl 이 생 성 되 고 첫 번 째 layot 가 진 행 됩 니 다.이 럴 때 주 스 레 드 에서 ui 를 작 동 하 는 지 확인 할 수 있 습 니 다.

좋은 웹페이지 즐겨찾기