这里我把作这个功能中遇到的一些问题写在前面,是为了你们能先了解有什么问题存在,遇到这些问题的时候就不慌了,这里我把应用图标和名称先统一使用icon代替进行说明。android
一、动态替换icon,只能替换内置的icon,没法从服务器端获取来更新icon;git
二、动态替换icon之后,应用内更新的时候必需要切换到原始icon),不然可能致使更新安装失败(AS上表现为adb运行会失败),或者升级后应用图标出现多个甚至应用图标都不显示的状况(这些问题均可以经过下面我推荐的开发规则解决掉,因此这是一个坑点,不是确定会发生的问题,只不过大多数人会遇到。);github
三、Android系统动态替换app icon会有延迟,在不一样的手机系统上刷新icon的时间不同,大概在10秒左右,在这个时间内点击icon会提示应用未安装(提示可能会有差异,目前个人小米就不会提示任何信息,点了没有反应);shell
四、更换icon的代码运行后一会应用就闪退了,或者致使显示中的Dialog和PopupWindow报错崩溃(这个问题和第二个问题有很大的相关性,按我下面给出的规则实行的话是能够解决的。bash
update: 2019/02/25服务器
五、在android9.0系统上使用了修改应用图标功能后,在最近任务栏里面不显示咱们的app。关于这个问题在最后的开发规则里面也会给出解决方案。app
多入口配置,字面意思就是应用程序的多个入口配置,在AndroidManifest.xml中有一个叫activity-alias的标签,这个标签从字面上看就能理解是activity别名的意思,这里我给出一个示例做下相应的说明。布局
activity-alias例子说明:测试
<activity-alias
android:name="NewActivity1" // 注册这个组件的名字,不须要生成文件
android:enabled="false" // 是否显示这个启动项
android:label="Alias1" // 名称,也就是对应这个启动项显示在桌面上的app名称
android:icon="@mipmap/ic_launcher_round" //图标,也就是对应这个启动项显示在桌面上的app图标
android:targetActivity=".MainActivity" //对应的原来的Activity组件,这里路径要跟注册的Activity对应。
>
<intent-filter> // LAUNCHER 启动入口
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
复制代码
而后这里我先作一个多个启动入口所有显示的app示例,这里须要写的代码都在清单文件中,代码以下:ui
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wepon.switchicondemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!--原Activity-->
<activity
android:enabled="true"
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--别名1-->
<activity-alias
android:name="NewActivity1"
android:enabled="true"
android:label="Alias1"
android:icon="@mipmap/ic_launcher_round"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<!--别名2-->
<activity-alias
android:name="NewActivity2"
android:enabled="true"
android:label="Alias2"
android:icon="@mipmap/ic_launcher"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
</manifest>
复制代码
运行后的效果以下:
固然了,实际项目中咱们只会显示一个图标,这里咱们只须要把"别名1"和"别名2"的android:enabled="true"改成"false"就好了,这样就只显示一个图标了,就不放效果图了。
立刻春节了,咱们产品说到哪一个时间点咱们的应用图标就要换成春节用的图标了,固然,前面说了这些图标要先在应用写好,不是经过服务器动态拿的,而是应用内已经写好的。那这个时候咱们就须要经过代码进行应用图标的动态切换了,这里我给出Demo里面布局如图:
这里三个按钮点击后切换到相应的应用图标和名称,"原ACTIVITY"表明只显示MainActivity这个原来的启动入口,"ALIAS_1"表明别名1,以此类推。
这三个按钮点击对应的代码以下:
/**
* 设置Activity为启动入口
* @param view
*/
public void setActivity(View view) {
PackageManager packageManager = getPackageManager();
packageManager.setComponentEnabledSetting(new ComponentName(this, getPackageName() +
".NewActivity1"), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager
.DONT_KILL_APP);
packageManager.setComponentEnabledSetting(new ComponentName(this, getPackageName() +
".NewActivity2"), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager
.DONT_KILL_APP);
packageManager.setComponentEnabledSetting(new ComponentName(this, getPackageName() +
".MainActivity"), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager
.DONT_KILL_APP);
}
/**
* 设置别名1为启动入口
* @param view
*/
public void setAlias1(View view) {
PackageManager packageManager = getPackageManager();
packageManager.setComponentEnabledSetting(new ComponentName(this, getPackageName() +
".NewActivity1"), PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
packageManager.setComponentEnabledSetting(new ComponentName(this, getPackageName() +
".NewActivity2"), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager
.DONT_KILL_APP);
packageManager.setComponentEnabledSetting(new ComponentName(this, getPackageName() +
".MainActivity"), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager
.DONT_KILL_APP);
}
/**
* 设置别名2为启动入口
* @param view
*/
public void setAlias2(View view) {
PackageManager packageManager = getPackageManager();
packageManager.setComponentEnabledSetting(new ComponentName(this, getPackageName() +
".NewActivity1"), PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
packageManager.setComponentEnabledSetting(new ComponentName(this, getPackageName() +
".NewActivity2"), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager
.DONT_KILL_APP);
packageManager.setComponentEnabledSetting(new ComponentName(this, getPackageName() +
".MainActivity"), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager
.DONT_KILL_APP);
}
复制代码
!!!这里要注意一个点,就是ComponentName里面的路径必定要写全了,若是在报错日志看到相似找不到这个路径的日志的话,那十有八九就是这个问题了。
切换的代码其实不多,你们看了基本上也都明白了,这里就不作过多解释了。这里我基于隐藏因此别名的状况下,也就是只显示原来的一个APP图标的状况,点一下"ALIAS_1"这个按钮,也就是将图标切换到"别名1",最终效果以下:
能够看到只显示这一个入口了,可是若是你们在点了"ALIAS_1"以后,立刻就返回到主页看盯着这个app的图标,咱们会发如今它在大概10s内是没有变化的,在大概10s后才更新成咱们切换的那个图标,还有,在它没更新成功的时候若是咱们点这个原来的图标,通常会吐司一条“未安装”之类的信息(华为是未安装),这里个人小米是点了没有反应,要等大概10s秒后更新成功了才能点这个图标进入应用。因此,经过代码咱们"已经作到了"图标的切换,可是!!!
那是否是这样就完了呢??显然不是的,问题还挺多的,我一一道来。
不知道你们在点了切换的按钮后有没有一直停在app里面,没有的话咱们尝试点完后在app里面不要回到桌面,若是停在app里面的话,咱们会在大概10s,也就是更新成功的时候,应用就会发生闪退了,也就是坑4这个问题。这个问题我作了不少测试,总结了一下缘由和规避的方法,缘由是咱们在代码里面设置了咱们原来的真实的那个MainActiviy的enable为false,代码以下:
packageManager.setComponentEnabledSetting(new ComponentName(this, getPackageName() +
".MainActivity"), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager
.DONT_KILL_APP);
复制代码
只要代码设置了真实的那个Activity的enable为false,也就是代码对应的PackageManager.COMPONENT_ENABLED_STATE_DISABLED,那就会致使咱们的应用闪退,那是否是咱们不设置这个就行了呢?那咱们不设置这个的话怎么隐藏真实的MainActivity的图标呢?这个解决方法后面我会提出来。
可是,你觉得只有这个问题吗?其实还有坑,只是这个坑不容易发现,这个时候咱们回到咱们当前的状况,也就是当前咱们已经切换到"别名1"了,桌面上也只有这个图标了,咱们也能点击这个图标正常使用咱们的应用,这些都没有问题,咱们觉得都是正常的了。可是,这个时候,若是咱们经过adb,使用Android Studio运行项目的时候,会提示launch app失败,失败的信息以下:
01/10 16:48:54: Launching app
$ adb shell am start -n "com.wepon.switchicondemo/com.wepon.switchicondemo.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Error while executing: am start -n "com.wepon.switchicondemo/com.wepon.switchicondemo.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.wepon.switchicondemo/.MainActivity }
Error type 3
Error: Activity class {com.wepon.switchicondemo/com.wepon.switchicondemo.MainActivity} does not exist.
Error while Launching activity
复制代码
一样致使的问题还有一个,就是咱们代码动态切换了app图标以后,应用升级,也就是更新应用的时候,会致使安装失败,或者是安装完成后出现多个图标甚至是没有图标出如今桌面上了!!这些问题是要遇到运行,或者升级包的时候才会发现的,可是那时候发现就晚了,因此这是一个比较大的坑,这里对应的坑就是我在前面提到的坑2这个点。
这里还有一种状况也会致使坑2的发生,例如,咱们Demo如今是一个MainActivity和两个别名,若是咱们在下一个版本把这两个别名删除了,或者删除了咱们当前安装包正在显示的别名,那么安装的新版本可能就不会有应用图标显示了,那就会致使咱们应用安装成功了,可是却没有入口!
相似的问题还有一些,主要都是在应用升级后发生,并且不论是致使安装失败、安装后没有图标或者安装后产生多个图标,这些现象都是很是严重的,可是这些问题咱们都是能够避免的,这里我总结了一些规则,按这些规则进行操做的话是不会产生以上这些问题的,固然,若是还有其余问题的话欢迎交流,由于咱们的app也在作这个功能。
一、Activity的android:enabled属性,必定不要在代码里面去设置enabled这个值,不然会在切换图标的过程致使应用闪退,目前测试了小米、华为和官方模拟器都有在这个问题。
二、清单文件中设置Activity的android:enabled="false”,这个在以后的版本就固定这个值,若是设置为true了,则有可能在应用升级后出现多个图标;
三、而后为咱们的应用设置一个默认的Activity-alias用来显示图标(也是惟一一个显示的,通常咱们也只须要显示一个图标),也是用来代替第一点设置Activity的android:enabled="false”可能致使的桌面上没有应用图标的问题;
四、Activity-alias的android:enabled="true"的默认显示的项尽量不要中途进行变更,若是确实须要使用新的默认值,则使用代码进行动态变换;
五、Activity-alias的android:enabled="true"的不要设置为多个,不然会出现多个图标,若是试图经过代码进行隐藏其中的一个或者几个,可能会出现图标消失的状况,这个第2点已经有提过了;
六、后面新的版本若是要加新的Activity-alias,那么都要设置android:enabled=“false”,这个清单文件中的值要设置成false,而后再经过代码动态变换;
七、后面新的版本的Activity-alias必须包含上一个版本的全部Activity-alias,主要是防止覆盖安装后应用图标消失的状况;
update:2019年1月14日下午5:09 新发现须要注意的问题--------------
八、设置enabled为false的Activity没法在代码中经过显式intent打开,会报错。例如:我在应用里面推送服务推送了一条指定打开页面SplashActivity的通知消息,而这个SplashActivity恰好设置了enabled为false的话,是打不开的,会有错误日志以下,其它同理(因此在项目里我将启动入口的Activity单独写出来了,除了做为启动入口用,就没有别的地方再用到这个Activity了。):
update:2019年2月25日 新发现须要注意的问题--------------
九、这个问题是关于一开始说的第5个点,在9.0系统的最近任务栏不不显示咱们的应用了,若是遇到这个问题,能够尝试设置一个闪屏activity,启动模式设置为SingleInstance,经过这个设置的闪屏activity来启动咱们的应用就能够了。或者设置咱们的主页activity为SingleInstance启动模式也是能够的,关键是看你们的项目需求,设置不同从后台回到应用显示的页面也就不同。这里的关键就是咱们设置了enabled为false的activity要和其余的activity不在一个activity栈里面就好了(我暂时没明白这块的原理,也是猜测加代码实践后解决的)。
以上就是我在作这个功能的过程当中总结出来的规则,目前没有发如今其它的问题,有别的问题的朋友欢迎留言讨论,还有,按照这些规则作的话,覆盖安装后的应用图标也会是你上一次经过代码动态修改为功的图标,由于手机的Launcher会有记录,也就是咱们经过代码会修改这个在Launcher中的记录。
对了,咱们在清单文件中配置的Activity和Activity-alias的icon和label信息在新的版本中都是能够换的,这些跟代码无关了,也就是跟咱们日常换下app图标名称是同样的操做,但愿你们不要误解了这里 -_-!!!。
最后,可能有的同窗会想,我如今的应用入口就是默认的一个Activity,默认的enable也是true,也没有配置任何的Activity-alias,而我在上面说的规则中都是建议清单文件中的Activity的android:enabled="false”,那有人可能就会想个人新版本设置成false会不会致使个人图标入口不见了呢?那么我告诉你,若是按照我上面说的规则对你的新版本(能够动态切换图标的版本)进行设置的话,是不会有以上状况产生的,这里我给一个针对这种状况进行升级的版本的清单文件的示例:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wepon.switchicondemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!--原Activity enabled固定为false,且不经过代码进行设置 -->
<activity
android:enabled="false"
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 固定设置一个默认的别名,用来替代原Activity-->
<activity-alias
android:name="DefaultAlias"
android:enabled="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher_round"
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,双12,51,国庆等等,均可以给配置一个别名在清单文件,这里我只示例了一个。-->
<activity-alias
android:name="NewActivity1"
android:enabled="false"
android:label="Alias1"
android:icon="@mipmap/ic_launcher"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
</manifest>
复制代码
这里放一个简单的示例demo仅供参考