靈魂畫師,Android繪製流程——Android高級UI

目錄java

1、前言面試

2、咱們的目標是啥canvas

3、繪製流程從何而起windows

4、Activity 的界面結構在哪裏開始造成app

5、繪製流程如何運轉起來的ide

6、實戰函數

7、寫在最後oop

1、前言

繪製流程能夠說是Android進階中必不可少的一個內容,也是面試中被問得最多的問題之一。這方面優秀的文章也已是很是之多,可是小盆友今天仍是要以本身的姿態來炒一炒這冷飯,或許就是蛋炒飯了😄。話很少說,老規矩先上實戰圖,而後開始分享。 標籤佈局 佈局

2、咱們的目標是啥

其實這篇文章,小盆友糾結了挺久,由於繪製流程涉及的東西很是之多,並不是一篇文章能夠寫完,因此這篇文章我先要肯定一些目標,防止由於追查源碼過深,而迷失於源碼中,最後致使一無所得。咱們的目標是:post

1.繪製流程從何而起 2.Activity 的界面結構在哪裏開始造成 3.繪製流程如何運轉起來

接下來咱們就一個個目標來 conquer。

3、繪製流程從何而起

咱們一說到繪製流程,就會想到或是聽過onMeasure、onLayout、onDraw這三個方法,可是有沒想過爲何咱們開啓一個App或是點開一個Activity,就會觸發這一系列流程呢?想知道繪製流程從何而起,咱們就有必要先解釋 App啓動流程 和 Activity的啓動流程。 咱們都知道 ActivityThread 的 main 是一個App的入口。咱們來到 main 方法看看他作了什麼啓動操做。

ActivityThread 的 main方法是由 ZygoteInit 類中最終經過 RuntimeInit類的invokeStaticMain 方法進行反射調用。有興趣的童鞋能夠自行查閱下,限於篇幅,就再也不展開分享。

// ActivityThread 類
public static void main(String[] args) {
    // ...省略不相關代碼
    
    // 準備主線程的 Looper
    Looper.prepareMainLooper();

    // 實例化 ActivityThread,用於管理應用程序進程中主線程的執行
    ActivityThread thread = new ActivityThread();
    // 進入 attach 方法
    thread.attach(false);
    
    // ...省略不相關代碼
    
    // 開啓 Looper
    Looper.loop();

    // ...省略不相關代碼

}
複製代碼

進入 main 方法,咱們便看到很熟悉的 Handler機制。在安卓中都是以消息進行驅動,在這裏也不例外,咱們能夠看到先進行 Looper 的準備,在最後開啓 Looper 進行循環獲取消息,用於處理傳到主線程的消息。 這也是爲何咱們在主線程不須要先進行 Looper 的準備和開啓,emmm,有些扯遠了。 回過頭,能夠看到夾雜在中間的 ActivityThread 類的實例化而且調用了 attach 方法。具體代碼以下,咱們接着往下走。

// ActivityThread 類
private void attach(boolean system) {
    // ...省略不相關代碼
    
    // system 此時爲false,進入此分支
    if (!system) {
        // ...省略不相關代碼
        
        // 獲取系統的 AMS 服務的 Proxy,用於向 AMS 進程發送數據
        final IActivityManager mgr = ActivityManager.getService();
        try {
            // 將咱們的 mAppThread 傳遞給 AMS,AMS 即可控制咱們 App 的 Activity
            mgr.attachApplication(mAppThread);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        
        // ...省略不相關代碼
    } else {
        // ...省略不相關代碼
    }

    // ...省略不相關代碼

}

// ActivityManager 類
public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}

// ActivityManager 類
private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                // 在這裏獲取 AMS 的binder
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                // 這裏獲取 AMS 的 proxy,能夠進行發送數據
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
        };
複製代碼

咱們進入attach 方法,方法內主要是經過 ActivityManager 的 getService 方法獲取到了 ActivityManagerService(也就是咱們所說的AMS) 的 Proxy,達到與AMS 進行跨進程通訊的目的。

文中所說的 Proxy 和 Stub,是以系統爲咱們自動生成AIDL時的類名進行類比使用,方便講解。Proxy 表明着發送信息,Stub 表明着接收信息。

