2024年Android深色模式适配原理分析,掌握这套精编android高级面试题解析及答案

最后

给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;

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

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

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

lottie_android_animate.addLottieOnCompositionLoadedListener {

lottie_android_animate.resolveKeyPath(KeyPath(“**”)).forEach {

Log.d(TAG, it.keysToString())

}

setupValueCallbacks()

}

对于机器小人打印的KeyPath如下:

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [MasterController]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Head]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Head, Group 3]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Head, Group 3, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 2]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 2, Rectangle Path 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 2, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 1, Rectangle Path 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 1, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Eyes]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Eyes, Group 3]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Eyes, Group 3, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [BeloOutlines]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [BeloOutlines, Group 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [BeloOutlines, Group 1, Stroke 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Shirt]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Shirt, Group 5]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Shirt, Group 5, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Body]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Body, Group 4]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Body, Group 4, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftFoot]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftFoot, Group 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftFoot, Group 1, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightFoot]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightFoot, Group 2]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightFoot, Group 2, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave, LeftArm]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave, LeftArm, Group 6]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave, LeftArm, Group 6, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave, LeftArm, Group 5]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave, LeftArm, Group 5, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightArm]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightArm, Group 6]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightArm, Group 6, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightArm, Group 5]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightArm, Group 5, Fill 1]

我们抽取其中的某些形状来动态改变颜色,例如我们抽取左右手臂以及机器小人身上的T恤

private fun setupValueCallbacks() {

// 机器人右手臂

val rightArm = KeyPath(“RightArm”, “Group 6”, “Fill 1”)

// 机器人左手臂

val leftArm = KeyPath(“LeftArmWave”, “LeftArm”, “Group 6”, “Fill 1”)

// 机器人T恤

val shirt = KeyPath(“Shirt”, “Group 5”, “Fill 1”)

// 设置右手臂颜色

lottie_android_animate.addValueCallback(rightArm, LottieProperty.COLOR) {

ContextCompat.getColor(this, R.color.color_main_1)

}

// 设置左手臂颜色

lottie_android_animate.addValueCallback(shirt, LottieProperty.COLOR) {

ContextCompat.getColor(this, R.color.color_light)

}

// 设置T恤颜色

lottie_android_animate.addValueCallback(leftArm, LottieProperty.COLOR) {

ContextCompat.getColor(this, R.color.color_custom)

}

// 播放动画描边颜色

lottie_playing_animate.addValueCallback(KeyPath(“**”), LottieProperty.STROKE_COLOR) {

ContextCompat.getColor(this, R.color.color_text_0)

}

}

由于color_main_1、color_light以及color_custom都已经定义过深色模式和明亮模式的色值,因此在深色模式切换时,Lottie动画的这个机器小人的左右手臂和T恤颜色会随着深色模式切换而变化。

同样的对于播放动画,我们也可以设置描边颜色,来达到深色模式切换的效果。

网络获取图片

对于网络获取的图片,可以让服务接口分别给出明亮模式和深色模式两套素材,然后根据上述的深色模式判断来进行切换

Glide.with(this)

.load(if(isNightMode() nightImageUrl else imageUrl))

.into(imgView)

Force Dark

看到这里可能会有人有疑问,对于大型的项目而言,里面已经hardcore了很多的颜色值,并且很多图片都没有设计成深色模式的,那做深色模式适配是不是一个不可能完成的任务呢?答案是否定的。对于大型项目而言,除了对所有的颜色和图片定义night资源的自定义适配方法外,我们还可以对使用Light风格主题的页面进行进行强制深色模式转换。

我们可以分别对主题和View设置强制深色模式。对于主题,在Light主题中设置android:forceDarkAllowed,例如:

对于View,设置View.setForceDarkAllowed(boolean))或者xml来设置是否支持Force Dark,默认值是true。

<View

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:forceDarkAllowed=“false”/>

