Android動態變更圖標和應用名

需求背景

大家可能會有注意到,每逢重大節日,很多應用圖標會自動調整,類似于春節版、國慶版等等。
這個功能最簡單的實現方式可能就是發布一個新的版本了,直接替換相關資源,然后應用升級體驗。 但是這種方式工作量較大,很不方便。并且像今日頭條、支付寶這類軟件,我們好像也沒有注意到有應用升級就實現了圖標替換,很神奇吧,今天我們就實現這個功能。

實現過程

以開源項目睡眠助手為例,實現應用切換圖標功能。 首先我們找到清單文件AndroidManifest.xml,可以看到啟動Activity配置如下:

<activity
    android:exported="true"
    android:name=".activity.GuideActivity"
    android:theme="@style/AppTheme.Launcher">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>

然后我們在該activity定義之后,添加新的定義文件,定義一個activity-alias。 需注意,該activity-alias一定要在啟動activity之后定義才可。

<activity
    android:exported="true"
    android:name=".activity.GuideActivity"
    android:theme="@style/AppTheme.Launcher">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity-alias
    android:exported="true"
    android:icon="@mipmap/icon"
    android:label="睡眠豬豬"
    android:name=".activity.NewGuideActivity"
    android:targetActivity=".activity.GuideActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity-alias>

此時安裝應用,我們會發現,桌面上會出現兩個應用:睡眠助理、睡眠豬豬,點擊兩個圖標均可實現打開應用,使用功能。那么很顯然activity-alias實現了新的應用入口。我們要實現應用圖標變更,那么可以先把activity-alias狀態關閉,需要開啟時再進行開啟,通過android:enabled="false"進行設置:

<activity
    android:exported="true"
    android:name=".activity.GuideActivity"
    android:theme="@style/AppTheme.Launcher">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity-alias
    android:enabled="false"
    android:exported="true"
    android:icon="@mipmap/icon"
    android:label="睡眠豬豬"
    android:name=".activity.NewGuideActivity"
    android:targetActivity=".activity.GuideActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity-alias>

現在啟動應用會看到圖標又恢復成一個了,接下來實現控制圖標的變更。
動態控制應用圖標可以使用PackageManager實現,可以借助于推送、時間判斷、用戶點擊等方式觸發,我們演示功能就采用用戶點擊的方式。在設置界面添加操作按鈕,實現點擊進行變更:

