Windows下当我们双击某个可执行文件时由explorer进程负责启动。所以一般手动启动的程序进程的父进程都是explorer。但是当我们观察explorer进程的父进程时(1)在xp系统下我们发现,通过wmic能找箌explorer进程的父进程号但是任务管理器中找不到对应的进程。(2)在win7下我们能找到explorer进程的父进程是winlogon.exe。这是什么原因待解决。
本文内容主题是透过应用程序來分析Android系统的设计原理与构架。我们先会简单介绍一下Android里的应用程序编程然后以这些应用程 序在运行环境上的需求来分析出,为什么我們的Android系统需要今天这样的设计方案这样的设计会有怎样的意义, Android究竟是基于怎样的考虑才变成今天的这个样子所以本文更多的分析Android应鼡程序设计背后的思想,品味良好架构设计的魅力分五次连 载完成,第一部分是最简单的部分解析Android应用程序的开发流程。 Android应用程序开發以及背后的设计思想深度剖析 1 |
在这个图形界面的示例里我们可以看到,这样的图形编程方式比如传统的方式里要学习大量的API要方便嘚多。
<LinearLayout>这一标签会决定应用程序如何在界面里摆放相应的控件
使用这种XML构成的UI界面,是MVC设计的附属产品但更大的好处是,有了标准化嘚XML结构就可以创建可以用来画界面的IDE工具。一流的系统提供工具让设计师来设计界面、工程师来逻辑,这样生产出来的软件产品显示效果与用户体验会更佳比如iOS;二流的系统,界面与逻辑都由工程师来完成在这种系统上开发出来的软件,不光界面不好看用户体验吔会不好。我们比如在Eclipse里的工程里查看我们会发现,我们打开res/layout//training/basics/supporting-devices/查看具体的使用方式
于是,透过资源文件我们进一步验证了我们对于Android MVC嘚猜想,在Android应用程序设计里也跟iOS类似,可以实现界面与逻辑完全分离而另一点,就是Android应用程序天然具备屏幕自适应的能力这一方面帶来的影响是Android应用程序天生具备很强的适应性,另一方面的影响是Android里实现像素精度显示的应用程序是比较困难的维护的代价很高。
我们鈳以再通过应用程序的代码部分来看看应用程序是如何将显示与逻辑进行绑定的
package=",Java等都有这样的加强的健壮性
但是纯Java环境不要说在嵌入式平台上,就是在PC环境里也是以缓慢淡定著称的,Java的自优化能力与处理器能力、内存大小成正比使用纯Java写嵌入式方案,最终达到的结果也就只会是奇慢无比的JAVA ME另外,Java是使用商业授权嘚语言无论是在以前它的创建者Sun公司,还是现在已经收购Sun公司的Oracle对Java一贯会收取不低的商业授权费用,一旦基于纯粹Java环境来构建系统朂后肯定会造成Android系统不再是免费的午餐。既然Android所需要的环境只是Java语言本身原始的Java虚拟机的授权又难以免费,这就迫使Android的开发者开发出來另一套Java虚拟机环境,也就是我们的Dalvik虚拟机
于是,我们基于多进程模型的系统构架出于跨平台、安全、编程的简易性等多方面的原因,使我们得到的Android的设计方案成为下面的这个新的样子:核心进程这部分的实现我们还没分析到但应用程序此时在引入Java环境之后,都变成叻通过Dalvik虚拟机所管理起来的更受限的环境于是更安全。
而在Java环境里一个Java程序的主入口实际上还是传统的main()入口的方式,而以main()方法作为主叺口则意味着编程时,特别是图形界面编程需要用户更多地考虑如何实现,如何进行交互整个Java环境,全都是由一个个的有一定生存周期的对象组合而成任何一个对象里存在的static属性的main()方法,则可以在Java环境里作为一个程序的主入口得到执行如果使用标准Java编程,则我们嘚图形界面编程将复杂得多比如我们下面的使用Swing编程写出来的,跟我们前面的Helloworld类似的例子:
交叉编译器、Bionic C库与Dalvik虚拟机的移植(如果不是ARM、X86和MIPS这三种基本构架) 提供板卡支持所需要的配置 实现所需要使用的HAL Android产品化完成界面或是功能上的定制这些移植过程的步骤如下图所示:
对于我们做Android移植与系统级开发而言,可能我们所需要花的代码并不是那么大像Bootloader与Linux内核的移植,这一般都在进行Android系统移植时早就会就绪嘚比如我们去选购某个产商的主芯片时(Application Processor,术语为AP)这些Android之前的支持大都已经就绪。而作为产业的霸主我们除非极其特殊的情况,峩们也不需要接触交叉编译器和Dalvik虚拟机的移植所以一般情况下,我们的Android移植是从建立repo源代码管理环境开始然后再进行板卡相关的配置,然后实现HAL而Android的产品化这个步骤,Framework的细微调整与编写自己平台上特殊的应用程序严格意义上来说,Framework也不属于Android移植工作范围内的我们┅般把它定位于Android产品化或是Android定制化这个步骤里。Android移植相对来说非常简单而真正完成Android产品化则会是一个比较耗时耗人力的过程。
所谓的板鉲的配置文件一般是放在一个专门的目录里,在2.3以前是发在vendor目录下,从2.3开始vendor目录只存放二进制代码,配置文件移到了device目录在这一目录里,会以“产商名/设备名”的命名方式来规范配置的目录结构比如是产商名是ti,设备名是panda则会以“device/ti/panda”来存放这些配置文件,再在這个目录里放置平台相关的配置项配置文件,则是会几个关键文件构成:
} AndroidProducts.mk这是会被编译系统扫描的文件,通过在这一文件里再导入具體的编译配置文件比如ti_panda.mk
} ti_panda.mk,在这一文件里定义具体的产品名设备名这些关键变量,这些变量是在Android编译过程里起关键配置作用的变量一般说来,这个文件不会很复杂主要依赖导入一些其他的配置文件来完成所有的配置,比如语言配置等而设备特殊的设置,则一般是使鼡同目录下的device.mk文件来进行定制化的设置
} device.mk,在这一文件会使用一些更加复杂一些配置包含一些需要编译的子工程,设置某些特殊的编译參数以及进行系统某些特性的定制化,比如需要自定义怎样的显示效果、配置文件等
} BoardConfig.mk在这一文件则是板子相关的一些定制项,以宏的方式传入到编译过程里比如BOARD_SYSTEMIMAGE_PARTITION_SIZE来控制system分区的大小, TARGET_CPU_SMP来控制是否需要使用SMP(对称多处理器)支持等。一般对于同一个板卡环境,这些参数可鉯照抄勿须修改。
所有的这些配置文件并不是必须的,只不过是建议性的在这一点上也常会透露出产商在开源文化的素质。毕竟是開源的方案如果都使用约定俗成的解决方案,则大家都会不用看也知道怎么改但一些在开源做得不好的厂商,对这些的配置环境都喜歡自己搞一套东西出来要显得自己与众不同,所以对于配置文件的写法与移植过程也需要具体情况具体对待。
当我们完成了这些配置仩的工作后可以先将这些配置上传到repo的服务器管理起来,剩下的移植工作就是实现所需要的HAL了在Android移植过程里,很多HAL的实现是可以大量复用的,可以找一个类似的配置复制过来然后再进行细微调整,比如使用ALSA ASoC框架的音频支持基本功能都是通用的,只需要在Audio Path和HiJack功能上進行微调即可
/tech/dalvik/dalvik-bytecode.html
这时会发现系统还是会受到影响但Linux的健壮性表现在,虽然系统会暂时因为资源不足而变得响应迟缓但还是可以保证系统不會崩溃。为了进程数过多而影响系统运行Linux内核里有一种OOM Killer(Out Of Memory
Killer)机制,系统里通过一种叫notifier的机制(顾名思义跟我们的Listener设计模式类似的实现)监听目前系统里内存使用率,当内存使用达到比率时就开始杀掉一些进程,回收内存这里系统就可以回到正常执行。当然在真正發生Out Of Memory错误也会提前触发这种杀死进程的操作。 Memory事件的不再是Linux内核里的Notifier,而由Android系统进程来驱动潒我们前面说明的,在Android里负责管理进程生成与Activity调用栈的会是这个系统进程这样在遇到系统内存不够(可以直接通过查询空闲内存来得到)时,就触发Low Memory Killer驱动来杀死进程来释放内存 这种设计,从我们感性认识里也可以看到用adb shell free登录到设备上查看空闲内存,这时都会发现的内存的剩余量很低因为在Android设备里,系统里空闲内存数量不低到一定的程度是不会去回收内存的,Android在内存使用上是“月光族”。Android通过这種方式让尽可能多的应用程序驻留在内存里,从而达到一个加速执行的目的在这种模型时,内存相当于一个我们TCP协议栈里的一个窗口尽可能多地进行缓冲,而落到窗口之外的则会被舍弃 理论上来说,这是一种物尽其用勤俭执家的做法,这样使Android系统保持运行流畅洏且从侧面也刺激了Android设备使用更大内存,因为内存越多则内存池越大可同时运行的任务越多,越流畅唯一不足之处,一些试图缩减Android内存的厂商就显得很无辜精减内存则有可能影响Android的使用体验。 我们经常会见到系统间的对比说Android是真实的多任务操作系统,而其他手机操莋平台只是伪多任务的这是实话,但这不是被Android作为优点来设计的而只是整个系统设计迫使Android系统不得不使用这种设计,来维持系统的流暢度至于多任务,这也是无心插柳柳成荫的运气吧 在Android系统里,无论我们今天可以得到的硬件平台是多么强大我们还是有降低系统里嘚运算量的需求。作为一个开源的手机解决方案我们不能假设系统具备多么强劲的运算能力,出于成本的考虑也会有产商生产一些更廉价的低端设备。而即便是在一些高端硬件平台之上我们也不能浪费手机上的运算能力,因为我们受限于有限的电池供电能力就算是將来这些限制都不存在,我们最好也还是减少不必要的损耗将计算能力花到最需要使用它们的地方。于是我们在前面谈到的各种设计技巧之外,又增加了降低运算量的需求 这些技巧,貌似更高深但实际上在Android之前的嵌入式Linux开发过程里,大家也被迫干过很多次了主要嘚思路时,所有跟运行环境无关运算操作我们都在编译时解决掉,与运行环境相关的部分则尽可能使用固化设计,在安装时或是系统啟动时做一次 与运算环境无关的操作,在我们以前嵌入式开发里Codec会用到,比如一些码表实际上每次算出来都是同样或是类似的结构,于是我们可以直接在编译时就把这张表算出来在运行时则直接使用。在Android里因为大量使用了XML文件,而XML在运行时解析很消耗内存也会占用大量内存空间,于是就把它在编译时解析出来在应用程序可能使用的内存段位里找一个空闲位置放进去,然后再将这个内存偏移地址写到R.java文件里在执行时,就是直接将二进制的解析好的xml树形结构映射到内存R.java所指向的位置这时应用程序的代码在执行时就可以直接使鼡了。 在Android系统里使用的另一项编译态运算是prelink我们Linux内核之睥系统环境,一般都会使用Gnu编译器的动态链接功能从而可以让大量代码通过动態链接库的方式进行共享。在动态链接处理里一般会先把代码编译成位置无关代码(Position Independent Code,PIC)然后在链接阶段将共用代码编译成.so动态链接庫,而将可执行代码链接到这样的.so文件而在动态链接处理里,无论是.so库文件还是可执行文件在.text段位里会有PLT(Procedure Linkage Table),在.data段位里会有GOT(Global Offset Table)這样,在代码执行时这两个文件都会被映射到同一进程空间,可执行程序执行到动态链接库里的代码会通过PLT,找到GOT里定位到的动态链接库里代码具体实现的位置然后实现跳转。 通过这样的方式我们就可以实现代码的共享,如上图中我们的可执行文件a.out,是可以与其怹可执行程序共享libxxx.so里实现的func_from_dso()的在动态链接的设计里,PLT与GOT分开是因为.text段位一般只会被映射到只读字段避免代码被非法偷换,而.data段位映射後是可以被修改的所以一般PLT表保持不动,而GOT会根据.so文件被映射到进程空间的偏移位置再进行转换这样就实现了灵活的目的。同时.so文件内部也是这样的设计,也就是动态链接库本身可以再次使用这样的代码共享技术链接到其他的动态链接库在运行时这些库都必须被映射到同一进程空间里。所以实际上,我们的进程空间可能使用到大量的动态链接库 动态链接在运行时还进行一些运行态处理,像GOT表是需要根据进程上下文换算成正确的虚拟地址上的依稀另外,还需要验证这些动态链接代码的合法性并且可能需要处理链接时的一些符號冲突问题。出于加快动态连接库的调用过程PLT本身也会通过Hash表来进行索引以加快执行效率。但是动态链接库文件有可能很大里面实现嘚函数很多很复杂,还有可能可执行程序使用了大量的动态链接库所有这些情况会导致使用了动态链接的应用程序,在启动时都会很慢在一些大型应用程序里,这样的开销有可能需要花好几秒才能完全于是有了prelink的需求。Prelink就是用一个交叉编译的完整环境模拟一次完整哋运行过程,把参与运行的可执行程序与动态链接所需要使用的地址空间都算出来一个合理的位置然后再就这个值写入到ELF文件里的特殊段位里。在执行时就可以不再需要(即便需要,也只是小范围的改正)进行动态链接处理可以更快完成加载。这样的技术一直是Linux环境裏一个热门研究方向像firefox这样的大型应用程序经过prelink之后,可以减少几乎一半的启动时间这样的加速对于嵌入式环境来说,也就更加重要叻 但这种技术有种致命缺陷,需要一台Linux机器运行交叉编译环境,才能使用prelink而Android源代码本就设计成至少在MacOS与Linux环境里执行的,它使用的交叉编译工具使用到Gnu编译的部分只完成编译链接还是通过它自己实现的工具来完成的。有了需求但受限于Linux环境,于是Android开发者又继续创新在Android世界里使用的prelink,是固定段位的在链接时会根据固定配置好地址信息来处理动态链接,比如libc.so对于所有进程,libc.so都是固定的位置在Android一矗到2.3版本时,都会使用build/core/prelink-linux-arm.map这个文件来进行prelink操作而这个文件也可以看到prelink处理是何其简单: 在Android发展的初期,这种简单的prelink机制一直是有效的,泹这不是一种很合理的解决方案首先,这种方式不通用也不够节省资源,我们很难想像要在系统层加入firefox、openoffice这样大型软件(几十、上百個.so文件)同时虽然绝大部分的.so是应用程序不会用到的,但都被一股脑地塞了进来最好,这些链接方式也不安全我们虽然可以通过“沙盒”模式来打造应用程序执行环境的安全性,但应用程序完全知道一些系统进程使用的.so文件的内容则破解起来相对比较容易,进程空間分布很固定则还可以人为地制造一些栈溢出方式来进行攻击。 虽然作了这方面的努力但当Android到4.0版时,为了加强系统的安全性开始使鼡新的动态链接技术,地址空间分布随机化(Address Space Layout RandomizationASLR),将地址空间上的固定分配变成伪随机分布这时就也取消了prelink。 Android系统设计上对于性能,在各方面都进行了相当成功的尝试最后得到的效果也非常不错。大家经常批评Android整个生态环境很恶劣高中低档的设备充斥市场,五花仈门的分辨率但抛开商业因素不谈,Android作为一套操作系统环境可以兼容到这么多种应用情境,本就是一种设计上很成功的表现如果说這种实现很复杂,倒还显得不那么神奇问题是Android在解决一些很难的工程问题的时候,用的技巧还是很简单的这就非常不容易了。我们写過代码的人都会知道把代码写得极度让人看不懂,逻辑复杂其实并不需要太高智商,反而是编程能力不行所致逻辑清晰,简单明了又能解决问题,才真正是大神级的代码业界成功的项目,linux、git、apache都是这方面的典范。 Android所有这些提升性能的设计都会导致另一个间接收益,就是所需使用的电量也相应大大降低同样的运算,如果节省了运算上的时间变相地也减少了电量上的损失。但这不够我们的掱机使用的电池非常有限,如果不使用一些特殊的省电技术也是不行的。于是我们可以再来透过应用程序,看看Android的功耗管理 Android应用程序开发以及背后的设计思想深度剖析(5) 在嵌入式领域,功耗与运算量几乎成正比操作系统里所需要的功能越来越复杂、安全性需求越來越高,则会需要更强大的处理能力支持像在老式的实时操作系统里,没有进程概念不需要虚拟内存支持,这时即便是写一些简单应鼡所需要的运算量、内存都非常小,而一旦换用支持虚拟内存的系统则所需要的硬件处理能力、电量都会成倍上涨,像一些功能性手機平台可以成为一台不错的手机,但运行起一个Linux操作系统都很困难而随着操作系统能力增强,则所能支持的硬件又得以提升可以使鼡更大的屏幕、使用更大量内存、支持更多的无线芯片,这些功能增强的同时也进一步加剧了电量的消耗。虽然现在芯片技术不断提高苼产工艺降低制程(就是芯片内部烧写逻辑时的门电路尺寸)几乎都已经接近了物理上的极限(40纳米、28纳米、22纳米),但是出于设计更複杂芯片为目的的随着双核、四核、以及越来越高的工作频率,事实上功耗问题不但没有降低,反而进一步被加剧了 面对这样越来樾大的功耗上的挑战,Android在设计上必须在考虑其他设计因素之前,更关注功耗控制问题Android在设计上的一些特点,使系统所需要的功耗要高於传统设计:Android是使用Java语言执行环境的所有在虚拟机之上运行的代码都需要更大的运算量,使用机器代码中需要一条指令的地方在虚拟機环境下执行则可能需要十几条指令;与其他伪多任务不同,Android是真实多任务的多任务则意味着在同一时刻会有更多任务在运行;Android是构建仩Linux内核之上的系统,Linux内核在性能上表现奇佳在功耗处理上则是短板,就拿PC环境来说Linux的桌面环境在功耗控制上从来不如其他操作系统,MacOS戓是Windows 当然,有时没有历史包袱也未必就是坏事,比如Linux内核在功耗管理上做得还不够好于是就不会在Linux内核环境里死磕,Android可以通过新的設计来进行功耗控制上的提升出于跟前面我们所说过的可减小对Linux内核依赖性、加强系统可移植性的设计需求,于是不可避免的功耗控淛将会尽可能多地被推到系统的上层。在我们前面对于安全性的分层中可以看到Android相当于把整个操作系统都在用户态重新设计了一次,SystemServer这個系统级进程相当于用户态的一个Linux内核于是将功耗控制更多地抽到用户态来执行,也没有什么不合理的 在Android的整体系统设计里,功耗控淛会先从应用程序着手通过多任务并行时减小不必要的开销开始;在整个系统构架里,唯一知道当前系统对功耗需求的是SystemServer于是可以通過相应的安全接口,将功耗的控制提取出来可由SystemServer来进行后续的处理。Android系统所面临的运行环境需求里电源是极度有限的资源,于是功耗控制应该是暴力型的尽可能有能力关闭不需要使用的电源输出。当然暴力关电则可能引起某些外设芯片不正常工作,于是在芯片驱动裏需要做小范围修改与其他功能部分的设计不同,既然我们功耗控制是通过与驱动打交道来实现可能无法避免地需要驱动,但要让修妀尽可能小以提供可移植性。 在这种修改方案里最需要解决的当然首先是多任务处理。我们可以得到的就是我们的生命周期所谓的苼命周期,是不是仅仅只是提供更多一些编程上的回调接口而已呢不仅如此,我们的所谓生命周期是一种休眠状态点更多地起到休眠操作时我们有机会插入代码的作用。如果仅是提供编程功能我们可以参考JAVA ME里对于应用程序实现: JAVA ME框架里对待应用程序只有三个状态点,運行、暂停、关闭对应提供三种回调接口就可以驱动起这种编程模型。但我们的Android不是这样处理的Android在编程模型上,把带显示与不带显示嘚代码逻辑分别抽象成Activity与Service每种不同逻辑实现都有其独特的生命周期,以更好地融入到系统的电源管理框架里 像我们的与显示相关的处悝,Activity它拥有6种不同状态: 它的不同生命周期阶段,取决于这一Activity是否处于交互状态是否处理可见状态。如果加入这两个限制条件于是Activity嘚生命周期则是为这两种状态而设计的。onResume()与onResume()分别是进入交互与退出交互时的状态点在onResume()执行完之后,这时系统进入了交互状态也就是Activity的Running狀态,而此时如果由于Activity发生调用或是另一个Activity主动执行弹出一个小对话框,使原来处于Running状态的Activity被挡住这时Activity就被视为不需要交互了,这时Activity進入不可见互状态触发onPause()回调。onStart()与onStop()则是对应于是否可见在onStart()回调之后,应用程序这里就可以被显示出来但不会真正进入交互期,当Activity变得唍全不可见之后则会触发onStop()。而Android的多任务实现还会造成进程会被杀死掉,于是也提供两个onCreate()与onDestroy()两种回调方法来提供进程被创建之后与进程被杀死之前的两种不同操作 这种设计的技巧在于,当Activity处于可交互状况时这是系统里的全马力执行的周期。而再向外走一个状态期只昰处于可见但不可交互状态时,我们就可以开始通过技巧降功耗了比如此时界面不再刷新、可以关闭一些所有与用户交互相关的硬件。當Activity再进一步退出可见状态时可以进一步退出所有硬件设备的使用,这时就可以全关电了编写应用程序时,当我们希望它有不一样的表現时我们可以去通过IoC去灵活地覆盖并改进这些回调接口,而假如这种标准的模型满足我们的需求我们就什么都不需要用,自动地被这種框架所管理起来 当然,这种模型也不符合所有的需求比如对于很多应用程序来说,在后台不可见状态下仍然需要做一些特定的操莋。于是Android的应用程序模型里又增加了一个Service。对于一些暴力派的开发者比较喜欢使用后台线程来实现这种需求,但这种实现在Android并不科学因为只通过Activity承载的后台线程,有可能会被杀死掉在有状态更新需求时,后台线程需要通过Activity重绘界面实际上这样也会破坏Android在功耗控制仩的这种合理性设计。比较合适的做法所有不带界面、需要在后台持续进行某些操作的实现,都需要使用Service来实现而状态显示的改变应該是在onStart()里完成的,状态上的交互则需要放到onResume()方法里这样的实现可以有效绕开进程被杀死的问题。并且在我们后面介绍AIDL的部分还可以看箌,这样实现还可以加强后台任务的可交互性当我们进一步将Service通过AIDL转换成Remote Service之后,则我们的实现会具备强大的可复用性多个进程都可以訪问到。 Service也会有其生存周期但Service的生存周期相对而言要简单得多,因为它的生存周期只存在“是否正在被使用”的区别当然,同样出于Android嘚多任务设计“使用中”这个状态之外,也会有进程是否存在的状态 于是,我们的Service也可被纳入到这种代码活跃状态的受控环境当是鈈需要与后台的Service发生交互,这时我们可能只是通过一个startService()发出Intent,这时Service在执行完相应的处理请求则直接退出而如果是一个AIDL方式抛出的Remote 当我們的应用程序的各种不同执行逻辑,都是处于一个可控状态下时这时,我们的功耗控制就可以被集中到一个系统进程的SystemServer来完成这时,峩们面临一种设计上的选择是默认提供一种松散的电源控制,让应用程序尽可能多自由地控制电源使用还是提供一种严格逻辑,默认凊况下实施严格的电源输出管理只允许应用程序出于特殊的需求来调高它的需求?当然前一种方式灵活,但出于电源的有限性这时Android系统里使用了第二次逻辑,尽可能多地严格电源输出控制 在默认情况下,Android会尝试让系统尽可能多地进入到休眠状态之中在从用户开始進行了最后一次交互之后,系统则会触发一个计时器计时器会在一定的时间间隔后超时,但每次用户的交互操作都会重置这一计时器洳果用户一直没有进行第二次交互,计时器超时则触发一些功耗控制的操作比如第一步,会先变暗直至关闭系统的屏幕如果在后续的┅定时间内用户继续没有任何操作,这时系统则会进一步尝试将整个系统变成休眠状态 休眠部分的操作,基本上是Linux内核的功耗控制逻辑叻休眠操作的最后,会将内存控制器设成自刷新模式关掉CPU。到这种低功耗运行模式之下这时系统功耗会降到最低,如果是不带3G模组嘚芯片待机电流应该处于1mA以下。但我们的系统是手机一般2G、3G、或是4G是必须存在的,而且待机状态时关掉这种不同网络制式下的Modem也失詓了手机存在的意义,于是一般功耗上会加上一个移动Modem,专业术语是基带(Baseband)的功耗这时一般要控制在10 – 30mA的待机电流,100mW左右的待机功耗如果这时,用户按些某些用于唤醒的按键、或是基带芯片上过来了一些短信或是电话之类的信息则系统会通过唤醒操作,回到休眠の前的状态 但是Linux内核的Suspend与Resume方案,是针对ACPI里通用计算环境(我们的PC、笔记本、服务器)的功耗控制方案并不完全与手机的使用需求相符匼。而Linux内核所缺失的主要是UI控制上功耗管理,手机平台上耗电最大的元器件是屏幕与背光,是无法通过Linux内核的suspend/resume两级模型来实现高效的電源管理于是,Android系统在原始的suspend与resume接口之外,再增加了两级early_suspend与late_resume用于UI交互时的提前休眠。 我们的Android系统在出现用户操作超时的情况下,會先进入early_suspend状态点关闭一些UI交互相关的硬件设备,比如屏幕、背光、触摸屏、Sensor、摄像头等然后,在进一步没有相应唤醒操作时会进入suspend關闭系统里的其他类的硬件。最后系统进入到内存自刷新、CPU关电的状态如果在系统完全休眠的情况下,发生了某种唤醒事件比如电话咑进来、短信、或是用户按了电源键,这时就会先进resume将与UI交互不相关的硬件唤醒,再进入late_resume唤醒与UI交互相关的硬件但如果设备在进入early_suspend状態但还没有开始suspend操作之前发生了唤醒事件,这时就直接会走到late_resume唤醒UI交互的硬件驱动,从而用户又可以看到屏幕上的显示并且可以进行茭互操作。 经过了这样的修改在没有用户操作的情况下,系统会不断进入休眠模式省电而用户并不会感受到这种变化,在频繁操作时实际上休眠与唤醒只是快进快出的UI相关硬件的休眠与唤醒。但完全暴力型的休眠也会存在问题比如我们有些应用程序,QQ需要保持登录下载需要一直在后台下载,这些都不符合Android的需求的于是,我们还需要一种机制让某些特殊的应用程序,在万不得已的情况下我们還是可以得这些应用程序足够的供电运行得下去。 于是Android在设计上又提出了一套创新框架,wake_lock在多加了early_suspend与late_resume之外,再加上可以提供功耗上的特殊控制Wake_lock这套机制,跟我们C++里使用的智能指针(Smart pointer)借用智能指针的思想来设计电源的使用和分配。我们也知道Smart Pointer都是引用则它的引用計数会自动加1,取消引用则引用计数减1使用了智能指针的对象,当它的引用计数为0时则该对象会被回收掉。同样我们的wake_lock也保持使用計数,只不过这种“智能指针”的所使用的资源不再是内存而是电量。应用程序会通过特定的WakeLock去访问硬件然后硬件会根据引用计数是否为0来决定是不是需要关闭这一硬件的供电。 Suspend与wake_lock这两种新加入的机制最后也是需要加放SystemServer这个进程里,因为这是属于系统级的服务需要特权才能保证“沙盒”机制。于是我们得到了Android里的电源管理框架: 当然,这里唯一不太好的地方就是Android系统设计必须对Linux内核原有的电源管理机制进行改动,需要加入wake_lock机制的处理也需要在原始的内核驱动之上加入新的early_suspend与late_resume两个新的电源管理级别与wake_lock相配套。这部分的代码则會造成Android系统所需要的驱动,与标准Linux内核的驱动并不完全匹配同时这种简单粗暴的方式,也会破坏掉内核原有的清晰简要的风格这方面吔造成了Linux社区与Android社区之间曾一度吵得很凶,Linux内核拒绝Android提交的修改而Android源代码则不再使用标准的Linux内核源代码,使用自己特殊的分支进行开发 我们再来看Android系统对于功能接口的设计。 我们实现一个系统必须尽可能多地提供给应用程序尽可能多的开发接口,作为一个开源系统更應该如此虽然我们前面提到了,我们需要有权限控制机制来限制应用程序可访问系统功能与硬件功能但是这是权限控制的角度,如果應用程序得到了授权应该有理由来使用这一功能,一个能够获得所有权限的应用程序则理所当然应该享受系统里所提供的一切功能。 對于一个标准的Java系统无论是桌面环境里使用的Java SE还是嵌入式环境里使用的Java ME,都不存在任何问题因为这时Java本就只是系统的一层“皮”,每個Java写成的应用程序只是一层底层系统上的二次封装,实际上都是借用底层操作系统来完成访问请求的对于传统的应用程序,一个main()进入迉循环处理UI也不存在这个问题,通过链接到系统里的动态链接库或是直接访问设备文件也可以实现。但这样的方式到了Android系统里,就會面临一个功能接口的插分问题因为我们的Android,不再是一层操作系统之上的Java虚拟机封装而是抽象出来的在用户态运转的操作系统,同时還会有“沙盒”模式应用程序并不见得拥有所有权限来访问系统资源,则又不能影响它的正常运行 于是,对于Android在功能接口设计上会被划分成两个层次的,一种是以“受托管”环境下通过一个系统进程SystemServer来执行另一种是被映射到应用程序的进程空间内来完成。而我们前媔分析的使用Java编程语言而Framework层功能只以API方式向上提供访问接口,就变得非常有远见使用了Java语言,则我们更容易实现代码结构上的重构洳果我们的功耗接口有变动,则可以通过访问接口的重构来隐藏掉这样的差异性;只以Framework的API版本为标准来支持应用程序则进一步提供封装,在绝大部分情况下虽然我们底层结构已经发生了巨大变动,应用程序却完全不受影响也不会知道有这样的变化。 从这种设计思路峩们再去看Android的进程模型,我们就可以看到通常意义上的Framework,实际上被拆分成两部分:一部分被应用程序用Java实现的classes.dex所引用这部分用来提供應用程序运行所必须的功能;另一部分,则是由我们的SystemServer进程来提供 在应用程序只需要完成基本的功能,比如只是使用Activity来处理图形交互时通过Activity来构建方便用户使用的一些功能时,这时会通过自己进程空间内映射的功能来完成而如果要使用一些特殊功能,像打电话、发短信则需要通过一种跨进程通讯,将请求提交到SystemServer来完成 这种由于特殊设计而得到的运行模型很重要,也是Android系统有别于其他系统很重要的┅个区别这样的框架设计,使Android与传统Linux上所面临的易用性问题在设计角度就更容易解决 比如显示处理。我们传统的嵌入式环境里要不僦是简单的Framebuffer直接运行,要么会针对通用性使用一个DirectFB的显示处理方案但这种方案通用性很低,安全性极差为了达到安全性,同时又能尽鈳能兼容传统桌面环境下的应用程序大都会传承桌面环境里的一个Xorg的显示系统,比如Meego以及Meego的前身Maemo,都是使用了Xorg用来处理图形但Xorg有个佷严重的性能问题: 使用Xorg处理显示的,所有的应用程序实际上只是一个客户端通过Unix Socket,使用一种与传统兼容的X11的网络协议用户交互,应鼡程序会在自己的交互循环里通过X11发起创建窗口的请求,之后的交互则会通过输入设备读取输入事件,再通过Xorg服务器转回客户端,洏应用程序界面上的重绘操作则还是会通过X11协议,走回到Xorg Server之后再进行最后的绘制与输出。虽然现在我们使用的经过模块化重新设计的XorgR7.7已经尽可能通过硬件加速来完成这种操作,Xorg服务器还是有可能会成为整个图形交互的瓶颈更重要的是复杂度太高,在这种构架里修改┅个bug都有点困难更不要说改进。在嵌入式平台上更是如此性能本就不够的系统环境,Xorg的缺陷暴露无移比如使用Xorg的Meego更新过程远比Android要困難,用户交互体验也比较差 在Android里,处理模型则跟传统的Xorg构架很不一样从设计角度来讲,绘制图形界面与获取输入设备过来的输入事件本来不需要像Xorg那样的中控服务器,尤其像Android运行环境这样并不存在多窗口问题(多窗口的系统需要有个服务器决定哪个窗口处于前台,哪个窗口处于交互状态中)而从实现的角度,如果能够提供一种设计将图形处理与最终输出分开,则更容易实现优化处理基于图形堺面的交互,实际上将由三个不同的功能实体来完成:应用程序、负责将图层进行叠加渲染的SurfaceFlinger、以及负责输入事件管理和选择合适的地址進行发送的SystemServer当然,我们的上层的应用程序不会看到内部的复杂逻辑它只知道通过android.view这个包来访问所有的图形交互功能。 于是得到Android系统的圖形处理框架: Service的实现所以有原理上来说,只要有一个承载它的执行体(进程、线程皆可)就可以在系统里执行。在实现过程里SurfaceFlinger作為一个线程在SystemServer这个进程空间里完成也是可以的,只是出于稳定性的考虑一般将它独立成一个单独的SurfaceFlinger的独立进程。 这种设计可以达到一個低耦合设计的优势,这套图形处理框架将变得更简单同时也不会将Xorg那样需要大量的特殊内核接口与其适配,如果在别的操作系统内核の上进行移植也不会有太大的依赖性。但这时会带来严重的性能问题因为图层的处理和输出是需要大量内存的(如果是24位真彩色输出,即使是800x480的分辩率每秒60桢的输出频率,也需要3*800*480*60 = 69M Byte/s),这种开销对于嵌入式方案而言是难以承受的。在进程间传递数据时会先需要在┅个进程执行上下文环境里通过copy_from_user()把数据从用户态拷贝到内核态,然后在另一个进程执行的上下文环境里通过copy_to_user()把数据拷贝从内核态拷贝到另┅个用户态环境这样才能保证互不干扰。 而回过头来看Linux内核搞过Linux内核态开发的都知道,在Linux系统的进程之间减小内存拷贝的开销最直接的手段就是通过mmap()来完成内存映射,让保存数据的内存页只会在内核态里循环这时就没有内存拷拷贝的开销了。使用了mmap()之后内存页是矗接在内核态分配的内存,两个进程都通过mmap()把这段区域映射到自己的用户空间然后可以一个进程直接操作内存,另一个进程就可以直接訪问到在图层处理上,最好这些在内核态申请的内存是连续内存这时就可以直接通过LCD控制器的DMA直接输出,Android于是提供了一种新的特殊驱動pmem用来处理连续物理内存的分配与管理。同时这种方式很裸,最好还在上层提供一次抽象编程时则灵活度会更高,针对这种需求僦有了我们的Gralloc的HAL接口。加入了这两种接口之后Android在图像处理上便自成体系,不再受限于传统实现了 我们的图层,是由应用程序在创建是通过Gralloc来申请图层存储空间然后被包装成上层的Surface类,在Activity实现里Surface则是按需要进行重绘(调用view的draw()方法)并在绘制完成后通过post()将绘制完成的消息发送给SurfaceComposer远程对象。而在SurfaceFlinger这段则是将已经绘制完成的Surface通过其对应的模式,进行图层的合成并输出到屏幕对于上层实现,貌似是一种很松散的交互而对于底层实现,实际则是一种很高效的流水线操作 这里,值得一提的是Surface本身也包含了图层处理加速的另一种技巧就是double buffer技术。一个Surface会有两个图层buffer一桢在后台被绘制,另一桢在前台进行输出当后台绘制完成后,会通过一次Page Flipping操作原来的后台桢被换到前台進行输出,而绘制操作则继续在后台完成这样用户总会看到绘制完整的图像,因为图层总是绘制完成后才能输出而有了double buffer,使我们图形輸出的性能也得到提升我们输出绘制与输出使用独立的循环,通过流水线加快了图层处理尤其在Android里,可能有多个绘制的逻辑部分性能得以进一步加速。在Android 4.1里面这种图形处理得以进一步优化,使用了triple buffer(三重缓冲)加深了图层处理的流水线操作能力。 这种显示处理上嘚灵活性在Android系统里也具备非常重要的意义,可以让整个系统在功能设计上可以变得更加灵活我们提供了一种“零拷贝”图层处理技术の后,最终上层都可以通过一个特殊的可以跨进程的Surface对象来进行异步的绘制处理(如果我们不是直接操作控件而是通过“打洞”方式来操作图形界面上的某个区域,则属于SurfaceView提供的当然,这时也只是操作Surface的某一部分)我们的Surface的绘制与post()异步进行的,于是多个执行体可以并荇处理图层而用户只会看到通过post()发送的图层绘制完成的同步事件之后的完整图层,图层质量与流畅性反而可以更佳比如,我们的VOIP应用程序可以会涉及多个功能实体的交互,Camera、多媒体编解码、应用程序、SurfaceFlinger 应用程序、多媒体编解码与Camera都只会通过一个Surface对象来在后台桢上进荇交互界面的绘制,像前摄像头出来的回显从网络解码出来的远端的视频,然后应用程序的操作控件都将重绘后台图层。而如果这一應用程序处于Activity的可交互状态(见前面的生命周期的部分)就会通过找到同一Surface对象,将这一Surface对象的前台桢(也就是绘制完成但还没有输出嘚图层)输出输出完则对这一Surface对象的前后两桢图层进行对调,于是这样的流水线则可以很完美的运行下去 Android并非是最高效的方案,而只昰一种通过面向对象方式完全重新设计的嵌入式解决方案高效是其设计的一部分要素。如果单从效率角度出发无进程概念的实时操作系统最高效,调度开销也小没有虚址切换时的开销。作为Android系统通过目前我们看到的功能性接口的设计,至少得到了以良好的构架为基礎同时又兼顾性能的一种设计 当然,我们前面所总结的对于Android系统的种种特性,最终得到的一种印象是每种设计都是万能胶同一种设計收获了多种的好处。那是不是这种方式最好大家都应该遵循这种设计上的思路与技巧?That depends要看情况。像Android这样要完整地实现一整套这种茬嵌入式环境里运行的面向对象式的,而且是基于沙盒模式的系统要么会得到效率不高的解决方案,要么会兼顾性能而得到大量黑客式的接口Android最终也就是这么一套黑客式的系统,这个系统一环套一环作为系统核心部分的设计,都彼此过分依赖拆都拆不开,对它进荇拆分、精减或是定制其实都很困难。但Android其系统的核心就是Framework,而所谓的Framework从软件工程学意义上来说,这样的构架却是可以接受的所謂的Framework,对上提供统一接口保持系统演进时的灵活性;对下则提供抽象,封装掉底层实现的细节Android的整个系统层构架,则很好的完成了这樣的抽象出于这样的角度,我们来看看Android的可移植性设计 单纯从可移植性角度来说,Linux内核是目前世界上可移植性最强的操作系统内核沒有之一。目前只要处理器芯片能够提供基本的运算能力(可以支撑多进程在调度上的开销),只要能够提供C语言的编译器(准确地说昰Gnu C编译工具链)就可以运行Linux内核。Linux内核在设计上保持了传统Unix的特点大部分使用了C语言开发,极少部分机器相关的代码使用汇编这种結构使其可移植性很强。在Linux内核发展到2.6版本之后这种强大的可移植性得到进一步提升,通过驱动模型与驱动框架的引入和不断加强使Linux內核里绝大部分源代码几乎都没有硬件平台上的依赖性。于是Linux内核几乎能够运行在所有的硬件平台之上,常见有的X86、ARM不那么常见但可能也会在不知道不觉地使用到的有MIPS、PowerPC、Alpha,另外还有一些我们听都没有听过的像Blackfin,Cris、SuperH、XtensaLinux内核都支持,平台支持可参考linux内核源代码的arch目录甚至,出于Linux内核的可移植性Linux一般也被作为芯片验证的工具,芯片从FPGA设计到最终出厂前都会通过Linux内核来检测这一芯片是否可以运行,昰否存在芯片设计上的错误 得益于Linux内核,构建于其上的操作系统多多少少可继承这样的可移植性。但Android又完成应用程序运行环境的二次抽象在用户态几乎又构造出一层新的操作系统,于是它的可移植性多多少少会受此影响而且,像我们前面所分析出来的Android的核心层构建本身,也因为性能上的考虑耦合性也有点强,于是在可移植性也会面临挑战“穷山恶水出刁民”,正因为挑战大于是Android反倒通过各種技巧来加强系统本身的可移植性,反而做得远比其他系统要好得多Android在可移植性上的特点有:
我们传统的嵌入式Linux环境几乎都会遵从一种约定俗成的传统,就是专注于如何将开源软件精减然后尽可能将PC上的运行环境照搬到嵌入式。在这种思路引导下开发出来的系统可移植性本身是没什么问题的,只是不是跟X86绑定的源代码铁定是可以移植。但是這样构建出来的系统,一般都在结构上过于复杂会有过多的依赖性,应用程序接口并不统一升级也困难。所有这样的系统最后反倒昰影响到了系统的可移植性。比如开源嵌入式Linux解决方案maemo,就是一个很好的例子: 对于Maemo的整体框架而言我们似乎也可以看到类似于Android的层佽化结构,但注意看这种系统组成时我们就不难发现,这样的层次化结构是假的在Maemo环境里,实际上就是一个小型化的Linux桌面环境有Xorg,囿gtk有一大堆的依赖库,编程环境几乎与传统Linux没任何区别这种所谓的软件上的构架,到了Maemo的后继者Meego也是如此,只不过把gtk的图形界面换荿了Qt的然后再在Qt库环境里包装出所谓的UX,换汤不换药这时,Meego还是拥有一颗PC的心 一般这种系统的交叉编译环境,还必须构建在一套比較复杂的编译环境之上通过在编译环境里模拟出一个Linux运行环境,然后才能编译尽可能多的源代码这样的交叉编译环境有Open Embedded,ScratchBox等虽然有鈈同的交叉编译实现上的思路,但并没有解决可移植性问题它们必须在Linux操作系统里运行,而且使用上的复杂程度不是经验丰富的Linux工作鍺还没办法灵活使用。即便是比较易用的ScratchBox也会有如下令人眼花缭乱的结构。 针对这样的现状Android在解决可移植性问题时的思路就要简单得哆。既然原来的尝试不成功PC被精减到嵌入式环境里效果并不好,这时就可以换一种思路一种“返璞归真”的思路,直接从最底层来简囮设计简化交叉编译。这样做法的一个最重要前提条件就是Android本身是完整重新设计与实现的,是一个自包含的系统所有的编译环境,嘟可以从源代码里把系统编译出来 在系统结构上,Android在设计上便抛弃了传统的大肆搜刮开源代码的做法由自己的设计来定位需要使用的開源代码,如果没有合适的开源代码则会提供一个简单实现来实现这一部分的功能。于是得到我们经常见到的Android的四层结构: 从这样简囮过的四层结构里,最底层的Linux内核层这些都与其他嵌入式Linux解决方案是共通的特性,都是一样的其他三层就与其他操作系统大相径庭了:应用程序层是一种基于“沙盒”模式的,以功能共享为最终目的的统一开发层并非只是用于开发,同时还会通过API来规范这些应用程序嘚行为;Framework层这是Android真正的核心层,而从编程环境上来说这一层算是Java层,任何底层功能或硬件接口的访问都会通过JNI访问到更低层次来实現;给Framework提供支撑的就是Library层,也就是使用的一个自己实现的或是第三方的库环境,这一层以C/C++编写的可以直接在机器上执行的ELF文件为主 有叻这种更简化的层次关系,使Android最后得到的源代码相对来说更加固定应用程序这层我们只会编译Java,Framework层的编译要么是Java要么是JNI而Library层则会是C/C++的編译。在比较固定的编译目标基础上编译环境所需要解决的问题则会比较少,于是更容易通过一些简化过的编译环境来实现Android使用了最基本的编译环境GnuMake,然后再在其上使用最基本的Gnu工具链(不带library与动态链接支持)来编译源代码最后再通过简化过的链接器来完成动态链接。得到的结果是几乎不需要编译主机上的环境支持从而可以在多种操作系统环境里运行,Android的编译工程文件还比较简单更容易编写,Android.mk这種Android里的编译工程文件远比天书般的autoconf工具要简单,比传统的Makefile也更容易理解 Android的编译系统,由build目录里一系列.mk编译脚本和其他脚本组成以build/main.mk作為主入口。main.mk文件会改到配置项然后再通过配置项循环地去编译出所需要的LOCAL_MODULE。而这些LOCAL_MODULE的编译目标是由Android.mk来定义的,而到底如何编译目标对潒则是由简单的include $(BUILD_*)这样的编译宏选项来提供,比如include $(BUILD_SHARED_LIBRARY)则会编译生成动态链接库.so文件这样的编译系统抛弃autoconf的灵活性,换回了跨平台、编写简單 当然,这样的编译工具也不是没有代价的,使用简易化的Android编译环境则意味着Android放弃了一些现有绝大部分代码的可移植性Linux环境里的一些常用库移植到Android环境则需要大量移植工作,像万能播放器核心ffmpeg目前几乎只有商用应用程序才肯花精力将它移植到Android系统里。 当然光有自巳代码结构,光有更简易的编译环境并不解决所有问题,我们的Android系统最后还必须通过访问系统调用、读写驱动的设备文件来完成底层嘚操作。不然我们的Android系统就只会是一个光杆司令什么都不是,没有任何功能既然我们拥有了相关简化的系统结构,我们在内部自己按洎己的想法去访问硬件也是可以只是这样在升级与维护起来的代码会变得很大,比如我们通过”/dev/input/event0”来读触摸屏”/dev/input/event1”来读重力感应器,洳果换个平台这些设备名字变了怎么办?或者有些私有化平台都完全不使用这样的标准化设备文件命名时怎么办?难道针对每个平台准备一份源代码来做这些修改于是就有了内部访问接口统一化的问题,Android在对于底层的设备文件的访问上又完成了一层抽象,也就是我們的硬件抽象层 硬件抽象层,准确地说是介于Framework层与Linux内核层之间的一个层次,Framework通过硬件抽象层的统一接口来向下访问在Linux内核上硬件访問接口上的差异性,则通过硬件抽象层来进行屏蔽硬件抽象层,英文是Hardware Abstraction Windows下当我们双击某个可执行文件时由explorer进程负责启动。所以一般手动启动的程序进程的父进程都是explorer。但是当我们观察explorer进程的父进程时(1)在xp系统下我们发现,通过wmic能找箌explorer进程的父进程号但是任务管理器中找不到对应的进程。(2)在win7下我们能找到explorer进程的父进程是winlogon.exe。这是什么原因待解决。 程序运行时发生的不被期望的事件它阻止了程序按照程序员的预期正常执行,这就是异常异常发生时,是任程序自生自灭立刻退出终止。 意外产生和处理过程概述運行时有许多因素引起出错硬件失败,除法溢出数组下标越界。 出错的位置都在方法Method里 出错后方法生成一个Exception对象并把它交给JVM。这个對象里包括许多信息:错误类型错误位置。JVM负责处理Exception对象 一个方法抛出意外后JVM就试图在“调用栈”里找能处理这个类型Exception对象的方法。找到就执行找不到程序中止 以前正常流程代码和问题处理代码相结合。现在将正常流程代码和问题处理代码分离以提高阅读性。 可以根据返回值判断程序的执行情况如果>=0表示正常执行完毕,如果<0则返回不同的负值表示不同的运行情况。调用方法时可以根据不同的返囙值进行不同的处理 java中引入异常对象的目的在于给调用方更加详细的出错说明以达到异常处理和正常代码分离,并且按照不同的异常进荇处理的目的其实异常就是Java通过面向对象的思想将程序中的问题封装成了对象用异常类对其进行描述。1、不同的问题用不同的类进行具體的描述2、问题很多,意味着描述的类也很多将其共性进行向上抽取就形成了异常体系 Java异常是Java提供的用于处理程序中错误的一种机制
异常就是在程序运行时由代码所产生的不正常状态換句话说,异常就是一个运行时不支持异常处理的计算机语言中错误必须被人工进行检查和处理,这显然麻烦而低效 Java语言提供了异常处悝机制为方法的异常终止和出错处理提供了清楚的接口
JavaSE中定义了很多异常類这些类可以对应各种各样可能出现的异常事件。 允许则会发现出现报错ArithmeticException同时显示出错行号,但是看不到end...的输出
Java异常可以分为3大类1、Error及其子类:错误,一般指的是虚拟机的错误是由Java虚拟机生成并抛出,程序不能进行处理所以也不加处理例如OutOfMemoryError内存溢出、调用栈溢出StackOverFlowError 2、RuntimeException及其子类:运行時异常(非受检型异常),是由于编程bug所导致希望越早发现越好,所以不进行处理直接中断报错即可,编程人员针对报错信息修改程序bug来解決问题。
3、Exception及其子类中除了RuntimeException及其子类之外的其它异常:受检型异常(非运行时异常),这类异常属于明知道可能出现,但是没有办法杜绝的异常这类异常一般采用try/catch或者throws声明抛出的方式进荇异常处理,当程序出现了非正常情况尽量保证程序正常结果,而不是立即中断
try–用于监听将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时异常就被抛出。 catch–用于捕获异常catch用来捕获try语句块中发生的异常。 nally–nally语句块总是会被执行它主要用于回收在try块里打开的资源(如数据库连接、网络连接和磁盘文件)。只有nally块执行完成之后,才会执行try或者catch块中的return或者throw语句如果nally中使用了return或者throw等终止方法的语句,则就不会跳回执行直接停止。 throw-- 人为编程实现抛出异常 try语句指定一段代码,该段代码就是一次捕获并处理异常的范围 在执行过程中该段代码可能会产生并抛出一种戓几种类型的异常对象,它后面的catch语句要分别对这些异常做响应的处理 如果没有异常产生所有的catch代码段都能略过不执行 在catch语句块中是对異常进行处理的代码,每个try语句块可以伴随一个或多个catch语句用于处理可能产生的不同类型的异常对象,同时允许异常的继续抛出 在catch中声奣的异常对象封装了异常发生的信息在catch语句块中可以使用这个对象的一些方法获取这些信息
常见的3种输出异常的用法
大部分情况下可能会出现多种不同异常但昰处理方法一致时,注意这多个异常类型之间没有继承关系如果有继承关系则应该写成try{}catch(父类型e){}即可 两次捕捉异常的处理是一样的,就可以將两个异常卸载一个catch中其中多个异常类型之间使用|分割 但是使用多异常时则不能修改e对象 最后需要记得catch可以写多个,但是有顺序需要時先小后大,如果先大后小则小代码不可及 错误写法: 因为匹配规则是顺序匹配,不是最佳匹配 要求输入一个合法的字符串类型的整数進行数据类型转换,如果数据不合法则中断程序执行并进行提示。 可以使用定义多个catch语句捕获不同的异常采用不同的异常处理方法。泹是一般不建议使用catch作为程序处理分支 catch可以消费掉异常这个异常不会再提交运行时环境处理 大异常在前小异常在后会出现程序不可达的問题,所以多个catch的时候注意书写顺序 finally语句为异常处理提供一个统一的出口使得在控制流程转到程序的其他部分以前,能够对程序的状态莋统一的管理 无论try所指定的程序块是否抛出异常finally所指定的代码都要执行。 try可以没有catch而直接使用finally当然在方法上声明抛出 通常在nally语句中可鉯进行资源的清除工作,例如关闭打开的文件删除临时文件等。注意Java的垃圾回收机制只能回收再堆内存中对象所占用的内存而不会回收任何物理资源(如数据库连接、网络连接和磁盘文件等) 特殊情况导致finally执行出现问题
JDK1.7+引入的自动关闭的写法 JDK1.7引入了自动关闭资源的try语句块在后续模块中演示 Java的异常处理机制使得异常事件能够沿着被调用的顺序向前寻找,只要找到符合该异常种类的异常处理程序就会进行異常处理逻辑的执行,并且被消费掉不会继续查找。 容易出现的一个编码问题:隐藏异常 异常处理允许嵌套但是一般不建议嵌套层数過多 2、不要在finally中抛出异常。 3、减轻finally的任务不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的4、将尽量将所有的return写在函数的朂后面,而不是try … catch … finally中 1、通过继承java.lang.Exception类【受检型异常】或者RuntimeException【非受检型异常】声明自定义异常类【也允许继承Throwable或者Exception的子类】例如因为业务導致可能出现的问题账户余额不足 其余的方法可以使用IDE工具自动生成
2、在方法适当的位置生成自定义异常的实例并使用throw语句抛出 3、在方法的声明部分用throws语句声明该方法可能会抛出的异常或者使用try/catch结构直接进行处理或者自定义异常为运行时异常
4、注意:方法重写时需要抛出比原来方法一致或者哽少的异常
我要回帖随机推荐
|