先說(shuō)說(shuō)遇到的坑
坎坷在前,坦途在后。這里先把遇到的一些問(wèn)題寫在前面,為了便于各位看官能先了解有什么問(wèn)題存在,步驟心中有,雙手直顫抖的情況一點(diǎn)也不鮮見(jiàn),所以此處對(duì)問(wèn)題做一個(gè)匯總。
1、動(dòng)態(tài)替換icon,只能替換內(nèi)置的icon,無(wú)法從服務(wù)器端獲取來(lái)更新icon;
2、動(dòng)態(tài)替換icon以后,應(yīng)用內(nèi)更新的時(shí)候必須要切換到原始icon,否則可能導(dǎo)致出現(xiàn)更新安裝失敗(AS上表現(xiàn)為adb運(yùn)行會(huì)失敗),或者升級(jí)后出現(xiàn)多個(gè)應(yīng)用圖標(biāo)或者應(yīng)用圖標(biāo)桌面不顯示的情況(這些問(wèn)題都可以通過(guò)下面的開(kāi)發(fā)規(guī)則規(guī)避掉,這個(gè)坑不是絕對(duì)會(huì)發(fā)生,但是大多數(shù)人可能會(huì)遇到。);
3、Android系統(tǒng)動(dòng)態(tài)更換Icon會(huì)有時(shí)延,在不同的手機(jī)系統(tǒng)上刷新Icon的時(shí)間有差別,大概率會(huì)在5~10s左右,在這個(gè)時(shí)間內(nèi)點(diǎn)擊icon會(huì)"提示應(yīng)用未安裝"(大致都是這個(gè)提示);
4、手機(jī)更換Icon后,有時(shí)候會(huì)在3~5s內(nèi),退回到桌面(有的可能是閃退)(這個(gè)問(wèn)題和第2個(gè)問(wèn)題有很大的相關(guān)性,按我下面的步驟操作可以解決該問(wèn)題。
多入口配置
顧名思義就是配置應(yīng)用程序多個(gè)入口,在AndroidManifest.xml中有一個(gè)叫activity-alias的標(biāo)簽,望文生義,你理解的沒(méi)錯(cuò),就是activity別名,示例代碼如下:
<activity-alias
android:name="DefaultAlias" // 注冊(cè)這個(gè)組件的名字,不需要?jiǎng)?chuàng)建該文件
android:enabled="false" // 桌面是否顯示這個(gè)啟動(dòng)項(xiàng)
android:label="支付寶" // 顯示的名稱,也就是對(duì)應(yīng)這個(gè)啟動(dòng)項(xiàng)顯示在桌面上的app名稱
android:icon="@mipmap/ic_launcher" // Icon圖標(biāo),也就是對(duì)應(yīng)這個(gè)啟動(dòng)項(xiàng)顯示在桌面上的app圖標(biāo)
android:targetActivity=".MainActivity" //對(duì)應(yīng)的原來(lái)的Activity組件,這里路徑要跟注冊(cè)的Activity對(duì)應(yīng)。
>
<intent-filter> // LAUNCHER 啟動(dòng)入口
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
顯示多個(gè)啟動(dòng)入口
如何做一個(gè)多個(gè)啟動(dòng)入口的app(桌面上顯示多個(gè)圖標(biāo)),代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xk.ChangeIcon">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
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" // 這里設(shè)置的可是 true 喲
android:label="Alias1"
android:icon="@mipmap/ic_alias1_launcher"
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" // 這里設(shè)置的可是 true 喲
android:label="Alias2"
android:icon="@mipmap/ic_alias2_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>
效果見(jiàn)下圖,有圖有真相
可以看到桌面上顯示了三個(gè)圖標(biāo),進(jìn)入的都是MainActivity這個(gè)頁(yè)面,
當(dāng)然了,實(shí)際項(xiàng)目中我們只會(huì)顯示一個(gè)圖標(biāo),這里我們只需要把"別名1"和"別名2"的android:enabled="true"改為"false"就行了,這樣就只顯示一個(gè)圖標(biāo)了,就不放效果圖了。

1.jpg
通過(guò)代碼動(dòng)態(tài)更換應(yīng)用圖標(biāo)
今天是2020年1月17日,農(nóng)歷臘月23,也就是民俗中的小年,馬上春節(jié)了,我們領(lǐng)導(dǎo)要求大年三十晚上我們的應(yīng)用Icon變換成春節(jié)特供Icon,到正月十五的晚上切換為原來(lái)的Icon。當(dāng)然,前面說(shuō)了這些圖標(biāo)要預(yù)先設(shè)置在應(yīng)用里。然后通過(guò)服務(wù)端提供的接口作為開(kāi)關(guān)控制圖標(biāo)的切換。
下面貼代碼嘍
算了....你想罵就罵我吧,我混蛋,我下賤.....咳,還是貼點(diǎn)吧。
PackageManager mPackageManager = getApplicationContext().getPackageManager();
//拿到默認(rèn)的組件
ComponentName defaultComponent = new ComponentName(getBaseContext(), "com.xk.ChangeIcon.DefaultAlias");
//拿到我注冊(cè)的別名ChangeIcon組件
ComponentName newComponent = new ComponentName(getBaseContext(), "com.xk.ChangeIcon.ChangeIcon");
/**
* 啟用組件
*
* @param componentName
*/
private void enableComponent(ComponentName componentName) {
int state = mPackageManager.getComponentEnabledSetting(componentName);
if (state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
//已經(jīng)啟用
return;
}
mPackageManager.setComponentEnabledSetting(componentName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}
/**
* 禁用組件
*
* @param componentName
*/
private void disableComponent(ComponentName componentName) {
int state = mPackageManager.getComponentEnabledSetting(componentName);
if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
//已經(jīng)禁用
return;
}
mPackageManager.setComponentEnabledSetting(componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
特別說(shuō)明:ComponentName里面的路徑一定要寫全了,如果在報(bào)錯(cuò)日志看到類似找不到這個(gè)路徑的日志的話,那十有八九就是這個(gè)問(wèn)題了。
其實(shí)切換的代碼很少,邏輯也很簡(jiǎn)單,啥?你還看不明白?蹲墻根去.....不解釋。這里我基于隱藏Alias2別名和MainActivity的情況下,顯示Alias1 Icon的情況,見(jiàn)圖如面:

2.jpg
可以看到只顯示這一個(gè)入口了,但是如果執(zhí)行切換Icon的代碼之后,退回到桌面觀察App的Icon,你會(huì)發(fā)現(xiàn)會(huì)在5~10s內(nèi)才會(huì)更改,再者,在沒(méi)有更換成功的時(shí)候如果我們點(diǎn)這個(gè)原來(lái)的App Icon,會(huì)有"提示應(yīng)用未安裝"的提示。至此,通過(guò)代碼我們已經(jīng)實(shí)現(xiàn)了Icon的切換,但是?。?!
這就完了??你以為這就完了??
走兩步,,聽(tīng)我口令,走兩步......一口老血噴出口......這么多bug.....繼續(xù)往下看
你發(fā)現(xiàn)了什么問(wèn)題?
更換了Icon之后,你停留在app打開(kāi)的界面(不要通過(guò)手動(dòng)回到桌面),也就那么一會(huì)兒,倏爾、一剎那、一瞬間.....應(yīng)用自動(dòng)會(huì)到了桌面類似于應(yīng)用發(fā)生了閃退,這就是前面提到的坑4這個(gè)問(wèn)題。為什么會(huì)這樣子?經(jīng)過(guò)多次測(cè)試,原來(lái)是是因?yàn)榇a里面設(shè)置了我們?cè)瓉?lái)的真實(shí)的那個(gè)MainActiviy的enable為false,就是下面這行代碼:
mPackageManager.setComponentEnabledSetting(componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
只要代碼設(shè)置了真實(shí)的那個(gè)Activity的enable為false,也就是代碼對(duì)應(yīng)的PackageManager.COMPONENT_ENABLED_STATE_DISABLED,那就會(huì)導(dǎo)致我們的應(yīng)用閃退,那是不是我們不設(shè)置這個(gè)就好了呢?那我們不設(shè)置這個(gè)的話怎么隱藏真實(shí)的MainActivity的圖標(biāo)呢?這里先賣個(gè)關(guān)子。
你以為只有這個(gè)問(wèn)題嗎?淡定,其實(shí)還有坑,只是這個(gè)坑不容易發(fā)現(xiàn),這個(gè)時(shí)候我們回到我們當(dāng)前的情況,也就是當(dāng)前我們已經(jīng)切換到"Alias2"了,桌面上也只有這個(gè)圖標(biāo)了,我們也能點(diǎn)擊這個(gè)圖標(biāo)正常使用我們的應(yīng)用,這些都沒(méi)有問(wèn)題,我們以為都是正常的了。但是,這個(gè)時(shí)候,使用Android Studio運(yùn)行項(xiàng)目的時(shí)候,會(huì)提示launch app失敗,控制臺(tái)輸出信息如下:
Error while executing: am start -n "com.xk.ChangeIcon/com.xk.ChangeIcon.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.xk.ChangeIcon/.MainActivity }
Error type 3
Error: Activity class {com.xk.ChangeIcon/com.xk.ChangeIcon.MainActivity} does not exist.
Error while Launching activity
你以為坑完了,當(dāng)然不會(huì),因?yàn)殚_(kāi)篇提到了4個(gè)坑,當(dāng)然下文都會(huì)給出相關(guān)的解決方法。就是我們代碼動(dòng)態(tài)更換了App Icon之后,在應(yīng)用升級(jí)(更新應(yīng)用)的時(shí)候,會(huì)導(dǎo)致安裝失敗,或者是安裝完成后出現(xiàn)多個(gè)圖標(biāo)甚至是沒(méi)有圖標(biāo)出現(xiàn)在桌面上了!!這些問(wèn)題是要遇到運(yùn)行,或者升級(jí)包的時(shí)候才會(huì)發(fā)現(xiàn)的。臥槽,菜都上桌了,你才發(fā)現(xiàn)氯化鈉放多了,這能忍???忍一時(shí)風(fēng)平浪靜,滾犢子..... 這就是我在前面提到的坑2這個(gè)點(diǎn)。
這里還有一種情況也會(huì)導(dǎo)致坑2的發(fā)生,例如,我們Demo現(xiàn)在是一個(gè)MainActivity和兩個(gè)別名,如果我們?cè)谙乱粋€(gè)版本把這兩個(gè)別名刪除了,或者刪除了我們當(dāng)前安裝包正在顯示的別名,那么安裝的新版本可能就不會(huì)有應(yīng)用圖標(biāo)顯示了,那就會(huì)導(dǎo)致我們應(yīng)用安裝成功了,但是卻沒(méi)有入口!
類似的問(wèn)題還有一些,主要都是在應(yīng)用升級(jí)后發(fā)生,而且不管是導(dǎo)致安裝失敗、安裝后沒(méi)有圖標(biāo)或者安裝后產(chǎn)生多個(gè)圖標(biāo),這些現(xiàn)象都是非常嚴(yán)重的,但是這些問(wèn)題我們都是可以避免的。做了這么多鋪墊,真像唐僧,下面上主菜。
動(dòng)態(tài)更換應(yīng)用圖標(biāo)填坑指南
1、Activity的android:enabled屬性,一定不要在代碼里面去設(shè)置enabled這個(gè)值,否則會(huì)在切換圖標(biāo)的過(guò)程導(dǎo)致應(yīng)用閃退,目前測(cè)試了小米、華為和官方模擬器都有在這個(gè)問(wèn)題。
2、清單文件中設(shè)置Activity的android:enabled="false”,這個(gè)在之后的版本就固定這個(gè)值,如果設(shè)置為true了,則有可能在應(yīng)用升級(jí)后出現(xiàn)多個(gè)圖標(biāo);
3、然后為我們的應(yīng)用設(shè)置一個(gè)默認(rèn)的Activity-alias用來(lái)顯示圖標(biāo)(也是唯一一個(gè)顯示的,一般我們也只需要顯示一個(gè)圖標(biāo)),也是用來(lái)代替第一點(diǎn)設(shè)置Activity的android:enabled="false”可能導(dǎo)致的桌面上沒(méi)有應(yīng)用圖標(biāo)的問(wèn)題;
4、Activity-alias的android:enabled="true"的默認(rèn)顯示的項(xiàng)盡可能不要中途進(jìn)行變動(dòng),如果確實(shí)需要使用新的默認(rèn)值,則使用代碼進(jìn)行動(dòng)態(tài)變換;
5、Activity-alias的android:enabled="true"的不要設(shè)置為多個(gè),否則會(huì)出現(xiàn)多個(gè)圖標(biāo),如果試圖通過(guò)代碼進(jìn)行隱藏其中的一個(gè)或者幾個(gè),可能會(huì)出現(xiàn)圖標(biāo)消失的情況,這個(gè)第2點(diǎn)已經(jīng)有提過(guò)了;
6、后面新的版本如果要加新的Activity-alias,那么都要設(shè)置android:enabled=“false”,這個(gè)清單文件中的值要設(shè)置成false,然后再通過(guò)代碼動(dòng)態(tài)變換;
7、后面新的版本的Activity-alias必須包含上一個(gè)版本的所有Activity-alias,主要是防止覆蓋安裝后應(yīng)用圖標(biāo)消失的情況;
以上就是在做這個(gè)功能的過(guò)程中總結(jié)出來(lái)的規(guī)律,目前暫時(shí)沒(méi)有發(fā)現(xiàn)在其它的問(wèn)題。還有,按照這些規(guī)則做的話,覆蓋安裝后的應(yīng)用圖標(biāo)也會(huì)是你上一次通過(guò)代碼動(dòng)態(tài)修改成功的圖標(biāo),因?yàn)槭謾C(jī)的Launcher會(huì)有記錄,也就是我們通過(guò)代碼會(huì)修改這個(gè)在Launcher中的記錄。
對(duì)了,我們?cè)谇鍐挝募信渲玫腁ctivity和Activity-alias的icon和label信息在新的版本中都是可以換的,這些跟代碼無(wú)關(guān)了,也就是跟我們平常換下app圖標(biāo)名稱是一樣的操作,希望大家不要誤解了這里。
所有的想法都要落地為產(chǎn)品
有的看官會(huì)心生疑問(wèn)了,假如現(xiàn)在的應(yīng)用入口就是默認(rèn)的一個(gè)Activity,默認(rèn)的enable也是true,也沒(méi)有配置任何的Activity-alias,而上文中提到建議清單文件中的Activity的android:enabled="false”,那新版本設(shè)置成false會(huì)不會(huì)導(dǎo)致桌面Icon入口不見(jiàn)了呢?可以確切的說(shuō),如果按照上文的步驟對(duì)你的新版本(可以動(dòng)態(tài)切換圖標(biāo)的版本)進(jìn)行設(shè)置的話,是不會(huì)有以上情況產(chǎn)生的,光說(shuō)不練,就是混蛋,上清單文件的示例代碼:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xk.ChangeIcon">
<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,且不通過(guò)代碼進(jìn)行設(shè)置 -->
<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>
<!-- 固定設(shè)置一個(gè)默認(rèn)的別名,用來(lái)替代原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>
<!--別名 春節(jié) 都可以給配置一個(gè)別名在清單文件 -->
<activity-alias
android:name=".ChangeIcon"
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>