这里需要注意的是,Force Dark的设置有以下几个规则:

  1. 要强制深色模式生效必须开启硬件加速(默认开启)

  2. 主题设置的Force Dark仅对Light的主题有效,对非Light的主题不管是设置android:forceDarkAllowed为true或者设置View.setForceDarkAllowed(true)都是无效的。

  3. 父节点设置了不支持Force Dark,那么子节点再设置支持Force Dark无效。例如主题设置了android:forceDarkAllowed为false,则View设置View.setForceDarkAllowed(true)无效。同样的,如果View本身设置了支持Force Dark,但是其父layout设置了不支持,那么该View不会执行Force Dark

  4. 子节点设置不支持Force Dark不受父节点设置支持Force Dark影响。例如View设置了支持Force Dark,但是其子Layout设置了不支持,那么子Layout也不会执行Force Dark。

Tips:一个比较容易记的规则就是不支持Force Dark优先,View 的 Force Dark设置一般会设置成 false,用于排除某些已经适配了深色模式的 View。

下面我们从源码出发来理解Force Dark的这些行为,以及看看系统是怎么实现Force Dark的。

Tips:善用 https://cs.android.com/ 源码搜索网站可以方便查看系统源码。

1. 主题

从主题设置的forceDarkAllowed入手查找,可以找到

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

private void updateForceDarkMode() {

if (mAttachInfo.mThreadedRenderer == null) return;

// 判断当前是否深色模式

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

// 如果当前是深色模式

if (useAutoDark) {

// 获取Force Dark的系统默认值

boolean forceDarkAllowedDefault =

SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);

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

// 判断主题是否浅色主题 并且 判断主题设置的forceDarkAllowed

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

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

a.recycle();

}

// 将是否强制使用深色模式赋值给Renderer层

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

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

invalidateWorld(mView);

}

}

而这个方法正式在ViewRootImpl.enableHardwareAcceleration()方法中调用的,因此可以得到第一个结论:强制深色模式只在硬件加速下生效。由于userAutoDark变量会判断当前主题是否为浅色,因此可以得到第二个结论:强制深色模式只在浅色主题下生效。直到这一步的调用链如下:

mAttachInfo.mThreadedRenderer为ThreadRenderer,继承自HardwareRenderer,指定了接下来的渲染操作由RanderThread执行。继续跟踪setForceDark()方法:

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

public boolean setForceDark(boolean enable) {

// 如果强制深色模式变化

if (mForceDark != enable) {

mForceDark = enable;

// 调用native层设置强制深色模式逻辑

nSetForceDark(mNativeProxy, enable);

return true;

}

return false;

}

private static native void nSetForceDark(long nativeProxy, boolean enabled);

查找nSetForceDark()方法

frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp

static const JNINativeMethod gMethods[] = {

// …

// 在Android Runtime启动时,通过JNI动态注册

{ “nSetForceDark”, “(JZ)V”, (void*)android_view_ThreadedRenderer_setForceDark },

{ “preload”, “()V”, (void*)android_view_ThreadedRenderer_preload },

};

查找android_view_ThreadedRenderer_setForceDark()方法

frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp

static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz,

jlong proxyPtr, jboolean enable) {

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

// 调用RenderProxy的setForceDark方法

proxy->setForceDark(enable);

}

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

void RenderProxy::setForceDark(bool enable) {

// 调用CanvasContext的setForceDark方法

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

}

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

// Force Dark的默认值是false

bool mUseForceDark = false;

// 设置mUseForceDark标志

void setForceDark(bool enable) { mUseForceDark = enable; }

bool useForceDark() {

return mUseForceDark;

}

接着查找调用userForceDark()方法的地方

frameworks/base/libs/hwui/TreeInfo.cpp

TreeInfo::TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext)
mode(mode)

, prepareTextures(mode == MODE_FULL)

, canvasContext(canvasContext)

// 设置disableForceDark变量

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

, screenSize(canvasContext.getNextFrameSize()) {}

} // namespace android::uirenderer

frameworks/base/libs/hwui/TreeInfo.h

class TreeInfo {

public:

// …

int disableForceDark;

// …

};

到了这里,可以看出,当设置了Force Dark之后,最终会设置到TreeInfo类中的disableForceDark变量,如果没有设置主题的Force Dark,那么根据false的默认值,disableForceDark变量会别设置成1,如果设置了使用强制深色模式,那么disableForceDark会变成0。

