關(guān)于安卓應用圖標的幾個問題

一、 ic_launcher.png --> drawable vs mipmap

元旦要到了,如果要在當天發(fā)版,想必各位工程師最近應該都提交了代碼,之后元旦也少不了加班。

今天我也提交了代碼,沒過多久 QA 就跑過來,對我說,Arno 你改了啥? 怎么連圖標都沒了?我仔細一看,確實沒有了,變成了 Ecplise 的綠色機器人。奇怪的是,我用的是 AS 編譯的,應該沒有那個圖片。

事實上,之前一直是沒有問題的。那么應該是這次的操作出了問題,想想這次更換 logo 我都做了些什么。由于工程比較古老,是 Eclipse 轉(zhuǎn)成 AS 的工程,我看到 drawable 路徑下 ldpi mdpi hdpi xhdpi xxhdpi 中各有一個 ic_launcher.png ,但是實際上只有兩個尺寸,所以我只保留了兩張圖,刪除了 ldpi mdpi xxdpi 的圖片。

那么大概是圖片出了問題,我來到了 Manifest 文件,追蹤這個文件的路徑:


效果圖-g.png

可以看到,明明在 drawable 路徑下不存在的圖片,但是它自動取出了默認圖幫我們補齊了。想起之前學習 Android 的時候, ic_launcher.png 都是放在 mipmap 路徑下的。所以嘗試把 hdpi 和 xhdpi 的文件從 drawable 路徑移動到 mipmap 路徑后,更改 Manifest 文件中的引用,顯示就正確了。

二、修改桌面圖標

既然提到了圖標,就想順便提一個很早就有的疑問:怎么動態(tài)修改桌面的圖標?之前是一個微信公眾號推送的文章,但是后來怎么也找不到了,這次趁機會,順便解決這個問題。

需求很簡單,就是要在活動時間,使 app 圖標自動變?yōu)榛顒訄D標,活動失效后,讓圖標變回原來的樣子。比如這次要元旦了,就目前而言,替換圖標的方式就是在我們發(fā)版之后更新我們的 app,但是我們可以看到淘寶之類的 app 可以在不更新的情況下,切換圖標。這個是怎么做到的呢?利用 google 檢索,可以很快找到解決方案。

我們知道,桌面圖標的名稱、圖片設置都是在 Application 節(jié)點下進行設置。代碼如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.arno.logoex">
    <!--一些權(quán)限聲明-->
    <application android:allowBackup="true"
        android:icon="@mipmap/icon_normal"
        android:label="@string/app_name"
        android:supportsRtl="true" android:theme="@style/AppTheme">
    <!-其他聲明-->
    </application>

這里設置的,是針對全局入口的屬性配置,也就是說,如果有多個入口,且入口信息中沒有配置名稱和圖片,那么就會默認使用這里配置的信息。因此,對于大部分的應用而言,我們只需要在這里設置了應用名稱和圖標。但是如果你的應用需要像淘寶天貓那樣,在某個時候更改圖標、甚至需要更改應用名稱或者入口活動頁,那么你就需要來看看這里的知識了。

Android 在清單文件中提供了 <activity-alias/> 標簽,這個標簽可以作為某個 targetActivity 的別名,其中,這個 targetActivity 需要定義在別名之前并且在相同的<application/>下。接下來標簽的大部分介紹都來自官網(wǎng),國內(nèi)也可以進,放心點擊:鏈接


從文檔得知別名可以設置的屬性:

<activity-alias 
        android:enabled=["true" | "false"] 
        android:exported=["true" | "false"] 
        android:icon="drawable resource" 
        android:label="string resource" 
        android:name="string" 
        android:permission="string" 
        android:targetActivity="string" >
 . . .
</activity-alias>

下面是各個屬性的含義:

  • android:enabled 屬性,布爾類型,是否開啟別名設置,默認值為 true;
  • android:exported 屬性,布爾類型,是否支持其他應用通過這個別名訪問目標 Activity,默認值為 true;
  • android:iconlabel 屬性:類似 <activity> 標簽,表示目標 Activity 的顯示圖標和標簽;
  • android:label 屬性:當指定 Activity 為啟動頁面時,這個值就會被作為顯示的應用名稱。
  • android:name 屬性:Activity 別名,在 <activity> 標簽中, name 屬性必須與對應 Activity 文件的名字保持一致,而這里的別名可任意設置,保證唯一性即可;
  • android:permission 屬性:權(quán)限設置,對別名的使用加以限制,詳細可以看 自定義屬性
  • android:targetActivity 屬性:指定別名能夠啟動的目標 Activity,注意,屬性值一定要對應到 <activity> 標簽中的 name 屬性,并且該 <activity> 標簽一定要位于<activity-alias> 標簽前面;

了解了<activity-alias>,接下來我們就可以寫代碼了。

