关于安卓应用图标的几个问题

一、 ic_launcher.png --> drawable vs mipmap

元旦要到了,如果要在当天发版,想必各位工程师最近应该都提交了代码,之后元旦也少不了加班。

今天我也提交了代码,没过多久 QA 就跑过来,对我说,Arno 你改了啥? 怎么连图标都没了?我仔细一看,确实没有了,变成了 Ecplise 的绿色机器人。奇怪的是,我用的是 AS 编译的,应该没有那个图片。

事实上,之前一直是没有问题的。那么应该是这次的操作出了问题,想想这次更换 logo 我都做了些什么。由于工程比较古老,是 Eclipse 转成 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 文件中的引用,显示就正确了。

二、修改桌面图标

既然提到了图标,就想顺便提一个很早就有的疑问:怎么动态修改桌面的图标?之前是一个微信公众号推送的文章,但是后来怎么也找不到了,这次趁机会,顺便解决这个问题。

需求很简单,就是要在活动时间,使 app 图标自动变为活动图标,活动失效后,让图标变回原来的样子。比如这次要元旦了,就目前而言,替换图标的方式就是在我们发版之后更新我们的 app,但是我们可以看到淘宝之类的 app 可以在不更新的情况下,切换图标。这个是怎么做到的呢?利用 google 检索,可以很快找到解决方案。

我们知道,桌面图标的名称、图片设置都是在 Application 节点下进行设置。代码如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.arno.logoex">
    <!--一些权限声明-->
    <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/>下。接下来标签的大部分介绍都来自官网,国内也可以进,放心点击:链接


从文档得知别名可以设置的属性:

<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 属性:权限设置,对别名的使用加以限制,详细可以看 自定义属性
  • 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 启动的时候调用 init 方法,将所有入口别名初始化,然后在切换入口的时候调用 enable 方法。具体实现可以查看下面的代码:

/**
 * 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    组件别名数组,需要将默认入口别名放在第一位
     */
    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 状态为 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();
    }

}

不过在刚刚调用了 enable 方法的几秒内,如果回到桌面并点击应用图标,会报出未安装应用的异常,这个是正常现象,过一会之后,应用图标切换好了,就可以再次进入。因此,请谨慎选择切换入口的时机,以免造成不好的用户体验。除此之外,你还可以在调用后重启 app,以加快这个切换的过程。

附上调用代码:

@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 动态修改app icon
3.Android动态更换应用Icon之玩转桌面图标

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容