AndroidManifest--你真的理解了吗?

最近做二次开发,修改别人代码的时候发现清单文件中多了很多奇怪的属性和标签(自己以前没见过的),在不明白的情况下直接开发出现了很多奇怪的问题。所以痛下决心,重新复习下这些基础知识,以下以6.0系统中的Settings模块源码为例讲解。

<manifest/>标签层:

这是整个清单文件的最上层,用来做一些最基本的声明,如(包名,权限,资源命名空间等)。老规矩,通过栗子来讲解:

<manifest coreApp="true"
          package="com.android.settings"
          xmlns:android="http://schemas.android.com/apk/res/android"
          android:sharedUserId="android.uid.system"
          android:versionCode="20150101"
          android:versionName="3.0.5">
    <original-package android:name="com.android.settings"/>
    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="21"/>

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • 1.package="com.android.settings"
    整个应用的包名。这里有个坑,当我们通过ComponentName来启动某个Activity时,所用的包名一定是这个应用的包名,而不是当前Activity的包名。

  • 2.xmlns:android="http://schemas.android.com/apk/res/android"
    命名空间的声明,使得各种Android系统级的属性能让我们使用。当我们需要使用自定义属性时,可以将其修改为res-auto,编译时会为我们自动去找到该自定义属性。

  • 3.android:sharedUserId="android.uid.system"
    将当前应用进程设置为系统级进程(不推介随意这么做,会产生很多隐患)。拥有此属性后,我们的应用就可以无视用户,无法无天地处理很多事情,比如擅自修改手机system分区的内容、静默安装等。之前开发过一个类似切换多套开关机动画和音效的模块,添加此属性后,就可以明目张胆地将我们的数据节点存在system分区,可以让用户恢复出厂设置都清空不了我们的数据。
    但是添加此属性后,我们需要在当前模块的MakeFile中添加LOCAL_CERTIFICATE := platform,然后在安卓源码环境下使用原生make命令编译才能生效(原生编译虽然比使用ide工具麻烦很多,但是却能使用很多ide工具无权限使用的api)。
    如果非要在ide工具中使用则必须通过系统密钥重签名生成的apk才行(未亲自验证)。

  • 4.uses-permission
    为我们的应用添加必须的权限。同时我们也可以该层声明自定义的权限。

    <permission
        android:name="com.cold.permission.appfreeze"
        android:protectionLevel="signatureOrSystem"/>

--*

<application/>标签层:

应用层标签,用来配置我们的apk的整体属性,也可以统一指定所有界面的主题。栗子如下:

    <application
        android:name=".SettingsApp"
        android:allowBackup="false"
        android:hardwareAccelerated="true"
        android:icon="@mipmap/ic_launcher_settings"
        android:label="@string/settings_label"
        android:requiredForAllUsers="true"
        android:supportsRtl="true"
        android:taskAffinity=""
        android:theme="@style/Theme.Aui">
  • 1."android:name"、"android:icon"、"android:label"
    顾名思义,用来指定应用的名称、在桌面的启动图标、应用的标签名

  • 2."android:theme"
    为当前应用的每个界面都默认设置一个主题,可以后续在activity标签层单独覆盖此Theme。

  • 3."android:allowBackup"
    关闭应用程序数据的备份和恢复功能,注意该属性值默认为true,如果你不需要你的应用被恢复导致隐私数据暴露(如果值为true,甚至可以直接通过adb命令获取该应用中的数据),必须手动设置此属性。

  • 4.android:hardwareAccelerated="true"
    开启硬件加速,一般应用不推介使用。就算非要使用也最好在某个Activity单独开启,避免过大的内存开销。

  • 5.android:taskAffinity
    设置Activity任务栈的名称,可忽略。
    --*

<具体组件/>标签层:

因为</provider>、</service>在实际开发中接触得不多,这部分主要讲解 </activity> 、</receiver>标签。

关于Activity标签的属性,个人最觉得绕和难掌握的就是Intent-filter的匹配规则了,每次使用错了都要去查资料修改,所以这边总结得尽可能仔细。

</activity>

先来一段热身的代码,一个最简单的Activity声明:

        <activity
            android:name="AgedModeActivity"
            android:icon="@drawable/ic_aged_mode"
            android:label="@string/app_name_label"
            android:theme="@android:style/Theme.NoDisplay"
            android:exported="true"
            android:configChanges="orientation|screenSize|fontScale">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
  • 1.android:configChanges
    当我们的界面大小,方向,字体等config参数改变时,我们的Activity就会重新执行onCreate的生命周期。而当我们设置此属性后,就可以强制让Activity不重新启动,而是只会调用一次onConfigurationChanged方法,所以我们可以在这里做一些相关参数改变的操作。

  • 2."android.intent.category.LAUNCHER"、"android.intent.action.MAIN"
    这两个属性共同将当前Activity声明为了我们应用的入口,将应用注册至系统的应用列表中,缺一不可。

这里还有一点需要注意,如果希望我们的应用有多个入口,每个入口能进入到app的不同Activity中时,光设置这两个属性还不够,还要为它指定一个进程和启动模式。

 android:process=".otherProcess" 
 android:launchMode ="singleInstance"

至于Activity的四种启动模式请各位看官自己复习,就不在这儿重述了。

  • 3.android:exported="true"
    将当前组件暴露给外部。属性决定它是否可以被另一个Application的组件启动。

