}
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);
}
}
//根据 UsageHint 设置变色策略:Dark(压暗)、Light(提亮)
mDisplayList->mDisplayList.applyColorTransform(
usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);
}
//frameworks/base/libs/hwui/RecordingCanvas.cpp
// frameworks/base/libs/hwui/RecordingCanvas.cpp
void DisplayListData::applyColorTransform(ColorTransform transform) {
// transform: Dark 或 Light
// color_transform_fns 是一个对应所有绘制指令的函数指针数组,主要是对 op 的 paint 变色或对 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;
// 遍历当前的绘制的 op
for (const uint8_t* ptr = fBytes.get(); ptr < end;) {
auto op = (const Op*)ptr;
auto type = op->type;
auto skip = op->skip;
// 根据 type 找到对应的 fn,根据调用关系,我们知道 fns 数组对应 color_transform_fns,这个数组其实是一个函数指针数组,下面看看定义
if (auto fn = fns[type]) { // We replace no-op functions with nullptrs
// 执行
fn(op, args…); // to avoid the overhead of a pointless call.
}
ptr += skip;
}
}
#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
若大于0
,RenderNode
在执行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
来设置RenderNode
的dark
属性。宏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) {
info.out.hasFunctors |= mDisplayList->hasFunctor();
// 递归调用子 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 恢复回原来的值,不让它影响 Tree 中同级的其他 RenderNode
// 但是本 RenderNode 的子节点还是会受影响的,这就是为什么父 view 关闭了夜间模式,子 view 也会受影响的原因
// 因为还原 info.disableForceDark 操作是在遍历子节点之后执行的
if (!mProperties.getAllowForceDark()) {
info.disableForceDark–;
}
…
}
五、总结
Android Q深色模式原理流程并不复杂,主要是对颜色进行了处理和设置了colorFilter
,这个跟魅族的夜间模式有着异曲同工之妙,只不过一个是 Java层,一个是 Native层。而这套逻辑难的部分就在DisplayList
、RenderNode
等图形相关的概念,这部分后续有机会再作展开。
总结
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的Android开发中高级必知必会核心笔记,共计2968页PDF、58w字,囊括Android开发648个知识点,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。
虽然面试失败了,但我也不会放弃入职字节跳动的决心的!建议大家面试之前都要有充分的准备,顺顺利利的拿到自己心仪的offer。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
五、总结
Android Q深色模式原理流程并不复杂,主要是对颜色进行了处理和设置了colorFilter
,这个跟魅族的夜间模式有着异曲同工之妙,只不过一个是 Java层,一个是 Native层。而这套逻辑难的部分就在DisplayList
、RenderNode
等图形相关的概念,这部分后续有机会再作展开。
总结
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的Android开发中高级必知必会核心笔记,共计2968页PDF、58w字,囊括Android开发648个知识点,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
[外链图片转存中…(img-vaHLkl8G-1714288094963)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。
虽然面试失败了,但我也不会放弃入职字节跳动的决心的!建议大家面试之前都要有充分的准备,顺顺利利的拿到自己心仪的offer。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!