Android组件模型解析
Android中的Mashup
将应用切分红不一样类别的组件,经过统一的定位模型和接口标准将他们整合在一块儿,来共同完成某项任务。
在Android的Mashup模式下,每一个组件的功能均可以被充分的复用。来自不一样应用的组件能够有机地结合在一块儿,共同完成任务。前端
基于Mashup的Android应用模型
三个基本要素:组件、链接、配置android
接口就是实现单元。
从代码来看,组件就是派生自特定接口或基类的子类的实现,如界面组件Activity就是指派生自android.app.Activity类的子类实现。web
链接指的是组件与组件之间的通讯通道,是android为不一样类别的组件之间进行调用和通讯预设的模式。好比,与界面组件的通讯链接,经过Intent对象来创建,与数据源组件的通讯,则经过URI地址来定位并搭建链接通路数据库
配置是用来描述组件的功能和实现特征的信息。
Android的组件管理服务,就是经过配置文件中的信息去了解每一个组件的特征。数组
以发送邮件为例子缓存

基于Mashup的应用架构特征
核心是组件,在安卓中,组件执行时的聚合单元是任务(Task),每一个任务都由若干个界面组件对象构成,组件可能来自不一样的应用,运行在不一样的进程中,它们彼此独立,无需关注具体调用者或被调用者的实现细节。
组件间的数据传输,都是经过消息、进程间的通讯模型等序列化数据传输的方式来进行,而不是经过对象指针的直接传递,这就使得Android的应用天生具备了良好的跨进程特征。网络
界面组件Activity解析
android的界面组件并无沿用MVC架构,它的设计理解更接近于Web页面。毕竟,Android应用的架构思想就源自于Web2.0中的Mashup的概念。数据结构
- 从运行模式来看,Android是个多任务的操做系统,能够同时运行多个任务,每一个任务都有一个界面组件栈,栈中的元素是界面组件对象的实例,其中负责与用户进行交互的是前台任务的栈顶组件。

- Android的界面组件是用过类型信息,数据URI信息,数据类型信息等描述信息进行定位的。而界面组件的切换和数据传输,都依赖于Android组件管理服务的统一调度和传递。
- Android界面组件的功能设计和Web页面相似,都近似于功能黑盒。在Web开发中,会经过Cooik来存储一些状态信息,出于一样的设计考虑,Android的每一个应用进程都有一个应用环境对象(Application Context),小数据量的共享数据能够经过它来进行存储。
【处理构造界面】经过R类的帮助,使用setContentView()方法。
【处理交互事件】一类是在当前界面的全局事件,能够经过重载Activity中特定的方法来实现,另外一类则是和具体控件相关的交互时间,Android的控件采用了观察者模式,能够经过添加监听者处理相关事件。
【管理界面组件的数据】Android是一个多任务的操做系统,同时运行的任务过多时,就须要自动结束部分应用和组件,以保证系统有充足的内存空间来执行新的任务。Android采用进程托管的策略。对于开发者,须要依照界面组件的生命周期模型,妥善维护好相关的状态,在组件被销毁时序列化保存相关的信息,当应用被从新构造时精准地恢复成销毁前的状态,以保证用户体验的一致性。
【配置界面组件的任务模型】Android界面组件在运行时,会经过任务进行组织。同一个任务中的界面组件,会按照栈模型线性排列。好比,当一个节目组件须要占用大量资源的时候,就不该该有太多的实例同时存在于人物中,而是要可能多地进行复用,以下降系统的开销。为此,android提供了多种组件任务模型,来调整栈中元素的次序,或者是将单个任务拆分红多个任务,甚至将任务放到不一样的进程中去,以提高执行效率,经过配置文件中的launchMode、clearTaskOnLaunch、Process等参数进行设置,在使用其余界面组件时,也须要经过Intent的标志位(flags)来控制目标组件的任务模型。
【适应环境配置变化】不少配置信息会随着设备和环境因素的变动而有所改变,好比硬键盘消失,屏幕朝向,语言环境等。当配置信息产生变化时,正在与用户进行交互的界面组件须要根据这些变化及时做出调整,为用户提供最合适的交互方式。在默认状况下,当配置信息发生变化时,Android会简单地销毁当前交互的界面组件对象,并根据新的配置信息从新构建该组件对象。若是不指望组件随着某个配置信息进行销毁重建,能够经过activity配置项configChanges来标明。并在Activity.onConfigurationChanged函数中更精细地处理相关配置的变化事件。架构
界面组件的数据结构