热身结束,我们就来重点分析<intent-filter>的匹配规则(显式调用只需正确使用包名类名即可,隐式调用才需要考虑匹配的问题)。

        <activity android:name="Settings$WirelessSettingsActivity"
                android:taskAffinity="com.android.settings"
                android:label="@string/wireless_networks_settings_title"
                android:parentActivityName="Settings">
            <intent-filter android:priority="1">
                <action android:name="android.settings.WIRELESS_SETTINGS" />
                <action android:name="android.settings.AIRPLANE_MODE_SETTINGS" />
                <action android:name="android.settings.NFC_SETTINGS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.VOICE_LAUNCH" />
                <data
                    android:scheme="content"
                    android:host="com.android.externalstorage.documents"
                    android:mimeType="vnd.android.document/root" />
            </intent-filter>
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                android:value="com.android.settings.WirelessSettings" />
            <meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"
                android:resource="@id/wireless_settings" />
            <!-- Note that this doesn't really show any Wireless settings. -->
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                android:value="true" />
        </activity>

当我们通过intent去隐式调用一个Activity时,需要同时匹配注册activity中的action、category、data才能正常启动,而这三个属性的匹配规则也略有不同。

  • 1.action
    action是最简单的匹配项,我们将其理解为一个区分大小写的字符串即可,一般用来代表某一种特定的动作,隐式调用时intent必须setAction。一个过滤器中可以有多个action属性,只要我们的itent和其中任意一项equal则就算匹配成功。

  • 2.category
    category属性也是一个字符串,匹配时也必须和过滤器中定义的值相同。当我们不为intent主动地addCategory时,系统为帮我们默认添加一个值为"android.intent.category.DEFAULT"的category。反过来说,如果我们需要我们自己写的Activity能接受隐式intent启动,我们就必须在它的过滤器中添加"android.intent.category.DEFAULT"(深坑!),否则无法成功启动。

  • 3.data
    data比较复杂,幸运地是我们几乎用不到它。data可以分为mimeType和URI路径两部分:
    mimeType指定媒体格式类型,音频、文件、图片都有特定的属性值。
    URI则有android:scheme、android:host、android:port等属性组成,scheme代表模式(常用的有http,content,file,package),Host就是一个主机地址,Port则是端口号。
    依照上面给出的代码,为intent设置data时我们可以这样做:
    intent.setDataAndType(Uri.parse("content://com.android.externalstorage.documents"), "vnd.android.document/root");

额外扩展一些关于activity的属性:
  • <meta-data/>标签:
    标签<meta-data>是提供组件额外的数据用的,它本身是一个键值对,写在清单文件中之后,可以在代码中获取。栗:
    private void getMetaData() {
        PDebug.Start("getMetaData");
        try {
            ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
                    PackageManager.GET_META_DATA);
            if (ai == null || ai.metaData == null) return;
            mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
            mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);

            // Check if it has a parent specified and create a Header object
            final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE);
            String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);
            if (parentFragmentClass != null) {
                mParentHeader = new Header();
                mParentHeader.fragment = parentFragmentClass;
                if (parentHeaderTitleRes != 0) {
                    mParentHeader.title = getResources().getString(parentHeaderTitleRes);
                }
            }
        } catch (NameNotFoundException nnfe) {
            // No recovery
        }
        PDebug.End("getMetaData");
    }
  • android:excludeFromRecents="true"
    设置为true后,当用户按了“最近任务列表”时候,该activity不会出现在最近任务列表中,可达到隐藏应用的目的。很黑科技吧~
</receiver>

关于receiver,个人觉得容易混淆的就一个permission问题:

    <receiver
        android:name="com.android.settings.AliAgeModeReceiver"
        android:permission="com.android.settings.permission.SWITH_SETTING">
        <intent-filter>
            <action android:name="com.android.settings.action.SWITH_AGED_MODE"/>
        </intent-filter>
    </receiver>

起初认为这是receiver中处理一些操作需要使用到此权限,后来查阅资料后发现是通过在</receiver>中添加permission标签,我可以发送一些敏感的广播,只有添加了该permission的receiver才能接收到,而不让其他的应用收到。栗:

Intent intent = new Intent("com.android.settings.action.SWITH_AGED_MODE");
sendBroadcast(intent,"com.android.settings.permission.SWITH_SETTING");

这要就只有我们自己的接收者才能收到该广播,但是当我们

Intent intent = new Intent("com.android.settings.action.SWITH_AGED_MODE");
sendBroadcast(intent);

则所有有此action的接收者都能收到我们发出的广播。
总结来说就是:

  1. 一些敏感的广播并不想让第三方的应用收到 ;
  2. 要限制自己的Receiver接收某广播来源,避免被恶意的同样的ACTION的广播所干扰。

--*

11.01更新:
  • 发现一个可以处理Activity中界面与软键盘显示的属性:
    android:windowSoftInputMode="stateAlwaysHidden"
    共有9个属性,可以分别为软键盘设置禁止、显示、大小调整等情况。具体怎么使用,同学们请自行使用搜索引擎~
    --*
码字不易,给个赞呗~
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,201评论 0 17
  • Application 标签 android:allowTaskReparenting android:allow...
    Shawn_Dut阅读 7,582评论 2 61
  • 有的人苦苦浸入过去无法自拔,有的人痴痴幻想未来不切实际。却极少有人能看清那层层迷雾后的真谛——唯有珍惜当下,才不辜...
    江朽阅读 379评论 0 3
  • 投射宝贝开心快乐健康成长,早点能在妈妈身边 投射孩子他爹能珍惜这个小家,能为了孩子多给孩子留一些钱 投射我的小事业...
    翟美丽阅读 234评论 0 0