Android Q深色模式及源码解析,程序员面试防坑宝典

}

}

#define X(T) colorTransformForOp(),

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

constexpr color_transform_fn colorTransformForOp() {

if

// op 变量中是否同时包含 paint 及 palette 属性,若同时包含,则是绘制 Image 或者 VectorDrawable 的指令

// 参考:frameworks/base/libs/hwui/RecordingCanvas.cpp 中各 Op 的定义

constexpr(has_paint && has_palette) {

return [](const void* opRaw, ColorTransform transform) {

const T* op = reinterpret_cast<const T*>(opRaw);

// 关键变色方法,根据 palette 叠加 colorfilter

transformPaint(transform, const_cast<SkPaint*>(&(op->paint)), op->palette);

};

}

else if

// op 变量中是否包含 paint 属性,普通绘制指令

constexpr(has_paint) {

return [](const void* opRaw, ColorTransform transform) {

const T* op = reinterpret_cast<const T*>(opRaw);

// 关键变色方法,对 paint 颜色进行变换

transformPaint(transform, const_cast<SkPaint*>(&(op->paint)));

};

}

else {

// op 变量不包含 paint 属性,返回空

return nullptr;

}

}

static const color_transform_fn color_transform_fns[] = {

// 根据 Flush、Save、DrawImage等不同绘制 op,返回不同的函数指针

colorTransformForOp

};

让我们再一次看看 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

// 对 op 的 paint 进行颜色变换或叠加 colorfilter

fn(op, args…); // to avoid the overhead of a pointless call.

}

ptr += skip;

}

}

我们先来整理下:

  • CanvasContext.mUseForceDark 只会影响 TreeInfo.disableForceDark 的初始化

  • TreeInfo.disableForceDark 若大于 0RenderNode 在执行 handleForceDark就会直接退出

  • handleForceDark 方法里会根据 UsageHint 类型,对所有 op 中的 paint 颜色进行变换,如果是绘制图片,则叠加一个反转的 colorfilter。变换策略有:Dark、Light

接下来让我们来看 paint 和 colorfilter 的变色实现,这部分是交由CanvasTransform中处理:

bool transformPaint(ColorTransform transform, SkPaint* paint) {

applyColorTransform(transform, *paint);

return true;

}

static void applyColorTransform(ColorTransform transform, SkPaint& paint) {

if (transform == ColorTransform::None) return;

//关键代码,对颜色进行转换

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++) {

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

color = transformColor(transform, color);

//将转换后的颜色通过ColorFilter的方式设置到画笔上

paint.setColorFilter(SkColorFilter::MakeModeFilter(color, mode));

}

}

}

接下来我们来看看颜色是如何进行转换的:

static SkColor transformColor(ColorTransform transform, SkColor color) {

switch (transform) {

case ColorTransform::Light: //要求变亮

return makeLight(color);

case ColorTransform::Dark://要求变暗

return makeDark(color);

default:

return color;

}

}

//颜色提亮

static SkColor makeLight(SkColor color) {

//从颜色从rgb转换成Lab模式

Lab lab = sRGBToLab(color);

//对明度进行反转,明度越高,反转后越低

float invertedL = std::min(110 - lab.L, 100.0f);

//反转后的明度高于原明度,则使用反转后的颜色

if (invertedL > lab.L) {

lab.L = invertedL;

return LabToSRGB(lab, SkColorGetA(color));

} else {

//若反转后反而明度更低,起不到提亮效果,则直接返回原颜色

return color;

}

}

//颜色变深

static SkColor makeDark(SkColor color) {

//同上

Lab lab = sRGBToLab(color);

float invertedL = std::min(110 - lab.L, 100.0f);

//若反转后的明度低于原明亮,则使用反转后的颜色

if (invertedL < lab.L) {

lab.L = invertedL;

return LabToSRGB(lab, SkColorGetA(color));

} else {

//若反转后明度更高,则起不到压暗明度的效果,则继续使用原来的颜色

return color;

}

}

可以很清楚的看到,深色模式的变色规则,就是从paint的color中提取出明度,然后根据当前是浅色模式还是深色模式,对明度进行相应的调整,以达到更好的显示效果。

再来看看对图片的变换:

bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) {

// 根据 palette 和 colorfilter 判断图片是亮还是暗的

palette = filterPalette(paint, palette);

bool shouldInvert = false;

if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) {

// 图片本身是亮的,但是要求变暗,反转

shouldInvert = true;

}

if (palette == BitmapPalette::Dark && transform == ColorTransform::Light) {

// 图片本身是暗的,但是要求变亮,反转

shouldInvert = true;

}

if (shouldInvert) {

SkHighContrastConfig config;

config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness;

// 叠加一个亮度反转的 colorfilter

paint->setColorFilter(SkHighContrastFilter::Make(config)->makeComposed(paint->refColorFilter()));

}

return shouldInvert;

}

到这里Theme级别的forceDarkAllowed要讲完了,你看明白了吗?

4.2、View级别

View 级别的 forceDarkAllowed,通过 View 级别 forceDarkAllowed 可以关掉它及它的子 view 的夜间模式开关。因为是View级别,那入口很有可能就在构造方法中。事不宜迟,我们这就去看看是不是这样的。

