首页 / 资讯中心 / 文章详情

Unity手游动态更换App图标:Android与iOS双端实现与避坑指南

Unity手游动态更换App图标:Android与iOS双端实现与避坑指南 ★ FEATURED ARTICLE
开局说点实在的这个需求到底是什么先说结论Unity手游在运行时动态更换桌面App图标在Android和iOS上是两条完全不同的路一条是官方API支持的正路一条是绕不开平台限制的偏方。很多做发行和运营的朋友都提过类似需求——比如节日活动换个喜庆图标新版本上线提前换个预热图标或者根据用户画像推送不同风格的图标这些在纯原生App里不算稀奇但放到Unity工程里坑就多了不少。这篇文章我按自己的实操经验把Android和iOS双端的实现思路、Unity侧的桥接方式、以及我在真机上踩过的坑全部整理出来。内容偏工程落地适合已经有Unity打包经验的客户端开发也适合想了解双端差异的技术负责人。不管你是打算自己动手实现还是需要评估这个需求的工作量看完这篇文章应该都能心里有数。先说点本质的东西桌面图标这个东西Android和iOS的机制差异非常大。Android的桌面图标本质上是Launcher根据App的activity-alias或者icon属性读取的资源而iOS的桌面图标是系统在安装时固化到SpringBoard的App自身无法在运行时修改。这个底层差异决定了我们的实现方案——Android可以做真正的“动态切换”iOS只能做“引导用户手动操作”的曲线救国方案。1. 方案整体设计与双端差异拆解1.1 Android与iOS的机制差异为什么决定了方案走向Android从7.1API 25开始引入了ActivityManager的setComponentEnabledSetting接口可以动态启用或禁用某个组件。这本来是用来控制应用内组件开关的但很多人发现它可以用来切换桌面上显示的图标入口。原理不复杂你在Manifest里声明多个activity-alias每个alias有不同的icon和label然后运行时通过PackageManager把其中一个alias设为启用、其他设为禁用。Launcher收到组件变更广播后会刷新桌面图标。iOS那边就难了系统层面根本不开放“运行时换图标”的能力。App的图标在打包时就写死在Info.plist的CFBundleIcons里用户看到的永远是那个安装时的图标除非用户手动删除App再重装新包。所以iOS端能做的只有两种一是发布新版本让用户手动更新二是在App内引导用户用Safari“添加到主屏幕”把网页书签做成一个伪图标这个伪图标可以每天动态生成内容。第二条路本质上是用Web技术绕过限制体验上比原生图标差一些但胜在能动态变。1.2 Unity工程里的桥接层设计思路Unity工程是C#为主Android和iOS的原生能力必须通过桥接层暴露给C#调用。我的习惯是保持一个统一的C#接口内部用#if UNITY_ANDROID和#if UNITY_IOS做平台分支上层业务完全不用关心双端差异。public static class AppIconManager { public static void ChangeIcon(string iconType) { #if UNITY_ANDROID !UNITY_EDITOR AndroidAppIconBridge.ChangeIcon(iconType); #elif UNITY_IOS !UNITY_EDITOR IOSAppIconBridge.GuideAddToHomeScreen(iconType); #endif } }这个接口看上去简单但真正写起来的时候要处理很多细节Android的调用是异步的Launcher刷新图标有延迟iOS的书签引导涉及跳转Safari和URL Scheme回调状态管理要小心。后面我会详细展开。1.3 影响范围评估这个功能不是你想的那么单纯做这个需求之前必须先想清楚影响范围。动态换图标不只是“换个图”那么简单它牵扯到Android桌面图标切换后进程会被系统杀死因为组件状态变了游戏需要能恢复到原来的业务场景部分Android厂商ROM比如华为、小米对图标缓存得很死可能需要用户手动刷新桌面iOS的书签方案在部分机型上会出现“描述文件拦截跳转”的问题商店审核方面如果你的应用在商店里宣传“动态换图标”有些商店审核会特别注意。这些坑我后面会逐个讲现在先记住一个原则这个功能做完之后自测永远不够一定要准备双端各至少3台不同品牌或系统版本的真机。2. Android端核心实现Activity-alias方案全拆解2.1 为什么要用Activity-alias而不是直接改icon属性很多人第一反应是“运行时动态替换icon资源不就行了”实际上做不到。Android的资源打包后在安装时已经固定运行时你无法修改已安装的资源文件Launcher读取图标时用的是安装包里的资源引用不是文件路径。所以可行的思路就是“换入口”——让Launcher显示另一个入口的图标。Activity-alias就是为这个场景准备的。你在Manifest里注册多个指向同一个Activity的别名给每个别名配上不同的icon和label。正常情况下所有别名都是禁用状态只有主Activity的入口显示。调用的时候把目标alias启用、把当前显示的入口禁用系统通知Launcher刷新桌面上显示的图标就变了。2.2 Manifest配置与图标资源准备一个标准的配置大概是这样的application android:iconmipmap/ic_launcher android:labelstring/app_name !-- 主入口 -- activity android:name.MainActivity android:exportedtrue intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity !-- 别名入口1节日图标 -- activity-alias android:name.MainActivity_Festival android:targetActivity.MainActivity android:iconmipmap/ic_launcher_festival android:labelstring/app_name_festival android:enabledfalse intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity-alias !-- 别名入口2预热图标 -- activity-alias android:name.MainActivity_Preheat android:targetActivity.MainActivity android:iconmipmap/ic_launcher_preheat android:labelstring/app_name_preheat android:enabledfalse intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity-alias /application这里有个细节值得注意android:icon指向的是mipmap资源如果你的图标是自适应图标Adaptive IconAndroid 8.0icon属性里需要同时包含前景和背景层否则有些机型上图标会显示异常。很多人在这一步踩坑因为平时打包时程序会帮你处理自适应但手写alias的时候容易忽略。图标资源本身建议准备多套尺寸。mipmap-mdpi、mipmap-hdpi、mipmap-xhdpi、mipmap-xxhdpi、mipmap-xxxhdpi各放一份尺寸按常规的48、72、96、144、192dp来。如果你只放一套大图小屏设备上没问题但大屏高分辨率设备会明显模糊发行那边验收时会说“图标糊了”很尴尬。2.3 Java层核心代码PackageManager组件切换Java层的核心方法如下public static void changeIcon(Context context, String aliasName) { ComponentName enableComponent new ComponentName(context, aliasName); ComponentName disableComponent new ComponentName(context, DEFAULT_ALIAS); PackageManager pm context.getPackageManager(); pm.setComponentEnabledSetting( enableComponent, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP ); pm.setComponentEnabledSetting( disableComponent, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP ); }这段代码本身不难DONT_KILL_APP标志表示不杀掉当前进程。但从实际测试来看有的厂商ROM就是无视这个标志切换完以后仍然会把进程杀掉尤其是MIUI。所以你的游戏侧一定要做好进程被杀后的恢复逻辑这个细节后面我会专门讲。还有一个容易踩的坑setComponentEnabledSetting是异步的调用返回后桌面图标不会立刻变化可能需要等几秒甚至更久。你需要监听Intent.ACTION_PACKAGE_CHANGED广播来确认切换是否成功或者在实际切换后延迟几秒再对UI做提示。做过这个功能的人都知道用户点完按钮结果桌面图标没变然后过了一分钟才变这种体验很糟。2.4 验证与还原逻辑切错了怎么回来切换代码写完一定要写“还原逻辑”因为用户随时可能反悔或活动结束要恢复原图标。我的做法是用SharedPreferences记录当前生效的alias名下次切换时先把当前的这个disable掉再enable新的避免出现“两个入口同时启用”的情况。private static final String KEY_CURRENT_ICON current_icon_alias; public static void switchTo(Context context, String targetAlias) { SharedPreferences sp context.getSharedPreferences(app_icon, Context.MODE_PRIVATE); String currentAlias sp.getString(KEY_CURRENT_ICON, DEFAULT_ALIAS); if (currentAlias.equals(targetAlias)) { return; // 已经切到目标图标 } pm.setComponentEnabledSetting( new ComponentName(context, currentAlias), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP ); pm.setComponentEnabledSetting( new ComponentName(context, targetAlias), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP ); sp.edit().putString(KEY_CURRENT_ICON, targetAlias).apply(); }这里最怕的是记录不一致。比如当前实际启用的是A但存储记录里写的是B那就会把A禁用、把B启用结果就是桌面图标没变因为A启用的入口才是同一个。所以存储的更新和操作的执行顺序要确保在同一事务逻辑里而且要在切换成功后再写不能先写再操作。2.5 Android 7.1以下版本怎么处理Activity-alias方案对系统版本有硬性要求Android 7.1API 25及以上的PackageManager才支持通过组件启停来切换桌面图标。低于这个版本的系统组件启停只能影响应用内部的调用Launcher不一定会刷新图标。我的处理方式是在ChangeIcon接口入口判断Build.VERSION.SDK_INT 25时直接返回一个“不支持”的回调业务层收到不支持的回调后弹Toast提示用户“当前系统版本不支持动态换图标”高版本系统走正常切换逻辑低版本系统彻底放弃图形入口。目前新装机设备的系统版本普遍都在Android 8.0以上7.1以下的存量设备占比已经很低但游戏发行如果有下沉渠道的需求还是要兼容好。3. iOS端实现思路没有官方API只有书签引导3.1 为什么iOS做不到原生动态换图标iOS的图标存储在SpringBoard的安装记录里系统在App安装时从Info.plist读取图标并缓存运行时App进程没有权限修改这部分系统数据。有人说可以尝试用MobileCoreServices私有API或者LSApplicationWorkspace改图标但这些都是私有APIApp Store上架审核必炸而且系统更新后大概率失效。所以结论很明确正常上架渠道的iOS应用只能通过“添加书签到主屏幕”来实现动态图标入口。3.2 “添加到主屏幕”实现的原理思路是这样的制作一个网页这个网页会根据不同的活动主题在服务端动态生成favicon和标题。引导用户用Safari打开这个网页然后通过系统的分享菜单“添加到主屏幕”。用户在桌面上看到的是一个WebClip图标点击之后打开的是网页而不是App。这个图标看起来和App图标差不多但实际打开的是Safari页面。这个方案的核心控制权在服务端——网页的favicon和标题随时可以改用户桌面的书签图标会在某些条件下自动更新比如系统重新抓取favicon但更新周期不可控。如果想强制刷新用户需要删除书签重新添加。3.3 Unity侧怎么引导用户完成书签添加流程在Unity里我们一般做一个游戏内活动页面页面底部有“添加到主屏幕”的按钮。用户点击后Unity通过Application.OpenURL打开Safari直接跳到活动页面。网页上写清楚引导步骤用户自己调出分享菜单、选择“添加到主屏幕”完成后回到游戏。public static class IOSAppIconBridge { public static void GuideAddToHomeScreen(string activityUrl) { Application.OpenURL(activityUrl); } }这里要特别注意Safari返回游戏的体验问题。Safari打开网页后用户完成添加操作要手动切回游戏App不能自动调回。你可以用Universal Links或者URL Scheme做从网页跳回App的入口但“添加到主屏幕”的WebClip打开后并没有你的App调用能力它只是一个网页快捷方式。所以整个流程的设计需要考虑用户耐心步骤越少越好文案越明确越好。3.4 一个更进阶的玩法预览网页智能判断如果你希望用户在游戏内就能看到换图标后的效果而不是跳去Safari靠想象可以在网页里用JavaScript读取当前时间或活动ID动态显示“当前图标”这个预览和用户最终添加到桌面的图标保持一致。实现上网页根据URL参数生成对应favicon和图标预览Safari的书签会使用网页的favicon作为桌面图标源。需要注意的一点Safari的“添加到主屏幕”图标和网页favicon之间不是严格的等同关系系统会参考HTML里的apple-touch-icon标签。所以网页里必须加上link relapple-touch-icon hrefhttps://your.cdn/icon.png这个标签正经ABC容易漏掉。如果不加iOS桌面书签会显示网页截图缩略图那效果就惨不忍睹了。我见过不少团队做这个功能时图标显示成网页截图体验非常差本质就是少了这行标签。3.5 系统版本的兼容性问题书签方式其实不挑iOS版本从iOS 7到iOS 17都能用。但不同版本的Safari对“添加到主屏幕”的交互细节有细微差别老版本系统分享菜单叫“添加到主屏幕”新版本叫“添加到主屏幕”还是“添加到主屏幕”基本一致。大字版在iOS 13以后新增了“编辑主屏幕”模式用户可以长按图标再编辑理论上用户自己改图标的概率也有但我们就当不存在不能依赖。另外一个要注意的是高版本iOS对剪贴板和网页跳转的隐私弹窗越来越多引导页别做太多花活老老实实三步走打开Safari、点分享、添加到主屏幕。4. Unity工程整合细节与关键代码4.1 Android原生插件的Unity接入方式Unity调用Android原生代码我习惯用Android Library工程生成AAR包放到Unity的Assets/Plugins/Android目录。Unity官方推荐的Gradle导出模式要求使用插件包的方式而不是简单放一个.class文件。具体步骤Android Studio新建工程模块类型选Library写Java类和方法方法必须为静态方法把AAR包复制到Unity工程的Assets/Plugins/Android/Unity里写一个AndroidJavaClass或AndroidJavaObject桥接类调用using UnityEngine; public class AndroidAppIconBridge { private const string ClassName com.youcompany.appicon.IconSwitcher; public static void ChangeIcon(string aliasName) { using (AndroidJavaClass javaClass new AndroidJavaClass(ClassName)) { using (AndroidJavaObject activity GetUnityActivity()) { javaClass.CallStatic(switchTo, activity, aliasName); } } } private static AndroidJavaObject GetUnityActivity() { using (AndroidJavaClass unityPlayer new AndroidJavaClass(com.unity3d.player.UnityPlayer)) { return unityPlayer.GetStaticAndroidJavaObject(currentActivity); } } }这里有个老坑在Android 11API 30以上如果Launcher读取图标时需要访问其它应用的信息有可能导致页面刷新慢。好在我们的场景只是组件启停不涉及跨应用读包信息问题不大。但在Activity启动模式下如果游戏是单实例切换完图标后进程被杀用户重新点到的时候会启动一个新的Task游戏可能回到启动页。所以你的启动页逻辑要处理好别让用户感觉“我进度丢了”。4.2 iOS侧需要额外处理的工程设置iOS侧的桥接类更简单因为只是打开URL。但这里有一个容易被忽略的配置如果你的iOS工程启用了SceneDelegateiOS 13的SwiftUI工程OpenURL的回调传递路径可能和旧版不同。Unity的iOS工程是老式AppDelegate结构一般不受影响但如果你们团队后来又叠加了原生iOS模块就要注意回调路径是否被截获。iOS 13的UIScene生命周期里scene(_:openURLContexts:)是接收外部URL回调的地方。Unity原生插件如果自己实现了SceneDelegate要确保URL继续传递给Unity的UnityAppController否则从Safari跳回App会失灵。这个坑比较隐蔽一般人不跳进去根本不知道。4.3 C#与原生交互的异步状态管理动态换图标是个有延迟的操作尤其是Android侧组件切换可能几百毫秒到数秒不等iOS的书签流程更是长达几分钟的用户手动操作。所以C#侧的状态机必须考虑到超时、取消、中途切后台等情况。我对外暴露的C#接口带回调函数public static void ChangeIcon(string iconType, Actionbool onFinished) { #if UNITY_ANDROID !UNITY_EDITOR // Android: 通过AndroidJavaObject调用稍后通过UnitySendMessage回调结果 AndroidJavaClass playerClass new AndroidJavaClass(com.unity3d.player.UnityPlayer); AndroidJavaObject activity playerClass.GetStaticAndroidJavaObject(currentActivity); using (AndroidJavaClass iconSwitcher new AndroidJavaClass(com.youcompany.icon.IconSwitcher)) { iconSwitcher.CallStatic(switchToWithCallback, activity, iconType, GameObjectName, OnIconChanged); } #elif UNITY_IOS !UNITY_EDITOR // iOS: 直接打开URL用户手动操作这里只能认为打开成功 if (!string.IsNullOrEmpty(GetActivityUrl(iconType))) { Application.OpenURL(GetActivityUrl(iconType)); onFinished?.Invoke(true); } else { onFinished?.Invoke(false); } #endif }回调到C#的错误处理我的建议是至少加一个5秒超时如果超时未收到回调则提示“切换失败请稍后重试”。Android原生那边如果PackageManager抛出SecurityException或IllegalArgumentException要把异常信息封装好返回具体错误码而不是裸抛异常。Unity侧的问题是这样的原生异常是不会自动打到C#的它会被Android框架吞掉或者导致崩溃最好在原生代码里就做了try-catch随后通过统一回调通道返回状态。4.4 图标资源如何Build进Unity工程Android的mipmap资源要放在Assets/Plugins/Android/res/mipmap-xxxhdpi之类的目录下Unity Gradle导出时会自动打包。iOS的图标设置不需要在Unity里特别配置因为书签方式使用网页端资源与iOS原生包无关。但这里有个容易发现不了的问题Unity的默认打包流程会把Assets/Plugins/Android/res下的资源合并进AAB或APK。如果你的图标资源文件被放到Assets/Resources目录里当普通游戏资源打包Android原生层是无法通过mipmap/xxx引用的。很多人把我们开发的图标图片直接扔进Assets/Resources结果Manifest里引用不到运行时直接异常。正确的放置方式Assets/Plugins/Android/res/mipmap-mdpi/ic_launcher_festival.png Assets/Plugins/Android/res/mipmap-hdpi/ic_launcher_festival.png Assets/Plugins/Android/res/mipmap-xhdpi/ic_launcher_festival.png ...这样打包后原生资源引用mipmap/ic_launcher_festival才不会报错。4.5 状态记录与回调封装的完整示例我再给一段Android侧完整的原生代码含状态记录和回调public class IconSwitcher { private static final String PREFS_NAME app_icon_state; private static final String KEY_CURRENT current_alias; // 默认入口 private static final String DEFAULT_ALIAS com.youcompany.game.MainActivity; public static void switchTo(Context context, String targetAlias, String callbackObj, String callbackMethod) { try { SharedPreferences sp context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); String currentAlias sp.getString(KEY_CURRENT, DEFAULT_ALIAS); if (currentAlias.equals(targetAlias)) { sendCallback(context, true, -1); return; } PackageManager pm context.getPackageManager(); ComponentName enableCN new ComponentName(context, targetAlias); ComponentName disableCN new ComponentName(context, currentAlias); pm.setComponentEnabledSetting(enableCN, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); pm.setComponentEnabledSetting(disableCN, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); sp.edit().putString(KEY_CURRENT, targetAlias).apply(); sendCallback(context, true, 0); } catch (Exception e) { sendCallback(context, false, 1); } } private static void sendCallback(Context context, boolean success, int code) { if (context instanceof UnityPlayerActivity) { UnityPlayer.UnitySendMessage(AppIconManager, OnNativeCallback, success | code); } } }这里有个细节UnitySendMessage的GameObject必须是场景中真实存在的对象否则消息会没人接收。普遍做法是在游戏里常驻一个名为AppIconManager的隐藏GameObject否则回调发不出来还找不到原因。Unity侧接收的方法里用OnNativeCallback(string message)即可内部再解析是否成功。5. 常见问题与排查实战记录5.1 Android换图标后桌面不刷新怎么办这是最常见的问题而且不同ROM表现还不一样。分情况排查原生Android或Pixel系列切换后基本几秒内自动刷新最多等10秒MIUI小米部分版本桌面图标刷新很慢策略是主动触发一次Launcher刷新。调用sendBroadcast发一个android.intent.action.PACKAGE_CHANGED的广播有概率触发桌面重绘ColorOSOPPO部分机型切换后图标不会变必须用户手动长按桌面或重启LauncherHarmonyOS华为新版本系统对组件启停的监控更严格有时候需要用户手动重启手机才会刷新。这类兼容性问题没有完全统一的解法建议策略是切换后立即把进程保活一段时间防止Launcher刷新时你进程没了提示用户“图标可能延迟生效”让用户有心理预判灰度发布后收集各机型反馈有针对性地做适配。我这里给一个非官方的“土办法”切换后延迟1秒发ACTION_PACKAGE_CHANGED广播可以加速部分桌面的刷新。实测在MIUI和Flyme上有效但在ColorOS上无效。这个广播需要权限吗不需要因为你自己应用的信息变更应用自己可以广播但接收方能不能收到是另一回事。5.2 桌面出现两个图标或图标错乱出现这种情况90%是因为之前的切换没有把旧入口disable干净。例如第一次切换成功旧入口确实被disable了但设备重启后某些ROM会把某些组件状态还原导致新入口和主入口同时可用。排查方法杀掉应用去设置-应用-查看已启用的组件状态确认是否所有activity-alias的enabled属性在Manifest里写的是false——只有主入口默认是true检查代码中是否并发调用了多次切换——比如快速点击了两次按钮。发版前我强烈建议覆盖以下场景的自动化测试从默认图标切到活动图标再切回默认连续切换3次不同图标切换后立即杀进程、重启手机切换过程中断网、来电、切后台。一套跑下来双图标问题基本能暴露。5.3 更换图标后Unity游戏进程被杀如何无缝恢复这个坑我非常确定很多人会遇到。setComponentEnabledSetting即使传了DONT_KILL_APP某些ROM下应用进程仍然被杀。用户从桌面点击新图标启动时走的是普通冷启动流程。如果游戏没有做进程恢复处理用户会直接回到启动页丢进度体验极差。我的应对方案尽量不杀进程在AndroidManifest里MainActivity加android:launchModesingleTask切换时从Activity中调用而非Application中调用降低被杀的几率做好状态恢复游戏侧保存用户当前所在业务场景、上一局数据、弹窗状态等关键数据到本地持久化存储启动时检测冷启动时读取上一次保存的“非启动页”标记如果存在则直接跳转到对应场景或弹出恢复提示。这个不只是技术活还得产品和运营一起配合设计流程因为“用户被杀了之后回来要看到什么”是一个产品决策不是纯技术决策。5.4 华为/小米等设备上图标没有变化但功能没报错这种情况最气人代码跑了一遍没有异常但桌面就是没反应。背后有几类根因ROM的桌面是第三方进程比如小米的MIUI桌面不爱及时听PACKAGE_CHANGED的广播它有自己的图标缓存策略设备上没有进入Launcher桌面如果用户停留在游戏界面或锁屏界面桌面压根没有重新绘制自然看不到变化图标资源错误引用比如alias的icon引用了不存在的mipmap资源系统静默失败。排查时先用adb shell命令检查组件状态是不是真的变了adb shell dumpsys package 你的包名 | grep -E MainActivity|ActivityAlias如果dumpsys输出显示组件状态已经是enabledtrue那说明代码是对的是Launcher缓存的问题。这时候只能等系统自己refresh或者让用户重启一次桌面。5.5 iOS书签添加失败或WebClip图标显示异常iOS端书签方案的失败率比很多人想象的高。常见的现象用户Safari打开了网页但找不到“添加到主屏幕”按钮添加到主屏幕后桌面图标显示的是网页截图缩略图而不是自定义图标用户完成添加后不知道怎么回到游戏。排查要点现象排查方向解决方案找不到添加到主屏幕用户可能用的是第三方浏览器必须引导用Safari打开不能用in-app browser图标显示截图缺少apple-touch-icon标签在网页head加上icon链接且图标必须为180x180无法回游戏缺少URL Scheme或Universal Links配置回调scheme并在页面上加“返回游戏”按钮标题乱码或空白网页标题为空必须在HTML的title标签里写清楚应用名5.6 应用商店审核风险哪些操作会踩线做这个功能最现实的问题就是应用商店审核。Android这边国内应用市场比较在意的是“是否篡改应用图标误导用户”“是否诱导用户点击非官方入口”。iOS更严格如果你在App Store审核时启用了“运行时动态换图标”的能力——审核员如果看到宣传或代码里有动态切换逻辑——可能被质疑违反高版本要求甚至要求剪掉这个功能。我的经验是不要在App Store的应用描述、截图、预览视频里宣传“动态换图标”能力iOS端只保留书签引导不要尝试任何私有APIAndroid端功能在审核时留一个服务器开关灰度地区关闭该功能正式版再打开。这里有个灰色空间不同审核员的判断标准略有差异稳妥为上。5.7 Unity侧编译怪象导出Android工程后图标资源找不到再增加一个Unity特有的问题。很多人把Manifest和mipmap资源直接放到Assets/Plugins/Android/AndroidManifest.xml里但图标资源放在Assets/Plugins/Android/res/mipmap-xxhdpi之后导出Gradle工程运行时却报资源找不到。原因是Unity在打包时会自动调整res目录的路径如果你的Gradle模板里声明了多个res目录或者AAR合并时的资源优先级有问题可能导致mipmap资源没有被正确合并。解决方案把图标资源放进AAR库的res目录而不是主工程保持Assets/Plugins/Android/res目录层级严格符合Gradle规范导出Android工程后用aapt dump badging查看APK里的mipmap资源是否存在。总之Unity和原生资源的交互经常是“明明放了为什么查不到”这类问题靠经验趁早用工具检查不要靠猜。6. 一些值得顺嘴聊聊的周边优化6.1 动态图标的A/B测试价值这个功能做出来后运营那边其实很兴奋——终于可以试验不同图标的点击率了。iOS是伪图标只能服务于网页入口Android可以真正做到原生图标A/B测试。比如给你三组用户分别显示不同颜色风格的图标统计首次启动率和次留数据。这种测试能带来真实的拉新收益建议在构建后台加一个下发配置。做法是服务端下发用户的图标分组客户端根据分组调用AppIconManager.ChangeIcon统计用户点击桌面图标进入游戏的次数与分组对照。要注意的是Android的图标切换不是瞬时的短时间频繁切换会出现桌面图标闪烁甚至用户误以为是病毒所以A/B测试的分组变更频率最多一天一次不要太夸张。6.2 节日主题图标的自动化运营如果你有运营后台可以做一个定时任务活动开始前10分钟服务端下发指令给所有在线用户进行图标切换活动结束后再切回默认图标。这个场景在游戏发行里很常见比如圣诞、春节、周年庆。技术实现上没什么难度难点在客户端要能及时收到服务端指令。游戏是长连接或推送体系客户端拿到指令后调ChangeIcon。但要注意“离线用户”没法收到指令他们下次打开游戏的时候客户端要主动拉一次当前图标配置。6.3 性能与适配经验总结作为收尾我把整个方案落地时要注意的优先级整理一下事项优先级说明Android 7.1以下降级高必须判断版本否则直接闪退或无效图标资源多尺寸适配高漏一套就可能导致某机型图标模糊原生回调路径测试高回调接不通等于功能不可用厂商ROM兼容测试中华为、小米、OPPO必须各测一台iOS书签引导文案中用户不会用书签流程就废了状态记录与恢复高进程被杀后没有恢复方案等于事故我在实际开发中体会最深的一点是千万不要觉得这个功能“就是几行代码的事”。它牵扯原生系统机制、Unity桥接、UI交互、运营后台下发、多机型适配是一个完整的小型业务模块。做之前最好做好资源排期留出至少一周的适配联调时间。最后再分享一个小技巧调试Android动态换图标时可以用adb shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n 包名/别名直接拉起某个alias入口验证当前启用的入口是否正确。这个命令在排查“图标变了但点开是错的入口”这类问题上非常管用。自己手动试个几次比看代码分析来得快得多。
阅读完成 · 觉得有帮助?
咨询建站