Android斬首行動(dòng)——應(yīng)用層開發(fā)Framework必知必會(huì)

前言

相信做應(yīng)用層業(yè)務(wù)開發(fā)的同學(xué),都跟我一樣,對(duì)Framework”深惡痛絕“。確實(shí)如此,如果平日里都在做應(yīng)用層的開發(fā),那么基本上我們很少會(huì)去碰Framework的知識(shí)。但生活所迫,面試總是逃不過這一關(guān)的,所以作為一名合格的打工人,我們還是必須得具備一些Framework的基本知識(shí)。

網(wǎng)上有很多文章或淺或深地講了Framework,有很多源碼大家肯定也都看過不止一兩次,但過一段時(shí)間就忘記了。我這篇文章將會(huì)從應(yīng)用層開發(fā)的視角,羅列下我認(rèn)為需要掌握的Framework基本知識(shí)。為了更方便大家去記憶與鞏固,我更多的是從流程上進(jìn)行講解,而不會(huì)對(duì)源碼進(jìn)行非常深入的解讀,文章會(huì)比較長(zhǎng),大家可以當(dāng)做一個(gè)知識(shí)小冊(cè),根據(jù)目錄針對(duì)性地進(jìn)行瀏覽(基于Android 8.0/9.0)。

那話不多說,我們沖!

一、系統(tǒng)啟動(dòng)流程

Zygote進(jìn)程

Zygote進(jìn)程是非常重要的一個(gè)進(jìn)程,它負(fù)責(zé)Android系統(tǒng)中虛擬機(jī)(DVM或者ART)的創(chuàng)建、應(yīng)用進(jìn)程的創(chuàng)建以及SystemServer進(jìn)程的創(chuàng)建。

系統(tǒng)在開機(jī)后會(huì)啟動(dòng)init進(jìn)程,init進(jìn)程進(jìn)而會(huì)fork出Zygote進(jìn)程。Zygote進(jìn)程在啟動(dòng)時(shí),主要做了這么幾件事情:

  1. 創(chuàng)建虛擬機(jī)。當(dāng)Zygote以fork自身的方式去創(chuàng)建應(yīng)用進(jìn)程和SystemServer進(jìn)程時(shí),它們就能拿到虛擬機(jī)的一個(gè)副本。
  2. 作為Socket服務(wù)端監(jiān)聽AMS請(qǐng)求創(chuàng)建進(jìn)程
  3. 啟動(dòng)SystemServer進(jìn)程

SystemServer進(jìn)程

SystemServer進(jìn)程是我們學(xué)習(xí)Framework中最重要的一個(gè)系統(tǒng)級(jí)進(jìn)程,我們熟知的很多服務(wù)(AMS、WMS、PMS等)都屬于它提供的一個(gè)服務(wù),是與我們APP進(jìn)程通信最頻繁的進(jìn)程。

SystemServer進(jìn)程在啟動(dòng)時(shí),主要做了這么幾件事情:

  1. 啟動(dòng)Binder線程池,為進(jìn)程間通信做準(zhǔn)備
  2. 啟動(dòng)各種系統(tǒng)服務(wù),比如AMS、WMS、PMS

Launcher進(jìn)程

Launcher進(jìn)程實(shí)際上就是我們的桌面進(jìn)程,熟悉它的同學(xué)都知道一頁桌面本質(zhì)上就是一個(gè)Grid類型的RecylerView,那它是怎么創(chuàng)建的呢?

這就是系統(tǒng)啟動(dòng)的最后一步,當(dāng)SystemServer進(jìn)程啟動(dòng)了AMS服務(wù)后,AMS會(huì)啟動(dòng)Launcher進(jìn)程。Launcher進(jìn)程通過與PMS服務(wù)通信,獲取到機(jī)器上所有的安裝包信息后,將數(shù)據(jù)(APP圖標(biāo)、APP名稱等)渲染到桌面上。

小結(jié):

系統(tǒng)啟動(dòng)流程可以用下圖概括:

二、應(yīng)用進(jìn)程啟動(dòng)流程

前面我們講了SystemServer進(jìn)程與桌面進(jìn)程是如何啟動(dòng)的,等它們啟動(dòng)好了之后,就可以從桌面啟動(dòng)我們的應(yīng)用進(jìn)程了。

在桌面點(diǎn)擊APP圖標(biāo)后,Launcher進(jìn)程將會(huì)向AMS請(qǐng)求創(chuàng)建APP的啟動(dòng)頁面。AMS識(shí)別到APP進(jìn)程不存在之后,就會(huì)用Socket的方式向Zygote進(jìn)程請(qǐng)求創(chuàng)建APP進(jìn)程。Zygote通過fork的方式創(chuàng)建APP進(jìn)程之后,會(huì)反射調(diào)用android.app.ActivityThreadmain()方法,初始化ActivityThread。

ActivityThread,可以理解為管理APP主線程任務(wù)的一個(gè)類。在main()方法中初始化時(shí),我們會(huì)初始化主線程的消息循環(huán),從而接收主線程需要處理的所有消息,保證UI在主線程的渲染(消息機(jī)制后文會(huì)詳細(xì)介紹)。


public static void main(String[] args) {
    ...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);//注釋1

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler(); //注釋2
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();
}

