当前位置: 首页 > news >正文

网站底部悬浮代码dedecms制作的网站

网站底部悬浮代码,dedecms制作的网站,免费的行情软件网站下载入口,龙岗高端网站建设Android 动态应用图标(activity-alias)完全指南:原理、踩坑、可运行 Demo你是否突然发现桌面上的某个 App 图标“焕然一新”?这并不需要发版更新——动态应用图标就能做到:在本地按需切换桌面图标与名称,为…

Android 动态应用图标(activity-alias)完全指南:原理、踩坑、可运行 Demo

你是否突然发现桌面上的某个 App 图标“焕然一新”?这并不需要发版更新——动态应用图标就能做到:在本地按需切换桌面图标与名称,为节日、运营活动、主题风格带来更多玩法。本文从 0 到 1 带你掌握 activity-alias 的原理与最佳实践,并给出一套可直接运行的 Kotlin Demo。

一、为什么用动态图标?

  • 运营拉新/促活:双 11、春节、周年庆等换图标,视觉提醒强。

  • 功能曝光:上线大版本或关键功能时,图标可临时加“NEW”等元素。

  • 主题联动:跟随深浅色/节日皮肤,图标同步变化,增强整体感。

注意:Android 并不支持在运行时直接替换图标资源;官方可行方案是利用 activity-alias:为同一个入口 Activity 配多个“别名”,每个别名绑定不同的 icon/label,再通过 PackageManager 启用/禁用别名来“切图标”。


二、实现原理速览

  1. 一个真实的主入口 Activity(不带 LAUNCHER)。

  2. 若干 activity-alias(带 LAUNCHER),各自绑定不同图标/名称,目标都指向主入口。

  3. 切换时:用 PackageManager.setComponentEnabledSetting() 启用一个别名、禁用另一个,桌面即显示被启用的那一个。


三、快速上手(最小可运行 Demo)

运行环境示例:AGP 8.x、Gradle 8、Kotlin 1.9+、Java 17、compileSdk/targetSdk=35minSdk=24
包名示例:com.wantime.dynamicicons(请按你工程替换)。

1)AndroidManifest.xml(放 app/src/main)完整版可复制

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"><applicationandroid:allowBackup="true"android:label="@string/app_name"android:icon="@mipmap/ic_launcher"android:roundIcon="@mipmap/ic_launcher"android:theme="@style/Theme.DynamicIcons"><!-- 主入口,不带 LAUNCHER --><activityandroid:name=".MainActivity"android:exported="true" /><!-- 别名:主图标(默认启用) --><activity-aliasandroid:name=".MainActivityAlias"android:targetActivity=".MainActivity"android:enabled="true"android:icon="@mipmap/ic_launcher"android:roundIcon="@mipmap/ic_launcher"android:label="@string/app_name"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity-alias><!-- 别名:备选图标(默认禁用) --><activity-aliasandroid:name=".MainActivityAliasB"android:targetActivity=".MainActivity"android:enabled="false"android:icon="@mipmap/ic_launcher_alt"android:roundIcon="@mipmap/ic_launcher_alt"android:label="@string/app_name_alt"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity-alias><!-- 广播接收器(如只允许应用内触发,可将 exported 设为 false) --><receiverandroid:name=".IconChangeReceiver"android:enabled="true"android:exported="true"><intent-filter><action android:name="com.wantime.dynamicicons.ACTION_CHANGE_ICON" /></intent-filter></receiver></application>
</manifest>

关键点

  • 主 Activity 不要LAUNCHER

  • 初始只启用一个别名,避免桌面出现多个图标。

  • 图标建议使用 adaptive icon@mipmap 前景/背景)。

2)IconSwitcher.kt(切换核心)

package com.wantime.dynamiciconsimport android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManagerobject IconSwitcher {/*** 启用 enableAlias,禁用 disableAlias。* alias 参数既可写 ".MainActivityAlias"(相对名),也可写完整类名。*/fun switchTo(context: Context, enableAlias: String, disableAlias: String) {val pm = context.packageManagerval enableFqcn = fqcn(context, enableAlias)val disableFqcn = fqcn(context, disableAlias)// 先校验组件存在(含禁用态),避免名字写错直接崩pm.getActivityInfo(ComponentName(context, enableFqcn),PackageManager.MATCH_DISABLED_COMPONENTS)pm.getActivityInfo(ComponentName(context, disableFqcn),PackageManager.MATCH_DISABLED_COMPONENTS)pm.setComponentEnabledSetting(ComponentName(context, enableFqcn),PackageManager.COMPONENT_ENABLED_STATE_ENABLED,PackageManager.DONT_KILL_APP)pm.setComponentEnabledSetting(ComponentName(context, disableFqcn),PackageManager.COMPONENT_ENABLED_STATE_DISABLED,PackageManager.DONT_KILL_APP)}private fun fqcn(ctx: Context, name: String): String {return if (name.startsWith(".")) ctx.packageName + name else name}
}

3)IconChangeReceiver.kt(广播触发轮换)