这个变量最终会用在RenderNode的RenderNode.handleForceDark()过程中,到达的流程如下图:

frameworks/base/libs/hwui/RenderNode.cpp

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

// …

// 同步正在处理的RenderNode Property变化

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

pushStagingPropertiesChanges(info);

}

// 如果当前View不允许被ForceDark,那么info.disableForceDark值+1

if (!mProperties.getAllowForceDark()) {

info.disableForceDark++;

}

// …

// 同步正在处理的Render Node的Display List,实现具体深色的逻辑

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

pushStagingDisplayListChanges(observer, info);

}

if (mDisplayList) {

info.out.hasFunctors |= mDisplayList->hasFunctor();

bool isDirty = mDisplayList->prepareListAndChildren(

observer, info, childFunctorsNeedLayer,

[](RenderNode* child, TreeObserver& observer, TreeInfo& info,

bool functorsNeedLayer) {

// 递归调用子节点的prepareTreeImpl。

// 递归调用之前,若父节点不允许强制深色模式,disableForceDark已经不为0,

// 子节点再设置允许强制深色模式不会使得disableForceDark的值减少,

// 因此有第三个规则:父节点设置了不允许深色模式,子节点再设置允许深色模式无效。

// 同样的,递归调用之前,若父节点允许深色模式,disableForceDark为0,

// 子节点再设置不允许强制深色模式,则disableForceDark值还是会++,不为0

// 因此有第四个规则:子节点设置不允许强制深色模式不受父节点设置允许强制深色模式影响。

child->prepareTreeImpl(observer, info, functorsNeedLayer);

});

if (isDirty) {

damageSelf(info);

}

}

pushLayerUpdate(info);

// 递归结束后将之前设置过+1的值做回退-1恢复操作,避免影响其他兄弟结点的深色模式值判断

if (!mProperties.getAllowForceDark()) {

info.disableForceDark–;

}

info.damageAccumulator->popTransform();

}

void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {

// …

// 同步DisplayList

syncDisplayList(observer, &info);

// …

}

void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {

// …

if (mDisplayList) {

WebViewSyncData syncData {

// 设置WebViewSyncData的applyForceDark

.applyForceDark = info && !info->disableForceDark

};

mDisplayList->syncContents(syncData);

// 强制执行深色模式执行

handleForceDark(info);

}

}

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

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

// 如果disableForceDark不为0,关闭强制深色模式,则直接返回

return;

}

auto usage = usageHint();

const auto& children = mDisplayList->mChildNodes;

// 如果有文字表示是前景策略

if (mDisplayList->hasText()) {

usage = UsageHint::Foreground;

}

if (usage == UsageHint::Unknown) {

// 如果子节点大于1或者第一个子节点不是背景,那么设置为背景策略

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

}

}

// 根据前景还是背景策略对颜色进行提亮或者加深

mDisplayList->mDisplayList.applyColorTransform(

usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);

}

Tips:View的绘制会根据VSYNC信号,将UI线程的Display List树同步到Render线程的Display List树,并通过生产者消费者模式将layout信息放置到SurfaceFlinger中,并最后交给Haredware Composer进行合成绘制。具体View渲染逻辑见参考章节的15~19文章列表。

frameworks/base/libs/hwui/RecordingCanvas.cpp

void DisplayListData::applyColorTransform(ColorTransform transform) {

// 使用transform作为参数执行color_transform_fns函数组

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,并调用对应类型的colorTransformForOp函数

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

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

}

ptr += skip;

}

}

typedef void (color_transform_fn)(const void, ColorTransform);

#define X(T) colorTransformForOp(),

static const color_transform_fn color_transform_fns[] = {

// 相当于 colorTransformForOp()

X(Flush)

X(Save)

X(Restore)

X(SaveLayer)

X(SaveBehind)

X(Concat44)

X(Concat)

X(SetMatrix)

X(Scale)

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)

X(DrawWebView)

};

#undef X

struct DrawImage final : Op {

static const auto kType = Type::DrawImage;

DrawImage(sk_sp&& image, SkScalar x, SkScalar y, const SkPaint* paint,

BitmapPalette palette)
image(std::move(image)), x(x), y(y), palette(palette) {

if (paint) {

this->paint = *paint;

}

}

sk_sp image;

SkScalar x, y;

// 这里SK指代skia库对象

SkPaint paint;

BitmapPalette palette;

void draw(SkCanvas* c, const SkMatrix&) const { c->drawImage(image.get(), x, y, &paint); }

};