在 mgr.attachApplication(mAppThread); 代碼中向 AMS 進程發送信息,攜帶了一個類型爲 ApplicationThread 的 mAppThread 參數。這句代碼的做用,其實就是把 咱們應用的 「控制器」 上交給了 AMS,這樣使得 AMS 可以來控制咱們應用中的Activity的生命週期。爲何這麼說呢?咱們這就有必要來了解下 ApplicationThread 類的結構,其部分代碼以下:

// ActivityThread$ApplicationThread 類
private class ApplicationThread extends IApplicationThread.Stub {
    
    // 省略大量代碼

    @Override
    public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                                             ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                                             CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                                             int procState, Bundle state, PersistableBundle persistentState,
                                             List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                                             boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

        updateProcessState(procState, false);

        // 會將 AMS 發來的信息封裝在 ActivityClientRecord 中,而後發送給 Handler
        ActivityClientRecord r = new ActivityClientRecord();

        r.token = token;
        r.ident = ident;
        r.intent = intent;
        r.referrer = referrer;
        r.voiceInteractor = voiceInteractor;
        r.activityInfo = info;
        r.compatInfo = compatInfo;
        r.state = state;
        r.persistentState = persistentState;

        r.pendingResults = pendingResults;
        r.pendingIntents = pendingNewIntents;

        r.startsNotResumed = notResumed;
        r.isForward = isForward;

        r.profilerInfo = profilerInfo;

        r.overrideConfig = overrideConfig;
        updatePendingConfiguration(curConfig);

        sendMessage(H.LAUNCH_ACTIVITY, r);
    }

    // 省略大量代碼
    
}
複製代碼

從 ApplicationThread 的方法名,咱們會驚奇的發現大多方法名以 scheduleXxxYyyy 的形式命名,並且和咱們熟悉的生命週期都挺接近。上面代碼留下了咱們須要的方法 scheduleLaunchActivity ,它們包含了咱們 Activity 的 onCreate、onStart 和 onResume。

scheduleLaunchActivity 方法會對 AMS 發來的信息封裝在 ActivityClientRecord 類中,最後經過 sendMessage(H.LAUNCH_ACTIVITY, r); 這行代碼將信息以 H.LAUNCH_ACTIVITY 的信息標記發送至咱們主線程中的 Handler。咱們進入主線程的 Handler 實現類 H。具體代碼以下:

// ActivityThread$H 類
private class H extends Handler {
    public static final int LAUNCH_ACTIVITY = 100;
    // 省略大量代碼

    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
                handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            }
            // 省略大量代碼
        }
    }
    
    // 省略大量代碼
}
複製代碼

咱們從上面的代碼能夠知道消息類型爲 LAUNCH_ACTIVITY,則會進入 handleLaunchActivity 方法,咱們順着往裏走,來到下面這段代碼

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;

    if (r.profilerInfo != null) {
        mProfiler.setProfiler(r.profilerInfo);
        mProfiler.startProfiling();
    }

    // Make sure we are running with the most recent config.
    handleConfigurationChanged(null, null);

    if (localLOGV) Slog.v(
            TAG, "Handling launch of " + r);

    // Initialize before creating the activity
    WindowManagerGlobal.initialize();
    
    // 得到一個Activity對象,會進行調用 Activity 的 onCreate 和 onStart 的生命週期
    Activity a = performLaunchActivity(r, customIntent);

    // Activity 不爲空進入
    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        Bundle oldState = r.state;
        
        // 該方法最終回調用到 Activity 的 onResume
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        if (!r.activity.mFinished && r.startsNotResumed) {
           
            performPauseActivityIfNeeded(r, reason);

            if (r.isPreHoneycomb()) {
                r.state = oldState;
            }
        }
    } else {
        // If there was an error, for any reason, tell the activity manager to stop us.
        try {
            ActivityManager.getService()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
}
複製代碼

咱們先看這行代碼 performLaunchActivity(r, customIntent); 最終會調用 onCreate 和 onStart 方法。眼見爲實,耳聽爲虛,咱們繼續進入深刻。來到下面這段代碼

// ActivityThread 類
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // 省略不相關代碼

    // 建立 Activity 的 Context
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        // ClassLoader 加載 Activity類,並建立 Activity
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
                
        // 省略不相關代碼
        
    } catch (Exception e) {
       // 省略不相關代碼
    }

    try {
        // 建立 Application
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        // 省略不相關代碼
        
        if (activity != null) {
            // 省略不相關代碼
            
            // 調用了 Activity 的 attach
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback);

            // 這個 intent 就是咱們 getIntent 獲取到的
            if (customIntent != null) {
                activity.mIntent = customIntent;
            }

            // 省略不相關代碼
            
            // 調用 Activity 的 onCreate
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            
            // 省略不相關代碼
           
            if (!r.activity.mFinished) {
                // zincPower 調用 Activity 的 onStart
                activity.performStart();
                r.stopped = false;
            }
            
            if (!r.activity.mFinished) {
               // zincPower 調用 Activity 的 onRestoreInstanceState 方法,數據恢復
               if (r.isPersistable()) {
                   if (r.state != null || r.persistentState != null) {
                       mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                               r.persistentState);
                   }
                } else if (r.state != null) {
                   mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                }
            }
            // 省略不相關代碼
        }
        
        // 省略不相關代碼

    } 

    // 省略不相關代碼


    return activity;
}

