Android Q 深色模式(Dark Mode)源碼解析

1. 簡介

隨著 Android Q 發(fā)布卖擅,「黑暗模式」或者說是「夜間模式」終于在此版本中得到了支持,官方介紹見:https://developer.android.com/guide/topics/ui/look-and-feel/darktheme坟冲,再看看效果圖:

2019-10-21-17-21-50.png

其實(shí)這個(gè)功能魅族在兩年前就已支持磨镶,不得不說 Android 有點(diǎn)落后了,今天我們就來看看原生是怎么實(shí)現(xiàn)全局夜間模的吧

2. 打開與關(guān)閉

從文檔上我們可以可知健提,打開夜間模式有三個(gè)方法:

  • 設(shè)置 -> 顯示 -> 深色主題背景
  • 下拉通知欄中開啟
  • Pixel 手機(jī)開啟省點(diǎn)模式時(shí)會(huì)自動(dòng)激活夜間模式

3. 如何適配

打開后琳猫,我們會(huì)發(fā)現(xiàn),除原生幾個(gè)應(yīng)用生效外私痹,其他應(yīng)用依然沒有變成深色主題脐嫂,那么應(yīng)用該如何適配呢?官方提供了下面兩種方法:

3.1. 讓應(yīng)用主題繼承 DayNight 主題

<style name="AppTheme" parent="Theme.AppCompat.DayNight">

或者繼承自

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">

繼承后紊遵,如果當(dāng)前開啟了夜間模式账千,系統(tǒng)會(huì)自動(dòng)從 night-qualified 中加載資源,所以應(yīng)用的顏色暗膜、圖標(biāo)等資源應(yīng)盡量避免硬編碼匀奏,而是推薦使用新增 attributes 指向不同的資源,如

?android:attr/textColorPrimary
?attr/colorControlNormal

另外学搜,如果應(yīng)用希望主動(dòng)切換夜間/日間模式娃善,可以通過 AppCompatDelegate.setDefaultNightMode() 接口主動(dòng)切換

3.2. 通過 forceDarkAllowed 啟用

如果應(yīng)用不想自己去適配各種顏色,圖標(biāo)等瑞佩,可以通過在主題中添加 android:forceDarkAllowed="true" 標(biāo)記聚磺,這樣系統(tǒng)在夜間模式時(shí),會(huì)強(qiáng)制改變應(yīng)用顏色炬丸,自動(dòng)進(jìn)行適配(這個(gè)功能也是本文主要探討的)瘫寝。不過如果你的應(yīng)用本身使用的就是 DayNightDark Theme,forceDarkAllowed 是不會(huì)生效的稠炬。

另外焕阿,如果你不希望某個(gè) view 被強(qiáng)制夜間模式處理,則可以給 view 添加 android:forceDarkAllowed="false" 或者 view.setForceDarkAllowed(false)首启,設(shè)置之后捣鲸,即使打開了夜間模式且主題添加了 forceDarkAllowed,該 view 也不會(huì)變深色闽坡。比較重要的一點(diǎn)是栽惶,這個(gè)接口只能關(guān)閉夜間模式愁溜,不能開啟夜間模式,也就是說外厂,如果主題中沒有顯示聲明 forceDarkAllowed冕象,view.setForceDarkAllowed(true) 是沒辦法讓 view 單獨(dú)變深色的。如果 view 關(guān)閉了夜間模式汁蝶,那么它的子 view 也會(huì)強(qiáng)制關(guān)閉夜間模式

總結(jié)如下:

  • 主題若添加 forceDarkAllowed=false渐扮,無論 view 是否開啟 forceDarkAllowed 都不會(huì)打開夜間模式
  • 主題若添加 forceDarkAllowed=true,view 可以通過 forceDarkAllowed 關(guān)閉夜間模式掖棉,一旦關(guān)閉墓律,子 view 的夜間模式也會(huì)被關(guān)閉
  • 如果父 view 或主題設(shè)置了 forceDarkAllowed=false,子 view 無法通過 forceDarkAllowed=true 單獨(dú)打開夜間模式為
  • 若使用的是 DayNightDark Theme 主題幔亥,則所有 forceDarkAllowed 都不生效

4. 實(shí)現(xiàn)原理