template

constexpr color_transform_fn colorTransformForOp() {

if

// 如果类型T有paint变量,并且有palette变量

constexpr(has_paint && has_palette) {

// It’s a bitmap(绘制Bitmap)

// 例如对于一个DrawImage的OP,最终会调用到这里

// opRaw对应DrawImage对象,transform为ColorTransform::Dark或者ColorTransform::Light

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

// TODO: We should be const. Or not. Or just use a different map

// Unclear, but this is the quick fix

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

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

};

}

else if

constexpr(has_paint) {

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

// TODO: We should be const. Or not. Or just use a different map

// Unclear, but this is the quick fix

// 非Bitmap绘制

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

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

};

}

else {

return nullptr;

}

}

frameworks/base/libs/hwui/CanvasTransform.cpp

这里进行具体的颜色转换逻辑,我们首先关注非Bitmap绘制的颜色转换

// 非Bitmap绘制颜色模式转换

bool transformPaint(ColorTransform transform, SkPaint* paint) {

applyColorTransform(transform, *paint);

return true;

}

// 非Bitmap绘制颜色模式转换

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

// 线性渐变并且渐变颜色少于等于10个的情况

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;

}

}

}

// 处理colorFilter

if (paint.getColorFilter()) {

SkBlendMode mode;

SkColor color;

// TODO: LRU this or something to avoid spamming new color mode filters

if (paint.getColorFilter()->asAColorMode(&color, &mode)) {

// 对colorFilter颜色进行转换

color = transformColor(transform, color);

paint.setColorFilter(SkColorFilters::Blend(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) {

// 将sRGB色彩模式转换成Lab色彩模式

Lab lab = sRGBToLab(color);

// 对亮度L维度取反

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

if (invertedL > lab.L) {

// 若取反后亮度变亮,则替换原来亮度

lab.L = invertedL;

// 重新转换为sRGB模式

return LabToSRGB(lab, SkColorGetA(color));

} else {

return color;

}

}

// 后景色变暗

static SkColor makeDark(SkColor color) {

// 将sRGB色彩模式转换成Lab色彩模式

Lab lab = sRGBToLab(color);

// 对亮度L维度取反

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

if (invertedL < lab.L) {

// 若取反后亮度变暗,则替换原来亮度

lab.L = invertedL;

// 重新转换为sRGB模式

return LabToSRGB(lab, SkColorGetA(color));

} else {

return color;

}

}

从代码中可以看出,深色模式应用之后,通过对sRGB色彩空间转换Lab色彩空间,并对表示亮度的维度L进行取反,并判断取反后前景色是不是更亮,后景色是不是更暗,若是的话就替换为原来的L,并再重新转换为sRGB色彩空间,从而实现反色的效果。

我们再来看对图片的强制深色模式处理:

// Bitmap绘制颜色模式转换

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

// 考虑加上filter之后图片的明暗

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;

// 设置skia反转亮度的filter

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

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

}

return shouldInvert;

}

// 获取paint filter的palette值,若没有filter直接返回原来的palette

static BitmapPalette filterPalette(const SkPaint* paint, BitmapPalette palette) {

// 如果没有filter color返回原来的palette

if (palette == BitmapPalette::Unknown || !paint || !paint->getColorFilter()) {

return palette;

}

SkColor color = palette == BitmapPalette::Light ? SK_ColorWHITE : SK_ColorBLACK;

// 获取filter color,并根据palette的明暗再叠加一层白色或者黑色

color = paint->getColorFilter()->filterColor(color);

// 根据将颜色转换为HSV空间,并返回是图片的亮度是亮还是暗

return paletteForColorHSV(color);

}

从代码中可以看出,对于Bitmap类型的绘制,先判断原来绘制Bitmap的明暗度,如果原来绘制的图像较为明亮但是需要变暗,或者原来绘制的图像较为暗需要变明亮,则设置一个明亮度转换的filter到画笔paint中。

至此,对于主题级别的强制深色转换原理已经非常清晰。总结一下,就是需要对前景色变亮和背景色变暗,然后对于非Bitmap类型明暗变化采用的是将色值转换为Lab颜色空间进行明亮度转换,对于Bitmap类型的明暗变化采取设置亮度转换的filter进行。

2. View

无论是设置View的xml的android:forceDarkAllowed属性,还是调用View.setForceDarkAllowed()最后还是调用到frameworks/base/core/java/android/view/View.java的mRenderNode.setForceDarkAllowed()方法。

frameworks/base/graphics/java/android/graphics/RenderNode.java

public boolean setForceDarkAllowed(boolean allow) {

return nSetAllowForceDark(mNativeRenderNode, allow);

}

nSetAllowForceDark通过JNI调用到android_view_RenderNode_setAllowForceDarkNavtive方法中。

frameworks/base/libs/hwui/jni/android_graphics_RenderNode.cpp

static const JNINativeMethod gMethods[] = {

// …

{ “nSetAllowForceDark”, “(JZ)Z”, (void*) android_view_RenderNode_setAllowForceDark },

// …

};

static jboolean android_view_RenderNode_setAllowForceDark(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jboolean allow) {

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

}

#define SET_AND_DIRTY(prop, val, dirtyFlag) \

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

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

最后这个是否允许深色模式的allow变量被设置到RenderProperties.h 中

frameworks/base/libs/hwui/RenderProperties.h

/*

  • Data structure that holds the properties for a RenderNode

*/

class ANDROID_API RenderProperties {

public:

// …

// 设置View是否允许强制深色模式

bool setAllowForceDark(bool allow) {

return RP_SET(mPrimitiveFields.mAllowForceDark, allow);

}

// 获取View是否允许强制深色模式

bool getAllowForceDark() const {

return mPrimitiveFields.mAllowForceDark;

}

// …

private:

// Rendering properties

struct PrimitiveFields {

// …

// 默认值为true

bool mAllowForceDark = true;

// …

} mPrimitiveFields;

我们回头看下上面分析过的RenderNode.cpp的prepareTreeImpl流程

frameworks/base/libs/hwui/RenderNode.cpp

// 经过了简化处理的prepareTreeImpl逻辑

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

// 如果当前View不允许被ForceDark,那么info.disableForceDark值+1

if (!mProperties.getAllowForceDark()) {

info.disableForceDark++;

}

// 同步正在处理的Render Node的Display List,实现具体深色的逻辑

pushStagingDisplayListChanges(observer, info);

mDisplayList->prepareListAndChildren([](RenderNode* child, TreeObserver& observer, TreeInfo& info) {

// 递归调用子节点的prepareTreeImpl。

最后

都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。

技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到Android架构师/技术专家?我总结了这 5大块;

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 PDF(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言

高级UI与自定义view;
自定义view,Android开发的基本功。

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

NDK开发;
未来的方向,高薪必会。

前沿技术;
组件化,热升级,热修复,框架设计

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!

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

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

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

rk,那么info.disableForceDark值+1

if (!mProperties.getAllowForceDark()) {

info.disableForceDark++;

}

// 同步正在处理的Render Node的Display List,实现具体深色的逻辑

pushStagingDisplayListChanges(observer, info);

mDisplayList->prepareListAndChildren([](RenderNode* child, TreeObserver& observer, TreeInfo& info) {

// 递归调用子节点的prepareTreeImpl。

最后

都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。

技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到Android架构师/技术专家?我总结了这 5大块;

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 PDF(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

[外链图片转存中…(img-QSO3fFSc-1714980497547)]

Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言

[外链图片转存中…(img-MsNq22UR-1714980497547)]

高级UI与自定义view;
自定义view,Android开发的基本功。

[外链图片转存中…(img-isaGXGV5-1714980497548)]

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

[外链图片转存中…(img-qI3ItC38-1714980497548)]

NDK开发;
未来的方向,高薪必会。

[外链图片转存中…(img-jdCTJWK9-1714980497549)]

前沿技术;
组件化,热升级,热修复,框架设计

[外链图片转存中…(img-f2lKpNbF-1714980497549)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!

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

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

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

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值