// Instrumentation 類
public void callActivityOnCreate(Activity activity, Bundle icicle,
                                 PersistableBundle persistentState) {
    prePerformCreate(activity);
    activity.performCreate(icicle, persistentState);
    postPerformCreate(activity);
}

// Activity 類
final void performCreate(Bundle icicle) {
    restoreHasCurrentPermissionRequest(icicle);
    // 調用了 onCreate
    onCreate(icicle);
    mActivityTransitionState.readState(icicle);
    performCreateCommon();
}

// Activity 類
final void performStart() {
    // 省略不相關代碼
    
    // 進行調用 Activity 的 onStart
    mInstrumentation.callActivityOnStart(this);
    
    // 省略不相關代碼
}

// Instrumentation 類
public void callActivityOnStart(Activity activity) {
    // 調用了 Activity 的 onStart
    activity.onStart();
}
複製代碼

進入 performLaunchActivity 方法後,咱們會發現不少咱們熟悉的東西,小盆友已經給關鍵點打上註釋,由於不是文章的重點就再也不細說,不然篇幅過長。

咱們直接定位到 mInstrumentation.callActivityOnCreate 這行代碼。進入該方法,方法內會調用 activity 的 performCreate 方法,而 performCreate 方法裏會調用到咱們常常重寫的 Activity 生命週期的 onCreate 方法。😄至此,找到了 onCreate 的調用地方,這裏須要立個 FLAG1,由於目標二須要的開啓即是這裏,我下一小節分享,勿急。

回過頭來繼續 performLaunchActivity 方法的執行,會調用到 activity 的 performStart 方法,而該方法又會調用到 mInstrumentation.callActivityOnStart 方法,最後在該方法內便調用了咱們常常重寫的 Activity 生命週期的 onStart 方法。😊至此,找到了 onStart 的調用地方。

找到了兩個生命週期的調用地方,咱們須要折回到 handleLaunchActivity 方法中,繼續往下運行,便會來到 handleResumeActivity 方法,具體代碼以下:

// ActivityThread 類
final void handleResumeActivity(IBinder token,
                                boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    // 省略部分代碼
    
    r = performResumeActivity(token, clearHide, reason);

    // 省略部分代碼

    if (r.window == null && !a.mFinished && willBeVisible) {
        // 將 Activity 中的 Window 賦值給 ActivityClientRecord 的 Window
        r.window = r.activity.getWindow();
        // 獲取 DecorView,這個 DecorView 在 Activity 的 setContentView 時就初始化了
        View decor = r.window.getDecorView();
        // 此時爲不可見
        decor.setVisibility(View.INVISIBLE);

        // WindowManagerImpl 爲 ViewManager 的實現類
        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;
           
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                // 往 WindowManager 添加 DecorView,而且帶上 WindowManager.LayoutParams
                // 這裏面便觸發真正的繪製流程
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
        
    }

    // 省略不相關代碼
}
複製代碼