private void attach(boolean system, long startSeq) {
    ...
    if (!system) {
        ...
        final IActivityManager mgr = ActivityManager.getService();
        try {
            mgr.attachApplication(mAppThread, startSeq); // 注釋3
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        ...
    } else {
        ...
        try {
            mInstrumentation = new Instrumentation();
            mInstrumentation.basicInit(this);
            ContextImpl context = ContextImpl.createAppContext(
                    this, getSystemContext().mPackageInfo);
            mInitialApplication = context.mPackageInfo.makeApplication(true, null);
            mInitialApplication.onCreate();
        } catch (Exception e) {
            throw new RuntimeException(
                    "Unable to instantiate Application():" + e.toString(), e);
        }
    }
    ...
}

可以看到注釋2處會(huì)創(chuàng)建主線程Handler,這里的Handler類名為H,屬于ActivityThread的內(nèi)部類。后面會(huì)多次提到它。

注釋1處調(diào)用attach方法時(shí),傳入的system參數(shù)為false,所以是會(huì)到注釋3處的代碼的,這里又回到了AMS,調(diào)用AMS的attachApplication方法。attachApplication方法又會(huì)一直調(diào)用到ApplicationThreadbindApplication方法,最終一直到ActivityThread.handleBindApplication方法:

private void handleBindApplication(AppBindData data) {
    ...
    try {
        final ClassLoader cl = instrContext.getClassLoader();
        // 注釋1
        mInstrumentation = (Instrumentation)
            cl.loadClass(data.instrumentationName.getClassName()).newInstance();
    } catch (Exception e) {
        throw new RuntimeException(
            "Unable to instantiate instrumentation "
            + data.instrumentationName + ": " + e.toString(), e);
    }

    final ComponentName component = new ComponentName(ii.packageName, ii.name);
    mInstrumentation.init(this, instrContext, appContext, component,
            data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
    ...
    
    try {
        ...
        try {
            // 注釋2
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            ...
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    // 注釋3
                    installContentProviders(app, data.providers);
                    ...
                }
            }
            // 注釋4
            mInstrumentation.onCreate(data.instrumentationArgs);
        } catch (Exception e) {
            throw new RuntimeException(
                "Exception thrown in onCreate() of "
                + data.instrumentationName + ": " + e.toString(), e);
        }
        try {
            // 注釋5
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            if (!mInstrumentation.onException(app, e)) {
                throw new RuntimeException(
                  "Unable to create application " + app.getClass().getName()
                  + ": " + e.toString(), e);
            }
        }
    }
    ...
}

注釋1處會(huì)初始化Instrumentation,Instrumentation類非常重要,后文會(huì)介紹到。這里主要是用它在注釋2、注釋4、注釋5處創(chuàng)建Application并調(diào)用Application的onCreate生命周期。創(chuàng)建Application時(shí)會(huì)先調(diào)用Application的attachBaseContext方法。另外,注釋3處我們可以看到,隨著應(yīng)用進(jìn)程的啟動(dòng),ContentProviders也會(huì)被啟動(dòng)。

小結(jié):

應(yīng)用進(jìn)程啟動(dòng)流程可以用下圖概括:

三、Activity啟動(dòng)過程

上一節(jié)我們已經(jīng)將APP進(jìn)程成功啟動(dòng),但我們的頁面還沒有起來,這一節(jié)我們就講一下Activity是如何啟動(dòng)的。

讓我們回憶一下,上一節(jié)最開始是Launcher進(jìn)程請(qǐng)求AMS創(chuàng)建APP的啟動(dòng)頁面,那么Launcher進(jìn)程的桌面實(shí)際上也是一個(gè)Activtiy,它啟動(dòng)我們的啟動(dòng)頁面,也是調(diào)用的Activity#startActivity()方法。一層層進(jìn)去后,我們可以發(fā)現(xiàn),實(shí)際上是調(diào)用的InstrumentationexecStartActivity()方法:

#Activity

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
        @Nullable Bundle options) {
    if (mParent == null) { //注釋1
        options = transferSpringboardActivityOptions(options);
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode, options);  //注釋2
        ……
    } else {
        if (options != null) {
            mParent.startActivityFromChild(this, intent, requestCode, options);
        } else {
            // Note we want to go through this method for compatibility with
            // existing applications that may have overridden it.
            mParent.startActivityFromChild(this, intent, requestCode);
        }
    }
}

注釋1處可以看到,mParent代表著上一個(gè)頁面,打開啟動(dòng)頁面時(shí)mParent為null,調(diào)用注釋2處Instrumentation的execStartActivity()方法。 Instrumentation不止執(zhí)行startActivity,它還負(fù)責(zé)了所有Activity生命周期的調(diào)用:

但它也不是真正的執(zhí)行者,它只是包裝了一下,為什么要這樣包裝呢?個(gè)人理解是因?yàn)镮nstrumentation需要對(duì)這些行為加一下監(jiān)控,它的成員變量mActivityMonitors就是這個(gè)作用。

我們?cè)龠M(jìn)去看Instrumentation的execStartActivity()方法:

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    ……
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManager.getService()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, target != null ? target.mEmbeddedID : null,
                    requestCode, 0, null, options); //注釋1
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}

注釋1處實(shí)際上是獲取了ActivityManager.getService()去調(diào)用的startActivity。那這個(gè)ActivityManager.getService()又是何方神圣呢?再往下走

private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);//注釋1
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
        };

注釋1處我們可以看到ActivityManager.getService()最終拿到的就是IActivityManager。這段代碼采用的是AIDL(不了解的同學(xué)可以自行學(xué)習(xí)一下),拿到AMS在APP進(jìn)程的代理對(duì)象IActivityManager。那么最終調(diào)用的startActivity也就是AMS的startActivity了。

AMS在startActivity的過程中也會(huì)進(jìn)行一個(gè)比較長(zhǎng)的鏈路,主要是校驗(yàn)權(quán)限、處理ActivityRecord、Activity任務(wù)棧等,這里不細(xì)說,最終會(huì)調(diào)用到app.thread.scheduleLaunchActivity方法。app.thread實(shí)際上是IApplicationThread,跟之前的IActivityManager一樣,它也是一個(gè)代理對(duì)象,代理的是app進(jìn)程的ApplicationThread。ApplicationThread屬于ActivityThread的內(nèi)部類,我們可以看它的scheduleLaunchActivity方法:

