2024年Android Q深色模式及源码解析,面试时需要准备的内容

最后

总而言之,Android开发行业变化太快,作为技术人员就要保持终生学习的态度,让学习力成为核心竞争力,所谓“活到老学到老”只有不断的学习,不断的提升自己,才能跟紧行业的步伐,才能不被时代所淘汰。

在这里我分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

4.1、Theme级别

熟悉View树的构造原理的同学应该都知道,ViewRootImpl是View中的最高层级,属于所有View的根,所以该级别,我们需要在ViewRootImpl中查找原因,寻寻觅觅,最终在updateForceDarkMode函数中找到关于forceDarkAllowed属性的踪影

//frameworks/base/core/java/android/view/ViewRootImpl.java

private void updateForceDarkMode() {

//渲染线程为空,直接返回

if (mAttachInfo.mThreadedRenderer == null) return;

//判断当前uimode是否开启深色模式

boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;

if (useAutoDark) {

//读取开发者选项中强制smart dark的值

boolean forceDarkAllowedDefault =

SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);

TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);

//读取Theme是浅色主题或深色主题,并且配置了forceDarkAllowed=true,

useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)

&& a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);

a.recycle();

}

//是否强制使用深色模式

if (mAttachInfo.mThreadedRenderer.setForceDark(useAutoDark)) {

// TODO: Don’t require regenerating all display lists to apply this setting

invalidateWorld(mView);

}

}

updateForceDarkMode 调用的时机分别是在 ViewRootImpl#setView 和 ViewRootImpl#updateConfiguration,也就是DecorView初始化和uimode切换的时候调用,确保在设置中切换深色模式时,能通知ViewRootImpl进行界面刷新。

我们继续跟踪 下HardwareRenderer#setForceDark 函数

//frameworks/base/graphics/java/android/graphics/HardwareRenderer.java

public boolean setForceDark(boolean enable) {

//当forceDark值发生变化才会进入下面逻辑,否则返回false,无需刷新界面

if (mForceDark != enable) {

mForceDark = enable;

nSetForceDark(mNativeProxy, enable);

return true;

}

return false;

}

最终发现,这是一个 native 方法,nSetForceDark的真正实现是在ThreadedRenderer.cpp

//frameworks/base/core/jni/android_view_ThreadedRenderer.cpp

static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz,

jlong proxyPtr, jboolean enable) {

//将proxyPtr强转成RenderProxy

RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);

proxy->setForceDark(enable);

}

RenderProxy是一个代理类,也是MainThreadRenderThread通信的桥梁。关于MainThreadRenderThread的概念,后面会再单独讲述,这里不作展开。

//frameworks/base/libs/hwui/renderthread/RenderProxy.cpp

void RenderProxy::setForceDark(bool enable) {

mRenderThread.queue().post(this, enable { mContext->setForceDark(enable); });

}

这里从MainThread post了一个调用CanvasContext成员函数setForceDark的任务到RenderThread渲染线程

//frameworks/base/libs/hwui/renderthread/CanvasContext.h

void setForceDark(bool enable) {

mUseForceDark = enable;

}

bool useForceDark() {

return mUseForceDark;

}

发现只是设置了一个mUseForceDark变量而已,并没有看到关键性的调用。我们只能继续再跟一下mUseForceDark这个变量在哪里使用到了。最终发现,是在TreeInfo中被赋值给disableForceDark变量

//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 代表关闭夜间模式

, disableForceDark(canvasContext.useForceDark() ? 0 : 1)

, screenSize(canvasContext.getNextFrameSize()) {}

} // namespace android::uirenderer

而最终disableForceDark是在RenderNode中使用,调用路径为:prepareTree-->prepareTreeImpl-->pushStagingDisplayListChanges-->syncDisplayList-->handleForceDark

而最核心当属handleForceDark函数:

//frameworks/base/libs/hwui/RenderNode.cpp

void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {

// // 若没打开强制夜间模式,直接退出

if (CC_LIKELY(!info || info->disableForceDark)) {

return;

}

auto usage = usageHint();

const auto& children = mDisplayList->mChildNodes;

//根据是否有文字、是否有子节点、子节点数量等情况,得出当前 Node 属于 Foreground 还是 Background

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

}

}

//根据 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 若大于 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,

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-nVGFb2dY-1715580823518)]

【算法合集】

[外链图片转存中…(img-d64sZLZK-1715580823519)]

【延伸Android必备知识点】

[外链图片转存中…(img-9g6jFWEq-1715580823519)]

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值