performResumeActivity方法最終會調用到 Activity 的 onResume 方法,由於不是咱們該小節的目標,就不深刻了,童鞋們能夠自行深刻,代碼也比較簡單。至此咱們就找齊了咱們一直重寫的三個 Acitivity 的生命週期函數 onCreate、onStart 和 onResume 。按照這一套路,童鞋們能夠看看 ApplicationThread 的其餘方法,會發現 Activity 的生命週期均在其中能夠找到影子,也就證明了咱們最開始所說的 咱們將應用 「遙控器」 交給了AMS。而值得一提的是,這一操做是處於一個跨進程的場景。

繼續往下運行來到 wm.addView(decor, l); 這行代碼,wm 的具體實現類爲 WindowManagerImpl,繼續跟蹤深刻,來到下面這一連串的調用

// WindowManagerImpl 類
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    // tag:進入這一行
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

// WindowManagerGlobal 類
public void addView(View view, ViewGroup.LayoutParams params,
                    Display display, Window parentWindow) {
    // 省略不相關代碼  

    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
            
        // 省略不相關代碼

        // 初始化 ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        try {
            // 將 view 和 param 交於 root
            // ViewRootImpl 開始繪製 view
            // tag:進入這一行
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

// ViewRootImpl 類
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            // 省略不相關代碼            
            
            // 進入繪製流程
            // tag:進入這一行
            requestLayout();

            // 省略不相關代碼
        }
    }
}

// ViewRootImpl 類
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        // tag:進入這一行
        scheduleTraversals();
    }
}