Activity派生自Context类,Context类提供了应用运行的基本环境,是各组件和系统服务通讯的桥梁。
Context类是个抽象类,ContextImpl派生实现了它的抽象接口。
ContextImpl对象会与Android框架层的各个服务创建远程链接,经过Andorid进程间的通讯机制(IPC)和这些服务进行通讯。
经过Context的抽象和封装,隐藏了应用与系统服务通讯的细节,简化了上层应用的开发。ContextImpl是在Android组件管理服务构造各组件对象时被实例化的。
ContextWrapper的设计应用了修饰模式,它派生自Context,其中的具体实现都是经过组合的方式调用ContextImpl的实例来完成的。这样的设计,使得ContextImpl与ContextWrapper子类的实现能够单独变化,彼此独立。
Android的界面组件Activity、服务组件Service以及应用基类Application都派生于ContentWrapper,他们能够经过重载来修改Context接口的实现并发
服务组件Service解析
Android的服务组件派生自Service类,
从运行模式上来看,Android的服务组件没有运行在独立的进程或线程中。默认状况下,服务组件构造于应用进程中,而且和全部其余的Android组件同样,都在进程的主线程(即UI线程)中运行。这就意味着,若是直接在服务组件中同步执行耗时的操做,就会致使主线程阻塞或界面假死,从而没法响应用户的操做。从使用方式来看,服务组件能够与前端界面组件创建双向链接,提供数据和功能支持,也能够单向接受Intent对象的请求,进行数据的分析处理和功能调度。
服务组件的功能和特征
以闹钟为例:

在这种模式下,服务组件扮演的角色是功能调度者。从事件触发器对象那里收集各种事件信息,进一步分析和处理,而后更新界面、修改数据抑或进行其余相关的存在,调度整个应用使其保持正确的状态。
服务组件还能够扮演另外一个很重要的角色,界面组件的功能提供者。在有的场景下,应该须要停留在本身的交互界面与用户交流。此时它不须要复用第三方界面,而只须要得到一些功能和状态数据便可,这样的支持就是经过服务组件来提供的。
输入法框架便是一个基于服务组件进行复用的例子。

输入法界面组件经过调用bindService函数发起链接请求。输入法服务(InputMethodService)的onBind方法会被调用并构造一个IBinder对象返回给输入法界面组件,从而创建一个IPC链接,界面组件能够经过远程方法调用来进行输入法相关操做,结束服务后,应定义unbindService函数终止链接。
服务组件的开发和使用
构造Service的子类,将其注册在配置文件中。
构造一个扮演调度者角色的服务组件须要实现的函数是onStartCommand,须要注意的是,在Android中,全部的组件都是在主线程上构造的,所以,onStartCommand函数的执行会阻塞主线程。若是涉及数据库读写、网络通讯、复杂运算等耗时操做,那么就须要将相关操做放入独立的进程或线程中去执行。
将组件放入独立的进程中,能够经过配置文件的process参数来实现。
但这样的方式会增长进程开销,另外一种可行的策略是在服务组件中另起一个独立的线程,将那些耗时又费力的操做交给他来打理。
最简单的实现策略是经过派生IntentService来执行服务组件中的处理逻辑。
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
IntentService的实现原理:自己是Service的子类,启动时,会在onCreate函数中创建一个后台线程,该线程经过其独立的消息循环在后台等待事件,当onStartCommand函数接到相关的Intent参数时,主线程会将其中的内容打包发送到后台进程中,在onHandleIntent函数中进行处理,从实现来看,只需把本应放在onStartCommand中执行的逻辑内容挪到onHandleIntent中便可。若是用过bindService函数来绑定服务组件创建链接,那么要处理的就是onBind函数,onBind函数会接收到经过bindService函数发出的Intent对象,依照Intent对象的不一样,Service.onBind函数会返回对应的IBinder对象,整个绑定过程就是一个异步操做,组件管理服务参与其中进行调度。onBind函数返回的IBinder对象,会经过调用ServiceConnection对象的onServiceConnected方法传递给调用者,调用者拿到这个IBinder对象后,就能够经过它与服务组件进程远程方法调用。
当不须要与服务组件通讯时,调用unbindService方法关闭链接。
服务组件的进程间通讯模型
在实际应用中,前台界面组件和后台服务组件可能来自不一样的应用,这就须要进程间通讯。android的进程间通讯模型主要包含三个方面:
【Android的进程间通讯模型架构】典型的代理模式(Proxy Pattern)

在Proxy对象中,每一个函数接口都有一个对应的指令值。当调用者调用这个接口时,Proxy会将数据和指令序列化成一个消息,发送到远端的Stub对象。Stub对象会拆解出对应的指令和数据,并根据指令执行对应的逻辑,将结果返回给接口调用者,整个流程对于调用者而言彻底透明。
Proxy数据包的发生和回传工做,是经过Binder来实现的。在Binder对象中,有一个后台消息循环线程,Proxy传来的消息包会扔到消息队列中等待解析和处理,Stub对象都是Binder的子类型,在服务端被实例化,其接口和实现与Proxy一一匹配,负责将Proxy传递过来的消息进行解包,获得其中的指令和数据。在实际应用中。每一个Stub都会有一个实现子类Implement,其实是由它来真正负责在Binder的后台线程中执行所调用的功能。
界面组件能够经过bindService得到IBinder的对象,经过它的静态方法asInterface能够得到Proxy对象实例。界面端使用Proxy对象,就能够实现对服务端功能的远程调用。
对于IPC方法的调用是一个同步的流程,若是执行时间过长,就会阻塞调用方的线程,这时候须要用到异步IPC调用。