@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);

    ActivityClientRecord r = new ActivityClientRecord(); // 注釋1

    r.token = token;

    r.ident = ident;

    ...

    updatePendingConfiguration(curConfig);

    sendMessage(H.LAUNCH_ACTIVITY, r); // 注釋2

}

注釋1處會(huì)將啟動(dòng)Activity的參數(shù)封裝成ActivityClientRecord。注釋2處發(fā)送消息給H。之所以發(fā)給H是因?yàn)锳pplicationThread本身是一個(gè)Binder對(duì)象,它執(zhí)行scheduleLaunchActivity時(shí)處于Binder線程中,所以我們需要通過H轉(zhuǎn)換到主線程中來。

H接收到LAUNCH_ACTIVITY消息后,會(huì)調(diào)用handleLaunchActivity方法:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {

    ...

    Activity a = performLaunchActivity(r, customIntent);//注釋1

    if (a != null) {

        r.createdConfig = new Configuration(mConfiguration);

        reportSizeConfigurations(r);

        Bundle oldState = r.state;

        handleResumeActivity(r.token, false, r.isForward,//注釋2

        !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

    if (!r.activity.mFinished && r.startsNotResumed) {
        ...

        performPauseActivityIfNeeded(r, reason);//注釋3

    } else {

       ...

    }

}

看注釋2與注釋3處的代碼,可以聯(lián)想到它們必然與Activity生命周期有關(guān),并且這里也解釋了為什么上一個(gè)頁面的onPause生命周期是在下一個(gè)頁面的onResume之后調(diào)用的。注釋1處performLaunchActivity方法很重要,我們來看一下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

...

    try {
        // 注釋1

        java.lang.ClassLoader cl = appContext.getClassLoader();

        activity = mInstrumentation.newActivity(

        cl, component.getClassName(), r.intent);

        ...

    } catch (Exception e) {

        ...

    }

    try {

        Application app = r.packageInfo.makeApplication(false, mInstrumentation); // 注釋2

        ...
        // 注釋3

        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);

        ...

        int theme = r.activityInfo.getThemeResource();

        if (theme != 0) {

            activity.setTheme(theme); // 注釋4

        }

        activity.mCalled = false;
        
        // 注釋5
        if (r.isPersistable()) {

            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);

        } else {

            mInstrumentation.callActivityOnCreate(activity, r.state);

        }

        ...
        // 注釋6

        if (!r.activity.mFinished) {

            activity.performStart();

            r.stopped = false;

        }

        ...
        // 注釋7

        if (r.state != null || r.persistentState != null) {

            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,

        r.persistentState);

        }

        } else if (r.state != null) {

            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);

        }

    }

    ...

}

注釋1處通過反射創(chuàng)建了Activity實(shí)例。注釋2處調(diào)用r.packageInfo.makeApplication創(chuàng)建Application,如果這里Application已經(jīng)在ActivityThread#handleBindApplication()階段創(chuàng)建了,就會(huì)直接返回。

注釋3處調(diào)用了Activity的attach方法,內(nèi)部會(huì)創(chuàng)建PhoneWindow綁定Activity。注釋4設(shè)置Activity的主題,注釋5調(diào)用Activity的onCreate()生命周期,注釋6處調(diào)用onStart()生命周期,注釋7當(dāng)Activity恢復(fù)時(shí)調(diào)用OnRestoreInstanceState()生命周期。再結(jié)合上面有提到的onResume()生命周期,我們Activity的啟動(dòng)過程就已經(jīng)講完了。當(dāng)然,這里走的是啟動(dòng)Activity的鏈路,非啟動(dòng)Activity的邏輯其實(shí)大差不差,大家有興趣可以自行看一下源碼。

小結(jié)

啟動(dòng)頁面啟動(dòng)過程中各進(jìn)程的交互關(guān)系:

啟動(dòng)頁面啟動(dòng)過程可用下圖概括:

四、Context

APP中到底有多少個(gè)Context呢?答案是Activity的數(shù)量+Service的數(shù)量+1,1是指Application。

如上圖所示,ContextImplContextWrapper都繼承于Context,并且Context具體的實(shí)現(xiàn)類是ContextImpl,作為ContextWrapper的成員變量mBase存在。Activity、Service、Application都繼承于ContextWrapper,都屬于Context,但它們都是靠ContextImpl去實(shí)現(xiàn)Context相關(guān)的功能的。

ContextImpl具體的創(chuàng)建時(shí)機(jī)是在Activity、Service、Application創(chuàng)建的時(shí)候,調(diào)用attachBaseContext()方法,具體代碼這邊就不貼了,感興趣的同學(xué)可以自行查閱一下。

Application#getApplicationContext()方法獲取到的是什么呢?

# ContextImpl

@Override
public Context getApplicationContext() {
    return (mPackageInfo != null) ?
            mPackageInfo.getApplication() : mMainThread.getApplication();
}

最終調(diào)用到ContextImpl#getApplicationContext()方法,可以看到最終返回Context的是Application對(duì)象。而fragment#getContext()返回的Context是Activity對(duì)象。

五、View的工作原理

View與Window是相輔相成的兩個(gè)存在,本節(jié)先介紹View,涉及到Window的知識(shí)點(diǎn)可以先不管,下一節(jié)會(huì)詳細(xì)介紹??梢韵劝裌indow理解成畫布,View理解成畫,畫總是要在畫布上才能渲染的,也就是說View必須要借助于Window才能展示出來。

View的繪制流程

每一個(gè)View都會(huì)有一個(gè)ViewRootImpl對(duì)象,View繪制的起點(diǎn)就是ViewRootImpl的perfromTraversals方法,在perfromTraversals內(nèi)部會(huì)調(diào)用measure、layout、draw這三大流程。

