這裏我把作這個功能中遇到的一些問題寫在前面,是爲了你們能先了解有什麼問題存在,遇到這些問題的時候就不慌了,這裏我把應用圖標和名稱先統一使用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僅供參考