通過繼承主題適配夜間模式的原理本質(zhì)是根據(jù) ui mode 加載 night-qualified 下是資源耻讽,這個(gè)并非 Android Q 新增的東西,我們這里不再描述∨撩蓿現(xiàn)在主要來看看 forceDarkAllowed 是如何讓系統(tǒng)變深色的针肥。

既然一切的源頭都是 android:forceDarkAllowed 這個(gè)屬性,那我們就從它入手吧香伴,首先我們要知道慰枕,上面我們說的 android:forceDarkAllowed 其實(shí)是分為兩個(gè)用處,它們分別的定義如下:

frameworks/base/core/res/res/values/attrs.xml

<declare-styleable name="View">
        <!-- <p>Whether or not the force dark feature is allowed to be applied to this View.
            <p>Setting this to false will disable the auto-dark feature on this View draws
            including any descendants.
            <p>Setting this to true will allow this view to be automatically made dark, however
            a value of 'true' will not override any 'false' value in its parent chain nor will
            it prevent any 'false' in any of its children. -->
    <attr name="forceDarkAllowed" format="boolean" />
</declare-styleable>

 <declare-styleable name="Theme">
        <!-- <p>Whether or not the force dark feature is allowed to be applied to this theme.
             <p>Setting this to false will disable the auto-dark feature on everything this
             theme is applied to along with anything drawn by any children of views using
             this theme.
             <p>Setting this to true will allow this view to be automatically made dark, however
             a value of 'true' will not override any 'false' value in its parent chain nor will
             it prevent any 'false' in any of its children. -->
        <attr name="forceDarkAllowed" format="boolean" />
    </declare-styleable>

一個(gè)是 View 級(jí)別的即纲,一個(gè)是 Theme 級(jí)別的具帮。

4.1. Theme 級(jí)別 forceDarkAllowed

從上面的總結(jié)來看,Theme 級(jí)別的開關(guān)優(yōu)先級(jí)是最高的低斋,控制粒度也最大匕坯,我們看看源碼里面使用它的地方

    // frameworks/base/core/java/android/view/ViewRootImpl.java
    private void updateForceDarkMode() {
        // 渲染線程為空,直接返回
        if (mAttachInfo.mThreadedRenderer == null) return;

        // 系統(tǒng)是否打開了黑暗模式
        boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;

        if (useAutoDark) {
            // forceDarkAllowed 默認(rèn)值拔稳,開發(fā)者模式是否打開了強(qiáng)制 smart dark 選項(xiàng)
            boolean forceDarkAllowedDefault =
                    SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
            TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
            // useAutoDark = 使用淺色主題 && 主題中聲明的 forceDarkAllowed 值
            useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
                    && a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
            a.recycle();
        }

        // 關(guān)鍵代碼,設(shè)置是否強(qiáng)制夜間模式
        if (mAttachInfo.mThreadedRenderer.setForceDark(useAutoDark)) {
            // TODO: Don't require regenerating all display lists to apply this setting
            invalidateWorld(mView);
        }
    }

    // frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
    public boolean setForceDark(boolean enable) {
        if (mForceDark != enable) {
            mForceDark = enable;
            // native 代碼锹雏,mNativeProxy 其實(shí)是  RenderThread 代理類的指針
            nSetForceDark(mNativeProxy, enable);
            return true;
        }
        return false;
    }

這段代碼還是比較簡單巴比,判斷系統(tǒng):

  • 是否打開了夜間模式
  • 是否使用淺色主題
  • Theme_forceDarkAllowed 是否為 true

三者同時(shí)為 true 時(shí)才會(huì)設(shè)置夜間模式,而 updateForceDarkMode 調(diào)用的時(shí)機(jī)分別是在 ViewRootImpl#setViewViewRootImpl#updateConfiguration礁遵,也就是初始化和夜間模式切換的時(shí)候都會(huì)調(diào)用轻绞,確保夜間模式能及時(shí)啟用和關(guān)閉。繼續(xù)跟蹤 HardwareRenderer#setForceDark 發(fā)現(xiàn)佣耐,這是一個(gè) native 方法政勃,所以接下來讓我們進(jìn)入 native 世界,nSetForceDark 對(duì)應(yīng)的實(shí)現(xiàn)位于

// frameworks/base/core/jni/android_view_ThreadedRenderer.cpp
static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz,
        jlong proxyPtr, jboolean enable) {
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    proxy->setForceDark(enable);
}

// frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
void RenderProxy::setForceDark(bool enable) {
    mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
}

// frameworks/base/libs/hwui/renderthread/CanvasContext.h
class CanvasContext : public IFrameCallback {
public:
  
    ...
    
    void setForceDark(bool enable) { mUseForceDark = enable; }

    bool useForceDark() {
        return mUseForceDark;
    }

    ...

private:
   
    ...
    // 默認(rèn)關(guān)閉強(qiáng)制夜間模式
    bool mUseForceDark = false;
   
    ...
};

最終就是設(shè)置了一個(gè) CanvasContext 的變量值而已兼砖,什么都還沒有做奸远,那么這個(gè)變量值的作用是什么既棺,什么時(shí)候生效呢?我們進(jìn)一步查看使用的地方:

// frameworks/base/libs/hwui/TreeInfo.cpp
TreeInfo::TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext)
        : mode(mode)
        , prepareTextures(mode == MODE_FULL)
        , canvasContext(canvasContext)
        , damageGenerationId(canvasContext.getFrameNumber())
        // 初始化 TreeInfo 的 disableForceDark 變量懒叛,注意變量值意義的變化丸冕,0 代表打開夜間模式,>0 代表關(guān)閉夜間模式
        , disableForceDark(canvasContext.useForceDark() ? 0 : 1)
        , screenSize(canvasContext.getNextFrameSize()) {}

}

進(jìn)一步看看 disableForceDark 使用的地方

// frameworks/base/libs/hwui/RenderNode.cpp
/**
 * 這個(gè)可以說是核心方法了薛窥,handleForceDark 方法調(diào)用棧如下:
 * - RenderNode#prepareTreeImpl
 * - RenderNode#pushStagingDisplayListChanges
 * - RenderNode#syncDisplayList
 * - RenderNode#handleForceDark
 * 
 * 而 RenderNode#prepareTree 是繪制的必經(jīng)之路胖烛,每一個(gè)節(jié)點(diǎn)都會(huì)走一遍這個(gè)流程
 */
void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
    // 若沒打開強(qiáng)制夜間模式,直接退出
    if (CC_LIKELY(!info || info->disableForceDark)) {
        return;
    }

    // 根據(jù)是否有文字诅迷、是否有子節(jié)點(diǎn)佩番、子節(jié)點(diǎn)數(shù)量等情況,得出當(dāng)前 Node 屬于 Foreground 還是 Background
    auto usage = usageHint();
    const auto& children = mDisplayList->mChildNodes;
    if (mDisplayList->hasText()) {
        usage = UsageHint::Foreground;
    }
    if (usage == UsageHint::Unknown) {
        if (children.size() > 1) {
            usage = UsageHint::Background;
        } else if (children.size() == 1 &&
                children.front().getRenderNode()->usageHint() !=
                        UsageHint::Background) {
            usage = UsageHint::Background;
        }
    }
    if (children.size() > 1) {
        // Crude overlap check
        SkRect drawn = SkRect::MakeEmpty();
        for (auto iter = children.rbegin(); iter != children.rend(); ++iter) {
            const auto& child = iter->getRenderNode();
            // We use stagingProperties here because we haven't yet sync'd the children
            SkRect bounds = SkRect::MakeXYWH(child->stagingProperties().getX(), child->stagingProperties().getY(),
                    child->stagingProperties().getWidth(), child->stagingProperties().getHeight());
            if (bounds.contains(drawn)) {
                // This contains everything drawn after it, so make it a background
                child->setUsageHint(UsageHint::Background);
            }
            drawn.join(bounds);
        }
    }

    // 根據(jù) UsageHint 設(shè)置變色策略:Dark(壓暗)罢杉、Light(提亮)
    mDisplayList->mDisplayList.applyColorTransform(
            usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);
}
// frameworks/base/libs/hwui/RecordingCanvas.cpp
void DisplayListData::applyColorTransform(ColorTransform transform) {
    // transform: Dark 或 Light
    // color_transform_fns 是一個(gè)對(duì)應(yīng)所有繪制指令的函數(shù)指針數(shù)組趟畏,主要是對(duì) op 的 paint 變色或?qū)?bitmap 添加 colorfilter
    this->map(color_transform_fns, transform);
}