2.1 利用別名預設入口

<application android:allowBackup="true"
    android:icon="@mipmap/icon_normal"
    android:label="@string/app_name"
    android:supportsRtl="true" android:theme="@style/AppTheme">
    <activity
        android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <!--<category android:name="android.intent.category.LAUNCHER" />-->
        </intent-filter>
    </activity>
    <!--name:組件名字-->
    <!--enabled:該組件是否啟動-->
    <!--icon:組件圖標-->
    <!--label:組件標簽說明-->
    <!--targetActivity:組件的目標類-->
    <activity-alias
        android:name=".EntranceDefault"
        android:enabled="true"
        android:icon="@mipmap/icon_normal"
        android:label="EntranceDefault"
        android:targetActivity=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity-alias>
    <activity-alias
        android:name=".EntranceSpec"
        android:enabled="false"
        android:icon="@mipmap/icon_1"
        android:label="EntranceSpec"
        android:targetActivity=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity-alias>
</application>
2.2 代碼選擇入口

方法比較簡單,主要是利用 PackageManager 的一個方法。這里寫了一個方法類,在 app 啟動的時候調(diào)用 init 方法,將所有入口別名初始化,然后在切換入口的時候調(diào)用 enable 方法。具體實現(xiàn)可以查看下面的代碼:

/**
 * Created by Arno on 2018/1/2.
 *
 * 入口切換類,用于更改應用入口達到更換圖標的目的
 *
 * 需要更改清單文件中入口activity的配置,將 launcher 去掉,并添加別名
 * 
 * <!--name:組件名字-->
 * <!--enabled:該組件是否啟動-->
 * <!--icon:組件圖標-->
 * <!--label:組件標簽說明-->
 * <!--targetActivity:組件的目標類-->
 * <activity-alias
 *      android:name=".EntranceDefault"
 *      android:enabled="true"
 *      android:icon="@mipmap/icon_normal"
 *      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>
 *
 */
public class EntranceUtils {
    private static final String TAG = "EntranceUtils";
    private Map<String, ComponentName> mEntranceMap;
    private PackageManager mPackageManager;

    public static EntranceUtils getInstance() {
        return InstanceHolder.instance;
    }

    /**
     * 初始化
     * @param context           context
     * @param componentNames    組件別名數(shù)組,需要將默認入口別名放在第一位
     */
    public void init(Context context, String... componentNames) {
        if (mPackageManager == null) {
            mPackageManager = context.getPackageManager();
        }
        if (mEntranceMap == null) {
            mEntranceMap = new HashMap<>();
        }
        for (int i = 0; i < componentNames.length; i++) {
            ComponentName value = new ComponentName(context, componentNames[i]);
            mEntranceMap.put(componentNames[i], value);
            //默認情況下,組件的 enable 狀態(tài)為 default,需要手動設置
            mPackageManager.setComponentEnabledSetting(
                    value,
                    i == 0 ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                    PackageManager.DONT_KILL_APP);
        }
    }

    public void enable(Context context, String componentName) {
        ComponentName component = mEntranceMap.get(componentName);
        if (component != null && mPackageManager.getComponentEnabledSetting(component) == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
            for (Map.Entry<String, ComponentName> entry : mEntranceMap.entrySet()) {
                int newState = componentName.equals(entry.getKey()) ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
                mPackageManager.setComponentEnabledSetting(
                        entry.getValue(),
                        newState,
                        PackageManager.DONT_KILL_APP);
            }
            restartApp(context);
        }
    }

    private void restartApp(Context context) {
        Intent intent = context.getPackageManager()
                .getLaunchIntentForPackage(context.getPackageName());
        PendingIntent restartIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
        AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 2000, restartIntent); // 2秒鐘后重啟應用
        System.exit(0);

    }

    private static class InstanceHolder {
        private static EntranceUtils instance = new EntranceUtils();
    }

}

不過在剛剛調(diào)用了 enable 方法的幾秒內(nèi),如果回到桌面并點擊應用圖標,會報出未安裝應用的異常,這個是正?,F(xiàn)象,過一會之后,應用圖標切換好了,就可以再次進入。因此,請謹慎選擇切換入口的時機,以免造成不好的用戶體驗。除此之外,你還可以在調(diào)用后重啟 app,以加快這個切換的過程。

附上調(diào)用代碼:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    EntranceUtils.getInstance().init(this
            ,"com.arno.logoex.EntranceDefault"
            ,"com.arno.logoex.EntranceSpec");
}

public void onClick(View view){
    EntranceUtils.getInstance().enable(this,"com.arno.logoex.EntranceSpec");
}

以上。

參考:
1.developers_activity-alias
2.android 動態(tài)修改app icon
3.Android動態(tài)更換應用Icon之玩轉(zhuǎn)桌面圖標

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容