// ViewRootImpl 類
void scheduleTraversals() {
   if (!mTraversalScheduled) {
       mTraversalScheduled = true;
       mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
       // 提交給 編舞者,會在下一幀繪製時調用 mTraversalRunnable,運行其run
       mChoreographer.postCallback(
               Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
       if (!mUnbufferedInputDispatch) {
           scheduleConsumeBatchedInput();
       }
       notifyRendererOfFramePending();
       pokeDrawLockIfNeeded();
   }
}
複製代碼

中間跳轉的方法比較多,小盆友都打上了 // tag:進入這一行 註釋,童鞋們能夠自行跟蹤,會發現最後會調用到編舞者,即 Choreographer 類的 postCallback方法。Choreographer 是一個會接收到垂直同步信號的類,因此當下一幀到達時,他會調用咱們剛纔提交的任務,即此處的 mTraversalRunnable,並執行其 run 方法。

值得一提的是經過 Choreographer 的 postCallback 方法提交的任務並非每一幀都會調用,而是隻在下一幀到來時調用,調用完以後就會將該任務移除。簡而言之,就是提交一次就會在下一幀調用一次。

咱們繼續來看 mTraversalRunnable 的具體內容,看看每一幀都作了寫什麼操做。

// ViewRootImpl 類
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

// ViewRootImpl 類
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

// ViewRootImpl 類
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        // 進入此處
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

// ViewRootImpl 類
private void performTraversals() {
    
    // 省略不相關代碼

        if (!mStopped || mReportNextDraw) {
           
           // 省略不相關代碼
                
                // FLAG2
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                // 省略不相關代碼

                // 進行測量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    // 省略不相關代碼
                
        // 進行擺放
        performLayout(lp, mWidth, mHeight);

    // 省略不相關代碼

    // 佈局完回調
    if (triggerGlobalLayoutListener) {
        mAttachInfo.mRecomputeGlobalAttributes = false;
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
    }
    
    // 省略不相關代碼

        // 進行繪製
        performDraw();
    
}
複製代碼

調用了 mTraversalRunnable 的 run 方法以後,會發現也是一連串的方法調用,後來到 performTraversals,這裏面就有咱們一直提到三個繪製流程方法的起源地。這三個起源地就是咱們在上面看到的三個方法 performMeasure、performLayout 、performDraw 。

而這三個方法會進行以下圖的一個調用鏈(😄仍是手繪,勿噴),從代碼咱們也知道,會按照 performMeasure、performLayout 、performDraw 的順序依次調用。

performMeasure 會觸發咱們的測量流程,如圖中所示,進入第一層的 ViewGroup,會調用 �measure 和 onMeasure,在 onMeasure 中調用下一層級,而後下一層級的 View或ViewGroup 會重複這樣的動做,進行全部 View 的測量。(這一過程能夠理解爲書的深度遍歷)

performLayout 和 performMeasure 的流程大同小異,只是方法名不一樣,就再也不贅述。

performDraw 稍微些許不一樣,當前控件爲ViewGroup時,只有須要繪製背景或是咱們經過 setWillNotDraw(false) 設置咱們的ViewGroup須要進行繪製時,會進入 onDraw 方法,而後經過 dispatchDraw 進行繪製子View,如此循環。而若是爲View,天然也就不須要繪製子View,只需繪製自身的內容便可。

至此,繪製流程的源頭咱們便了解清楚了, onMeasure 、 onLayout、onDraw 三個方法咱們會在後面進行詳述並融入在實戰中。

4、Activity 的界面結構在哪裏開始造成

上圖是 Activity 的結構。咱們先進行大體的描述,而後在進入源碼體會這一過程。

咱們能夠清晰的知道一個 Activity 會對應着有一個 Window,而 Window 的惟一實現類爲 PhoneWindow,PhoneWindow 的初始化是在 Activity 的 attach 方法中,咱們前面也有提到 attach 方法,感興趣的童鞋能夠自行深刻。

在往下一層是一個 DecorView,被 PhoneWindow 持有着,DecorView 的初始化在 setContentView 中,這個咱們待會會進行詳細分析。DecorView 是咱們的頂級View,咱們設置的佈局只是其子View。

DecorView 是一個 FrameLayout。但在 setContentView 中,會給他加入一個線性的佈局(LinearLayout)。該線性佈局的子View 則通常由 TitleBar 和 ContentView 進行組成。TitleBar 咱們能夠經過 requestWindowFeature(Window.FEATURE_NO_TITLE); 進行去除,而 ContentView 則是來裝載咱們設置的佈局文件的 ViewGroup 了。

如今咱們已經有一個大概的印象,接下來進行詳細分析。在上一節中(FLAG1處),咱們最早會進入的生命週期爲onCreate,在該方法中咱們都會寫上這樣一句代碼setContentView(R.layout.xxxx) 進行設置佈局。通過上一節咱們也知道,真正的繪製流程是在 onResume 以後(忘記的童鞋請倒回去看一下),那麼 setContentView 起到一個什麼做用呢?我進入源碼一探究竟吧。

進入 Activity 的 setContentView 方法,能夠看到下面這段代碼。getWindow 返回的是一個 Window 類型的對象,而經過Window的官方註釋能夠知道其惟一的實現類爲PhoneWindow, 因此咱們進入 PhoneWindow 類查看其 setContentView 方法,這裏值得咱們注意有兩行代碼。咱們一一進入,咱們先進入 installDecor 方法。

// Activity 類
public void setContentView(@LayoutRes int layoutResID) {
    // getWindow 返回的是 PhoneWindow
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

// Activity 類
public Window getWindow() {
   return mWindow;
}

// PhoneWindow 類
@Override
public void setContentView(int layoutResID) {
    // 此時 mContentParent 爲空,mContentParent 是裝載咱們佈局的容器
    if (mContentParent == null) {
        // 進行初始化 頂級View——DecorView 和 咱們設置的佈局的裝載容器——ViewGroup(mContentParent)
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        // 加載咱們設置的佈局文件 到 mContentParent
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}
複製代碼

installDecor 方法的做用爲初始化了咱們的頂級View(即DecorView)和初始化裝載咱們佈局的容器(即 mContentParent 屬性)。具體代碼以下

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        // 會進行實例化 一個mDecor
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        // 初始化 mContentParent
        mContentParent = generateLayout(mDecor);

       // 省略不相關代碼
}
複製代碼

在 generateDecor 中會進行 DecorView 的建立,具體代碼以下,較爲簡單

protected DecorView generateDecor(int featureId) {
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext().getResources());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
}
複製代碼

緊接着是generateLayout 方法,核心代碼以下,若是咱們在 onCreate 方法前經過requestFeature 進行設置一些特徵,此時的 getLocalFeatures 就會獲取到,並根據其值選擇合適的佈局賦值給 layoutResource 屬性。最後將該佈局資源解析,賦值給 DecorView,緊接着將 DecorView 中 id 爲 content 的控件賦值給 contentParent,而這個控件未來就是裝載咱們設置的佈局資源。

protected ViewGroup generateLayout(DecorView decor) {

    // 省略不相關代碼

    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
        setCloseOnSwipeEnabled(true);
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
        // System.out.println("Title Icons!");
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        // Special case for a window with only a progress bar (and title).
        // XXX Need to have a no-title version of embedded windows.
        layoutResource = R.layout.screen_progress;
        // System.out.println("Progress!");
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        // Special case for a window with a custom title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        // If no other features and not embedded, only need a title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
        // System.out.println("Title!");
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }   

    mDecor.startChanging();
    // 進行加載 DecorView 的佈局
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    // 這裏就獲取了裝載咱們設置的內容容器 id 爲 R.id.content
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    
    // 省略不相關代碼

    return contentParent;
}
複製代碼

咱們折回到 setContentView 方法,來到 mLayoutInflater.inflate(...); 這行代碼,layoutResID 爲咱們設置的佈局文件,而 mContentParent 就是咱們剛剛獲取的id 爲 content 的控件, 這裏即是把他從 xml 文件解析成一棵控件的對象樹,而且放入在 mContentParent 容器內。

至此咱們知道,Activity 的 setContentView 是讓咱們佈局文件從xml 「翻譯」 成對應的控件對象,造成一棵以 DecorView 爲根結點的控件樹,方便咱們後面繪製流程進行遍歷。

5、繪製流程如何運轉起來的

終於來到核心節,咱們來繼續分析第三節最後說到的三個方法onMeasure、onLayout、onDraw,這即是繪製流程運轉起來的最後一道門閥,是咱們自定義控件中可操做的部分。咱們接下來一個個分析

一、onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
複製代碼

要解釋清楚這個方法,咱們須要先說明兩個參數的含義和構成。兩個參數都是 MeasureSpec 的類型

MeasureSpec是什麼

MeasureSpec 是一個 32位的二進制數。高2位爲測量模式,即SpecMode;低30位爲測量數值,即SpecSize。咱們先看下源碼,從源碼中找到這兩個值的含義。

如下是 MeasureSpec 類的代碼(刪除了一些不相關的代碼)

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    // 最終結果爲:11 ...(30位)
    private static final int MODE_MASK = 0x3 << MODE_SHIFT;

    // 父View 不對 子View 施加任何約束。 子View能夠是它想要的任何尺寸。
    // 二進制:00 ...(30位)
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    // 父View 已肯定 子View 的確切大小。子View 的大小即是父View測量所得的值
    // 二進制:01 ...(30位)
    public static final int EXACTLY = 1 << MODE_SHIFT;

    // 父View 指定一個 子View 可用的最大尺寸值,子View大小 不能超過該值。
    // 二進制:10 ...(30位)
    public static final int AT_MOST = 2 << MODE_SHIFT;

    public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                      @MeasureSpecMode int mode) {
        // API 17 以後,sUseBrokenMakeMeasureSpec 就爲 false
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    @MeasureSpecMode
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }

}
複製代碼
(1)測量模式