template <typename Fn, typename... Args>
inline void DisplayListData::map(const Fn fns[], Args... args) const {
    auto end = fBytes.get() + fUsed;
    // 遍歷當(dāng)前的繪制的 op
    for (const uint8_t* ptr = fBytes.get(); ptr < end;) {
        auto op = (const Op*)ptr;
        auto type = op->type;
        auto skip = op->skip;
        // 根據(jù) type 找到對(duì)應(yīng)的 fn,根據(jù)調(diào)用關(guān)系屑那,我們知道 fns 數(shù)組對(duì)應(yīng) color_transform_fns拱镐,這個(gè)數(shù)組其實(shí)是一個(gè)函數(shù)指針數(shù)組,下面看看定義
        if (auto fn = fns[type]) {  // We replace no-op functions with nullptrs
            // 執(zhí)行 
            fn(op, args...);        // to avoid the overhead of a pointless call.
        }
        ptr += skip;
    }
}

#define X(T) colorTransformForOp<T>(),
static const color_transform_fn color_transform_fns[] = {
        X(Flush)
        X(Save)
        X(Restore)
        X(SaveLayer)
        X(SaveBehind)
        X(Concat)
        X(SetMatrix)
        X(Translate)
        X(ClipPath)
        X(ClipRect)
        X(ClipRRect)
        X(ClipRegion)
        X(DrawPaint)
        X(DrawBehind)
        X(DrawPath)
        X(DrawRect)
        X(DrawRegion)
        X(DrawOval)
        X(DrawArc)
        X(DrawRRect)
        X(DrawDRRect)
        X(DrawAnnotation)
        X(DrawDrawable)
        X(DrawPicture)
        X(DrawImage)
        X(DrawImageNine)
        X(DrawImageRect)
        X(DrawImageLattice)
        X(DrawTextBlob)
        X(DrawPatch)
        X(DrawPoints)
        X(DrawVertices)
        X(DrawAtlas)
        X(DrawShadowRec)
        X(DrawVectorDrawable)
};
#undef X

color_transform_fn 宏定義展開

template <class T>
constexpr color_transform_fn colorTransformForOp() {
    if
        // op 變量中是否同時(shí)包含 paint 及 palette 屬性持际,若同時(shí)包含沃琅,則是繪制 Image 或者 VectorDrawable 的指令
        // 參考:frameworks/base/libs/hwui/RecordingCanvas.cpp 中各 Op 的定義
        constexpr(has_paint<T> && has_palette<T>) {
        
            return [](const void* opRaw, ColorTransform transform) {
                const T* op = reinterpret_cast<const T*>(opRaw);
                // 關(guān)鍵變色方法,根據(jù) palette 疊加 colorfilter
                transformPaint(transform, const_cast<SkPaint*>(&(op->paint)), op->palette);
            };
        }
    else if
        // op 變量中是否包含 paint 屬性蜘欲,普通繪制指令
        constexpr(has_paint<T>) {
            return [](const void* opRaw, ColorTransform transform) {
                const T* op = reinterpret_cast<const T*>(opRaw);
                // 關(guān)鍵變色方法益眉,對(duì) paint 顏色進(jìn)行變換
                transformPaint(transform, const_cast<SkPaint*>(&(op->paint)));
            };
        }
    else {
        // op 變量不包含 paint 屬性,返回空
        return nullptr;
    }
}

static const color_transform_fn color_transform_fns[] = {
        // 根據(jù) Flush姥份、Save郭脂、DrawImage等不同繪制 op,返回不同的函數(shù)指針
        colorTransformForOp<Flush>
        ...
};

讓我們?cè)僖淮慰纯?map 方法

template <typename Fn, typename... Args>
inline void DisplayListData::map(const Fn fns[], Args... args) const {
    auto end = fBytes.get() + fUsed;
    for (const uint8_t* ptr = fBytes.get(); ptr < end;) {
        auto op = (const Op*)ptr;
        auto type = op->type;
        auto skip = op->skip;
        if (auto fn = fns[type]) {  // We replace no-op functions with nullptrs
            // 對(duì) op 的 paint 進(jìn)行顏色變換或疊加 colorfilter
            fn(op, args...);        // to avoid the overhead of a pointless call.
        }
        ptr += skip;
    }
}

