探索Google日历图标的奥秘

在智能手机的桌面上,经常会看到一个蓝色的小圆圈,它安静地待在那里,或许在暗示着日历应用自上次查看以来有所更新。这不是关于那个小蓝点的故事,而是关于图片中心那对神秘数字的故事,它们恰好与今天的日期(7月29日)相吻合。

Google日历可能是能找到的最好的日历应用(或服务),因为它可以在所有设备上无缝同步,其二,它的图标背景上有友好的小数字,正确显示了每个月的日期,无论何时查看,这个神秘而有趣的小功能,正是试图弄清楚的。在尝试为波斯日历实现完全相同的功能时被吸引进来。

好的,让开始工作。在网上找到了一个Google日历的APK(由于版权等原因,不会在这里提供),并对其进行了解压缩。接下来,尝试了如下简单的命令:

grep -Ri dynamic . | grep icon

这是输出的重要部分:

./res/values-v26/arrays.xml: <array name="calendar_icons_dynamic_nexus_round">

这是该文件的重要部分:

<array name="calendar_icons_dynamic"> <item>@drawable/logo_calendar_01_adaptive</item> <item>@drawable/logo_calendar_02_adaptive</item> <item>@drawable/logo_calendar_03_adaptive</item> <item>@drawable/logo_calendar_04_adaptive</item> <item>@drawable/logo_calendar_05_adaptive</item> </array>

让看看res/drawable-v26/logo_calendar_03_adaptive.xml:

<?xml version="1.0" encoding="utf-8"?> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/adaptive_background"/> <foreground> <layer-list> <item android:drawable="@drawable/adaptive_base"/> <item android:drawable="@drawable/calendar_date_03_adaptive"/> </layer-list> </foreground> </adaptive-icon>

非常直观,对吧?这些资源都是png格式,包含了想到的内容:adaptive_base和calendar_date_03_adaptive。

在那之后,深入研究了.smali文件,并浏览了将apk转换为jar后的实际代码(使用了dex2jar,然后使用jd-gui)。经过数小时的代码导航,没有找到任何上面提到的drawables的引用。这就是为什么把日历应用放在一边,转而去研究像素启动器的源代码。在名为DynamicIconProvider的类中,可以找到以下内容:

private boolean fe(String paramString) { return "com.google.android.calendar".equals(paramString); } public Drawable getIcon(LauncherActivityInfo paramLauncherActivityInfo, int paramInt, boolean paramBoolean) { // ... }

突然之间,如果瞥一眼这个文件的其他部分,一切都变得有意义了:

public DynamicIconProvider(Context paramContext) { IntentFilter localIntentFilter = new IntentFilter("android.intent.action.DATE_CHANGED"); localIntentFilter.addAction("android.intent.action.TIME_SET"); localIntentFilter.addAction("android.intent.action.TIMEZONE_CHANGED"); paramContext.registerReceiver(this.gv, localIntentFilter, null, new Handler(LauncherModel.getWorkerLooper())); this.mContext = paramContext; this.mPackageManager = paramContext.getPackageManager(); }

注意动作类路径。还发现了这个c.class,它几乎是不可读的解压缩函数/类名,可能负责更新时钟应用(deskclock)的界面。这里有一些有趣的部分:

localPackageManager.getApplicationInfo("com.google.android.deskclock", 8320); localb.fW = localBundle.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX", -1); localb.fY = localBundle.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX", -1); localb.fZ = localBundle.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX", -1); localb.fS = localBundle.getInt("com.google.android.apps.nexuslauncher.DEFAULT_HOUR", 0); localb.fT = localBundle.getInt("com.google.android.apps.nexuslauncher.DEFAULT_MINUTE", 0); localb.fU = localBundle.getInt("com.google.android.apps.nexuslauncher.DEFAULT_SECOND", 0);

值得注意的是,日历应用和deskclock应用自适应图标之间的设计决策差异。为了进一步理解这一点,让看看deskclock的launcher_clock.xml的一部分:

<foreground> <layer-list> <item> <rotate android:drawable="@mipmap/launcher_clock_hour" android:fromDegrees="300.0" android:toDegrees="5300.0" android:pivotX="50.0%" android:pivotY="50.0%"/> </item> <item> <rotate android:drawable="@mipmap/launcher_clock_minute" android:fromDegrees="60.0" android:toDegrees="60060.0" android:pivotX="50.0%" android:pivotY="50.0%"/> </item> <item> <rotate android:drawable="@mipmap/launcher_clock_second" android:fromDegrees="180.0" android:toDegrees="6180.0" android:pivotX="50.0%" android:pivotY="50.0%" android:level="300"/> </item> <item android:drawable="@mipmap/launcher_clock_top"/> <item android:drawable="@mipmap/launcher_clock_banner"/> </layer-list> </foreground>

除了这个,代码库中没有其他对launcher_clock_second的引用。有趣的是,可以看到两种非常不同的解决方案来解决一个相当相似的问题:动态启动器图标。由于在线文本渲染的复杂性,作者们决定预先准备好日历应用所需的所有图标。然而,在deskclock的情况下,如果他们事先创建drawables,将会有太多的图标,这导致了将这种看似无关(就启动器而言)的逻辑引入到像素启动器的代码中:

bool1 = bool2; if (this.fV.getDrawable(this.fW).setLevel((i + (12 - j)) % 12 * 60 + this.fX.get(12))) { bool1 = true; }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485