一偏化、 ic_launcher.png --> drawable vs mipmap
元旦要到了励翼,如果要在當(dāng)天發(fā)版闲先,想必各位工程師最近應(yīng)該都提交了代碼骡和,之后元旦也少不了加班。
今天我也提交了代碼崭参,沒(méi)過(guò)多久 QA 就跑過(guò)來(lái),對(duì)我說(shuō)款咖,Arno 你改了啥何暮? 怎么連圖標(biāo)都沒(méi)了?我仔細(xì)一看铐殃,確實(shí)沒(méi)有了海洼,變成了 Ecplise 的綠色機(jī)器人。奇怪的是富腊,我用的是 AS 編譯的坏逢,應(yīng)該沒(méi)有那個(gè)圖片。
事實(shí)上赘被,之前一直是沒(méi)有問(wèn)題的是整。那么應(yīng)該是這次的操作出了問(wèn)題,想想這次更換 logo 我都做了些什么民假。由于工程比較古老浮入,是 Eclipse 轉(zhuǎn)成 AS 的工程,我看到 drawable 路徑下 ldpi mdpi hdpi xhdpi xxhdpi 中各有一個(gè) ic_launcher.png 羊异,但是實(shí)際上只有兩個(gè)尺寸事秀,所以我只保留了兩張圖彤断,刪除了 ldpi mdpi xxdpi 的圖片。
那么大概是圖片出了問(wèn)題易迹,我來(lái)到了 Manifest 文件宰衙,追蹤這個(gè)文件的路徑:
可以看到,明明在 drawable 路徑下不存在的圖片睹欲,但是它自動(dòng)取出了默認(rèn)圖幫我們補(bǔ)齊了供炼。想起之前學(xué)習(xí) Android 的時(shí)候, ic_launcher.png 都是放在 mipmap 路徑下的句伶。所以嘗試把 hdpi 和 xhdpi 的文件從 drawable 路徑移動(dòng)到 mipmap 路徑后劲蜻,更改 Manifest 文件中的引用,顯示就正確了考余。
二先嬉、修改桌面圖標(biāo)
既然提到了圖標(biāo),就想順便提一個(gè)很早就有的疑問(wèn):怎么動(dòng)態(tài)修改桌面的圖標(biāo)楚堤?之前是一個(gè)微信公眾號(hào)推送的文章疫蔓,但是后來(lái)怎么也找不到了,這次趁機(jī)會(huì)身冬,順便解決這個(gè)問(wèn)題衅胀。
需求很簡(jiǎn)單,就是要在活動(dòng)時(shí)間酥筝,使 app 圖標(biāo)自動(dòng)變?yōu)榛顒?dòng)圖標(biāo)滚躯,活動(dòng)失效后,讓圖標(biāo)變回原來(lái)的樣子嘿歌。比如這次要元旦了掸掏,就目前而言,替換圖標(biāo)的方式就是在我們發(fā)版之后更新我們的 app宙帝,但是我們可以看到淘寶之類的 app 可以在不更新的情況下丧凤,切換圖標(biāo)。這個(gè)是怎么做到的呢步脓?利用 google 檢索愿待,可以很快找到解決方案。
我們知道靴患,桌面圖標(biāo)的名稱仍侥、圖片設(shè)置都是在 Application 節(jié)點(diǎn)下進(jìn)行設(shè)置。代碼如下:
<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>
這里設(shè)置的鸳君,是針對(duì)全局入口的屬性配置访圃,也就是說(shuō),如果有多個(gè)入口相嵌,且入口信息中沒(méi)有配置名稱和圖片腿时,那么就會(huì)默認(rèn)使用這里配置的信息况脆。因此,對(duì)于大部分的應(yīng)用而言批糟,我們只需要在這里設(shè)置了應(yīng)用名稱和圖標(biāo)格了。但是如果你的應(yīng)用需要像淘寶天貓那樣,在某個(gè)時(shí)候更改圖標(biāo)徽鼎、甚至需要更改應(yīng)用名稱或者入口活動(dòng)頁(yè)盛末,那么你就需要來(lái)看看這里的知識(shí)了。
Android 在清單文件中提供了 <activity-alias/>
標(biāo)簽否淤,這個(gè)標(biāo)簽可以作為某個(gè) targetActivity 的別名悄但,其中,這個(gè) targetActivity 需要定義在別名之前并且在相同的<application/>
下石抡。接下來(lái)標(biāo)簽的大部分介紹都來(lái)自官網(wǎng)檐嚣,國(guó)內(nèi)也可以進(jìn),放心點(diǎn)擊:鏈接
從文檔得知?jiǎng)e名可以設(shè)置的屬性:
<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>
下面是各個(gè)屬性的含義:
- android:enabled 屬性啰扛,布爾類型嚎京,是否開(kāi)啟別名設(shè)置,默認(rèn)值為 true隐解;
- android:exported 屬性鞍帝,布爾類型,是否支持其他應(yīng)用通過(guò)這個(gè)別名訪問(wèn)目標(biāo) Activity煞茫,默認(rèn)值為 true帕涌;
-
android:icon 和 label 屬性:類似
<activity>
標(biāo)簽,表示目標(biāo) Activity 的顯示圖標(biāo)和標(biāo)簽续徽; - android:label 屬性:當(dāng)指定 Activity 為啟動(dòng)頁(yè)面時(shí)宵膨,這個(gè)值就會(huì)被作為顯示的應(yīng)用名稱。
-
android:name 屬性:Activity 別名炸宵,在
<activity>
標(biāo)簽中,name
屬性必須與對(duì)應(yīng) Activity 文件的名字保持一致谷扣,而這里的別名可任意設(shè)置土全,保證唯一性即可; - android:permission 屬性:權(quán)限設(shè)置会涎,對(duì)別名的使用加以限制裹匙,詳細(xì)可以看 自定義屬性 。
-
android:targetActivity 屬性:指定別名能夠啟動(dòng)的目標(biāo) Activity末秃,注意概页,屬性值一定要對(duì)應(yīng)到
<activity>
標(biāo)簽中的name
屬性,并且該<activity>
標(biāo)簽一定要位于<activity-alias>
標(biāo)簽前面练慕;
了解了<activity-alias>
惰匙,接下來(lái)我們就可以寫代碼了技掏。
2.1 利用別名預(yù)設(shè)入口
<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:該組件是否啟動(dòng)-->
<!--icon:組件圖標(biāo)-->
<!--label:組件標(biāo)簽說(shuō)明-->
<!--targetActivity:組件的目標(biāo)類-->
<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 代碼選擇入口
方法比較簡(jiǎn)單,主要是利用 PackageManager 的一個(gè)方法项鬼。這里寫了一個(gè)方法類哑梳,在 app 啟動(dòng)的時(shí)候調(diào)用 init 方法,將所有入口別名初始化绘盟,然后在切換入口的時(shí)候調(diào)用 enable 方法鸠真。具體實(shí)現(xiàn)可以查看下面的代碼:
/**
* Created by Arno on 2018/1/2.
*
* 入口切換類,用于更改應(yīng)用入口達(dá)到更換圖標(biāo)的目的
*
* 需要更改清單文件中入口activity的配置龄毡,將 launcher 去掉吠卷,并添加別名
*
* <!--name:組件名字-->
* <!--enabled:該組件是否啟動(dòng)-->
* <!--icon:組件圖標(biāo)-->
* <!--label:組件標(biāo)簽說(shuō)明-->
* <!--targetActivity:組件的目標(biāo)類-->
* <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ù)組,需要將默認(rèn)入口別名放在第一位
*/
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);
//默認(rèn)情況下沦零,組件的 enable 狀態(tài)為 default祭隔,需要手動(dòng)設(shè)置
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秒鐘后重啟應(yīng)用
System.exit(0);
}
private static class InstanceHolder {
private static EntranceUtils instance = new EntranceUtils();
}
}
不過(guò)在剛剛調(diào)用了 enable 方法的幾秒內(nèi),如果回到桌面并點(diǎn)擊應(yīng)用圖標(biāo)蠢终,會(huì)報(bào)出未安裝應(yīng)用的異常序攘,這個(gè)是正常現(xiàn)象寻拂,過(guò)一會(huì)之后程奠,應(yīng)用圖標(biāo)切換好了,就可以再次進(jìn)入祭钉。因此瞄沙,請(qǐng)謹(jǐn)慎選擇切換入口的時(shí)機(jī),以免造成不好的用戶體驗(yàn)慌核。除此之外距境,你還可以在調(diào)用后重啟 app,以加快這個(gè)切換的過(guò)程垮卓。
附上調(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 動(dòng)態(tài)修改app icon
3.Android動(dòng)態(tài)更換應(yīng)用Icon之玩轉(zhuǎn)桌面圖標(biāo)