貼了一大段代碼澈歉,雖然代碼中已經(jīng)包含了注釋展鸡,但還是可能比較暈,我們先來整理下:

  • CanvasContext.mUseForceDark 只會(huì)影響 TreeInfo.disableForceDark 的初始化
  • TreeInfo.disableForceDark 若大于 0埃难,RenderNode 在執(zhí)行 handleForceDark 就會(huì)直接退出
  • handleForceDark 方法里會(huì)根據(jù) UsageHint 類型莹弊,對(duì)所有 op 中的 paint 顏色進(jìn)行變換,如果是繪制圖片涡尘,則疊加一個(gè)反轉(zhuǎn)的 colorfilter忍弛。變換策略有:Dark、Light

接下來讓我們來看 paint 和 colorfilter 的變色實(shí)現(xiàn)

bool transformPaint(ColorTransform transform, SkPaint* paint) {
    applyColorTransform(transform, *paint);
    return true;
}

static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
    if (transform == ColorTransform::None) return;

    // 對(duì)畫筆顏色進(jìn)行顏色變換
    SkColor newColor = transformColor(transform, paint.getColor());
    paint.setColor(newColor);

    if (paint.getShader()) {
        SkShader::GradientInfo info;
        std::array<SkColor, 10> _colorStorage;
        std::array<SkScalar, _colorStorage.size()> _offsetStorage;
        info.fColorCount = _colorStorage.size();
        info.fColors = _colorStorage.data();
        info.fColorOffsets = _offsetStorage.data();
        SkShader::GradientType type = paint.getShader()->asAGradient(&info);

        if (info.fColorCount <= 10) {
            switch (type) {
                case SkShader::kLinear_GradientType:
                    for (int i = 0; i < info.fColorCount; i++) {
                        // 對(duì) shader 中的顏色進(jìn)行顏色變換
                        info.fColors[i] = transformColor(transform, info.fColors[i]);
                    }
                    paint.setShader(SkGradientShader::MakeLinear(info.fPoint, info.fColors,
                                                                 info.fColorOffsets, info.fColorCount,
                                                                 info.fTileMode, info.fGradientFlags, nullptr));
                    break;
                default:break;
            }

        }
    }

    if (paint.getColorFilter()) {
        SkBlendMode mode;
        SkColor color;
        // TODO: LRU this or something to avoid spamming new color mode filters
        if (paint.getColorFilter()->asColorMode(&color, &mode)) {
            // 對(duì) colorfilter 中的顏色進(jìn)行顏色變換
            color = transformColor(transform, color);
            paint.setColorFilter(SkColorFilter::MakeModeFilter(color, mode));
        }
    }
}

邏輯很簡單考抄,就是對(duì)顏色進(jìn)行變換细疚,進(jìn)一步看看變色邏輯:

// 提亮顏色
static SkColor makeLight(SkColor color) {
    // 轉(zhuǎn)換成 Lab 模式
    Lab lab = sRGBToLab(color);
    // 對(duì)明度進(jìn)行反轉(zhuǎn),明度越高川梅,反轉(zhuǎn)后越低
    float invertedL = std::min(110 - lab.L, 100.0f);
    if (invertedL > lab.L) {
        // 反轉(zhuǎn)后的明度高于原明度疯兼,則使用反轉(zhuǎn)后的明度
        lab.L = invertedL;
        return LabToSRGB(lab, SkColorGetA(color));
    } else {
        return color;
    }
}

// 壓暗顏色
static SkColor makeDark(SkColor color) {
    // 轉(zhuǎn)換成 Lab 模式
    Lab lab = sRGBToLab(color);
    // 對(duì)明度進(jìn)行反轉(zhuǎn)然遏,明度越高,反轉(zhuǎn)后越低
    float invertedL = std::min(110 - lab.L, 100.0f);
    if (invertedL < lab.L) {
        // 反轉(zhuǎn)后的明度低于原明度镇防,則使用反轉(zhuǎn)后的明度
        lab.L = invertedL;
        // 使用 rgb 格式返回
        return LabToSRGB(lab, SkColorGetA(color));
    } else {
        // 直接返回原顏色
        return color;
    }
}

static SkColor transformColor(ColorTransform transform, SkColor color) {
    switch (transform) {
        case ColorTransform::Light:
            return makeLight(color);
        case ColorTransform::Dark:
            return makeDark(color);
        default:
            return color;
    }
}

