Android的独白

                                                                

                                             Android的独白

  • Android应用程序提供了多种访问入口 
    一个Android应用程序由不同的独立组件构成,并且可以独立调用,比如一个Activity可以提供一屏UI显示,一个Service可以用于在后台执行任务。在一个组件中启动另一个组件,可以使用intent,甚至可以使用intent启动其他一般应用程序,这使得一个应用程序可以有多个访问的入口,并且可以让其他应用程序的某个组件看起来好像自己应用的一部分。


  • 一个应用程序可适配不同设备 
    Android提供了可适配的框架,一种资源可以适配不同的设备。比如一个layout布局会根据不同设备的屏幕自动匹配。开发者可以在应用运行时查看应用程序所需权限,开发者也可以在自己开发的应用中声明所需权限,Google Play可以自动筛选支持应用所需权限的设备,从而减少不必要的适配问题。


应用程序基础


Android应用程序主要由Java编写,Android SDK工具将代码、资源文件和数据内容打包成APK文件APK文件包含了该应用程序的所有组件,该APK文件可以在支持Android的设备上安装并使用。 


一旦安装成功,应用程序将受到沙箱机制的保护:


  • Android操作系统是一个多用户的Linux操作系统,每个应用程序就代表一个用户。


  • 默认情况下,系统为每个APP分配一个用户ID(该用户ID对于APP来说不可见,仅系统可见),系统使用一个APP来管理所有文件的权限,这个APP也有用户ID,使用这个ID才能访问这些权限。


  • 每个应用程序都有独立的虚拟机,这使得每个应用可以互不干扰地运行。


  • 默认情况下,每个APP都有自己的Linux进程。一个应用的某个组件需要执行,Android就会启动这个应用的进程;相反,当应用不再使用或系统内存紧张时,该应用所在进程将被停止。


通过上述要求,Android是一个满足最小权限原则(principle of least privilege)的系统,也就是说,在默认情况下,每个APP只能启动该它所需的功能组件,系统未给予其权限的内容,APP将无法访问。


