Android 动态切换应用图标方案

这篇具有很好参考价值的文章主要介绍了Android 动态切换应用图标方案。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

经常听到大家讨论类似的需求,怀疑大厂是不是用了此方案,据我个人了解,多数头部 app 其实都是发版来更新节假日的 icon。当然本方案也是一种可选的方案,以前我也调研过,存在问题和作者所述差不多,此外原文链接作者也回复了很多疑问,可以同时了解。

效果图

Android 动态切换应用图标方案

产品需求

市面上很多App能根据特定活动,动态切换应用图标达到宣传目的,例如淘宝双十一,国庆节等等。那么我们怎样才能在不发新版本的情况下,动态切换应用图标呢?

具体方案

1.图标更换:在AndroidManifest设置应用入口Activity的别名,然后通过setComponentEnabledSetting动态启用或禁用别名进行图标切换。

2.控制图标显示:冷启动App时,调用接口判断是否需要切换icon。

3.触发时机:监听App前后台切换,当App处于后台时切换图标,使得用户无感知。

代码实现

在AndroidManifest.xml中给入口Activity设置activity-alias

<application
    android:name=".MyApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/Theme.SwitchIcon">

    <!-- 原MainActivity -->
    <activity android:name=".MainActivity" />

    <!-- 固定设置一个默认的别名,用来替代原MainActivity -->
    <activity-alias
        android:name=".DefaultAliasActivity"
        android:enabled="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:targetActivity=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity-alias>


    <!-- 别名1,特定活动需要的图标如:双11,国庆节等 -->
    <activity-alias
        android:name=".Alias1Activity"
        android:enabled="false"
        android:icon="@mipmap/ic_launcher_show"
        android:label="@string/app_name"
        android:targetActivity=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity-alias>

</application>

activity-alias标签中的属性如下:

标签

作用

android:name

别名,命名规则同Actively

android:enabled

是否启用别名,这里的主要作用的控制显示应用图标

android:icon

应用图标

android:label

应用名

android:targetActivity

必须指向原入口Activity

在MainActivity中,通过启用或禁用别名进行图标切换

/**
 * 设置默认的别名为启动入口
 */
public void setDefaultAlias() {
    PackageManager packageManager = getPackageManager();

    ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity");
    packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);

    ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity");
    packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
    }

/**
 * 设置别名1为启动入口
 */
public void setAlias1() {
    PackageManager packageManager = getPackageManager();
    ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity");
    packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);

    ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity");
    packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}

ForegroundCallbacks监听App前后台切换

/**
 * 监听App前后台切换
 */
public class ForegroundCallbacks implements Application.ActivityLifecycleCallbacks {
    public static final long CHECK_DELAY = 500;
    public static final String TAG = ForegroundCallbacks.class.getName();

    public interface Listener {
        void onForeground();

        void onBackground();
    }

    private static ForegroundCallbacks instance;
    private boolean foreground = false, paused = true;
    private Handler handler = new Handler();
    private List<Listener> listeners = new CopyOnWriteArrayList<Listener>();
    private Runnable check;

    public static ForegroundCallbacks init(Application application) {
        if (instance == null) {
            instance = new ForegroundCallbacks();
            application.registerActivityLifecycleCallbacks(instance);
        }
        return instance;
    }

    public static ForegroundCallbacks get(Application application) {
        if (instance == null) {
            init(application);
        }
        return instance;
    }

    public static ForegroundCallbacks get(Context ctx) {
        if (instance == null) {
            Context appCtx = ctx.getApplicationContext();
            if (appCtx instanceof Application) {
                init((Application) appCtx);
            }
            throw new IllegalStateException(
                    "Foreground is not initialised and " +
                            "cannot obtain the Application object");
        }
        return instance;
    }

    public static ForegroundCallbacks get() {
        if (instance == null) {
            throw new IllegalStateException(
                    "Foreground is not initialised - invoke " +
                            "at least once with parameterised init/get");
        }
        return instance;
    }