PackageManager pm = getPackageManager();
if(PackageManager.COMPONENT_ENABLED_STATE_DISABLED != pm.getComponentEnabledSetting(new ComponentName(this, "com.devdroid.sleepassistant.activity.GuideActivity"))) {
    pm.setComponentEnabledSetting(new ComponentName(this, "com.devdroid.sleepassistant.activity.GuideActivity"),
        PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
    pm.setComponentEnabledSetting(new ComponentName(this, "com.devdroid.sleepassistant.activity.NewGuideActivity"),
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
} else {
    pm.setComponentEnabledSetting(new ComponentName(this, "com.devdroid.sleepassistant.activity.NewGuideActivity"),
        PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
    pm.setComponentEnabledSetting(new ComponentName(this, "com.devdroid.sleepassistant.activity.GuideActivity"),
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}

此時我們就通過按鈕實現圖標的切換功能了。

發現問題

  • 問題一

由小伙伴反饋,一旦切換圖標后,應用安裝會出現問題:

Error while executing: am start -n "com.devdroid.sleepassistant/com.devdroid.sleepassistant.activity.GuideActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.devdroid.sleepassistant/.activity.GuideActivity }
Error type 3
Error: Activity class {com.devdroid.sleepassistant/com.devdroid.sleepassistant.activity.GuideActivity} does not exist.
Error while Launching activity

仔細看提示,發現并不是應用安裝出現問題。之所以報這個錯誤,是因為該小伙伴直接從Android Studio運行應用。由于默認的啟動Activity已經被設置為COMPONENT_ENABLED_STATE_DISABLED(不可用),所以i同無法找到默認的Activity,無法啟動應用,報錯了。若是用戶使用安裝包或從應用商店安裝則不存在該問題。

  • 問題二

有小伙伴反饋變更應用圖標后,應用會再3秒后關閉。 我們看一下變更圖標的方法:setComponentEnabledSetting():

Set the enabled setting for a package component (activity, receiver, service, provider). This setting will override any enabled state which may have been set by the component in its manifest.

翻譯為:

設置包四大組件(activity, receiver, service, provider)的啟用設置。此設置將覆蓋組件在其清單文件(AndroidManifest.xml)中設置的任何啟用狀態。

其中有一個flags參數,可選為:DONT_KILL_APP,SYNCHRONOUS。其中: DONT_KILL_APP

Flag parameter for setComponentEnabledSetting(ComponentName, int, int) to indicate that you don't want to kill the app containing the component. Be careful when you set this since changing component states can make the containing application's behavior unpredictable.

翻譯:

setComponentEnabledSetting(ComponentName,int,int)的標志參數,用于指示您不希望終止包含該組件的應用程序。設置此選項時要小心,因為更改組件狀態會使包含應用程序的行為不可預測。

SYNCHRONOUS

Flag parameter for setComponentEnabledSetting(ComponentName, int, int) to indicate that the given user's package restrictions state will be serialised to disk after the component state has been updated. Note that this is synchronous disk access, so calls using this flag should be run on a background thread.

翻譯:

setComponentEnabledSetting(ComponentName,int,int)的標志參數,用于指示給定用戶的包限制狀態將在更新組件狀態后序列化到磁盤。請注意,這是同步磁盤訪問,因此使用此標志的調用應該在后臺線程上運行。

測試發現:使用DONT_KILL_APP時,應用在3秒內退出;使用SYNCHRONOUS應用立即退出。
但是DONT_KILL_APP和實際不符啊,為什么呢?
網上查閱資料,大多回答是一頭霧水,有人反饋是Android系統的一個系統級bug。自己到谷歌社區查找主題,通過官方人員溝通,了解到:當使用DONT_KILL_APP時,Application不會主動結束進程,但是由于作為啟動頁的GuideActivity被設置為COMPONENT_ENABLED_STATE_DISABLED(不可用),這時候APP會將GuideActivity創建的任務棧清空,由于APP所有Activity都是由GuideActivity任務棧創建的,所以就看到類似于退出應用的效果。 好了,原因確定了,那么就看如何解決了。

此時我們只需要使用新的任務棧啟動SettingsActivity,然后在SettingsActivity內清空啟動棧,應用就不會退出了。

Intent intent = new Intent(mAppCompatActivity, SettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
mAppCompatActivity.startActivity(intent);

注意: 實際使用時,發現setComponentEnabledSetting生效速度較慢,大概有3s左右。在3s內啟動應用,會仍然調用原來的啟動頁面,導致3s退出應用時,將新建的任務棧清空,應用退出。

原因了解了,通過代碼驗證,確實可以借助上面的方式實現圖標變更。
但是該方案還存在一個弊端:當GuideActivity設置不可用時,應用內其他頁面需要跳轉到GuideActivity時是不能實現的,同時也無法跳轉到activity-alias定義的NewGuideActivity中,這個暫時沒有找到解決方案。

通過谷歌官方人員溝通,了解到官方不建議通過使用activity-alias方式實現這種功能,他們提供了一種新的方案。

最終方案

谷歌認為,圖標變更功能應該使用獨立的LAUNCHER Activity實現,而不應借助activity-alias。
最建議方案如下:
首先創建類文件NewGuideActivity,實現如下代碼:

class NewGuideActivity extends GuideActivity{

}

清單文件添加聲明:

<activity
    android:enabled="false"
    android:exported="true"
    android:icon="@mipmap/icon"
    android:label="睡眠豬豬"
    android:name=".activity.NewGuideActivity"
    android:targetActivity=".activity.GuideActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

這時候,NewGuideActivity就是一個真實的LAUNCHER了。由于NewGuideActivity直接繼承GuideActivity,本身沒有任何實質代碼,所以功能也是完全一致的。對于NewGuideActivity、GuideActivity的設置和activity-alias方式類似。這個能夠滿足變更的需求。

以上相關代碼請參考開源項目:睡眠助手

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容