// frameworks/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:

//这里又回到前面我们分析的native层的RenderNode里面

mRenderNode.setForceDarkAllowed(a.getBoolean(attr, true));

break;

}

}

}

}

Java层的RenderNode的成员函数 setForceDarkAllowed会调用另外一个成员函数nSetAllowForceDark,而nSetAllowForceDark是一个jni函数,由Native层的android_view_RenderNode_setAllowForceDark实现的:

#define SET_AND_DIRTY(prop, val, dirtyFlag) \

(reinterpret_cast<RenderNode*>(renderNodePtr)->mutateStagingProperties().prop(val) \

? (reinterpret_cast<RenderNode*>(renderNodePtr)->setPropertyFieldsDirty(dirtyFlag), true) \
false)

static jboolean android_view_RenderNode_setAllowForceDark(jlong renderNodePtr, jboolean allow) {

return SET_AND_DIRTY(setAllowForceDark, allow, RenderNode::GENERIC);

}

android_view_RenderNode_setAllowForceDark函数通过宏SET_AND_DIRTY来设置RenderNodedark属性。宏SET_AND_DIRTY首先是调用RenderNode的mutateStagingProperties获得一个RenderProperties对象,如下所示:

//frameworks/base/libs/hwui/RenderNode.h

class RenderNode : public VirtualLightRefBase {

public:

RenderProperties& mutateStagingProperties() {

return mStagingProperties;

}

privagte:

RenderProperties mStagingProperties;

};

SET_AND_DIRTY接着再调用获得的RenderProperties对象的成员函数setAllowForceDark设置一个Render Node的AllowForceDark属性,如下所示:

//frameworks/base/libs/hwui/RenderProperties.h

class ANDROID_API RenderProperties {

bool setAllowForceDark(bool allow) {

return RP_SET(mPrimitiveFields.mAllowForceDark, allow);

}

bool getAllowForceDark() const {

return mPrimitiveFields.mAllowForceDark;

}

private:

// Rendering properties

struct PrimitiveFields {

bool mAllowForceDark = true;

} mPrimitiveFields;

RenderProperties对象的成员函数setAllowForceDark通过宏RP_SET来设置一个Render Node的AllowForceDark属性,这个AllowForceDark属性保存在Render Node内部的一个RenderProperties对象的成员变量mPrimitiveFields描述的一个PrimitiveFields对象的成员变量mAllowForceDark中。

如果一个Render Node的allowForceDark属性发生了变化,也就是它之前的allowForceDark值与新设置的allowForceDark值不一样,那么宏RP_SET的返回值就为true。在这种情况下,宏SET_AND_DIRTY就会调用Render Node的成员函数setPropertyFieldsDirty标记它的属性发生了变化,以便后面在渲染该Render Node的Display List时,可以进行相应的处理。

从前面分析的这个AllowForceDark属性设置过程就可以知道,每一个View关联的Render Node在内部通过一个RenderProperties对象保存了它的一些属性。当这些属性发生变化时,不必重新构建View的Display List,而只需要修改上述的RenderProperties对象相应成员变量值即可。通过这种方式,就可以提到应用程序窗口的渲染效率。

好了,回到话题 ,和 Theme 级别的一样,这里仅仅只是设置到mProperties变量中而已,关键是要看哪里使用这个变量,经过查找,我们发现,它的使用同样在 RenderNode 的 prepareTreeImpl 中:

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

// 1. 如果 view 关闭了夜间模式,会在这里让 info.disableForceDark 加 1

// 2. info.disableForceDark 正是 handleForceDark 中关键变量,还记得吗?

// 3. nfo.disableForceDark 大于 0 会让此 RenderNode 跳过夜间模式处理

// 4. 如果 info.disableForceDark 本身已经大于 0 了,view.setForceDarkAllowed(true) 也毫无意义

if (!mProperties.getAllowForceDark()) {

info.disableForceDark++;

}

prepareLayer(info, animatorDirtyMask);

if (info.mode == TreeInfo::MODE_FULL) {

// 这里面会调用 handleForceDark 方法处理夜间模式

pushStagingDisplayListChanges(observer, info);

}

if (mDisplayList) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

一线互联网Android面试题含详解(初级到高级专题)

这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率

如果设置门槛,很多开发者朋友会因此错过这套高级架构资料,错过提升成为架构师的可能。这就失去了我们的初衷;让更多人都能通过高效高质量的学习,提升自己的技术和格局,升职加薪。

最后送给大家一句话,望共勉,永远不要放弃自己的梦想和追求;

img-UQdP8Zgt-1711727347667)]
[外链图片转存中…(img-CPVamnUy-1711727347667)]
[外链图片转存中…(img-wpirXCFG-1711727347668)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-LyeNTvx9-1711727347668)]

最后

一线互联网Android面试题含详解(初级到高级专题)

这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率

[外链图片转存中…(img-3GC0YpLx-1711727347668)]

如果设置门槛,很多开发者朋友会因此错过这套高级架构资料,错过提升成为架构师的可能。这就失去了我们的初衷;让更多人都能通过高效高质量的学习,提升自己的技术和格局,升职加薪。

最后送给大家一句话,望共勉,永远不要放弃自己的梦想和追求;

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值