    public boolean isForeground() {
        return foreground;
    }

    public boolean isBackground() {
        return !foreground;
    }

    public void addListener(Listener listener) {
        listeners.add(listener);
    }

    public void removeListener(Listener listener) {
        listeners.remove(listener);
    }

    @Override
    public void onActivityResumed(Activity activity) {
        paused = false;
        boolean wasBackground = !foreground;
        foreground = true;
        if (check != null)
            handler.removeCallbacks(check);
        if (wasBackground) {
            Log.d(TAG, "went foreground");

            for (Listener l : listeners) {
                try {
                    l.onForeground();


                } catch (Exception exc) {
                    Log.d(TAG, "Listener threw exception!:" + exc.toString());
                }
            }
        } else {
            Log.d(TAG, "still foreground");
        }
    }

    @Override
    public void onActivityPaused(Activity activity) {
        paused = true;
        if (check != null)
            handler.removeCallbacks(check);
        handler.postDelayed(check = new Runnable() {
            @Override
            public void run() {
                if (foreground && paused) {
                    foreground = false;
                    Log.d(TAG, "went background");
                    for (Listener l : listeners) {
                        try {
                            l.onBackground();
                        } catch (Exception exc) {
                            Log.d(TAG, "Listener threw exception!:" + exc.toString());
                        }
                    }
                } else {
                    Log.d(TAG, "still foreground");
                }
            }
        }, CHECK_DELAY);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }
}

需要在Application中调用ForegroundCallbacks.init(this)进行初始化。

在MainActivity中实现ForegroundCallbacks.Listener对App进行监听,当处于后台判断是否切换应用图标

完整的MainActivity代码:

public class MainActivity extends AppCompatActivity implements ForegroundCallbacks.Listener {

    private int position = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //添加app前后台监听
        ForegroundCallbacks.get(this).addListener(this);

        findViewById(R.id.tv_default).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                position = 0;
            }
        });

        findViewById(R.id.tv_alias1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                position = 1;
            }
        });

    }

    @Override
    protected void onDestroy() {
        // 移除app前后台监听
        ForegroundCallbacks.get(this).removeListener(this);
        super.onDestroy();
    }

    @Override
    public void onForeground() {

    }

    @Override
    public void onBackground() {
        //根据具体业务需求设置切换条件,我公司采用接口控制icon切换
        if (position == 0) {
            setDefaultAlias();
        } else {
            setAlias1();
        }
    }


    /**
     * 设置默认的别名为启动入口
     */
    public void setDefaultAlias() {
        PackageManager packageManager = getPackageManager();

        ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity");
        packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);

        ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity");
        packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
    }

    /**
     * 设置别名1为启动入口
     */
    public void setAlias1() {
        PackageManager packageManager = getPackageManager();

        ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity");
        packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);

        ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity");
        packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
    }
}

具体缺陷

具体缺陷如下:

1. 切换icon会关闭应用进程,不是崩溃所以不会上报bugly。

2. 切换icon需要时间,部分华为机型要10s左右,之后能正常打开。

3. 切换icon过程中,部分机型点击图标无法打开应用,提示应用未安装。文章来源地址https://www.toymoban.com/news/detail-429126.html

Demo的github地址

https://github.com/FengFeiBiao/SwitchIcon