package com.wantime.dynamiciconsimport android.content.BroadcastReceiver
import android.content.Context
import android.content.Intentclass IconChangeReceiver : BroadcastReceiver() {override fun onReceive(context: Context?, intent: Intent?) {val ctx = context ?: returnif (intent?.action != ACTION_CHANGE_ICON) returnval useAlt = flipFlag(ctx) // 每次取反,实现“轮换”val aliasA = ".MainActivityAlias"val aliasB = ".MainActivityAliasB"IconSwitcher.switchTo(context = ctx,enableAlias = if (useAlt) aliasB else aliasA,disableAlias = if (useAlt) aliasA else aliasB)}private fun flipFlag(context: Context): Boolean {val sp = context.getSharedPreferences("icon_demo", Context.MODE_PRIVATE)val next = !sp.getBoolean("useAlt", false)sp.edit().putBoolean("useAlt", next).apply()return next}companion object {const val ACTION_CHANGE_ICON = "com.wantime.dynamicicons.ACTION_CHANGE_ICON"}
}

4)MainActivity.kt(演示 UI:发广播/直接切)

package com.wantime.dynamiciconsimport android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.activity.ComponentActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompatclass MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 可选:边到边WindowCompat.setDecorFitsSystemWindows(window, false)setContentView(R.layout.activity_main)ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content)) { v, ins ->val sysBars = ins.getInsets(WindowInsetsCompat.Type.systemBars())v.setPadding(sysBars.left, sysBars.top, sysBars.right, sysBars.bottom)ins}// 方式一:广播触发(走 Receiver 轮换逻辑)findViewById<Button>(R.id.btnBroadcast).setOnClickListener {sendBroadcast(Intent(IconChangeReceiver.ACTION_CHANGE_ICON))}// 方式二:直接切(跳过 Receiver)findViewById<Button>(R.id.btnDirectA).setOnClickListener {IconSwitcher.switchTo(context = this,enableAlias = ".MainActivityAlias",disableAlias = ".MainActivityAliasB")}findViewById<Button>(R.id.btnDirectB).setOnClickListener {IconSwitcher.switchTo(context = this,enableAlias = ".MainActivityAliasB",disableAlias = ".MainActivityAlias")}}
}

5)activity_main.xml(三按钮演示)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:padding="24dp"android:gravity="center"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/btnBroadcast"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="广播触发(轮换切换)" /><View android:layout_width="match_parent" android:layout_height="12dp" /><Buttonandroid:id="@+id/btnDirectA"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="直接切到主图标(Alias)" /><View android:layout_width="match_parent" android:layout_height="12dp" /><Buttonandroid:id="@+id/btnDirectB"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="直接切到备用图标(AliasB)" />
</LinearLayout>

6)res/values/strings.xml(示例)

<resources><string name="app_name">Dynamic Icons</string><string name="app_name_alt">Dynamic Icons ✨</string>
</resources>

四、难点 & 常见坑(你刚踩过的坑都在这里)

1)Component class ... does not exist

根因:切换的别名名称与实际安装包中的不一致,或别名未合入最终 Manifest。
解决

  • 代码与 Manifest 名称逐字一致(如本文用 .MainActivityAlias / .MainActivityAliasB)。

  • 别名写在 app 主工程的 Manifest,不要藏在某个库里被覆盖。

  • 切换前用 getActivityInfo(... MATCH_DISABLED_COMPONENTS)存在性校验(本文已内置)。

  • Android Studio 打开 Merged Manifest,确认别名真的在且 targetActivity 正确。

2)包名/命名空间混乱

  • 你曾把 IconSwitcher 放在 com.example... 包,其他类在 com.wantime...,导致导包混乱。

  • 建议统一到同一包名,或确保 import 正确。

3)命名参数写错

  • 你的调用写了 enableAliasFqcn=,方法签名实际是 enableAlias=

  • Kotlin 命名参数拼错会编译不过或调用失败。

4)enableEdgeToEdge 未解析

  • androidx.activity:activity-ktx 1.6+,或直接改用:

    WindowCompat.setDecorFitsSystemWindows(window, false)

5)字符串常量不一致(动作、别名)

  • Manifest 的 <action> 与代码中 Intent(action) 必须完全一致

  • 建议改为 常量,别依赖 strings.xml。

6)图标资源规范

  • 优先用 @mipmap 的 Adaptive Icon(-anydpi-v26 前景/背景),避免拉伸/圆角不一致。