构造一个异步的IPC方法调用,须要传入另外一个IInterface对象,它的Stub对象(图中的Callback.Stub)在调用组件,而它的Porxy对象(图中的Callback.Proxy)会随着序列化传输到服务组件,供服务端在操做执行完成后回调通知。服务组件完成操做后,会转换角色扮演调用方,经过Callback.Proxy对象中的方法,将执行结果通知给调用组件。
与第一次调用不一样,经过Callback.Proxy对象中的方法,这个调用一般是一个非阻塞性质的,即服务组件不等调用组件执行完成后便马上返回。
【框架代码自动生成】整个进程通讯模型中有固定的类型和方法须要实现,包含不少琐碎而雷同的序列化与反序列化操做。经过AIDL(Android Interfacr Deifinition Language)的帮助来自动生成这些框架代码的
AIDL是一种接口描述语言,语法取自Java,在参数上增长了输入输出方向的控制。android SDK中提供了AIDL的解析工具,根据所提供的AIDL文件,自动生成对应的MY_API、MY_API.Proxy、MY_API.Stub等类型的Java文件。开发者只须要基础MY_API.Stub,实现Implement类型,填充真实的执行代码便可,无需关注其余的底层通讯细节。
【参数序列化】整个进程间通讯的流程中,为了将一个进程中的数据传递到另外一个进程中,还有一个重要的步骤,就是数据的序列化和反序列化—-这是全部的进程间通讯的基础。
在Android中,负责序列化和反序列化数据的是Parcel类,它提供了一系列的write和read接口,支持多种类型数据的序列化操做。
支持的数据类型主要有三种
- 基本数据和他们的列表、数组对象。
- 实现Parcelable接口的子类型对象—-子类型经过派生writeToParcel方法和提供构造函数的途径实现序列化相关的操做。
- IBinder和IInterface的子类型对象也能够经过Parcel来序列化。
Parcel序列化后的数据是齐位的二进制流。
进程间通讯机制是android的重要基础,Parcel类型的实现主要经过C++来实现,上层Parcel对象经过JNI接口进行调用,从而提升了序列化相关操做的执行效率,确保Android系统能够高效运行。
Context.getSystemService接口来得到指定的系统服务,这些系统服务,并非经过服务组件来实现的,他们都位于系统的核心进程中,有独立的线程空间。
经过Context.getSystemService得到的,实际上是这些服务的代理对象,这些对象会和真正的服务线程创建链接,经过IPC调用来实现对应的方法。
触发器组件Broadcast Receiver解析
所谓触发器组件,是派生自BroadcastReceiver的子类型。它的实现集中在onReceive方法中。
触发器组件的功能和特征
在使用触发器组件时,只可以把它看成一个函数来使用,除了一些初始化构造时传入的成员变量,其他都没有用武之地,不会有机会被用到。功能函数onReceive的执行必须是同步且快速的,不然会阻塞与用户交互的当前进程。
常见的触发器组件使用模式
触发器组件的设计,解决了后台事件监听问题。在安卓中,只有当事件真正发生时,组件管理服务才会根据配置信息通知对应的触发器组件对象,构造执行组件的进程。
触发器组件的使用
使用触发器组件进行时间监听有两种方法,分别为冷拔插和热拔插。
- 冷拔插,就是将触发器组件的相关信息写在应用的配置文件中。
- 热拔插,经过registerReceiver和unregisterReceiver,动态的将触发器组件与所须要监听的时间进行绑定。实际开发中,通常会在activity的onResume函数中进行触发器组件的注册,而在onPause函数中注销对应的触发器组件。