類中有三個常量: UNSPECIFIED 、EXACTLY 、AT_MOST,他們對應着三種測量模式,具體含義咱們在註釋中已經寫了,小盆友整理出如下表格方便咱們查閱。

image

(2)makeMeasureSpec

makeMeasureSpec 方法,該方法用於合併測量模式和測量尺寸,將這兩個值合爲一個32位的數,高2位爲測量模式,低30位爲尺寸。

該方法很簡短,主要得益於 (size & ~MODE_MASK) | (mode & MODE_MASK) 的位操做符,但也帶來了必定的理解難度。咱們拆解下

  • size & ~MODE_MASK 剔除 size 中的測量模式的值,即將高2位置爲00

  • mode & MODE_MASK 保留傳入的模式參數的值,同時將低30位置爲 0...(30位0)

  • (size & ~MODE_MASK) | (mode & MODE_MASK) 就是 size的低30位 + mode的高2位(總共32位)

至於 &、~、|這三個位操做爲什麼能作到如此的騷操做,請移步小盆友的另外一博文——Android位運算簡單講解。(內容很簡短,不熟悉這塊內容的童鞋,強烈推薦瀏覽一下)

(3)getMode

getMode 方法用於獲取咱們傳入的 measureSpec 值的高2位,即測量模式。