到此啦鸣,對(duì) paint 的變換結(jié)束,看來無非就是反轉(zhuǎn)明度来氧。

再來看看對(duì)圖片的變換:

bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) {
    // 根據(jù) palette 和 colorfilter 判斷圖片是亮還是暗的
    palette = filterPalette(paint, palette);
    bool shouldInvert = false;
    if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) {
        // 圖片本身是亮的诫给,但是要求變暗,反轉(zhuǎn)
        shouldInvert = true;
    }
    if (palette == BitmapPalette::Dark && transform == ColorTransform::Light) {
        // 圖片本身是暗的啦扬,但是要求變亮中狂,反轉(zhuǎn)
        shouldInvert = true;
    }
    if (shouldInvert) {
        SkHighContrastConfig config;
        config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness;
        // 疊加一個(gè)亮度反轉(zhuǎn)的 colorfilter
        paint->setColorFilter(SkHighContrastFilter::Make(config)->makeComposed(paint->refColorFilter()));
    }
    return shouldInvert;
}

終于,bitmap 的變換也分析完了扑毡,呼~

4.2. View 級(jí)別 forceDarkAllowed

但是胃榕,還沒完呢~~~還記得我們最開始說的,除了 Theme 級(jí)別瞄摊,還有一個(gè) View 級(jí)別的 forceDarkAllowed勋又,通過 View 級(jí)別 forceDarkAllowed 可以關(guān)掉它及它的子 view 的夜間模式開關(guān)。依然從 java 層看下去哈

// rameworks/base/core/java/android/view/View.java
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {

    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
        final int N = a.getIndexCount();
        for (int i = 0; i < N; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.View_forceDarkAllowed:
                    // 注意换帜,這個(gè)默認(rèn)是 true 的
                    mRenderNode.setForceDarkAllowed(a.getBoolean(attr, true));
                    break;
            }
        }
    }
}

// frameworks/base/graphics/java/android/graphics/RenderNode.java
public final class RenderNode {
    public boolean setForceDarkAllowed(boolean allow) {
        // 又是 native 方法
        return nSetAllowForceDark(mNativeRenderNode, allow);
    }
}
// frameworks/base/core/jni/android_view_RenderNode.cpp
static jboolean android_view_RenderNode_setAllowForceDark(jlong renderNodePtr, jboolean allow) {
    return SET_AND_DIRTY(setAllowForceDark, allow, RenderNode::GENERIC);
}

// frameworks/base/libs/hwui/RenderProperties.h
class ANDROID_API RenderProperties {
public:
    bool setAllowForceDark(bool allow) {
        // 設(shè)置到 mPrimitiveFields.mAllowForceDark 變量中
        return RP_SET(mPrimitiveFields.mAllowForceDark, allow);
    }

    bool getAllowForceDark() const {
        return mPrimitiveFields.mAllowForceDark;
    }
}  

和 Theme 級(jí)別的一樣楔壤,僅僅只是設(shè)置到變量中而已,關(guān)鍵是要看哪里使用這個(gè)變量惯驼,經(jīng)過查找蹲嚣,我們發(fā)現(xiàn),它的使用同樣在 RenderNode 的 prepareTreeImpl 中:

void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {

     ...
    // 1. 如果 view 關(guān)閉了夜間模式祟牲,會(huì)在這里讓 info.disableForceDark 加 1
    // 2. info.disableForceDark 正是 handleForceDark 中關(guān)鍵變量隙畜,還記得嗎?
    // 3. nfo.disableForceDark 大于 0 會(huì)讓此 RenderNode 跳過夜間模式處理
    // 4. 如果 info.disableForceDark 本身已經(jīng)大于 0 了说贝,view.setForceDarkAllowed(true) 也毫無意義
    if (!mProperties.getAllowForceDark()) {
        info.disableForceDark++;
    }

    prepareLayer(info, animatorDirtyMask);
    if (info.mode == TreeInfo::MODE_FULL) {
        // 這里面會(huì)調(diào)用 handleForceDark 方法處理夜間模式
        pushStagingDisplayListChanges(observer, info);
    }

    if (mDisplayList) {
        info.out.hasFunctors |= mDisplayList->hasFunctor();
        // 遞歸調(diào)用子 Node 的 prepareTreeImpl 方法
        bool isDirty = mDisplayList->prepareListAndChildren(
                observer, info, childFunctorsNeedLayer,
                [](RenderNode* child, TreeObserver& observer, TreeInfo& info,
                   bool functorsNeedLayer) {
                    child->prepareTreeImpl(observer, info, functorsNeedLayer);
                });
        if (isDirty) {
            damageSelf(info);
        }
    }

    ...
    // 重要议惰,把 info.disableForceDark 恢復(fù)回原來的值,不讓它影響 Tree 中同級(jí)的其他 RenderNode
    // 但是本 RenderNode 的子節(jié)點(diǎn)還是會(huì)受影響的乡恕,這就是為什么父 view 關(guān)閉了夜間模式言询,子 view 也會(huì)受影響的原因
    // 因?yàn)檫€原 info.disableForceDark 操作是在遍歷子節(jié)點(diǎn)之后執(zhí)行的
    if (!mProperties.getAllowForceDark()) {
        info.disableForceDark--;
    }
    ...
}

