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"(深坑?。駝t無法成功啟動。

  • 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個屬性,可以分別為軟鍵盤設置禁止、顯示、大小調整等情況。具體怎么使用,同學們請自行使用搜索引擎~
    --*
碼字不易,給個贊唄~
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,353評論 25 708
  • ¥開啟¥ 【iAPP實現進入界面執行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,524評論 0 17
  • Application 標簽 android:allowTaskReparenting android:allow...
    Shawn_Dut閱讀 7,918評論 2 61
  • 有的人苦苦浸入過去無法自拔,有的人癡癡幻想未來不切實際。卻極少有人能看清那層層迷霧后的真諦——唯有珍惜當下,才不辜...
    江朽閱讀 407評論 0 3
  • 投射寶貝開心快樂健康成長,早點能在媽媽身邊 投射孩子他爹能珍惜這個小家,能為了孩子多給孩子留一些錢 投射我的小事業...
    翟美麗閱讀 250評論 0 0