然而使用下列方式,应用程序之间可以共享数据,或者程序可以访问系统服务:


  • APP之间可以共享Linux用户ID,使用这种方式,APP之间可以共享数据,为了节省系统资源,共享相同Linux用户ID的APP将使用同一个进程,并运行在同一个虚拟机上(APP必须有相同签名)。


  • APP可以申请访问系统数据的权限,如用户联系人,短消息,SD卡,蓝牙,摄像头等。用户必须显式地授予应用这些权限,如需了解更多,请您复制这个链接:《Working with System Permissions》(https://developer.android.com/training/permissions/index.html),或参考我翻译的博文《Android 6.0及以上版本的运行时权限介绍》。


下面将介绍APP的核心基本组件、manifest文件、与代码分离的资源文件及如何使用资源文件修饰符适配不同的设备。


APP组件


也就是所谓的四大组件,每个组件都有自己独立的生命周期,管理者组件的创建和销毁。


  • Activities 
    Activity代表了与用户交互的UI(它并不是一个UI,而是一个用于呈现UI 的载体),您所看到的屏幕上的内容,都是以Activity作为载体的。如需访问有关Activity的官方文档,请您复制链接:《Activities》(https://developer.android.com/guide/components/activities.html),当然我也将在后续博文中翻译该文档。


  • Services 
    Service是一个运行在后台的组件,它主要用于长连接和远程操作,它没有与用户交互的UI界面。比如说,Service可以与在后台运行音乐,或者从网上下载数据而不影响用户的UI操作。可以在Activity中启动或者绑定Service以便与其通信。


  • Content providers 
    content provider管理着一个APP中的数据信息。您可以将数据存储在任何可以持久化保存的地方,如SQLite数据库,文件系统,网络上等。其他应用可以通过content provider访问本应用的数据信息。例如,系统的联系人应用程序中的Content Provider提供了通讯录信息,使用ContactsContract.Data类(需配合Content Resolver)就可以访问系统的通讯录信息。 
    Content Provider对应用的数据隐私性做了较好的保护。


  • Broadcast receivers 
    Broadcast receivers用于接收系统发出的各式广播信息。系统广播有很多,如:点灭屏幕时,电量低时,获取图片时 等。应用程序也可以自定义广播,如当APP下载了一些信息需通知其他APP ”信息已下载完毕可供使用” 时,应用可发出一条广播,而其他应用可截获该广播。Broadcast receivers同样无法进行UI展示,但是可以显示一条Notification表示有广播发出。更简单地说,Broadcast receivers仅是发起了一个意图,可以指导对该意图感兴趣的应用做一些事情。


Android系统的一个独特设计就是任何一个APP都能启用其中另一APP的组件,例如,如果您打算使用系统摄像头照一张照片,使用自己的应用就能启动,而不必再从头编写照相功能的代码,您也无需将自己APP的代码与照相的应用程序的代码有什么关联。您只需使用自己的activity启动带有照相功能的activity并返回照相的结果就行了。这个带有照相功能的activity就好像是自己应用的一部分。 


当您启动了一个APP的组件,就启动了该APP所在的进程,并初始化该组件的类,比如,您的APP启动了相机应用中的activity,该activity将运行在属于相机应用的进程中,而不是您的APP中。因此,Android应用并不存在唯一的入口(如没有main()方法)。


启动组件


通过intent这个异步请求,可以启动 activities, services, 和 broadcast receivers 这三大组件,Intent在运行时将不同的组件做绑定,不论这些组件是否来自同一应用。通过intent启动组件有两种方式:隐式intent和显式intent,这两种方式将在后续文章中具体介绍。 


对于启动 activity或serviceintent 可以附加一些 action,或是一些指向具体数据内容的URI。例如某些 intent 打算启动一个带有展示图片功能的 activity,或是打算打开一个网页,在某些情况下,启动后的组件会通过 intent 返回一些数据(比如说您希望intent打开的是某个通讯录的联系人,并返回这个联系人的信息,返回的信息就包含在 intent 中,而且还包含了指向这个联系人的URI信息)。


对于 broadcast receivers 来说,可以在 intent-filter 中声明想要接收什么样的广播,比如说您打算在您的应用中收到系统发出的“电量低”的广播,那么只需要在您的 broadcast receivers 中配置有关“电量低”的消息就行了。


对于 content provider 来说,intent 无法启动。Content Provider 需要和 ContentResolver 配对使用。 content resolver 负责处理与之对象的 ContentProvider 事务,所以 ContentProvider 不用显式地调用 contentResolver 中的方法,就能将数据传递出去。这种做法很好地将应用中的数据与访问该数据的方法分离,提高了应用的安全性。


可以使用下列方法启动相应组件:


  • 启动Activity: startActivity() 或 startActivityForResult()。


  • 启动Service:startService();绑定Service:bindService()。


  • 启动Broadcast:sendBroadcast(), sendOrderedBroadcast(), 或 sendStickyBroadcast()。


  • 如需查询content provider 中提供的数据信息,可调用ContentResolver.query()方法。


Manifest文件


在使用组件之前,需在 Manifest 文件中对组件进行注册。除了注册组件以外,Manifest 文件还可以作如下事情:


  • 为应用申请所需权限(如度取系统联系人、访问intent等),需配置 use-permissions


  • 定义最小API ,需配置 minimum SDK Version


  • 声明使用应用所需的硬件和软件配置(如蓝牙、相机、支持多点触控的屏幕),需 use-features


  • 应用所需链接的API库,如 Google Maps library,需配置 meta-data


  • 其他


声明组件


配置activity的方法如下:




其中 <application> 标签的 icon属性 用于配置启动图标。<activity> 标签的 name属性 用于注册应用中的 activity 的子类,需使用全限定名注册。


broadcast receivers 除了可以在 ManiFest 文件中静态注册,还可以在代码中使用 registerReceiver() 方法动态注册。有关 ManiFest 文件的更多内容,请您点击这个:


《App Manifest》

https://developer.android.com/guide/topics/manifest/manifest-intro.html


声明组件的兼容性


intent 的真正强大之处在于它的隐式启动,隐式intent 可以描述其想要启动的目标组件所具备的特性而不具体指明目标组件的名字。


通过为组件配置 intent filters,组件具有被 隐式intent 筛选的能力,只有匹配了 intent filters 中的条件,该组件才能被 隐式intent启动。例如,您打算在应用中配置一个发送邮件的 activity,使得通过隐式intent表明“发送邮件”的意图,那么您可以这样为 activity 配置 intent filters




更多有关intent filter的官方介绍,请您访问这个链接:


《Intents and Intent Filters》

https://developer.android.com/guide/components/intents-filters.html


声明APP所需的软件和硬件信息


支持Android的设备各不相同,它们支持的底层驱动也各有千秋,为了防止将需要某个底层驱动的应用(如带有照相功能的应用)安装在并不支持该功能的设备上(该设备没有摄像头),需在 manifest 文件中配置,这个配置信息在系统中仅是说明性质的(即Android系统并不读取这个配置说明),但将应用发布到某个平台上(如Google play)时,该平台会读取这些声明。比如说, 您的应用需要调用系统摄像头并运行在最小版本为 Android 2.1(API 7)的设备上,您需要作如下配置:




低于android 2.1或不支持摄像头的设备将无法在Google play上搜索到该应用。


您也可以声明您的应用使用摄像头但不是必须的,这需要将 use-features 标签的 android:required属性 设置为 false,并在代码中检测设备是否有摄像头、关闭所有与摄像头有关的功能。


APP资源


APP资源实现了与代码的分离,这些资源包括图片、影音等可视资源。例如,您可以定义 animations、menus、 styles、 colors或是activity的layout 资源,使用资源配置,可以做到代码的解耦,并使其与更多地设备适配。


对于每一个资源,SDK工具都会为之设置一个唯一的整型ID,通过这个ID,您可以在代码中引用这个资源, 为各种不同的设备适配资源是APP资源的最大优势,您可以在资源文件名中追加修饰符(qualifier)以匹配不同的设备。如 res/values-fr/ 文件夹中放置法语的字符串信息,而默认的 res/values/ 目录下放置默认的字符串信息,系统会根据您安装的设备系统语言自动匹配不同文件的内容。


类似这种限定符还有很多,比如您还可以为适配横竖屏的不同布局而追加修饰符等。有关资源修饰符的官方文档,请您复制到浏览器访问这个链接:


《Providing Resources》

https://developer.android.com/guide/topics/resources/providing-resources.html


设备的兼容性


Android 设备千千万,这需要您的 Android 应用程序具备很好的兼容性。为了更好的适配各式屏幕,Android 提供了修饰符。


关于兼容性,分为两种:设备的兼容性(device compatibility)和 APP的兼容性(app compatibility)。


对于设备的兼容性,您不必担心,只要是能发布在Google play上的应用,都兼容android 设备。


但是对于APP的兼容性,您就得留意了,因为不同设备对硬件的支持不尽相同,比如有的应用需要指南针的底层支持,那么不支持指南针的设备将无法运行该应用。


以下是检测设备是否支持指南针功能,若不支持,应将其关闭:



每一个android版本都为应用做了向后兼容的适配,也就是说,您应该为应用程序适配新的版本以做到向前兼容(以我的经验来看,假设当前市面上占有率最高的版本是android 5.1,那么您应将应用的targetSDKVersion设置为 23 (android 6.0),这个设置表明您的应用程序在android6.0 的设备上可以正常运行,这可以保证占有率最高的android 5.1的设备升级到android6.0时,您的应用程序不会崩溃。所以一般将targetSDKVersion设置为比目前市场占有率最高的版本设备高1-2个版本即可。


系统权限


Android的安全架构


在默认情况下,每个应用都没有任何权限去访问其他应用、系统设置和用户的隐私数据。包括读写用户的隐私信息(通讯录和邮件),读写其他应用的数据,访问Internet、使设备保持唤醒状态等等。 


基于android系统的沙箱机制,应用必须显式地分享数据和资源,这需要显式地申请权限:应用显式地在配置中申请权限,还需获得用户的同意。


应用的签名


所有apk文件都必须用证书签名,并由开发持有一个私有的key。这张签了名的证书就是对该应用程序开发者的认证,这张证书无需由权威机构认证,而是由开发工具进行自我签名,使用证书可以保护开发者的权益,并且系统可以允许或拒绝应用使用 签名级别的权限(signature-level permissions);证书还可以允许或拒绝应用 请求与其他应用持有相同Linux ID(request to be given the same Linux identity) 的权利。


用户ID与文件访问


安装应用时,android 赋予每个一包(android以包名区分不同应用)一个确定的Linux用户ID(Linux User ID),从该应用的安装到卸载,这个ID一直存在且保持不变。在不同的设备中,ID可能会不同;但这个ID在同一台设备上会保持唯一。


出于安全性的考虑,在一般情况下,两个不同的包(应用)不能处于同一进程中;但是,您可以在 AndroidManifest.xml 文件的 manifest 标签中的 sharedUserId属性 中为不同的包(应用)配置相同的用户ID(User ID),这时两个包(应用)会被看作是一个应用,它们具有相同的用户ID和文件权限(user ID and file permissions)。需特别注意的是,为了安全性的考虑,两个应用具有相同的签名,同时还配置了相同的sharedUserId属性,这两个应用才拥有相同的用户ID


一个应用存储的数据都会被赋予该应用的用户ID,其他应用无法共享这些数据。


使用权限


在默认情况下,一个 Android 的应用程序为被赋予任何权限,这对用户的体验或数据的存取会产生不利影响。为了使用被设备保护的特性,您必须使用 <uses-permission> 标签添加权限。


比如说,您希望模拟器可以接收发送过来的短消息时,可是使用下列代码添加权限:




如果您申请的是基本权限(normal permission;基本权限意味着该权限不会对用户的隐私或设备的运行造成太多负面影响),系统会自动赋予这些权限;


如果您申请的是危险权限(dangerous permission;危险权限意味着该权限可能对用户的隐私或设备的运行造成潜在的影响(potentially affect))【有关权限级别的官方介绍,请您复制这个链接至浏览器访问:Normal and Dangerous Permissionshttps://developer.android.com/guide/topics/security/permissions.html#normal-dangerous)】,系统会显式地告知并请求用户授权。设备的版本不同,告知和请求的方式也不同:


  • 若您的设备是 Android 6.0 (API level 23)或以上版本,并且应用设定的targetSdkVersion是 23 或以上时,应用会在运行阶段向用户请求权限,用户也可以随时撤销赋予的权限,所以应用会在每次运行时查看自己的权限。更多有关运行时权限的介绍,你可以访问我的博文(指本篇作者的博文):《Android 6.0及以上版本的运行时权限介绍》


  • 若您的设备是Android 5.1 (API level 22)或以下版本,并且应用设定的targetSdkVersion是 22 或以下时,应用会在安装时请求用户赋予权限。若您在新的APP版本中加入了新的权限,安装新版本时,应用会请求用户赋予该新增的权限。一旦您安装了应用,就意味着您同意赋予所有请求的权限,若想取消任何权限,只能卸载该应用。


有些时候,应用请求权限失败时,会抛出 SecurityException 异常,但这个一场不什么时候都会抛出,大多是情况下,请求权限失败时只会打印Log。


一个特别的权限可能会在多处地方被使用:


  • 调用系统内部的一些方法时:系统会阻止应用访问系统内部的一些核心方法


  • 当启动一个其它APP的Activity时;


  • 当发送和接收一个广播时,该权限可以决定谁可以接收该广播,或者谁可以将该条广播发动给您;


  • 当操作content provider时;


  • 当启动或绑定服务时。


自适应权限


为了保证您的应用总能适配新的API版本增加的权限,建议将targetSdkVersion设置得尽可能的高(as high as possible)。


基本权限和危险权限


系统为权限分成了若干个级别,其中最重要的两个级别即为基本权限和危险权限。


  • 基本权限说的是您的应用需要访问沙箱以外的数据或资源,但是这对用户隐私和系统的运行几乎不会造成什么影响。比如说“设置时区”权限就是一个基本权限。若您在manifest文件中配置了基本权限,那么该权限会被自动赋予而无需请求用户。


  • 危险权限说的是应用会访问用户的隐私信息、对用户存储的数据造成潜在的影响或是影响了其他APP的运行等。比如说“读取用户的联系人”就是危险权限,如果您在manifest文件中配置了危险权限,那么该权限需要在使用时由用户显式赋予。


权限组


所有的危险权限都被分成了权限组,若设备的版本号是 Android 6.0 (API level 23) 或以上,并且 targetSdkVersion为23 或更高。当您的APP请求危险权限时,下列行为将会发生:


  • 若您的应用正在申请manifest中配置的危险权限,系统会弹出一个对话框,该对话框描述了该危险权限所属的权限组,而不仅仅是该权限。比如说,您的应用正在请求 READ_CONTACTS 权限时,系统仅仅会弹出该应用正在申请设备通讯录(device’s contacts)的对话框。若用户同意了该请求,系统仅会赋予请求的单个权限。


  • 若您的应用正在申请manifest中配置的危险权限,并且用户之前已经赋予了该权限所属权限组的其他权限给应用,那么此时这个危险权限将自动被赋予无需用户同意。比如说,若应用之前已经获得了READ_CONTACTS权限,那么当应用请求WRITE _CONTACTS权限时,该权限将自动被赋予,而无需请求用户。


事实上,无论是基本权限还是危险权限都被分成了权限组,但是只有危险权限所在的权限组会发生上述的情形,所以您可以忽略权限组中的基本权限。


若设备的版本是 Android 5.1 (API level 22)或以下,而且 targetSdkVersion为22或以下,系统会在应用安装时,声明所需的所有权限,这些声明是以权限组展示给用户的而不是单个权限。


以下列出危险权限及其权限组:





自定义并执行权限


若打算自定义权限,您需要在 manifest文件 中使用 <permission> 标签声明。


比如说,若打算控制某个 Activity 的启动权(谁可以启动该Activity),您可以配置如下代码:




其中:


  • <protectionLevel>属性 是不能缺省的,该属性表明了请求该权限的级别,或谁可以被允许持有该权限。


  • <permissionGroup>属性 是可选的,该属性仅是用来以分组的形式将权限显示给用户,您可以将该属性设置到一个标准权限组 标准权限组(android.Manifest.permission_group)里,也可以设置一个自定义的权限组。推荐使用前者,因为这样可以减少弹出对话框的次数。


  • label 和 description 都是用户自定义的属性,当用户查看应用拥有的权限时,显示的将是label属性中的内容,而权限的具体介绍将是 description 中的内容。自定义的属性名称label应该简洁明了,能准确地表示该权限保护的内容,而 description 应该用几句话描述权限允许应用所做的事和可能带来的对用户隐私的窥探。下面是一个自定义的 CALL_PHONE的label 和 description 的例子:




在manifest文中强制实行的权限


若您在 manifest 文件中声明了高级权限(High-level permissions),那么这种权限不是所有组件都能获取的,若希望针对某个组件获取高级权限,您应当在该组件的标签中用 android:permission属性 声明该高级权限。


  • Activity权限:activity标签中声明的高级权限可以控制 谁可以启动该activity,您应当在调用 Context.startActivity()和Activity.startActivityForResult() 方法时判断是否能启动该activity。若未被赋予该权限,则这两个方法将抛出 SecurityException 异常;


  • Service权限: service标签 中声明的高级权限可以控制 谁可以启动或绑定该service,您需要在调用 Context.startService(), Context.stopService() 和 Context.bindService() 方法时判断是否可以启动或绑定该service,若未被赋予该权限,则这三个方法将抛出SecurityException 异常;


  • BroadcastReceiver权限: receiver标签 中声明的高级权限可以控制谁可以发送广播给该BroadcastReceiver,您应当在 Context.sendBroadcast() 方法返回以后再判断权限是否被赋予,因为会尝试将该broadcast发送给其他满足条件的receiver,所以,如果声明了高级权限的receiver没有接受到broadcast,sendBroadcast() 方法也不会抛出异常;若使用 Context.registerReceiver() 方法动态注册receiver,同样可以赋予该receiver高级权限;


  • ContentProvider权限: provider标签 中声明的高级权限可以控制 谁有权访问该provider提供的数据(contentprovider还提供了URI Permission,将在稍后介绍)。与其他的组件稍有不同,provider中可以声明两种权限属性: android:readPermission和android:writePermission,前者可以控制谁能读取该provider的数据,后者可以控制谁能写入该provider的数据。仅有写入该provider权限的组件不代表您也能读取,反之亦然。当您第一次检索该provider时,该高级权限将被检测(如果没有权限,SecurityException将被抛出),其中ContentResolver.query() 方法需要 android:readPermission属性 ContentResolver.insert(),ContentResolver.update(), ContentResolver.delete()  这三者需要 android:writePermission 权限,若相应权限未设置,上述方法将抛出 SecurityException 异常。


发出广播时强制执行的权限


除了上述提到的为 broadcast receiver 声明高级权限外,您还可以在调用 Context.sendBroadcast() 方式传入一个在String类型的权限。 
由于 Broadcasts和receiver 都可以被赋予权限,两种权限都需要经过 intent 的筛选才能匹配。


URI权限


处于安全的考虑,对于 content provider,可以为其声明保护自己的读写权限。比如说,获取邮件需要权限,但赋予的URI却指向了一个图片浏览器,该浏览器无法打开邮件,因为浏览器没有被赋予打开邮件的权限。


为了解决这个问题,可以在启动activity时为intent设置Intent.FLAG_GRANT_READ_ URI _PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION字段。




如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值