广播事件的发送
广播事件是经过Intent对象来表示,广播事件须要经过sendBroadcast或sendOrderedBroadcast函数进行发送
经过sendBroadcast的普通广播模式,这种模式,全部注册了该广播事件的触发器组件都会得到事件通知,并发地在各自的应用进程中执行。
经过sendOrderedBroadcast的有序广播模式,全部监听该事件的触发器组件,都会依照设定的优先级进行排序,从高到低依次处理该事件。高优先级的触发器组件能够经过abortBroadcast方法优先终止这个广播的传播,这样,低优先级的触发器组件就再也不有机会处理该事件了。在有序广播事件的传递过程当中,每一个执行中的触发器组件均可以经过setResult等函数在该事件消息中附加额外的数据,而下一个处理该事件的触发器组件则可以使用这些数据。经过这样的方式,事件广播来构成一个消息数据处理链,为了保证该事件必定会被处理,广播事件的发送者还能够指明默认触发器组件,若是事件的传播没有被提早终止,该触发器组件会在最后来响应该事件。
数据源组件Content Provider解析
和其余组件不一样,数据源组件并不包含特定的功能逻辑,而是负责为应用提供数据访问的接口。
数据源组件的定位和操做
数据源组件派生自抽象类ContentProvider,须要实现其中的query、update、insert和delete等抽象接口。数据源组件中数据存储的方式没有任何限制,能够经过数据库、文件等任意方式来实现
整个数据源组件的接口设计集合了REST标准和数据库设计的概念,它经过URI(Uniform Resource Identifier)进行定位,像数据库同样,经过SQL语句来描述具体的操做
URI,全局统必定位标志,经过一个结构化的字符串,惟一标志数据源的地址信息。网络地址URL,是其中的一个子类,每一个数据源组件都有一个惟一的URI标识

组件开发者须要在配置文件中对应的provider条目下声明URI信息的地址描述。
<provider android:name="SimpleContentProvider"
android:authorities="com.duguhome.prodiver.sample">
</provider>
数据源组件URI的命名一般要求与所在应用的包名相关联,以保证在同一个Android设备上具备惟一性。若是在安装应用的时候,发现设备中已经存在具备相同URI地址的数据源组件,新的应用会因为数据源组件冲突而安装失败。
数据源组件也能够用REST的方式来定义操做。
# 对列表类型操做
content://com.duguhome.provider.sample/items
# 对id为1的条目进行操做
content://com.duguhome.provider.sample/items/1
REST是基于HTTP协议而设计的。
为了增长对数据的控制力,android同时为数据源组件引入了SQL语句的支持。

数据源组件的开发
从ContentProvider类派生,并实现抽象方法,同时须要在配置文件中描述其URI等信息。
直接对数据源组件进行数据的读写操做可能会阻塞主线程,从而影响应用于用户的交互。当涉及大量的读写和查询时,调用者能够经过AsyncQueryHandler对象来实现对数据源组件的异步访问。每一个AsyncQueryHandler对象都会开启一个后台线程,在线程中执行与数据源组件的数据交互,进行数据增删改查操做。

token帮助调用者肯定是那一次请求
数据源组件的实现细节
ContentResolver至关于数据源组件的DNS和本地代理,它负责将各个URI定位到具体的数据源组件,并经由它对数据源进行增删改查等操做。
ContentResolver对数据的操做,实际上是分两个步骤完成的,首先是定位,根据URI找到对应的数据源组件,而后,经过对应的数据源组件执行所请求的操做。

有一个数据源组件的缓存对象ProviderMap,它存储各个URI对应到数据源组件对象。
ContentResolver缓存的数据源组件对象,实际上是对于数据源组件的代理。当ContentResolver调用其接口进行操做时,相关指令打包成消息,经过Android进程间通讯机制传到远端的数据源组件中,而数据源组件执行完成后再将结果序列化传回,整个流程是个同步的操做。
默认状况下,每一个数据源组件都只有一个实例,来自不一样进程的要求都会经过进程间通讯机制与其交互,若是频繁地进行交互则开销较大,能够配置参数mutiprocess为true,此时,数据源组件会在每一个调用它的应用进程中构造一个组件对象,避免进程间通讯的开销,从而提升操做和数据传输的效率
对数据源进程查询时,数据须要从数据源组件所在的进程中拷贝到调用者所在的进程中。
一次性拷贝浪费时间,浪费内存,每次拷贝一条增长进程间通讯的成本。android采用了数据窗口的模式。在数据指针对象Cursor中,包含一个CursorWindow对象,它会在调用者一端缓存部分数据,缓存数据的内容包含当前指针指向位置相关的若干条数据。CursorWindow类的底层实现是基于C++的
应用配置文件解析

权限配置
权限配置包含权限的定义和权限使用声明两部份内容。
应用须要使用的权限,须要经过配置项user-permission来声明。
若是开发者须要定义权限来自第三方应用的访问,则经过permission配置项来进行定义。
<
permission
android:name=""
android:label="权限的名字"
android:description="权限的具体描述"
android:permissionGroup=
"android.permission-group.COST_MONEY"
android:protectionLevel="normal"
>
定义了的权限还须要部署到对应的组件才能生效,组件管理服务在构造一个组件对象时,会校验请求组件的权限声明是否与该组件的权限配置相匹配(若是请求组件和实现组件位于同一应用,无需进行检查),若是匹配失败,会抛出异常阻止此次调用。android的权限体系没有传递性。

权限也能够不事先部署在组件上,而是在运行时调用checkPermission函数动态校验。