5. 總結(jié)

本文到目前為止,總算把 Android Q 夜間模式實(shí)現(xiàn)原理梳理了一遍几颜,總的來說實(shí)現(xiàn)不算復(fù)雜,說白了就是把 paint 中的顏色轉(zhuǎn)換一下或者疊加一個(gè) colorfilter讯屈,雖然中間還有關(guān)聯(lián)知識(shí)沒有細(xì)說蛋哭,如 RenderThread、DisplayList涮母、RenderNode 等圖形相關(guān)的概念谆趾,限于文章大小躁愿,請(qǐng)讀者自行了解

另外,由于水平有限沪蓬,難免文中有錯(cuò)漏之處彤钟,若哪里寫的不對(duì),請(qǐng)大家及時(shí)指出跷叉,蟹蟹啦~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市云挟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帖世,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沸枯,死亡現(xiàn)場離奇詭異日矫,居然都是意外死亡绑榴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門彭沼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缔逛,“玉大人,你說我怎么就攤上這事褐奴∮诒校” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵脖旱,是天一觀的道長介蛉。 經(jīng)常有香客問我,道長币旧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任巍虫,我火速辦了婚禮占遥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瓦胎。我一直安慰自己,他們只是感情好担忧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布瓶盛。 她就那樣靜靜地躺著,像睡著了一般示罗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上轧房,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天绍绘,我揣著相機(jī)與錄音陪拘,去河邊找鬼。 笑死左刽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的迄靠。 我是一名探鬼主播喇辽,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼菩咨,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼舅世!你這毒婦竟也來了奇徒?” 一聲冷哼從身側(cè)響起缨硝,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤查辩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后长踊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萍倡,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡列敲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凑术。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片所意。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖泄鹏,靈堂內(nèi)的尸體忽然破棺而出姻檀,到底是詐尸還是另有隱情,我是刑警寧澤胶台,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布杂抽,位于F島的核電站缩麸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吹散,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一八酒、第九天 我趴在偏房一處隱蔽的房頂上張望羞迷。 院中可真熱鬧,春花似錦衔瓮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昼接。三九已至,卻和暖如春慢睡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泪喊。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國打工袒啼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纬纪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓摘仅,卻偏偏與公主長得像问畅,于是被迫代替她去往敵國和親六荒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矾端,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 一秩铆、簡介 鑒于Android Q 適配如火如荼的情況,我們今天也來講講Android Q全新的深色主題背景。不過該...
    _Justin閱讀 3,114評(píng)論 3 11
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程族阅,因...
    小菜c閱讀 6,365評(píng)論 0 17
  • <manifest> 首先膝捞,我們的根標(biāo)簽就是 manifest,有開始標(biāo)簽就有結(jié)束標(biāo)簽鲤遥,所以每個(gè)標(biāo)簽都是成對(duì)出現(xiàn)的...
    IT_xiao小巫閱讀 812評(píng)論 0 2
  • 2017年據(jù)說是找工作的寒冬林艘,作為一個(gè)Android開發(fā)狐援,下面的一些問題可能在面試的時(shí)候遇到哦: 1、java 內(nèi)...
    小相柳閱讀 513評(píng)論 0 4
  • 先說一下這篇文章里面的內(nèi)容:TCP 客戶端, 自定義對(duì)話框, 自定義按鈕, ProgressBar豎直顯示, 重力...
    楊奉武閱讀 3,283評(píng)論 0 3