(4)getSize

getSize 方法用於獲取咱們傳入的measureSpec 值的低30位,即測量的值。

解釋完 MeasureSpec 的是什麼,咱們還有兩個問題須要搞清楚:

1.這兩個參數值從哪來 2.這兩個參數值怎麼使用

這兩個參數值從哪來

藉助下面這張簡圖,設定當前運行的 onMeasure 方法處於B控件,則其兩個MeasureSpec值是由其父視圖(即A控件)計算得出,計算的規則ViewGroup 有對應的方法,即 getChildMeasureSpec。

getChildMeasureSpec 的具體代碼以下。咱們繼續使用上面的情景, B中所得到的值,是 A使用自身的MeasureSpec 和 B 的 LayoutParams.width 或 LayoutParams.height 進行計算得出B的MeasureSpec。

// ViewGroup 類
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
        // 父視圖爲肯定的大小的模式
        case MeasureSpec.EXACTLY:
            /**
             * 根據子視圖的大小,進行不一樣模式的組合:
             * 一、childDimension 大於 0,說明子視圖設置了具體的大小
             * 二、childDimension 爲 {@link LayoutParams.MATCH_PARENT},說明大小和其父視圖同樣大
             * 三、childDimension 爲 {@link LayoutParams.WRAP_CONTENT},說明子視圖想爲其本身的大小,但
             * 不能超過其父視圖的大小。
             */
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        // 父視圖已經有一個最大尺寸限制
        case MeasureSpec.AT_MOST:
            /**
             * 根據子視圖的大小,進行不一樣模式的組合:
             * 一、childDimension 大於 0,說明子視圖設置了具體的大小
             * 二、childDimension 爲 {@link LayoutParams.MATCH_PARENT},
             * -----說明大小和其父視圖同樣大,可是此時的父視圖還不能肯定其大小,因此只能讓子視圖不超過本身
             * 三、childDimension 爲 {@link LayoutParams.WRAP_CONTENT},
             * -----說明子視圖想爲其本身的大小,但不能超過其父視圖的大小。
             */
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
複製代碼

咱們將這段代碼整理成表格

image

因此最終,B的 onMeasure 方法得到的兩個值,即是 父視圖A 對 B 所作的約束建議值。

你可能會有一個疑惑, 頂級DecorView 的約束哪裏來,咱們切回 FLAG2 處,在進入 performMeasure 方法時,攜帶的兩個MeasureSpec 是由 WindowManager 傳遞過來的 Window 的 Rect 的寬高 和 Window 的 WindowManager.LayoutParam 共同決定。簡而言之,DecorView的約束從 Window的參數得來。

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

// 進行測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
複製代碼
這兩個參數值怎麼使用

咱們上面一直提到的一個詞叫作 「建議」,是由於到達B的兩個維度(橫縱)的 MeasureSpec,不是就已經決定了控件B的寬高。

這裏咱們能夠類比爲 父母老是語重心長的跟本身的孩子說,你要怎麼作怎麼作(即算出了子View 的 MeasureSpec),懂事的孩子會知道遵從父母的建議可讓本身少走彎路(即遵循傳遞下來的MeasureSpec約束),而調皮一點的孩子,以爲打破常規更加好玩(即無論 MeasureSpec 的規則約束)。

按照約定,咱們是要遵循父View給出的約束。而B控件再進行計算其本身子View的MeasureSpec(若是有子View),子View 會再進行測量 孫View,這樣一層層的測量(這裏能感覺到樹結構的魅力了吧😄)。

B控件完成子View的測量,調用setMeasuredDimension 將自身最終的 測量寬高 進行設置,這樣就完成B控件的測量流程就完畢了。

二、onLayout
protected void onLayout(boolean changed, int l, int t, int r, int b)
複製代碼