7)Launcher 延迟刷新

  • 大多数桌面会立刻刷新;个别机型可能缓存:轻按 Home 再返回、清除近期任务、或系统自行刷新即可。我们使用 DONT_KILL_APP 不会杀死进程。

8)安全性

  • 若不希望外部 App 触发换图标:把 receiverexported 改为 false,或给广播加签名权限。


五、调试清单(强烈建议照做一遍)

  1. 卸载旧包再安装,避免历史签名/包名残留。

  2. 运行后在 Logcat 打印实际活动清单(确认别名存在):

    // 加在 MainActivity onCreate 尾部,调试用 val pm = packageManager val pkg = pm.getPackageInfo( packageName, PackageManager.GET_ACTIVITIES or PackageManager.MATCH_DISABLED_COMPONENTS ) pkg.activities?.forEach { android.util.Log.i("DynIcon", "name=${it.name}, target=${it.targetActivity}") }

    你应看到:

    name=com.wantime.dynamicicons.MainActivity name=com.wantime.dynamicicons.MainActivityAlias target=com.wantime.dynamicicons.MainActivity name=com.wantime.dynamicicons.MainActivityAliasB target=com.wantime.dynamicicons.MainActivity

  3. 发广播测试(ADB):

    adb shell am broadcast -a com.wantime.dynamicicons.ACTION_CHANGE_ICON

    再看桌面图标是否轮换。

  4. 直接切:点 Demo 中两个“直接切换”按钮,验证互斥显示。


六、进阶玩法

  • 多套图标:增加更多 activity-alias(C、D…),代码里选择“启用其一、禁用其他全部”。

  • 节日/时间段策略shouldUseAltIcon() 按日期/服务器开关/AB 实验组来决定。

  • 动态名称:各 alias 可配不同 android:label,图标与名称一并切换。

  • 按主题切换:配合应用内主题切换逻辑,图标也随主题变化。

  • 限制触发源:仅在用户打开某页面或完成某任务时切换,避免频繁闪烁。


七、FAQ

Q:iOS 也能这样做吗?
A:iOS 提供 UIApplication.setAlternateIconName(有系统弹窗提醒),机制不同,本文不展开。

Q:切换会重启 App 吗?
A:使用 DONT_KILL_APP 不会杀进程;Launcher 侧刷新与否依厂商实现。

Q:能“还原”到最初图标吗?
A:可以,只要把“主图标别名”再次设为启用、其余禁用即可。


八、总结

  • 动态图标在 Android 上的正确姿势是 activity-alias + PackageManager 开关。

  • 关键三点:别名名与 Manifest 一致、主 Activity 不带 LAUNCHER、只启用一个入口。

  • 结合本文 Demo 你即可在项目内快速落地,并避免“组件不存在”“依赖缺失”等常见错误。

http://www.dtcms.com/a/436527.html

相关文章:

  • 怎么做网站的seo排名知乎android官网
  • 做查询网站费用石家庄邮电职业技术学院
  • 专门做机器人的网站建设英文网站赚钱的36个方法
  • 怎么推广自己的网站贵州百度推广优化报告
  • 左、右伪逆
  • 东莞网站建设哪里好高端品牌护肤品有哪些
  • 衡水网站建设选哪家流量对于网站盈利
  • 深圳网博网站建设制作网页填数据
  • 后台管理系统网站模板凡科网站怎么做外链
  • 用c语言做网站网站的模块
  • 济宁北湖建设局网站域名购买查询
  • 做网站的基本流程wordpress怎样加入代码
  • 阿里域名价格wordpress和seo权重
  • 从“黑暗森林法则”到“灰色丛林法则”
  • 我想在购物网站做代理app开发平台软件
  • 实用网站模板wordpress dux qq登录
  • 网站轮播图教程在线给图片加水印
  • 有做赛车网站的吗厦门工商网站查询企业信息
  • 黄岛网站建设公司首选网站开发费怎样入账
  • 专业做食材网站不用建网站怎么做淘宝客
  • 如何自己做资源类网站网站一年得多少钱
  • 重庆网站提示河北邯郸的最新通告
  • 怎么做网站后台管理系统网站开发招聘实习
  • 如何制作自己的网站书签做家务的男人免费观看网站
  • 如何制作网站图片线上销售模式有哪些
  • 洛谷题解P1518 [USACO2.4] 两只塔姆沃斯牛 The Tamworth Two
  • 网站建设辅助导航平面设计公司简介怎么写
  • 杭州搭建网站小程序开发兼职要多少钱
  • php技术应用于中小企业网站开发郑州企业网站优化服务哪家好
  • 大连企业做网站公司排名python 网站开发小项目