measure是為了測(cè)量出View的尺寸大小,measure又會(huì)調(diào)用onMeasure,在onMeasure中又會(huì)先對(duì)子View進(jìn)行測(cè)量,最終得到整個(gè)View的大小。measure過程之后,我們就已經(jīng)可以調(diào)用View#getMeasuredWidth()View#getMeasuredHeight()獲取到View的測(cè)量寬高了。

layout過程決定了View在父容器中的位置與實(shí)際寬高,也即是View的四個(gè)頂點(diǎn)坐標(biāo)的位置。layout與measure相反,layout是先得到父View的位置,再onLayout遞歸下去得到子View的位置的。layout過程后,View#getWidth()View#getHeight()才有值,是View的實(shí)際寬高。

draw是繪制的最后一步,調(diào)用onDraw方法將View繪制在屏幕上。當(dāng)然,子View也是會(huì)一層一層遞歸(通過dispatchDraw方法)調(diào)用onDraw方法往下走的。

Measure

繪制流程中比較重要的個(gè)人感覺是measure方法,所以單拿出來說一說。我們?cè)诳磎easure相關(guān)方法的時(shí)候一定會(huì)看到MeasureSpec這個(gè)參數(shù),它是什么東西呢?

MeasureSpec是一個(gè)32位的int值,高2位代表SpecMode,低30位代表SpecSize。前者表示模式,后者表示大小,用1個(gè)值包含了兩種含義。我們可以通過MeasureSpec#makeMeasureSpec(size, mode)方法合成MeasureSpec,也可以通過MeasureSpec#getMode(spec)MeasureSpec#getSize(spec)獲取到MeasureSpec中的Mode或者Size。只有生成了子View的MeasureSpec,我們才能夠通過調(diào)用子View的measure方法測(cè)量出子View的大小。

那么MeasureSpec是怎么創(chuàng)建的呢?

SpecMode有三類,分別是UNSPECIFIED、EXACTLY、AT_MOST三種。UNSPECIFIED不用管,是系統(tǒng)內(nèi)部使用的。那么什么時(shí)候是EXACTLY,什么時(shí)候是AT_MOST呢?有些同學(xué)可能知道,它的取值跟LayoutParams是有關(guān)系的。但需要注意的是,它不是完全由自身的LayoutParams決定的,LayoutParams需要和父容器一起才能決定View本身的SpecMode。當(dāng)View是DecorView時(shí),MeasureSpec根據(jù)窗口尺寸和自身的LayoutParams就能確定:

# ViewRootImpl

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
        final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
    ...
    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); // 注釋1
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); // 注釋2
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
}

注釋1與注釋2處獲取的就是窗口尺寸與自身LayoutParams的尺寸。當(dāng)頂層View的MeasureSpec確認(rèn)后,在onMeasure方法中就會(huì)對(duì)下一層的View進(jìn)行測(cè)量,獲取子View的MeasureSpec。我們可以看一下ViewGroup的measureChildWithMargins方法,它在很多ViewGroup的onMeasure中都會(huì)被調(diào)用到:

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // 注釋1

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);  // 注釋2
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 注釋3
}

我們可以看到注釋1處首先是獲取到子View自身的LayoutParams。然后再在注釋2處根據(jù)父View的MeasureSpec以及padding、margin,生成了子View的MeasureSpec。最后再通過注釋3處調(diào)用measure測(cè)量出了子View的長(zhǎng)度。那么父View的MeasureSpec傳進(jìn)去有什么用呢?后面代碼有點(diǎn)長(zhǎng),我直接寫結(jié)論:

當(dāng)父View的MeasureMode是EXACTLY時(shí),子View的LayoutParams如果是MATCH_PARENT或者寫死的值,子View的MeasureMode是EXACTLY;子View的LayoutParams如果是WRAP_CONTENT,子View的MeasureMode是AT_MOST。

當(dāng)父View的MeasureMode是AT_MOST時(shí),子View的LayoutParams只有是寫死的值時(shí),子View的MeasureMode才會(huì)是EXACTLY,不然這種情況都是AT_MOST。