onLayout 則是進行擺放,這一過程比較簡單,由於咱們從 onMeasure 中已經獲得各個子View 的寬高。父View 只要按照本身的邏輯負責給定各個子View 的 左上座標 和 右下座標 便可。

三、onDraw
protected void onDraw(Canvas canvas)
複製代碼

繪製流程中,onDraw 應該說是童鞋們最爲熟悉的,只要在 canvas 繪製自身須要繪製的內容即可以。

6、實戰

上一節總結起來,就是咱們在面試時總會說的那句話,onMeasure負責測量、onLayout負責擺放、onDraw負責繪製,但理論老是過於空洞,咱們如今將理論融入到操做中來。咱們用標籤的流式佈局來講明進一步解釋這一切。

一、效果圖

二、編碼思路

在這種標籤流式佈局的情景中,咱們會往控件TagFlowLayout中放入標籤TextView(固然也能夠是更復雜的佈局,這裏爲了方便講清楚思路)。 咱們放入四個標籤,分別爲 「大Android」、「猛猛的小盆友」、「JAVA」、「 PHP是最好的語言」。

咱們藉助這張小盆友手繪的流程圖,來說清楚這繪製流程。

(1) onMeasure

最開始,控件是空的,也就是第一幅小圖。

接着將第一個標籤 「大Android」 放入,此時不超出 TagFlowLayout 的寬,如第二幅小圖所示。

而後將第二個標籤 「猛猛的小盆友」 放入,此時如第三幅小圖所示,超出了 TagFlowLayout 的寬, 因此咱們進行換行,將 「猛猛的小盆友」 放入第二行。

在接着將第三個標籤 「JAVA」 放入,此時不超出 TagFlowLayout 的寬,如第四幅小圖所示。

最後把剩下的 「PHP是最好的語言」 也放入,當此時有個問題,即便一行放一個也容不下(第五幅小圖),由於 「 PHP是最好的語言」 的寬已經超出 TagFlowLayout 的寬,因此咱們在給 「PHP是最好的語言」 測量的MeasureSpec時,須要進行「糾正」,使其寬度爲 TagFlowLayout 的寬,最終造成了第六幅小圖的樣子。

最後還須要將咱們測量的結果經過 setMeasuredDimension 設置咱們自身的 TagFlowLayout 控件的寬高。

(2) onLayout

通過 onMeasure ,TagFlowLayout 心中已經知道本身的 每一個孩子的寬高 和 每一個孩子要「站」在哪一行,但具體的座標仍是須要進行計算。

「大Android」 的標籤比較座標比較容易(咱們這裏討論思路的時候不考慮padding和margin),(l1,t1) 就是 (0,0),而 (r1,b1) 則是 (0+ width, 0+height)。

「猛猛的小盆友」 的座標須要依賴 「大Android」,(l2,t2) 則爲 (0, 第一行的高度) ,(r2,b2) 爲 (自身的Width,第一行的高度+自身的Height)。

「JAVA」 的座標則須要依賴「猛猛的小盆友」 和 「大Android」, (l3,t3) 爲 (「猛猛的小盆友」的Width, 第一行的高度) ,(r3,b3) 爲 (「猛猛的小盆友」的Width + 自身的Width, 第一行的高度+自身的Height)。

「PHP是最好的語言」 須要依賴前兩行的總高度,具體看座標的計算。 (l4,t4) 爲 (0,第一行高+第二行高), (r4,b4) 爲 (自身的Width,第一行高+第二行高+自身的Height)。

(3) onDraw

這個方法在咱們這個控件中不須要,由於繪製的任務是由各個子View負責。確切的說 onDraw 在咱們的 TagFlowLayout 並不會被調用,具體緣由咱們在前面已經說了,這裏就不贅述了。

三、小結

雖然鋪墊了不少,可是 TagFlowLayout 的代碼量並很少,這裏也再也不粘貼出來。咱們只須要在onMeasure 中進行測量,而後將測量的值進行存儲,最後在 onLayout 依賴測量的結果進行擺放便可。

原文連接:www.jianshu.com/p/ec96396a4…

相關文章
相關標籤/搜索