当创建工程时,一般情况下IDE都会自动在该工程的根目录下创建AndroidManifest的文件,而当编译工程打包apk时,该文件会一起被打包成apk,而这个文件是系统管理该apk的入口。个人认为在实际项目开发中,该AndroidManifest文件的作用有两点:一是向系统声明该app包含了哪些类型的组件,二是申请权限。
二、AndroidManifest的文件结构
1、android5.0的AndroidManifest的文件总体结构如下:
<manifest>
<application>
<compatible-screens>
<instrumentation>
<library>
<original-package>
<package-verifier>
<permission>
<permission-group>
<permission-tree>
<protected-broadcast>
<resource-overlay>
<supports-screens>
<upgrade-key-set>
<uses-configuration>
<uses-feature>
<uses-sdk>
<uses-permission>
</manifest>
2、根节点为<manifest>的属性
(1)package:应用的包名,它也是一个应用进程的默认名称
(2)android:versionCode="integer":一般用于记录版本的更新次数,通常是递增,不需要显示给用户,但上传至应用市场可能有要求。
android:versionName="string":代表应用程序的版本信息,需要显示给用户,注意低版本替换不了高版本的apk的。
(3)android:installLocation="auto|internalOnly|preferExternal",指定apk的安装位置,是在内部存储器还是在外部sdcard卡。
(4)android:sharedUserId="string",Android提供的一种应用程序之间共享数据的方式。因为默认情况下,在安装应用时,系统会给每个APK分配唯一的UserId。(UserId可以在data/system目录下面有packages.xml查看到,注意需要root权限。)
android:sharedUserLabel="string resource",一个用户可读的共享ID标签,在设置了sharedUserId属性的前提下才会有意义。
使用方式:
a、首先需要在两个的应用程序的AndroidManifest中,指定相同的sharedUserId和sharedUserLabel。
b、紧接着,可以通过如下代码,传入对方的应用包名,就可获取到对方应用的Context。
private Context getExternalContext() {
Context context = null;
try {
context = createPackageContext(EXT_PACKAGE_NAME, Context.CONTEXT_IGNORE_SECURITY);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return context;
}
c、最后,获取到对方的Context,就可使用该Context,访问对方的数据,如数据库、ContextProvider。
d、注意:声明了相同sharedUserId的应用,其签名必须一致,否则无法安装。另外,声明了相同的sharedUserId的两个应用程序,并不是运行在同一个进程中的。
3、<application>节点,关于application节点的属性非常多,重点关注以下几个属性
(1)android:allowTaskReparenting="true|false", 全局设置该<application>节点下所有<activity>节点中的allowTaskReparenting属性值。默认是false,可以被activity节点中的allowTaskReparenting属性值覆盖。具体作用在activity节点介绍。
android:taskAffinity="string", 全局设置该<application>节点下所有<activity>节点中的taskAffinity属性值。默认是应用程序的包名,可以被activity节点中的taskAffinity,属性值覆盖。具体作用在activity节点介绍。
(2)android:allowBackup="true",默认值为true,当为true时,终端下通过执行adb backup packagename对该应用的data/data目录下的数据进行备份,备份好之后会在当前的命令生成一个backup.ab文件,通过执行adb restore backup.ab就可以还原该应用数据。当设置为false,虽然可以执行adb backup和adb restore命令,生成了backup.ab文件,但实际上该文件是没有备份到该应用的数据目录下的数据文件的。
注意:执行adb backup和adb restore命令时,系统会弹框提示,点击左下角的备份/恢复按钮即可。
(3)android:backupAgent="class name",这个属性值是一类名。而这类需要继承并实现BackupAgent类。作用是声明备份代理。
android:restoreAnyVersion="true|false",默认值是“false”。这个属性表明在恢复数据时是否忽略当前程序和产生备份数据的程序之间的版本差异,即备份管理器将忽略android:versionCode属性 。
说明:关于这两个属性,具体可参考这篇http://www.cnblogs.com/over140/archive/2011/12/11/2284217.html,讲得非常清楚。
(4)android:debuggable="false|true",设置是否打开调试模式,在打包签名时会出现Error:(16) Error: Avoid hardcoding the debug mode; leaving it out allows debug and release builds to automatically assign one [HardcodedDebugMode]。
说明:如果使用的是Android Studio进行开发,默认是在工程根目录下的build.gradle文件中的debuggable属性进行配置的,默认情况下release版本是关闭调试信息的,而debug版本是打开调试信息。具体如下配置
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug{
debuggable true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
(5)android:enabled="true|false",默认值是true,作用是全局配置Android系统是否能够实例化该应用程序的组件 。如果值是"true",由具体的组件的enabled属性来决定,如果值是"false",所有组件都不能被实例化。
(6)android:largeHeap="false|true",默认为false,设置largeHeap的确可以增加内存的申请量。但并非无限制申请内存,而是受dalvik.vm.heapsize限制。可使用如下命令查看heapsize,存放在system目录下的build.prop文件中,注意方法该目录需要root权限。
adb root
adb pull system/build.prop
vim build.prop
(7)android:persistent="true|false",设置应用程序是否持续运行运行状态,默认为false。因为应用程序通常不应该设置本标识,持续模式仅仅应该设置给某些系统应用程序才是有意义的。设置该属性后的确就能够达到保证该应用程序所在进程不会被LMK杀死。但有个前提就是应用程序必须是系统应用,也就是说应用程序不能采用通常的安装方式。必须将应用程序的apk包直接放到/system/app目录下。而且必须重启系统后才能生效。
说明:可以使用adb shell dumpsys meminfo命令查看进程是否为persistent状态
(8)android:process="string",应用程序运行的进程名,它的默认值为<manifest>元素里设置的包名,当然每个组件都可以通过设置该属性来覆盖默认值。如果你想两个应用程序共用一个进程的话,你可以设置他们的android:process相同,但前提条件是他们共享一个用户ID及被赋予了相同权限的时候。
注意,进程名字必须包含“.”,否则会报错Failure [INSTALL_PARSE_FAILED_MANIFEST_MALFORMED]
4、<Activity>节点,重点关注以下属性
(1)android:alwaysRetainTaskState="true|false",这个属性用来标记应用的activity task是否保持原来的状态,“true”表示总是保持,“false”表示不能够保证,默认为“false”。此属性只对task的root Activity起作用,task 其他的Activity都会被忽略。
(2)android:autoRemoveFromRecents="true|false",android5.0新特性,是否自动从最近的浏览记录中移除。注意只是移除记录,并没有kill掉进程。
(3)android:exported="true|false",声明是否支持其它应用调用本组件。默认值:如果包含有intent-filter 默认值为false; 没有intent-filter默认值为true,通常与权限相结合,来控制外部应用对该组件的访问的权限。
(4)android:configChanges="uiMode|screenSize|orientation...",目前有14个属性值,该属性的作用是避免对Activity销毁再重新创建,而是调用onConfigurationChanged方法。当我们横竖屏切换的时候会直接调用onCreate方法中的onConfigurationChanged方法,而不会重新执行onCreate方法,那当然如果不配置这个属性的话就会重新调用onCreate方法了,注意android4.0以上,使用orientation属性值时,还要加上screenSize属性不可少,否则不会回调onConfigurationChanged方法。
注意:当需要监听系统的字体大小的修改时,可以配置该属性值为screenSize,当系统字体大小改变时,该activity的onConfigurationChanged会被回调,有可能需要在该方法做适配,此时需要重新加载布局,新的配置才会被应用。
(5)android:persistableMode="persistAcrossReboots|persistNever|persistRootOnly",android5.0新特性,只要将其设置成persistAcrossReboots,activity就有了持久化的能力,另外需要配合一个新的bundle才行,那就是PersistableBundle。 它的具体实现在Activity重载的onSaveInstanceState、onRestoreInstanceState和onCreate方法。API 21后增加了PersistableBundle参数,令这些方法有了系统关机重启后数据恢复的能力。
(6)android:showOnLockScreen="true|false",指定该activity显示在解锁界面,注意需要在activity create 中添加以下代码:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON|
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED|
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);)
5、<activity-alias>节点,该节点主要用于为一个的apk设置多个执行入口,简单理解就是安装后出现多个应用图标,可以设置为每个图标是该APP不同模块的入口点,并且各个模块运行在不同的进程中。
使用方法:首先为已经在manifest文件声明过的activity创建一个别名,必须指定该节点android:name和android:targetActivity,如下:
<activity-alias
android:name="AliasMainActivity"
android:targetActivity=".activity.TaskActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" /> //决定应用程序最先启动的Activity
<category android:name="android.intent.category.LAUNCHER" /> //决定应用程序是否显示在程序列表里
</intent-filter>
</activity-alias>
6、<service>节点:其他属性与activity类似,这里只介绍以下几个属性
注意:从android5.0开始google建议service使用显式Intent启动,否则会出现Service Intent must be explicit个错误,解决方法是指定包名。即
Intent intent = new Intent();
intent.setAction("XXX.XXX.XXX");//你定义的service的action
intent.setPackage(getPackageName());//这里你需要设置你应用的包名
context.startService(intent);
(1)android:isolatedProcess="true|false",默认为false,可设置service运行在隔离的usrID的进程中,即专门有一个进程用于执行service。
(2)android:stopWithTask="false|true",默认为false,默认情况下当清除recent task时,Service的生命周期方法中的onTaskRemoved会回调,但是service并没有执行onDestroy方法,而当设置为true时,onTaskRemoved方法就不会回调但是会执行onDestroy方法。(注意该属性值只在startservice启动方式有效)
7、<provider>节点:
(1)android:authorities="package.className",包名+类名,这两个属性必须指定
android:name="className",类名
(2)android:export="true|false",是否可供外部应用调用,注意:provider供外部使用时需要向外声明android:exported="true"属性,否则会出现如下错误
java.lang.SecurityException: Permission Denial: opening provider com.dson.test.provider.TestContentProvider from ProcessRecord{3749954d 30575:com.dson.manifest/u0a163} (pid=30575, uid=10163) that is not exported from uid 10144
(3)Provider的读写权限:
android:permission:指定整个provider的read/write权限。这个属性是给数据设置读写权限的便利的方法,但是readPermission和writePermission属性比这个属性的优先级要高。
android:readPermission:指定整个Provider的读权限。
android:writePermission:指定整个Provider的写权限。
android:path="string":给内容提供器的数据定义一个完整的URI(数据资源标识)路径。权限只能被授予这个路径所标识的具体数据
android:pathPrefix="string":这个属性定义了内容提供器的数据子集的URI的初始部分。权限能够被授予所有那些共享这个URI初始部分的数据子集
android:pathPattern="string":这个属性给内容提供器数据子集定义了一个完整的URI路径,但是URI中能够使用下列之一的通配
android:permission="string":这个属性定义了一个权限名称,为了读写内容提供器中的数,客户端必须要有这个权限。
android:readPermission="string":为了读取查询内容提供器中的数据,客户端必须要这个权限
android:writePermission="string":为了能够改变由内容提供器所控制的数据,客户端必须要有这个权限
(5)android:grantUriPermissions="true|false",该属性主要用于Intent传递权限,当涉及到三个应用的权限,如应用A为Provider,B为Client,C为GrantClient,那么通过设置该属性,可以在Client已经具备方法A的Provider的
情况下,将权限传递给应用C:GrantClient。(应用场景为一次性授权),具体使用如下:
private void startGrantActivity() {
try {
Intent it = new Intent(this,ClientActivity.class);
it.setClassName("com.dson.grant","com.dson.grant.GrantActivity");
it.setData(Uri.parse("content://com.dson.test.testprovider/event"));
it.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //传递权限
startActivity(it);
} catch (Exception e) {
Log.e(TAG, "Exception:" + e);
}
}
说明:如果内容提供器的grantUriPermissions属性被设置为true,那么权限能够被授予内容提供器范围内的任何数据。(即设置为true使用全局grant,而设置为false则使用部分uri的grant)但是,如果grantUriPermission属性被设置为false,那么权限就只能授予这个元素所指定的数据子集。一个内容提供器能够包含任意多个个<grant-uri-permission>元素。每个都只能指定一个路径(三个可能属性中的一个)。
三、AnroidManifest文件的权限,重点关注uses-permission和permission两个节点,前者是用于声明权限,后者是用于自定义权限。
1、<uses-permission android:name="string">:这个属性用于给应用程序授予正确的操作的所必须的权限。这些权限是在应用程序安装时被授予的,而不是在运行时授予的。apk执行所需要的权限都必须在这里声明,包括自定义权限都需要在这里声明。
2、<permission>: 这个元素用于自定义权限,以便限制对具体的组件、或组件功能、或其他的应用的访问。(自定义权限,应用场景为一个供外部服务的的apk,设置访问权限,注意者需要在uses-permission标签中声明该权限)
(1)android:description="string resource":这个属性用于给权限定义一个用户可读的懂的描述,它要比标签更长更详细。它可以显示给用户,以便向用户解释权限的含义---例如,当询问用户是否要给另一个应用程序授予对应的权限的时候,就会把这个属性所定义的说明显示给用户。
(2)android:icon="drawable resource":权限icon,必须引用一个可绘制的图标资源。
(3)android:label="string resource":定义一个能够显示给用户的权限名称。
(4)android:name="string",权限的名称,它是在程序代码中引用的权限的名称。例如,在一个<uses-permission>元素中和应用程序组件中的permission属性。这个名称必须是唯一的。
(5)android:protectionLevel=["normal"|"dangerous"|"signature"|"signatureOrSystem"],需要注意的是只能定义这四种权限级别,否则编译会报错。
normal 是最低的等级,声明此权限的app,系统会默认授予次权限,不会提示用户
dangerous 权限对应的操作有安全风险,系统在安装声明此类权限的app时会提示用户
signature 权限表明的操作只针对使用同一个证书签名的app开放
signatureOrSystem 与signature类似,只是增加了rom中自带的app的声明
实际测试:
当设置为normal/dangerous/时,只要在uses-permission中声明了就可访问,没发现这两者区别;
当设置为signatureOrSystem时,不仅要在uses-permission中声明了,且还需要调用者与被调用者中同时都是系统应用或者都签名相同。
(6)android:permissionGroup="string",该权限所属的权限组,它必须是在本应用程序或另一个应用中用<permission-group>元素声明的权限组。如果这个属性没有被设置,那么这个权限不属于任何权限分组。
(7)android:permissionFlags="costsMoney",只有唯一值,从单词理解即该类权限是需要花费钱的
3、<permission-group>节点: 给相关的权限声明一个逻辑上的分组名称。独立的权限要通过<permission>元素的permissionGroup属性来加入权限分组。同一分组的中成员会一起展现在用户的界面中。要注意的是这个元素本身并不能声明权限,它只是放置相关权限的一个分类。
作用:在设置的应用程序管理详情中可以查看到以permission-group为类型的label和description。
(1)android:description="string resource",这个属性用于给权限组定义一个用户可读的说明性文本。这个文本应该比标签更长、更详细。这个属性必须要引用一个字符串资源,跟label属性不一样,它不能够使用原生的字符串
(2)android:icon="drawable resource",属性定义了一个代表权限的图标。这个属性要使用包含图片定义的可绘制资源来定义。
(3)android:label="string resource",这个属性给权限组定义了一个用户可读的名称。
(4)android:name="string" />,定义了权限组的名称。
(5)android:permissionGroupFlags="personalInfo"
4、<permission-tree>节点:这个元素用于声明权限树的根节点名称,应用程序持有树中定义的所有权限名称所对应的权限。通过调用PackageManager.addPermission()方法能够动态的来添加新的权限。树中的名称是通过”.”来分离的。要注意的是,这个元素本身并不声明权限,它只是一个能够放置更多权限的命名空间 (动态权限)
(a)android:icon="drawable resource",属性定义了一个代表数中所有权限的图标。这个属性要使用包含图片定义的可绘制资源来定义。
(b)android:label="string resource" ],给权限树定义一个用户可读的名称
(c)android:name="string",这个属性定义了权限树根节点的名称,在命名中必须要有两个以上的”.”来进行分离。这个名称必须是唯一的。
调试问题:
1、java.lang.SecurityException: Not allowed to modify non-dynamic permission com.dson.tree.test.permission
2、android.content.pm.PackageManager$NameNotFoundException: com.dson.tree.test.permission
3、java.lang.SecurityException: No permission tree found for com.dson.test.permission