Activityページの描画プロセス
一.引用する
多くの友人は、Activityのページ描画はActivityのonResumeメソッドの実行が完了すると成功して描画されたと考えています.このとき、私たちが書いたページを見ることができると思います.本当にそうなの?実はそうではありません.ActivityのページはActivityのonResumeメソッドが実行されてから描画されます.また、描画が完了した時間は、メインスレッドの当時の状態やレイアウトの階層、CPUメモリと一定の関係があります.これらもActivityがブラックスクリーンを起動する主な方向を分析しています.私たちのActivityがonResumeを終えてユーザーの目の前に表示されるとは思わないでください.はい、くだらない話は多くありません.ソースコードから分析してみましょう.Activityのページの描画はいつですか.
二.ソース解析
- (1)ActivityThread.handleResumeActivity
Activityの起動プロセスに詳しい方はご存知ですが、AMS調ActivityのonResumeからAppプロセスのメインスレッドまで、ActivityのonResumeライフサイクルに関する最初の方法はhandleResumeActivityです.今日はActivityの起動プロセスについてはしばらく議論しませんが、次回は単独でActivityの起動プロセスについて詳しく説明します.この文章はhandleResumeActivityという方法から今日のテーマを始めます.次に、ソースコードを見てみましょう.
1.まず注釈1とそのソースコードを見てみましょう.コードは主な流れだけを貼っています.興味のある友达は自分で詳細を見ることができます.
上記performResumeActivityのソースコードについて説明すると、最後にActivityのonResumeが呼び出されたことがわかります.これにより、注釈1のときにActivityのonResumeメソッドが実行されたことがわかりますが、私たちのインタフェースの描画はまだ始まっていません.私たちが最初に言ったことを証明しました.
2.次に注釈2というwmがいったい何なのか、その具体的な実装クラスが何なのかを見てみましょう.
以上のソースコードから,注釈2のvmが実はWindowManagerImplオブジェクトであることが分かった.注釈1注釈2この2つの前菜はすでにみんなに分析し終わった.次に、トピックActivityページの描画に入ります.
- (2)WindowManagerImpl.addView
前述の解析から,注釈3では実際にWindowManagerImplのaddViewメソッドが呼び出されていることが分かる.次に、この方法が何をしたのか見てみましょう.
注記1は重要なポイントです.ここではViewRootImplオブジェクトを作成します.
このViewRootImplで注目すべき点をいくつか解析してみましょう.まず、ViewRootImplの構成方法を見てみましょう.
ViewRootImplの構造方法では、2つのことに一時的に注目しています.Sessionオブジェクトを取得しました.このオブジェクトは私たちがWindowManagerServiceと対話する橋渡しです.2.Choreographerオブジェクトが作成されました.
このChoreographerオブジェクトが何なのか、なぜそんなに重要なのかを見てみましょう.
ここでは、ViewRootImplの作成とChoreographerの作成について簡単に説明します.16.6 msごとに最下位レベルでjni呼び出しによりmDisplayEventReceiverのonVsyncメソッドが変更されていることが分かりました.このメソッドでは、メンバー変数mHandler(メインスレッドLooperにバインドされたHandler)によって、自分を1つのタスクとしてメインスレッドに送信して実行します.最後にdoFrame()メソッドを呼び出してmCallback Queuesキューのペイントタスクを処理します.最後にActivityページのペイントタスクもこのmCallbackQueuesキューに追加されると推測できます.次に最後に見てみるのも最も重要な一歩です.
- (3)ViewRootImpl.setView
今日は描画の流れについてお話ししますので、注釈1が何を書いたのかを見てみましょう.メソッドの呼び出しフローに従ってソースコードを見てみましょう.
三.まとめ
以上の分析を経て、多くの友达はActivityページの描画プロセスについて一定の理解を持っていると信じています.興味のある友达は自分で私の上の分析の流れに従ってソースコードについていくことができます.簡単なまとめをしましょう.
Activityページはいつ描画されますか?ActivityのonResumeメソッドの実行が完了した後、私たちのActivityページの描画はまだ始まっていません.WindowManagerImplのaddViewメソッドが後で呼び出されると、ViewRootImplオブジェクトが作成され、ViewRootImplオブジェクトが作成されると、Choreographerオブジェクトも作成されます.このChoreographerオブジェクトは、Handlerが受信した最下位から伝わるVsyncイベントに似ています.ViewRootImplオブジェクトが作成されると、Choreographerのメンバー変数mCallback Queuesにペイントタスクを追加するViewRootImplのsetViewメソッドが呼び出されます.下位層が16.6 ms毎にonVsyncメソッドを呼び出すと、タスクの時間に格納された実行したい時間に基づいてペイントタスクが実行されます.また、ペイントタスクの実行はメインスレッドであり、ActivityのonResumeが実行されても、メインスレッドにカートンが表示され、Activityページのペイントタスクが遅延したり、実行されなかったりする可能性があります.
多くの友人は、Activityのページ描画はActivityのonResumeメソッドの実行が完了すると成功して描画されたと考えています.このとき、私たちが書いたページを見ることができると思います.本当にそうなの?実はそうではありません.ActivityのページはActivityのonResumeメソッドが実行されてから描画されます.また、描画が完了した時間は、メインスレッドの当時の状態やレイアウトの階層、CPUメモリと一定の関係があります.これらもActivityがブラックスクリーンを起動する主な方向を分析しています.私たちのActivityがonResumeを終えてユーザーの目の前に表示されるとは思わないでください.はい、くだらない話は多くありません.ソースコードから分析してみましょう.Activityのページの描画はいつですか.
二.ソース解析
- (1)ActivityThread.handleResumeActivity
Activityの起動プロセスに詳しい方はご存知ですが、AMS調ActivityのonResumeからAppプロセスのメインスレッドまで、ActivityのonResumeライフサイクルに関する最初の方法はhandleResumeActivityです.今日はActivityの起動プロセスについてはしばらく議論しませんが、次回は単独でActivityの起動プロセスについて詳しく説明します.この文章はhandleResumeActivityという方法から今日のテーマを始めます.次に、ソースコードを見てみましょう.
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
......
//1. Activity onResume
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
......
final Activity a = r.activity;
......
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManager.getService().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
2. WindowManagerImpl
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
3. WindowManagerImpl addView
wm.addView(decor, l);
}
......
}
......
}
.......
}
1.まず注釈1とそのソースコードを見てみましょう.コードは主な流れだけを貼っています.興味のある友达は自分で詳細を見ることができます.
@VisibleForTesting
public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
String reason) {
//
.....
.....
final ActivityClientRecord r = mActivities.get(token);
//
.....
.....
// Activity performResume
r.activity.performResume(r.startsNotResumed, reason);
return r;
}
final void performResume(boolean followedByPause, String reason) {
//
....
....
// Instrumentation callActivityOnResume
mInstrumentation.callActivityOnResume(this);
//
....
....
}
public void callActivityOnResume(Activity activity) {
activity.mResumed = true;
// Activity onResume
activity.onResume();
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i
上記performResumeActivityのソースコードについて説明すると、最後にActivityのonResumeが呼び出されたことがわかります.これにより、注釈1のときにActivityのonResumeメソッドが実行されたことがわかりますが、私たちのインタフェースの描画はまだ始まっていません.私たちが最初に言ったことを証明しました.
2.次に注釈2というwmがいったい何なのか、その具体的な実装クラスが何なのかを見てみましょう.
//Activity getWindowManager
public WindowManager getWindowManager() {
return mWindowManager;
}
//mWindowManager Activity attach
// handleResumeActivity
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) {
// setWindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
// Window getWindowManager
mWindowManager = mWindow.getWindowManager();
}
//Window setWindowManager
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
.......
.......
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// WindowManagerImpl createLocalWindowManager mWindowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
//Window getWindowManager
public WindowManager getWindowManager() {
return mWindowManager;
}
//WindowManagerImpl createLocalWindowManager WindowManagerImpl
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
以上のソースコードから,注釈2のvmが実はWindowManagerImplオブジェクトであることが分かった.注釈1注釈2この2つの前菜はすでにみんなに分析し終わった.次に、トピックActivityページの描画に入ります.
- (2)WindowManagerImpl.addView
前述の解析から,注釈3では実際にWindowManagerImplのaddViewメソッドが呼び出されていることが分かる.次に、この方法が何をしたのか見てみましょう.
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// WindowManagerGlobal addView
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
//WindowManagerGlobal addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
......
ViewRootImpl root;
View panelParentView = null;
.....
.....
1. ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
2. ViewRootImpl setView
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
注記1は重要なポイントです.ここではViewRootImplオブジェクトを作成します.
このViewRootImplで注目すべき点をいくつか解析してみましょう.まず、ViewRootImplの構成方法を見てみましょう.
public ViewRootImpl(Context context, Display display) {
// Session
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
//
mThread = Thread.currentThread();
// Choreographer , , Handler
//Android 16.6ms
// Choreographer
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
}
ViewRootImplの構造方法では、2つのことに一時的に注目しています.Sessionオブジェクトを取得しました.このオブジェクトは私たちがWindowManagerServiceと対話する橋渡しです.2.Choreographerオブジェクトが作成されました.
このChoreographerオブジェクトが何なのか、なぜそんなに重要なのかを見てみましょう.
public static Choreographer getInstance() {
return sThreadInstance.get();
}
// Thread local storage for the choreographer.
private static final ThreadLocal sThreadInstance =
new ThreadLocal() {
@Override
protected Choreographer initialValue() {
// Looper
//ViewRootImpl
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
// Choreographer
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// looper looper
// Handler
mHandler = new FrameHandler(looper);
// FrameDisplayEventReceiver
//
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
// b/68769804: For low FPS experiments.
setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
}
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper) {
super(looper);
}
// onVsync ?
// Android 16.6ms , Vsync
// Vsync
//onVsync jni , java
// 16.6ms
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
// Ignore vsync from secondary display.
// This can be problematic because the call to scheduleVsync() is a one-shot.
// We need to ensure that we will still receive the vsync from the primary
// display which is the one we really care about. Ideally we should schedule
// vsync for a particular display.
// At this time Surface Flinger won't send us vsyncs for secondary displays
// but that could change in the future so let's log a message to help us remember
// that we need to fix this.
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
// Handler
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
// doFrame
doFrame(mTimestampNanos, mFrame);
}
}
void doFrame(long frameTimeNanos, int frame) {
.....
.....
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
scheduleVsyncLocked();
return;
}
//
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
......
......
}
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
final long now = System.nanoTime();
// callbackType mCallbackQueues
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
synchronized (mLock) {
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
//for
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameTimeNanos);
}
}
}
ここでは、ViewRootImplの作成とChoreographerの作成について簡単に説明します.16.6 msごとに最下位レベルでjni呼び出しによりmDisplayEventReceiverのonVsyncメソッドが変更されていることが分かりました.このメソッドでは、メンバー変数mHandler(メインスレッドLooperにバインドされたHandler)によって、自分を1つのタスクとしてメインスレッドに送信して実行します.最後にdoFrame()メソッドを呼び出してmCallback Queuesキューのペイントタスクを処理します.最後にActivityページのペイントタスクもこのmCallbackQueuesキューに追加されると推測できます.次に最後に見てみるのも最も重要な一歩です.
- (3)ViewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
//1.
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//2. Session WindowManangerService Surface
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
}
}
}
}
今日は描画の流れについてお話ししますので、注釈1が何を書いたのかを見てみましょう.メソッドの呼び出しフローに従ってソースコードを見てみましょう.
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// UI
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// Choreographer postCallback
//mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
.....
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
......
......
performTraversals();
......
......
}
//performTraversals performMeasure,performLayout,performDraw
// ,
private void performTraversals() {
....
.....
//
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
.....
.....
//
performLayout(lp, mWidth, mHeight);
...
...
//
performDraw();
}
// Choreographer postCallback
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
//
//
// SystemClock.uptimeMillis()
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//
// Choreographer
// onVsync
//onVsync 16.6ms jni
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
.....
.....
}
}
三.まとめ
以上の分析を経て、多くの友达はActivityページの描画プロセスについて一定の理解を持っていると信じています.興味のある友达は自分で私の上の分析の流れに従ってソースコードについていくことができます.簡単なまとめをしましょう.
Activityページはいつ描画されますか?ActivityのonResumeメソッドの実行が完了した後、私たちのActivityページの描画はまだ始まっていません.WindowManagerImplのaddViewメソッドが後で呼び出されると、ViewRootImplオブジェクトが作成され、ViewRootImplオブジェクトが作成されると、Choreographerオブジェクトも作成されます.このChoreographerオブジェクトは、Handlerが受信した最下位から伝わるVsyncイベントに似ています.ViewRootImplオブジェクトが作成されると、Choreographerのメンバー変数mCallback Queuesにペイントタスクを追加するViewRootImplのsetViewメソッドが呼び出されます.下位層が16.6 ms毎にonVsyncメソッドを呼び出すと、タスクの時間に格納された実行したい時間に基づいてペイントタスクが実行されます.また、ペイントタスクの実行はメインスレッドであり、ActivityのonResumeが実行されても、メインスレッドにカートンが表示され、Activityページのペイントタスクが遅延したり、実行されなかったりする可能性があります.