知乎Android客户端不重启Activity设置夜间模式实现分析

知乎Android客户端不重启Activity设置夜间模式实现分析

高爷高爷

在没有代码的情况下,我们需要分析一款应用的某个功能是如何实现的,往往有一定的难度,Systrace在信息不够多的情况下,分析起来也比较费劲。但是Android Studio给我们提供了一种新的分析问题的方式:Method Trace。

没有使用过Android Studio的开发者可以绕道了,然则这也是你抛弃Eclipse拥抱Android Studio的一个开始把。

安利完Android Studio,我们就通过Android Studio自带的调试工具Method Trace来进行一次实战把,毕竟“光说不练假把式”(此处假设你已经阅读过我的另一篇介绍如何使用Method Trace的文章了(此处假设这篇文章已经写完了,然而并没有))。刚好有人在提问说知乎的Android客户端是如何在不重启Activity的情况下切换到夜间模式的,虽然我第一时间就想到了设置背景色这种方法,不过自己去验证一下貌似更有说服力:

Let‘s start!首先用Android Studio的Method Trace抓一个Trace,下面的分析假设你已经打开了这个Trace文件。

从下面的图来看,一个onClick事件就完成了切换主题的功能,说明并没有重启Activity,也没有用setTheme这么古板的方法,那么其实就很简单了,换个颜色即可!



说白了白天模式和夜间模式,就是配色不一样,那么最简单的方法就是为每一个控件设置不同的颜色,白天模式显示这个颜色,夜间模式显示那个颜色。

基于上面的认识,我想大家其实也基本知道如何实现这货了。

鉴于只贴上面那张图有点敷衍,我们来仔细看一下,将其中的过程和逻辑都大概讲一下,话说举一反三嘛,学会了这个,以后碰到什么别的技术问题,想不通对方是如何实现的,打个Trace一看,也就八九不离十了。

那么我们开始吧:

1. 保存主题模式
从上面的图可以看到,点击切换主题之后,首先会将当前的值存起来,这样全局范围都可以使用到这个值,存储介质自然就是SharedPreferences了。

我们将上面那张图再放大一点:
可以看到,Android处理SharedPreferences其实就是打开一个xml文件,把对应的值写进去。当然你看代码也可以看出来:

private void writeToFile(SharedPreferencesImpl.MemoryCommitResult mcr) {

    ......

    try {
        FileOutputStream e = createFileOutputStream(this.mFile);
        if(e == null) {
            mcr.setDiskWriteResult(false);
            return;
        }

        XmlUtils.writeMapXml(mcr.mapToWriteToDisk, e);
        FileUtils.sync(e);
        e.close();
        ContextImpl.setFilePermissionsFromMode(this.mFile.getPath(), this.mMode, 0);

        try {
            StructStat e1 = Libcore.os.stat(this.mFile.getPath());
            synchronized(this) {
                this.mStatTimestamp = e1.st_mtime;
                this.mStatSize = e1.st_size;
            }
        } catch (ErrnoException var7) {
            ;
        }

        this.mBackupFile.delete();
        mcr.setDiskWriteResult(true);
        return;
    } catch (XmlPullParserException var8) {
        Log.w("SharedPreferencesImpl", "writeToFile: Got exception:", var8);
    } catch (IOException var9) {
        Log.w("SharedPreferencesImpl", "writeToFile: Got exception:", var9);
    }

    if(this.mFile.exists() && !this.mFile.delete()) {
        Log.e("SharedPreferencesImpl", "Couldn\'t clean up partially-written file " + this.mFile);
    }

    mcr.setDiskWriteResult(false);
}
不好意思一个激动贴了点代码,然而并没有什么卵用。

2. Build Cache
可以看到,接下来是调用了View的buildDrawingCache()方法。
buildDrawingCache的代码如下:(&(×@ ×&&)(×(……×…… 不好意思这次忍住了没贴代码,buildDrawingCache简单来说就是将当前的View用一张Bitmap Cache起来。
然后又调用了getDrawingChche()
public Bitmap getDrawingCache() {
    return getDrawingCache(false);
}
返回了之前我们Cache起来的Bitmap。

话说上面buildDrawingCache和getDrawingChche是为了做什么呢?我们下面再说

3 设置StatusBar的颜色
知乎客户端在变色的时候,同时也改变了StatusBar的颜色,下面就是改变StatusBar颜色的代码


4. 设置ActionBar的颜色
StatusBar变完色就该ActionBar了。
虽然混淆了,但是下面的我们还是可以看出来的嘛~。~

5. 内容变色
知乎的代码做了混淆,所以看上去就是一堆a/b之类的东西。
不过也可以看到几个知乎自己定义的View在设置自己的Theme,setTheme并非大家想的那样,我猜想是知乎的大神们自己写的setTheme,从下面的图中可以看到,就是设置了自定义控件的背景色。


6. 做Alpha动画

6.1 第六步回忆录:
这时候我们之前提到的Cache起来的那张Bitmap就用得上了,其实在getDrawingChche之后还有一些操作,当时没有列出来(第二步之后的一些操作):
具体就是先创建一个Bitmap对象,再初始化一个View(全屏目测是),然后把这个View的Background设置为刚刚创建的Bitmap对象。

然后将这个View添加到屏幕上:
这样做之后,我们看到的就是这个View了,这样给人的视觉效果就是屏幕没啥变化,其实多了一个View。
接下来就走第三步了,开始各个部位的变色,但是这时候其实我们是看不到的,等第三步,第四步,第五部都完成了之后,让这个View做Alpha动画即可。

6.2 第六步正文:
Alpha动画开始:


7 大功告成


最后吐槽一下知乎客户端的过度绘制问题:

有人提到内存问题, 其实知乎处理的蛮好的:
每一个尖角都是我点击切换主题按钮的时刻,内存飙高后迅速回落。干得漂亮~。~

(PS:配图来自网络,如有侵权,我立刻删除)

文章被以下专栏收录
17 条评论
推荐阅读