到了这里,关于Android 动态切换应用图标方案的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 拯救者windows11系统经常出现wifi图标消失断网解决思路

    最近刚买回来一台联想拯救者电脑,但是,在使用过程中总是会出现wifi图标突然消失的情况,而且多次重启电脑或者关机电脑也没有用。也不知道是电脑的原因还是系统的原因 win+x打开窗口,选择设备管理器,打开后,展开网络适配器。  选择自驱动(红框标注),双击打开

    2024年02月06日
    浏览(37)
  • 关于Android平板横竖屏切换失败的解决方案

    在安卓系统中,实现横竖屏切换可以通过修改AndroidManifest.xml文件和编写代码来实现。以下是详细的操作步骤: 1.修改AndroidManifest.xml文件: 在你的Android项目的AndroidManifest.xml文件中,找到你想要横竖屏切换的Activity。在activity标签中,添加 android:screenOrientation 属性,并设置为

    2023年04月22日
    浏览(58)
  • android 应用内切换语言

    1.1 选中res,右键选择Android Resource Directory = Locale = 选择你需要的语言 1.2 将values下的strings.xml复制到对应语言下的values下 1.3 将不同strings.xml中的内容改成对应的语言文字,不同strings.xml中同一个内容的name是相同的 1.4 在布局文件或者代码中引用strings.xml里的内容 我选择了英文和

    2023年04月08日
    浏览(23)
  • Android开发-应用中英文(语言)切换(二)

            APP中针对不同国家不同地区的人群使用那么应用的语言自然也要能够随时进行切换,最近做的项目有中文和英文切换的需求,所以在了解了一下网上常用的方法后记录一下我使用的方法,只是简单的应用,后续如果有不同需求需要自己去改。♻          新建工程就

    2024年02月09日
    浏览(34)
  • spring多数据源动态切换的实现原理及读写分离的应用

    AbstractRoutingDataSource 是Spring框架中的一个抽象类,可以实现多数据源的动态切换和路由,以满足复杂的业务需求和提高系统的性能、可扩展性、灵活性。 多租户支持:对于多租户的应用,根据当前租户来选择其对应的数据源,实现租户级别的隔离和数据存储。 分库分表:为了

    2024年02月14日
    浏览(30)
  • android 12.0Launcher3长按拖拽时,获取当前是哪一屏,获取当前多少个应用图标

    在12.0定制化开发手机项目中,如果专门适配老年机的时候,这时客户提出要求,如果最后一屏未满时,不让拖拽到后面一屏的空屏中这样就需要获取当前是哪一屏,并且要知道当前有多少个Item,总共一屏最多多少个item 所以就需要从Workspace.java入手,来分析解决这个问题 首选

    2024年02月06日
    浏览(48)
  • Android studio TCP网络调试助手应用开发(支持TCP Server与Client切换)

            在前几篇的文章中带大家完成了 基于TCP的物联网安卓应用开发 ,教程内容是创建了一个 TCP客户端并连接服务器完成数据通信的过程 ,后不久又发布了一个 ESP8266创建TCP 服务器与安卓的客户端进行通信 的一个文章,当时在文章中提到“如果大家有需要将ESP8266配置

    2024年02月06日
    浏览(49)
  • 【Android】Jadx动态调试应用

    Jadx已支持动态调试APP,但一直没试过,从逆向角度尝试走一遍流程并熟悉,方便日后翻阅。 2.1 动态调试原理 动态调试的原理可以概括为以下几个步骤: 启动应用程序进程:使用调试器或其他工具启动应用程序进程,并将其连接到调试器。 注入调试代码:在应用程序进程中

    2024年02月07日
    浏览(27)
  • ADSL经常断流与断线的问题分析与解决方案

    ADSL为什么经常断线呢?通常是用ADSL MODEM能成功拨号登陆,但上网的时候数据流传输突然中断,没有反应,过一阵子又自动恢复正常,表现为网页打不开,下载中断,在线收看或收听的视频或音频中断。下面是引起ADSL经常断线的原因和解决办法。 一、线路问题 解决办法 :是

    2024年02月07日
    浏览(27)
  • 【Android】RecyclerView的经常用到的属性解析与性能优化

    我们可以把创建对象从 oncreateviewholder 中提前到外面,用集合进行存储,用 的时候直接从集合中取 如果item的高度固定的话可以设置setHasFixedSize(true),这样RecyclerView在onMeasure阶段可以直接计算出高度,不需要多次计算子ItemView的高度。 setHasFixedSize(true)时如果是通过Adapter的增删

    2024年03月23日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包