Activityページの描画プロセス

19845 ワード

一.引用する
多くの友人は、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ページのペイントタスクが遅延したり、実行されなかったりする可能性があります.