Android 应用程序基础(Application Fundamentals )
转 http://www.cnblogs.com/TerryBlog/archive/2010/07/12/1775624.html
2010-07-12 11:12 by Terry_ 龙, 3909 visits, 收藏 , 编辑
Android 应用程序是用java 语言写的,通过aapt 工具把应用程序所需要的任何数据、资源文件打包成apk 文件,这个文件是一个将应用安装到手机上的一个载体。
有很多方式,每个android 应用程序存在于不同的世界:
(1 ) 默认的,每个应用在他自己的Linux 进程中运行,当应用中的任何代码需要执行时android 就启动相应的进程,当不需要执行时并且系统资源被其他应用请求时android 就关闭相应的进程。
(2 ) 每个进程都有他自己的虚拟机对象(VM ),所以应用程序代码与其他的应用运行是彼此隔离的。
(3 ) 默认的,每个应用被分配一个唯一的Linux user ID ,都被设置权限以便应用程序的文件只对用户可见或者只对应用自己可见。
安排两个应用程序共享一个user ID 是可能的,这种情况下他们彼此之间是可以看见对方的文件的,为了保护系统资源,拥有相同ID 的应用也能被安排运行在一个相同的Linux 进程中,共享相同的VM 。
1 、 应用组件(Application Components )
Android 一个核心的特点就是一个应用能使用另一个应用的元素(如果另一个应用允许的话),你的应用不需要包含你用到的另一个应用的代码也不需要你连接这些代码,相反的,只是当应用需要这些代码时,就启动另一个应用相应的代码(不是让另一个应用全部启动)
为了这个能工作,当一个应用的任何部分被需要时系统必须能启动这个应用进程,并且将这个部分实例化成java 对象,因此,和其他大多数系统不同的是,android 应用程序没有一个单独的程序入口(例如:没有main ()函数),相反的,android 应用有必要的组件以便当需要时系统能实例化并运行它,android 中有四种组件:
(1 ) Activity
一个Activity 是一个可见的用户可以使用的用户界面,如果一个应用中有多个Activity ,虽然彼此结合形成一个应用在一起工作,但是每个Activity 是彼此独立的,每个都是Activity 的一个子类。
一个应用程序可能由一个或多个Activity 组成,这些Activity 这么样显示,需要多少个Activity ,依赖于这个应用的设计者,一般的,有一个Activity 应该被标记成当这个应用启动时第一个呈现出来给用户的。
每个Activity 默认的被给予一个窗口来绘制,一般的,这个窗口占满整个屏幕,但是他可以比屏幕小并且浮在另一个窗口的上面。
一个窗口中的可见的内容是由一些具有层次关系的view 组成的,都是继承自View 类的,每个view 都控制一个窗口中的特定的矩形框,parent view 包含children view 和组织children view 的布局,leaf view (那些在继承层次最底层的view )绘制在他们所控制的矩形框中,并且对用户的动作做出直接的回应,因此view 就是Activity 和用户交互的地方,android 有很多已经做好的view 你可以使用,包括buttons ,text fields ,scroll bars ,menu items ,check boxes 等等
一个view hierarchy 是通过Activity.setContentView() 方法被放到一个Activity 的window 中的,content view 是view hierarchy 中最顶端的那个view 。
(2 ) Services
一个service 不是一个用户可见的组件,在不确定的一段时间内运行在后台,每个service 都继承自Service 类。
你可以连接(connect )或者绑定(bind )到一个正在运行的service (如果这个service 还没运行的话就启动它),当连接到service 后,你可以通过一个service 暴露出来的接口和这个service 交流,对music service 来说,这个接口可以是允许用户暂停,后退,停止,重新播放。
和Activity 或者其他组件一样,service 运行在这个应用进程的主线程中,所以他不会阻塞其他的组件或者用户界面,他们经常为那些耗时长的任务单独开一个线程。
(3 ) Broadcast receivers
一个broadcast receiver 这样一个组件,他只是接收广播并作出反应,在系统中有很多已有的广播,比如反应时区变化(timezone )的,电池变化(battery )的,用户修改了系统语言时的广播,应用程序也可以自己定义广播,比如定义这样一个广播,让其他的应用知道某些数据已经下载完毕了可以使用了。
一个应用可以有任意多个broadcast receiver 来对他所关心的广播进行监听并作出反应。所有的receiver 都继承自BroadcastReceiver 类。
BroadcastReceiver 不显示在用户界面上,然而,他们可以启动一个Activity 来作为他们接收到的信息一种反应,或者他们可以使用NotificationManager 来提示用户,Notifications 可以通过不同的方式获得用户的注意,比如点亮呼吸灯,震动电话,播放一个声音等等,他们一般放一个图标在状态栏上,来让用户可以打开获得这些信息。
(4 ) Content providers
Content providers 是一个应用程序数据的集合,来让其他的应用可以访问这些数据,这些数据可以被存在文件系统中,SQLite 数据库中,或者其他可以存数据的地方,Content providers 是一个基本的方法集合来使其他的应用可以获得和存储这些数据,然而应用不直接调用这些方法,而是使用一个ContentResolver 对象来调用这些方法,一个ContentResolver 可以和任何的Content providers 交流,他和provider 协作来管理系统中任何进程间的通信。
无论何时一个请求都应该由一个特定的组件来处理,android 系统来确保包含这个组件的应用进程运行,如果需要就启动它,如果需要就为这个组件创造一个实例,确保这个组件的一个适当的实例可以被得到。
2 、 启动组件:intent
当有一个来自于content resolver 的请求指向Content provider 时,content provider 启动,其他的三个组件(Activity ,service ,broadcast receiver )是通过一个叫做intent 的异步的消息来启动的,一个intent 持有一个message 的内容,对Activity 和service 来说,他是一个被要求的动作(action )和在该动作上的数据的URI ,对broadcast receiver 来说,intent 对象是一个被广播的动作。
针对每种组件分别有对应的方法来启动它:
(1 ) 一个Activity 是通过传递一个Intent 对象到Context.startActivity() 或者Activity.startActivityForResult() 来启动的(或者去做一些新的任务),被启动的这个Activity 可以通过getIntent ()来获得导致他启动的那个intent 的。
(2 ) 一个service 是通过传递一个Intent 对象到Context.startService() 来启动的(或者给一些新的命令给正在运行的service ),android 调用service 的onStart ()方法,并且把Intent 对象传递给他,同样的,一个Intent 可以传递到Context.bindService() 方法里来建立一个介于正在运行的service 和调用他的组件之间的连接,这个service 通过onBind ()方法来接收这个Intent 对象,(如果这个service 还没有运行,bindservice ()能选择性的启动它),在后面的部分,关于绑定service 的更多详细的信息请查看远程调用。
(3 ) 一个应用可以通过传递一个Intent 对象给像Context.sendBroadcast(), Context.sendOrderedBroadcast(), Context.sendStickyBroadcast() 这样的方法来开始一个广播,android 通过调用对应的onReceive ()方法将intent 传递给所有对这个广播感兴趣的broadcast receiver 。
3 、 关闭组件(Shutting down components )
当对来自于content resolver 的请求作出回应时content provider 就启动了,当有一个感兴趣的broadcast message 被广播时,broadcast receiver 启动,所以我们需要知道怎么关闭这些组件。
(1 ) Activity 可以通过调用它自己的finish ()方法来关闭,一个Activity 也可以通过调用finishActivity() 来关闭另一个Activity (这个Activity 是通过调用startActivityForResult() 来启动的)。
(2 ) 一个service 可以通过调用自己的stopSelf (),或者Context.stopService() 来关闭。
当组件不再使用时或者android 为了更多组件能运行而回收内存时,android 系统是关闭这些组件的,在后面的部分,可以在组件的生命周期中看到更多更详细的介绍。
4 、 Activities and Tasks
一个Activity 可以启动另一个Activity ,即使这个Activity 是定义在另一个应用里的,比如说,你想展示给用户一条街的地图,现在已经有一个Activity 可以做这件事,那么现在你需要做的就是将你请求的信息放进一个Intent 对象里,并且通过startActivity ()传递给他,这个地图就可以显示出来了,但用户按下BACK 键时,你的Activity 又重新出现在屏幕上。
对用户来说,显示地图的Activity 和你的Activity 好像在一个应用中的,即使是他们是定义在不用的应用中的,运行在各自的应用进程中,android 将两个Activity 放进一个task 里,一个task 是一组彼此联系的Activity ,被安排在一个堆栈中,堆栈中的根Activity 就是开辟这个task 的,一般的,他是用户选择应用后首先启动的那个Activity ,堆栈顶部的Activity 是当前正在运行的Activity ,当一个Activity 启动另一个Activity 时,新的Activity 被压进堆栈中,成为运行的Activity ,当用户按下BACK 键,当前的Activity 弹出堆栈,先前的Activity 恢复成为运行的Activity 。
一个task 就是一组Activity 的堆栈,不是在manifest 文件里的一个类,一个元素,所以没有方法来为一个task 里的Activity 独立的设置值,对task 设置值是在root Activity 里设置的。
一个task 里的所有Activity 组成一个单元,整个task (整个Activity 堆栈)可以在前台,也可以在后台(应用程序的切换就是task 的前后台的切换),假设,当前的task 有四个Activity 在堆栈里,用户按下HOME 键,去开启另一个应用(实际上是一个新的task ),那么当前的task 就退到后台运行,新开启的应用的root Activity 此时就显示出来了,然后,过了一段时间,用户回到主界面,又重新选择了以前的那个应用(先前的那个task ),那么先前的那个task 此时又回到了前台了,当用户按下BACK 键时,屏幕不是显示刚刚关闭的那个应用,而是移除回到前台的这个task 堆栈栈顶Activity ,将下一个Activity 显示出来。
刚才描述的情况是Activity 和task 默认的行为,但是有很多的方法来对几乎所有的方面进行修改,如Activity 和task 的联系。task 里Activity 的行为,是受启动它的Intent 对象的flag 和在manifest 文件中的Activity 的属性集合共同影响的。
Flag :
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
FLAG_ACTIVITY_SINGLE_TOP
<activity> 属性:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
5 、 Affinities and new tasks
默认的,一个应用里的所有Activity 都有联系,所有都是属于一个task 的,然而,可以通过<activity> 下的taskAffinity 属性来为每个Activity 单独的设置属性关系,定义在不同应用中的Activity 可以共享一种关系(affinity ),或者定义在同一个应用中的Activity 可以分配不同的关系(affinity )。这种关系在两种情况下生效,当启动Activity 的 Intent 对象包含有FLAG_ACTIVITY_NEW_TASK 标志,一个Activity 的allowTaskReparenting 属性设置为true 。
FLAG_ACTIVITY_NEW_TASK
一个Activity 调用startActivity ()启动一个新的Activity 时,新的Activity 会压入到相同的task 中的,如果传递给startactivity ()的Intent 对象含有FLAG_ACTIVITY_NEW_TASK 标志,系统就会寻找一个新的task 来装这个新的Activity ,然而,也不总是这么做,如果已经有一个task 和这个新的的Activity 有相同的关系,那么就把这个新的Activity 放进这个task 里,如果没有,就启动一个新的task 。
allowTaskReparenting 属性
如果一个Activity 的allowTaskReparenting 属性设置为true ,这个Activity 就可以从启动时的那个task 移动到一个和他有关系的当前在前台的一个task 里,比如,假设现在有一个天气预报的Activity 被定义在一个旅行的应用里,他和这个应用里的其他Activity 有相同的关系(默认的关系),并且他允许reparenting ,现在你自己应用有一个Activity 启动这个天气预报的Activity ,那么天气预报Activity 就会移动到你的Activity 所在的task 里,当旅行的应用又回到前台时,天气预报Activity 重新回到以前的那个task 并显示。(个人观点:如果说没有设置这个属性,或者这个属性设置为false ,那么一个应用里的Activity 调用另一个应用里的Activity 时,系统是为另一个应用里的Activity 创建一个实例,然后放到同一个task 里,但是如果设置了allowTaskReparenting 为true ,那么另一个应用里的Activity 是可以在不同的task 间来回移动的,那个task 在前台就移动到那个task 里)
6 、 启动方式
<activity> 下的launchMode 属性可以设置四种启动方式:
"standard" (the default mode)
"singleTop"
"singleTask"
"singleInstance"
这些不同的方式可以从下面的四点来说:
(1 ) 对一个Intent 作出回应时哪个task 应该去持有这个Activity 。
对standard 和singleTop 方式来说,新的Activity 和通过startActivity 调用他的Activity 处在同一个task 中,如果调用时Intent 对象里含有FLAG_ACTIVITY_NEW_TASK 标志,那么就像前面讲的那样的寻找一个新的task 。
相反的,singTask 和singleInstance 方式,总是标志Activity 为task 的root Activity ,他们不会进入到其他的task 中。
(2 ) 一个Activity 是否可以有多个实例。
一个standard 或者singleTop 属性的Activity 可以实例化多次,他们可以属于多个不同的task 。
相反的,singleTask 或者singleInstance 属性的Activity 只能有一个实例(单例)。
(3 ) 实例是否能允许在task 里有其他的Activity 。
一个singleInstance 属性的Activity 单独的在他自己的task 里,并且这个task 里只能有他自己一个Activity ,如果他启动了另一个Activity ,那个Activity 会根据启动模式来启动并装进一个不同的task 里。其他的方面,singleInstance 和singleTask 一样的。
其他三个方式允许有多个Activity 在一个task 里,一个singleTask 属性的Activity 总是一个task 里的root Activity ,但是他可以启动另外的Activity 并且将这个新的Activity 装进同一个task 里,standard 和singleTop 属性的Activity 可以出现在task 的任何地方。
(4 ) 一个类(Activity )的对象是否可以被启动来处理一个新的Intent 。
对默认的standard 方式,会实例化一个对象来处理每一个新的Intent ,每个实例处理一个新的Intent ,对singleTop 方式,如果一个已经存在的实例是在task 的栈顶,那么就重用这个实例来处理这个新的Intent ,如果这个实例不在栈顶,那就不复用他,而是重新创建一个实例来处理这个新的Intent 并且将这个实例压入堆栈。
例如现在有一个task 堆栈ABCD ,A 是root Activity ,D 是栈顶Activity ,现在有一个启动D 的Intent 来了,如果D 是默认的standard 方法,那么就会创建一个新的实例来处理这个Intent ,所以这个堆栈就变为ABCDD ,然而如果D 是singleTop 方式,这个已经存在的栈顶的D 就会来处理这个Intent ,所以堆栈还是ABCD 。D 此时调用onNewIntent (),此时D 可以调用getIntent ()来获得最初的Intent ,或者调用setIntent ()来更新这个Intent 。
如果现在有一个Intent 来启动B ,不管B 是standard 还是singleTop (因为现在B 不在栈顶),都会创建一个新的实例,所以堆栈变为ABCDB
在一个task 里,对singleTask 和singleInstance 属性的Activity 只能有一个实例。所以这仅有的一个会来处理所以的Intent ,一个singleInstance 属性Activity 总在栈顶(因为task 里就只有他一个Activity ),所以他会处理所以的Intent ,但是一个singleTask 属性的Activity 必须是task 的root Activity (也就是必须在栈底),不能确定他的上面是否还有其他的Activity ,如果没有,就可以处理,如果还有其他的Activity ,那么如果现在有一个Intent 来启动这个singleTask 属性的Activity ,这个Intent 将会被丢掉(即使是这个Intent 被丢掉,他的到来还是会导致这个task 回到前台)。
当创建一个类(Activity )的实例来处理一个新的Intent 时,用户可以按下BACK 键回到上一个Activity ,但是如果是用已经存在的栈顶的Activity 来处理Intent 的话,按下BACK 键是不能回到以前的状态的(没处理这个Intent 之前)。
7 、 清理堆栈
当用户离开一个task 一段时间后,系统就会清理掉task 里出了rootActivity 以外的Activity ,如果用户又回来了,显示的是rootActivity ,就像是用户离开又回来,是放弃以前的东西,开始新的东西。
上面说的是默认的情况,有一些Activity 的属性可以用来控制和修改这些行为。
alwaysRetainTaskState
如果一个task 里的root Activity 的alwaysRetainTaskState 属性设置为true ,那么前面描述的默认情况就不会出现了,task 即使过了一段时间也会一直保留所有的Activity 。
clearTaskOnLaunch
如果一个task 里的root Activity 的clearTaskOnLaunch 属性设置为true ,和alwaysRetainTaskState 相反,即使是一瞬间的离开,系统马上就会清理掉task 里出rootActivity 以外的所有Activity 。
finishOnTaskLaunch
这个属性和clearTaskOnLaunch 一样,但是他是对一个Activity 起作用,不是整个task ,他能引起所有的Activity 离开,包括root Activity ,当这个属性设置为true ,只是当用户使用这个应用时Activity 才在task 里,一旦用户离开后重新回来,显示的不是当前的界面。
还有其他的方法来从task 里强制移动Activity ,如果一个Intent 对象里包含FLAG_ACTIVITY_CLEAR_TOP 标志,并且目标task 里已经一个在自己task 里可以处理Intent 的Activity (就是处理这个Intent 无需实例化另外一个Activity ),那么在这个Activity 之上的所有Activity 将被清除,能处理这个Intent 的Activity 就移到栈顶来处理这个Intent ,例如ABCD 堆栈,含有FLAG_ACTIVITY_CLEAR_TOP 标志的Intent 来启动B ,那么清除CD ,B 到达栈顶来响应Intent ,此时是AB ,如果B 设置了standard 属性,那么还是清楚CD ,然后再创建一个实例来响应Intent ,此时是ABB ,因为standard 属性的Activity 总是创建一个新的实例来响应新的Intent 。
8 、 进程和线程(Processes and Threads )
当一个应用的第一个组件需要运行时,android 系统就为这个组件启动一个只有一个线程的Linux 进程,默认的,应用的所有组件都运行这个进程中的这个线程中。
但是,你可以安排组件运行在其他的进程中,并且为你的任意的进程增加若干线程。
1 、 进程
组件运行的进程是在manifest 文件里控制的,四大组件都一个process 属性可以指定进程来运行,这些属性可以被设置为了每个组件都可以运行在他自己的进程中,或者几个组件共享一个进程,或者不共享,如果两个应用共享一个Linux user ID 并且有相同的权限,那么就可以使这两个应用中的组件运行在相同的进程中,<application> 也有process 属性,用来指定对所有组件的属性。
所有的组件都在指定的进程中的主线程中实例化,系统调用这些组件就是从主线程里发出的,其他的线程将不会对每个组件再实例化,所有作为调用的回应的这些方法,比如说View.onKeyDown() 还是组件的生命周期函数等等都是运行在这个主线程中的,这就意味着当系统调用这个组件时,这个组件不能长时间的阻塞线程(比如说网络操作,循环计算),因为这样会阻塞这个进程中的其他组件,你可以将很耗时的任务分到其他的线程中。
当内存不足或者有其他更紧急的进程要求时,Android 系统可能关闭一个进程,运行在这个进程中的应用组件因此被销毁,当用户又重新回来时,进程才被重新启动。
至于究竟要停止哪个进程,android 系统是通过衡量哪个进程对用户来说更重要来实现的
2 、 线程
你可以限制你的应用运行在一个进程中,但是有的时候你需要新开一个线程在后台运行,用户界面需要随时对用户的要求做出反应,所以一些很耗时的工作应该重新启动一个线程来做,以免阻塞主进程。
Android 系统提供了一系列方便的类来管理线程(Looper ,Handler ,HandlerThread )
3 、 远程调用(Remote procedure calls )
Android 系统有一个轻量级的远程调用机制(RPC )----- 一个方法在本地调用,但是在远程执行(在另外一个进程里),返回给调用端的所有结果都必须的系统能理解的,将数据从本地进程和地址空间传递到远程的进程和地址空间,并在远端重新装配,返回值的时候传输方向相反,android 系统会去做这些传输的工作,让你能够集中精力来定义你的RPC
一个RPC 接口只能包含方法,默认的,即使是没有值返回,所有的方法都是同步执行的,就是说本地方法一直会阻塞直到远端的方法执行完毕)。
简单的说,这个远程调用的机制是这样工作的:
首先你需要用IDL (interface definition language )声明你的RPC 接口,然后android 系统会使用aidl 工具来形成一个java 接口,并且这个java 接口是本地进程和远端进程都可以获得的,这个java 接口包含了两个内部类,请看下图:
这两个内部类有管理远程调用( 你用IDL 声明的接口) 的所以代码, 两个内部类都实现IBinder 接口, 一个是在本地( 内部) 使用, 这个你可以不用自己写代码,另外一个叫做Stub ,继承自Binder 类的,包含所有完成进程间通信(IPC )的代码,他包含你在RPC 接口中声明的所有方法,你应该继续继承Stub 类来实现这些方法。
一般的,远端进程应该由一个service 来管理(因为一个service 能通知系统关于这个进程和他连接到的其他进程)。
9 、 进程的生命周期(Processes and lifecycles )
Android 系统总是尽最大的努力来维持一个应用的进程,但系统的内存不足时就可能需要关闭一些旧的进程了,但是决定关闭哪个进程呢,android 系统把所以的进程放进一个重要性树里,最低重要性的进程将会被停止,系统有5 种重要性等级,重要性从高到低如下:
(1 )、前台进程。一个前台进程是当前执行用户请求的进程,如果有如下的一种情形的那么他就是前台进程:
a 、这个进程里运行着一个正在和用户交互的Activity (这个Activity 的onResume ()方法被调用)。
b 、这个进程里有绑定到当前正在和用户交互的Activity 的一个service
c 、这个进程里有一个service 对象,这个service 对象执行了至少一个他生命周期的函数(onCreate(), onStart(), or onDestroy()).
d 、这个进程里有一个执行了onReceive ()方法的broadcastreceiver 对象
只有一定数量的前台进程在任何时间都存在,他们只有在最后的时刻被停止--- 系统的内存太少了而不能运行这些仅有的前台进程了),一般的,在那个时刻,手机会重新设置内存页的状态,所以停止一些前台的进程是为了保持对用户操作的快速响应。
(2 ) 可见进程。一个可见进程一个没有任何前台显示的组件,但是仍然可以影响到用户当前屏幕所看见的东西,如果有如下的一种情形那么他就是可见进程。
a 、 这个进程里一个Activity ,但是这个Activity 当前不是在前台显示,但是仍然对用户是可见的(这个Activity 的onPause ()方法被调用),比如说一个Activity 调用一个dialog ,那么这个dialog 是当前显示的组件,这个Activity 不是在前台显示,但是对用户是可见的。
b 、 这个进程里有一个绑定到一个可见Activity (如上所述的Activity )的service
一个可见进程是极端重要的,只有在为了显示所有前台进程时,即显示前台进程都不够时,才会停止可见进程。
(3 )、服务进程。一个服务进程是一个通过startService ()启动的但是没有在前两个分类中的进程,虽然服务进程不是用户直接能看见的,但是他也总是做一些用户很关心的事(如在后台播放mp3 ,从网络上下载东西),所以系统会一直保持服务进程运行,除非内存不足以运行服务进程,前台进程,可见进程。
(4 )后台进程。一个后台进程是运行一个当前对用户是不可见的Activity (这个Activity 的onStop ()被调用),这些进程对用户体验没有什么直接的影响,当内存不足以运行前台进程,可见进程,服务进程时,可以随时停止后台进程,通常有很多的后台进程在运行,系统会把这些后台进程放进一个LRU 中(最近使用队列),最近使用的就最后停止。
(5 ) 空进程。一个空进程就是进程里没有任何活动的应用组件,维持这种进程的唯一原因就是作为一种缓存,当一个组件需要启动时加快启动的速度,系统为了平衡进程缓存和核心缓存会停止这些空的进程。
Android 系统会取一个进程里的所以组件的最高重要性来安排进程的重要性,比如说,一个进程里有一个service 和一个可见的Activity ,那么这个进程会被安排成一个可见进程,而不是服务进程。
另外,一个进程的重要性有可能会因为其他进程的依赖而升高,一个进程不能比他所服务的进程的重要性低,比如有进程A 里的service 绑定到了进程B 的组件上,那么进程A 的重要性至少和进程B 的一样,或者更高。
因为一个服务进程的重要性比运行一个后台Activity 的进程高,所以,当一个Activity 做一些长时间运行的任务时,最好启动一个service 来做,而不是放到一个线程里去做,特别是这个任务的时间可能比Activity 运行的时间还长的时候,比如在后台播放音乐,或者上传一张图片到网上,使用一个service 保证了这个任务至少是服务进程的重要性,broadcast receiver 也是一样,长时间运行的任务也最好是放到一个service 里,而不是放到一个线程里。