當(dāng)父View的MeasureMode是AT_MOST時(shí),子View的LayoutParams只有是寫死的值時(shí),子View的MeasureMode才會(huì)是EXACTLY,不然這種情況都是AT_MOST。` </pre>

有一個(gè)比較容易理解的方法是,AT_MOST通常對(duì)應(yīng)的LayoutParams是WRAP_CONTENT。我們可以想想,父View如果是WRAP_CONTENT,子View即使是MATCH_CONTENT,那子View還不是相當(dāng)于尺寸不確定嗎?所以子View這種情況下的MeasureMode仍然是AT_MOST。只有在尺寸確定的情況下,View的MeasureMode才會(huì)是EXACTLY。如果這里混亂的同學(xué),可以再好好琢磨一下。

至于padding和margin,我們大概想一想就知道肯定是在測(cè)量長(zhǎng)度時(shí)用到的。比如在計(jì)算子View長(zhǎng)度時(shí),肯定是需要把它的padding和margin都去掉才能準(zhǔn)確。

另外,我們觀察View的onMeasure方法,發(fā)現(xiàn)它提供了默認(rèn)的實(shí)現(xiàn):

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

通過setMeasuredDimension(width, height)設(shè)置了View的寬高。但這里的getDefaultSize()是準(zhǔn)確的嗎?我們可以看一下:

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize; // 注釋1
        break;
    }
    return result;
}

注釋1處我們可以看到,當(dāng)SpecMode為AT_MOST時(shí),默認(rèn)直接用的是specSize。所以這里是有問題的,因?yàn)檫@個(gè)specSize代表父View的size,這樣會(huì)造成LayoutParams為WRAP_CONTENT的效果是MATCH_PARENT的效果。所以很多官方的自定義View都是重寫了onMeasure方法,自己去計(jì)算尺寸。我們?cè)趯懽远xView時(shí)也需要特別注意這一點(diǎn)。

而ViewGroup是一個(gè)抽象類,它并沒有實(shí)現(xiàn)View的onMeasure方法,那是因?yàn)槊總€(gè)ViewGroup的布局規(guī)則都不一樣,自然測(cè)量的方式也會(huì)不同,需要各個(gè)子類自己去實(shí)現(xiàn)onMeasure。

layout過程

layout過程只需要知道,layout主要是父容器用來確定容易中子View位置的一個(gè)過程。當(dāng)父容器確定位置后,在父容器的onLayout中會(huì)遍歷其所有子元素調(diào)用它們的layout方法。而layout方法實(shí)際上就是確定四個(gè)頂點(diǎn)坐標(biāo)的位置

我們可以看到onLayoutonMeasure方法類似,View和ViewGroup都沒有實(shí)現(xiàn)它,因?yàn)槊總€(gè)View布局方式不同,需要自己實(shí)現(xiàn)。但因?yàn)樵诖_定坐標(biāo)過程中需要用到長(zhǎng)和寬,所以layout的順序排在measure的后面。

觀察View#getWidth()View#getHeight()方法:

public final int getWidth() {
    return mRight - mLeft;
}

public final int getWidth() {
    return mRight - mLeft;
}

可以看到實(shí)際上它們就是坐標(biāo)之間做了減法,所以這兩個(gè)方法要在onLayout方法之后才能獲取到真正的值。也就是View的實(shí)際寬高。一般情況下measureWidth與width是會(huì)相等的,除非我們特意重寫了layout方法(layout方法與measure方法不一樣,它是可以被重寫的):

public void layout(int l, int t, int r, int b) {
    super.layout(l, t, r+11, b-11)
}

這樣,實(shí)際寬高與測(cè)量寬高就會(huì)不一致了。

自定義View

自定義View一共分為四種類型:

  • 繼承View
    • 這種類型常見于要繪制一些不規(guī)則的圖形,需要重寫它的onDraw方法。需要的注意的是,View的Padding屬性在繪制過程中默認(rèn)是不起作用的,如果要使Padding屬性起作用,就需要自己在onDraw中獲取Padding后繪制。另外,onMeasure時(shí)需要考慮wrap_content和padding的情況,上文也有提到過。
  • 繼承ViewGroup
    • 這種類型比較少,一般用于實(shí)現(xiàn)自定義的布局。那就意味著要自定義布局規(guī)則,也就是自定義onMeasureonLayout。在onMeasure中,也需要處理wrap_content和padding的情況。另外在onMeasureonLayout中,還需要考慮padding與子元素margin共同作用的場(chǎng)景。
  • 繼承某個(gè)特定的View,比如TextView
    • 這種類型一般用于擴(kuò)展已有的某個(gè)View的功能,比較容易實(shí)現(xiàn),不需要重寫onMeasure或者onLayout方法。
  • 繼承某個(gè)特定的ViewGroup,比如FrameLayout
    • 這種類型也很常見,一般用于將幾個(gè)View組合到一起,但相對(duì)第二種方式會(huì)簡(jiǎn)單許多,也不需要重寫onMeasure或者onLayout方法。

在自定義View中還需要額外注意的是,如果View中有額外開線程或者是動(dòng)畫的話,需要在合適的時(shí)機(jī)(比如onDetachedFromWindow生命周期)進(jìn)行回收或者暫停,否則容易引起內(nèi)存泄漏。另外,如果涉及到嵌套滑動(dòng)的話可能還需要處理滑動(dòng)沖突。

六、Window與WindowManager

Window相關(guān)類

Window是一個(gè)抽象類,它的具體實(shí)現(xiàn)是PhoneWindow。PhoneWindowActivity#attach方法中被創(chuàng)建:

#Activity

final void attach(Context context...) {
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback); // 注釋1
    ...
    mWindow.setWindowManager(
        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
        mToken, mComponent.flattenToString(),
        (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); // 注釋2

}

注釋1處初始化了PhoneWindow,注釋2處給Window設(shè)置了WindowManager。WindowManager,顧名思義就是用來管理Window的,它繼承了ViewManager接口:

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

從上面的代碼我們可以看出,管理Window,實(shí)際上就是管理View。context.getSystemService(Context.WINDOW_SERVICE)方法最終獲取的是WindowManager的實(shí)現(xiàn)類WindowManagerImpl,我們可以觀察WindowManagerImpl中的這三個(gè)方法,如addView:

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

可以看到實(shí)際上addView是由mGlobal去實(shí)現(xiàn)的,WindowManagerImpl只是橋接了一下。這個(gè)mGlobalWindowManagerGlobal,是個(gè)單例,全局唯一:

#WindowManagerImpl

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

Window類型

Window類型具體為Window的Type屬性。Type屬性其實(shí)是一個(gè)int指,分為三大類型:

  • 應(yīng)用程序窗口
    • 常見的Activity的Window就屬于應(yīng)用程序窗口,它的int值范圍是1-99。
  • 子窗口
    • 子窗口代表著要依附于其它窗口才能存在,比如PopupWindow就屬于一個(gè)子窗口,它的int值范圍是1000-1999。
  • 系統(tǒng)窗口
    • Toast、音量條、輸入法的窗口就屬于系統(tǒng)窗口,int值范圍是2000-2999。當(dāng)我們要?jiǎng)?chuàng)建一個(gè)系統(tǒng)窗口時(shí),需要申請(qǐng)系統(tǒng)權(quán)限android.permission.SYSTEM_ALERT_WINDOW才可以。

一般來說,type屬性值越大,那么Z-Order的排序就越靠前,窗口越接近用戶。

Window的操作

添加View

我們延續(xù)上方的addView代碼,看一下這里面的過程:

# WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params); // 注釋1
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
    // Only use the default token if we don't have a parent window.
    if (mDefaultToken != null && mParentWindow == null) {
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        // Only use the default token if we don't already have a token.
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (wparams.token == null) {
            wparams.token = mDefaultToken;
        }
    }
}

# WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...
    root = new ViewRootImpl(view.getContext(), display); // 注釋2

    view.setLayoutParams(wparams);

    // 注釋3
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    // do this last because it fires off messages to start doing things
    try {
        // 注釋4
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
            removeViewLocked(index, true);
        }
        throw e;
    }
}

注釋1調(diào)用了applyDefaultToken方法,將token放在了LayoutParams中。我們可以發(fā)現(xiàn)Window的很多方法中都有這個(gè)token,這個(gè)token到底是什么,有什么作用呢?實(shí)際上這個(gè)token是一個(gè)IBinder對(duì)象,是AMS創(chuàng)建Activity時(shí)就會(huì)創(chuàng)建的,用來唯一標(biāo)識(shí)一個(gè)Activity,創(chuàng)建后WMS也會(huì)拿到一份儲(chǔ)存起來。當(dāng)AMS調(diào)用scheduleLauncherActivity方法轉(zhuǎn)回到APP進(jìn)程時(shí),會(huì)將這個(gè)token傳到APP進(jìn)程中。當(dāng)我們?cè)贏PP進(jìn)程對(duì)Window進(jìn)行操作時(shí),就會(huì)用到這個(gè)token。因?yàn)樽罱KWindow的操作是在WMS,所以我們調(diào)用WMS方法時(shí)會(huì)將這個(gè)token傳過去,WMS就會(huì)比對(duì)這個(gè)token和最開始存儲(chǔ)的token,以此來找到這個(gè)Window是屬于哪個(gè)Activity。、

再繼續(xù)回來,我們看注釋2處WindowManagerGlobal#addView方法中每次都會(huì)new一個(gè)ViewRootImpl對(duì)象。ViewRootImpl我們很熟悉,上一節(jié)有講到,它負(fù)責(zé)View的繪制工作。這里它不僅負(fù)責(zé)View的繪制,還負(fù)責(zé)與最終的WMS進(jìn)行通信。具體可以看到注釋4處調(diào)用了ViewRootImpl#setView方法,內(nèi)部再調(diào)用IWindowSession#addToDisplay方法。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
        getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
    ...
}

IWindowSession實(shí)際上是WMS的Session對(duì)象在APP進(jìn)程的代理,這樣我們的邏輯就到了WMS那邊了,WMS會(huì)完成剩下的addView操作,包括為添加的窗口分配Surface,確定窗口顯示次序,最后將Surface交給SurfaceFlinger處理,合成到屏幕上進(jìn)行顯示。并且,addToDisplay方法的第一個(gè)參數(shù)mWindow,是ViewRootImpl的內(nèi)部類W,它是app進(jìn)程的Binder實(shí)現(xiàn)類,WMS可以通過它調(diào)用app進(jìn)程的方法。

另外我們可以看到WindowManagerGlobal在注釋3處維護(hù)了三個(gè)列表,一個(gè)是View,一個(gè)是ViewRootImpl,一個(gè)是Params布局參數(shù),在更新Window/移除Window時(shí)會(huì)用到。

小結(jié)

添加View的過程可用下圖概括:

更新Window

更新Window的過程與添加的過程是類似的,所經(jīng)過的類關(guān)系一模一樣。主要的區(qū)別在WindowManagerGlobal中需要從列表中獲取到ViewRootImpl,并更新布局參數(shù):

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    ...
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

ViewRootImpl會(huì)調(diào)用scheduleTraversals方法重新繪制頁面,最終在performTraversals方法中調(diào)用IWindowSession#relayout方法更新Window,并重新觸發(fā)View的三大繪制流程。

Activity渲染

在第三節(jié)我們講了Activity的啟動(dòng)過程,但其實(shí)還沒有完全講完,因?yàn)锳ctivity實(shí)際上是沒有渲染出來的。那Activity是怎么渲染出來的呢?當(dāng)然也是靠Window。

當(dāng)界面可與用戶進(jìn)行交互時(shí),AMS會(huì)調(diào)用ActivityThreadhandleResumeActivity方法:

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
    ...
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); // 注釋1
    ...
    if (r.window == null && !a.mFinished && willBeVisible) {
        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) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l); // 注釋2
            } else {
            ...
        }
        ...

注釋1處調(diào)用performResumeActivity方法,內(nèi)部會(huì)觸發(fā)Activity的onResume生命周期。注釋2處用WindowManager#addView方法將decorView繪制到了Window上,這樣整個(gè)Activity就渲染出來了。這也是Activity在onResume生命周期才會(huì)顯示出來的原因。

Dialog、PopupWindow、Toast

最后,還是覺得有必要搞明白Dialog、PopupWindow、Toast這三個(gè)東西到底是什么玩意兒。毫無疑問,它們都是通過Window渲染的,但Dialog屬于應(yīng)用程序窗口,PopupWindow屬于子窗口,Toast屬于系統(tǒng)級(jí)窗口。

Dialog的創(chuàng)建時(shí),跟Activity一樣會(huì)創(chuàng)建一個(gè)PhoneWindow:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean      createContextThemeWrapper) {
    ···
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

    final Window w = new PhoneWindow(mContext); // 注釋1
    mWindow = w;
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    w.setOnWindowSwipeDismissedCallback(() -> {
        if (mCancelable) {
            cancel();
        }
    });
    w.setWindowManager(mWindowManager, null, null);
    w.setGravity(Gravity.CENTER);

    mListenersHandler = new ListenersHandler(this);
}

我們看到注釋1處Dialog初始化時(shí)是創(chuàng)建了自己的Window,所以它不依附于其它Window存在,不屬于子窗口,而是一個(gè)應(yīng)用程序窗口。

Dialog#show()時(shí),就會(huì)用WindowManager將DecorView添加到窗口上,移除時(shí)同樣是將DecorView從窗口上移除。有一個(gè)特殊之處是,普通的dialog的context必須是Activity,否則show時(shí)會(huì)報(bào)錯(cuò),因?yàn)樘砑哟翱跁r(shí)需要校驗(yàn)Activity的token,除非我們把這個(gè)dialog的window設(shè)置成系統(tǒng)窗口,就不需要了。

而PopupWindow為什么就屬于子窗口呢?我們可以查閱PopupWindow的源碼:

# PopupWindow

private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
public PopupWindow(View contentView, int width, int height, boolean focusable) {
    if (contentView != null) {
        mContext = contentView.getContext();
        // 注釋1
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 
    }

    setContentView(contentView);
    setWidth(width);
    setHeight(height);
    setFocusable(focusable);
}

public void showAtLocation(IBinder token, int gravity, int x, int y) {
    ...
    final WindowManager.LayoutParams p = createPopupLayoutParams(token); // 注釋2
    preparePopup(p);

    ...
    invokePopup(p);
}
   
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
    final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
    p.gravity = computeGravity();
    p.flags = computeFlags(p.flags);
    p.type = mWindowLayoutType; // 注釋3
    p.token = token; // 注釋4
    p.softInputMode = mSoftInputMode;
    p.windowAnimations = computeAnimationResource();
    ...
}
    
private void invokePopup(WindowManager.LayoutParams p) {
    ...
    final PopupDecorView decorView = mDecorView;
    decorView.setFitsSystemWindows(mLayoutInsetDecor);

    setLayoutDirectionFromAnchor();

    mWindowManager.addView(decorView, p); // 注釋5
    ...
}

在源碼中,我們可以看到PopupWindow中不會(huì)有任何新建Window的操作,因?yàn)樗蕾嚨氖莿e人的Window。在注釋1處拿到WindowNManager,注釋2處調(diào)用createPopupLayoutParams方法給Window參數(shù)賦值。尤其注意注釋3和注釋4處兩個(gè)字段,type字段即代表了窗口的類型,我們可以看到mWindowLayoutType的值是WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,這就代表了是子窗口。且注釋4處的token,不再是Activity的token了,而是show的時(shí)候傳進(jìn)來的View的token。

Toast也是類似,我們可以看到它的源碼中WindowParams.LayoutParams.type的值是WindowManager.LayoutParams.TYPE_TOAST,對(duì)應(yīng)著系統(tǒng)窗口。具體內(nèi)部機(jī)制這里就不分析了,感興趣的同學(xué)可以自行查閱。

七、Handler消息機(jī)制

Handler消息機(jī)制,最主要的作用就是在多線程的背景下,通過消息機(jī)制,可以從子線程切換到主線程進(jìn)行UI的更新操作,并且保證了線程安全。

首先介紹其中的主要成員以及他們之間的關(guān)系

  • Message
    • 消息,可用來存放數(shù)據(jù),通過Message.obtain()可從緩存池中獲取一個(gè)消息對(duì)象。
  • MessageQueue
    • 用于存儲(chǔ)消息的隊(duì)列。
  • Looper
    • loop方法會(huì)持續(xù)監(jiān)聽MessageQueue,當(dāng)MessageQueue中有消息時(shí),將消息拿出來進(jìn)行分發(fā)。
    • 一個(gè)線程只會(huì)有一個(gè)Looper,一個(gè)Looper對(duì)應(yīng)一個(gè)MessageQueue,在Loope創(chuàng)建時(shí),對(duì)應(yīng)的MessageQueue就會(huì)創(chuàng)建。一個(gè)Handler也只能綁定一個(gè)Looper,但是一個(gè)Looper可以綁定多個(gè)Handler,Looper是以線程為單位的
    • Looper線程隔離,是通過Threadlocal實(shí)現(xiàn)的。Looper下有個(gè)靜態(tài)變量sThreadLocal,每個(gè)線程下調(diào)用Looper.myLooper()時(shí)就是從這個(gè)變量中拿,獲取當(dāng)前線程下的Looper。
    • Looper分為子線程Looper與MainLooper。MainLooper在ActivityThread的main方法中就會(huì)通過Looper.prepareMainLooper()方法準(zhǔn)備好。它們的區(qū)別一個(gè)是可以quit的,一個(gè)是不能quit的。所謂的quit就是調(diào)用looper.quit方法,實(shí)際上是在MessageQueue中進(jìn)行quit,將其中的消息給移除。quit又分是否安全quit。safequit下,會(huì)先將消息隊(duì)列中已有的消息先發(fā)完再quit。
  • Handler
    • 用于發(fā)送Message與接收Message,作為Message中的target
  • Message.Callback
    • 當(dāng)Message.callback存在時(shí),Handler將接收不到Message,而是直接回調(diào)到callback中。 Handler在post一個(gè)Runnable時(shí),實(shí)際上就是發(fā)送一個(gè)消息并將這個(gè)Runnable作為這個(gè)消息的callback存在。

那整個(gè)消息通信的流程又是什么樣的呢?我們可以串起來說一下。

  1. 首先通信準(zhǔn)備階段,Looper.prepare()給當(dāng)前線程創(chuàng)建一個(gè)Looper,同時(shí)創(chuàng)建一個(gè)MessageQueue。 new一個(gè)Handler。
  2. handler.post或者handler.sendMessage發(fā)送一個(gè)消息,入隊(duì)到MessageQueue。
  3. Looper將MessageQueue中的隊(duì)列按優(yōu)先級(jí)分發(fā)給Handler。
  4. Handler獲取到Message,并根據(jù)之中的數(shù)據(jù)處理消息。

在寫業(yè)務(wù)的時(shí)候我們常常會(huì)post/send一個(gè)延時(shí)消息,這個(gè)延時(shí)消息是怎么實(shí)現(xiàn)的呢?每個(gè)Message都有一個(gè)when字段,對(duì)應(yīng)著它什么時(shí)候需要被分發(fā)。在入隊(duì)到MessageQueue時(shí),就會(huì)根據(jù)when的先后進(jìn)行排列。當(dāng)loop取消息時(shí),會(huì)判斷這個(gè)消息的分發(fā)時(shí)間是否到了,如果還沒到就先不分發(fā),等時(shí)間到了再分發(fā)。

所以在消息機(jī)制中,并不是發(fā)一個(gè)消息就能夠保證它馬上會(huì)被處理的,因?yàn)闄C(jī)制內(nèi)部就有優(yōu)先級(jí)排列。那怎么能夠在我們需要的時(shí)候提高消息的優(yōu)先級(jí)呢?我們可以調(diào)用postAtFrontOfQueue以及sendMessageAtFrontOfQueue將消息放到隊(duì)頭,其實(shí)是將這個(gè)消息的when設(shè)置為0?;蛘?,使用異步消息與同步屏障。

異步消息與同步屏障

異步消息與同步屏障其實(shí)我們開發(fā)時(shí)很少會(huì)用到,一般都是系統(tǒng)自己使用,同步屏障的接口也是hide的,我們要調(diào)用只能反射調(diào)用。但因?yàn)槊嬖嚭艹R?,這里也順帶提一下。

異步消息的創(chuàng)建我們可以在創(chuàng)建Message時(shí)調(diào)用setAsynchronous方法將Message設(shè)置為異步消息,或者Handler創(chuàng)建時(shí)也可以傳參選擇是否為異步,如果是異步的話,Handler發(fā)送的所有消息都為異步消息。

那什么又是同步屏障呢?同步屏障的本質(zhì)其實(shí)就是特殊的Message,特殊在于這個(gè)Message的Target不是Handler,而是null,以此與其它Message區(qū)分。我們可以通過反射調(diào)用MessageQueue的postSyncBarrier方法發(fā)送一個(gè)同步屏障,以及removeSyncBarrier方法進(jìn)行移除。

我們可以將同步屏障與異步消息結(jié)合,以此來提高異步消息是被優(yōu)先執(zhí)行的。具體原理是MessageQueue在Loop取出Message時(shí),會(huì)判斷Message是否為同步屏障。若為同步屏障,則需要在隊(duì)列中找到第一個(gè)異步消息進(jìn)行優(yōu)先處理,而不是處理排在前面的同步消息:

#MessageQueue

Message next() {
    ...
    synchronized (this) {
        // Try to retrieve the next message.  Return if found.
        final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) {
            // Stalled by a barrier.  Find the next asynchronous message in the queue.
            do {
                prevMsg = msg;
                msg = msg.next;
            } while (msg != null && !msg.isAsynchronous()); // 注釋1
        }
    ...
}

注釋1處當(dāng)發(fā)現(xiàn)msg.target為null時(shí),代表著這個(gè)消息是同步屏障,就會(huì)去拿隊(duì)列中第一個(gè)異步消息。

上文中講到過view的更新操作,其中在最后的繪制階段ViewRootImpl會(huì)通過requestLayout()進(jìn)行布局的更新,requestLayout()內(nèi)部又調(diào)用scheduleTraversals()方法從而重新走一遍三大繪制流程。

# ViewRootImpl

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 注釋1
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //注釋2
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

在這個(gè)方法中我們沒有看到三大繪制流程的觸發(fā),而是在注釋1處發(fā)了一個(gè)同步屏障,阻塞主線程消息隊(duì)列。同時(shí)在注釋2處監(jiān)聽VSYNC信號(hào)。當(dāng)VSYNC信號(hào)來時(shí),Choreographer會(huì)發(fā)一個(gè)異步消息,這個(gè)異步消息在同步屏障的幫助下將會(huì)得到執(zhí)行,并且觸發(fā)監(jiān)聽回調(diào)。監(jiān)聽回調(diào)中ViewRootImpl將移除同步屏障,并調(diào)用performTraversals()執(zhí)行三大繪制流程刷新UI。

總結(jié)

本篇文章一共介紹了七點(diǎn)Framework知識(shí),分別是:系統(tǒng)啟動(dòng)流程、應(yīng)用進(jìn)程啟動(dòng)流程、Activity啟動(dòng)過程、Context、View的工作原理、Window與WindowManager、Handler消息機(jī)制。這幾點(diǎn)都是筆者目前認(rèn)為Android業(yè)務(wù)開發(fā)需要掌握的知識(shí)點(diǎn),它們有些是面試常問的,有些是平時(shí)開發(fā)或多或少會(huì)接觸到的,甚至我們做Hook與插件化時(shí)都會(huì)用到里面的知識(shí)(后面會(huì)專門出一篇文章講Hook與插件化)。像SystemServer進(jìn)程的代碼本章一律沒提,因?yàn)楣P者自己也不太會(huì),就不獻(xiàn)丑啦,而且平時(shí)的確也沒用到過。當(dāng)然,這篇文章會(huì)隨時(shí)更新,隨時(shí)填補(bǔ)知識(shí)的空白,哈哈~希望以上內(nèi)容可以幫到大家!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容