Android内核开发必备的基础知识

来源:互联网 发布:win10安装ubuntu失败 编辑:程序博客网 时间:2024/06/05 17:46
 随着移动互联网的越来越火爆,国内的移动开发者也紧跟潮流,尤其是在Android平台占据主导地位的今天。Android的开发相对较为简单,国内的开发者多数以应用开发为主,对内核开发懂得并不是太多,今天给大家介绍一些关于Android内核开发方面的一些知识,方便开发者朋友对Android平台能够有更深入的了解。同时希望这些知识能够像大餐中的甜点,给大家的美食增添色彩。

窗口相关的概念

  窗口相关概念解读

  做Android开发的朋友会经常提到以下概念,窗口、Window类、ViewRoot类以及W类,本文简单介绍这些概念的联系和区别。

  窗口(Window):这是一个纯语义的说法,即程序员所看到的屏幕上的某个独立的界面,比如一个带有Title Bar的Activity界面、一个对话框、一个Menu菜单等,这些都称之为窗口。这里所说的窗口管理一般也都泛指所有这些窗口,在Android的英文相关文章中则直接使用Window这个单词。而从WmS的角度来讲,窗口是接收用户消息的最小单元,WmS内部用特定的类表示一个窗口,以实现对窗口的管理。WmS接收到用户消息后,首先要判断这个消息属于哪个窗口,然后通过IPC调用把这个消息传递给客户端的ViewRoot类。

  Window类:该类在android.view包中,是一个abstract类,该类是对包含有可视界面的窗口的一种包装。所谓的可视界面就是指各种View或者ViewGroup,一般可以通过res/layout目录下的xml文件描述。

  ViewRoot类:该类在android.view包中,客户端申请创建窗口时需要一个客户端代理,用以和WmS进行交互,这个就是ViewRoot的功能,每个客户端的窗口都会对应一个ViewRoot类。

  W类:该类是ViewRoot类的一个内部类,继承于Binder,用于向WmS提供一个IPC接口,从而让WmS控制窗口客户端的行为。

  描述一个窗口之所以使用这么多类的原因在于,窗口的概念存在于客户端和服务端(WmS)之中,客户端所理解的窗口和服务端理解的窗口是不同的,因此,在客户端和服务端会用不同的类来描述窗口。同时,无论是在客户端还是服务端,对窗口都有不同层面的抽象,比如在客户端,用户能看到的窗口一般是View或者ViewGroup组成的窗口,而与Activity对应的窗口却是一个DecorView类,而具备常规Phone操作接口的窗口却又是一个PhoneWindow类。


  窗口切换逻辑设计中需要解决的问题

  Android中的窗口管理系统中,首先在AmS中保存所有与Activity相关的信息,在Activity启动和关闭时,AmS会通知WmS同步Activity窗口的状态,而在WmS中使用WindowState类保存一个窗口的信息,这些窗口信息需要根据Activity的状态而动态改变。在WmS中另外有一个InputManager对象,该对象内部保存了输入消息处理时所需要的窗口信息,借助这些信息,InputDispatcher能够决定输入消息应该对应哪个窗口,WindowState类的信息必须与InputManager内部的窗口信息也保持同步。

  切换过程中要解决的问题可归纳为三类,第一类是状态同步问题,即AmS如何把状态传递给WmS,WmS如何保存这些状态,又如何把这些状态传递给InputManager;第二类问题是屏幕绘制问题,即当AmS启动或者关闭一个Activity时,用户一般会看到一个动画,那么WmS如何定义这个动画,并如何在动画绘制前先隐藏目标窗口,直到动画结束后才显示目标窗口,而要实现这种动画的绘制就需要一种特别的变量来保存动画窗口和目标窗口的关系;第三类问题是消息处理问题,即在窗口切换的过程中,是老窗口应该继续捕获用户消息呢还是新窗口?

  首先来看状态同步问题

  在AmS中使用ActivityRecord类来保存一个Activity相关的信息,ActivityRecord类本身是一个Binder类,名称为IApplicationToken.Stub。每个ActivityRecord都会在WmS中对应一个AppWindowToken类,该类保存了和ActivityRecord相关的所有窗口信息,比如启动窗口、实际窗口、关闭窗口等。当启动一个新的Activity时,AmS中会先创建一个ActivityRecord对象,并请求WmS中也创建一个AppWindowToken对象;当销毁一个Activity时,AmS会请求WmS删除AppWindowToken对象。

  AppWindowToken中包含的窗口对象在WmS中的mWindows等列表变量中也都有记录,当新窗口启动时,必须保证这些列表中的对象和AppWindowToken中保存的窗口信息之间的同步。

  WmS中有一个InputManager对象mInputManager,该对象中保存了输入消息处理所需的窗口信息,当有新窗口添加或者旧窗口被删除时,该对象中的窗口信息同时需要被更新。

  下面再来看绘制问题。当新建一个窗口时,AmS会首先判断该窗口是同一个Task中的Activity还是一个新的Task。如果是同一个Task,则会指定一个Activity切换效果的动画,而如果是一个新的Task,则会指定一个Task切换的动画效果,这些动画效果的实施都是在WmS中完成的。每一个动画实际上都仅仅是一个窗口而已,动画的过程可以简单地理解为对同一个窗口进行不同的变化,并在连续的时间将其显示出来,从而形成动画。

  而在动画的过程中,如果目标窗口也已经创建好,则在动画结束之前不能显示目标窗口,只有当动画结束后才能显示目标窗口;而如果在动画结束后目标窗口还没有被创建,则启动窗口不消失,直到目标窗口被创建好才消失。这个逻辑的具体实现是在performLayoutAndPlaceSurfaceLocked()函数中完成的,当然该函数可能仅仅是个包装,内部又会调用performLayoutAndPlaceSurfaceLockedInner()函数。为了叙述的方便,本节以后将这两个函数统称为traversal,因为它们的功能就是根据所有窗口的状态参数,调整其在屏幕上的显示效果,有点类似于ViewRoot类中的performTravasals()函数。

  最后再来看消息处理问题。当要添加一个新窗口时,如果该窗口需要启动动画,那么在动画结束前,应该是哪个窗口获得输入消息呢?在WmS中提供了诸如startAppFreezingScreen()、topAppFreezingScreen()、resumeKeyDispatching()、pauseKeyDispatching()、setEventDispatch(true/false)这样的接口,用于暂停、恢复消息处理,这些函数将影响在动画过程前后的输入消息处理。那么这些API接口具体是被如何使用的?

 


  自定义布局的IMS

  什么是自定义输入法?简单的讲,如果你编写的输入法不是基于InputMethodService或者AbstractInputMethodService类,而是完全重新实现IInputMethodService.aidl中定义的Binder接口,那么就可以认为这是一个自定义输入法。

  IMF的本质是几个Binder在交互信息,至于如何实现这些Binder,IMF本身并没有规定。其中介绍的InputMethodService只是为了方便设计而提供的一种实现实例,因此从严格意义上讲,实现一个自定义输入法意味着要实现这些Binder接口,具体包括以下三个。

  InputMethod接口:当输入法所在的Service被启动时,必须在其onBind()函数中返回一个该Binder接口,在标准输入法中,返回的是IInputMethodWrapper类对象。

  InputMethodSession接口:当IMMS调用输入法所在Service的createSession()方法时,必须返回一个该Binder接口,在标准输入法中,返回的是IInputMethodSessionWrapper对象。

  InputContext接口:当IMM调用IMMS的startInput()时,参数中需要包含该Binder对象,而该Binder对象是在IMM中创建的,名称为ControlledInputConnectionWrapper。这个Binder对象本身是不能自定义的,但该Binder对象是通过一个InputConnection接口构造的,而这个InputConnection接口的实现却可以自定义。在标准输入法中,该接口的实现类为EditableInputConnection类。

  到目前为止,国内主流的输入法包括Android自带的Google拼音输入法、HTC的中文Touch手写输入法、搜狗拼音输入法,而这些输入法全部属于标准输入法。

  那么为什么要实现自定义的输入法呢?

  如果你对标准输入法窗口的那种“输入区”、“候选区”、“提取区”的布局不满意,并想设计一种该布局无法满足的布局,那么可以考虑实现自定义输入法。比如,设想一种没有候选区,或者候选区会嵌套到输入区中的布局,如下图所示。

  该输入法的设想是,当用户输入中文时,可以在目标词语对应的首字母处画一个路径。比如输入“葛永娇”时,只需要在G、Y、J三个字母处画一个路径,然后在屏幕的中央就会出现备选词语,这种输入法超越了标准输入法的框架,因此可以考虑为自定义输入法。

  如果你对标准输入法中的那种调用逻辑不满意,想设计一种更为简单的输入法,比如,该输入法可能只是为了输入某种特定的数据。

  如果你想设计一种自定义的视图,类似于EditView,但是却不是EditView,比如,该视图可以接收的字符,并自动把字符转换为图片。

  总之,凡是你能设想的,标准输入法中没有实现的,都可以考虑实现自定义输入法。


  Android Acitivity之间如何传递数据消息

  任何控制类程序都有一个入口,汇编程序的入口由处理器内部的复位(Reset)中断向量表决定;C程序的入口是main()函数,一个C程序只能有一个main()函数;Java程序的入口必须是某个类的静态成员函数main()。

  对于依赖于操作系统的程序,客户程序除了包含一个程序入口外,还需要和相关系统服务一起运行,以完成指定的任务。比如,Win32程序需要和GUI系统服务一起实现带有可视窗口的功能;X Window程序也需要和X Window Server一起实现窗口功能。

  Android程序也不例外,那么,Android程序的入口在哪里?Android Framework都包含哪些必需的系统服务?这些系统服务是如何与Android APK程序配合的?

  程序员需要在不同的Activity之间传递数据,然而,这个问题本身就有问题。所谓“传递消息”一般是指多个线程之间,而Activity本身并不是线程,ActivityThread才是一个线程,即UI线程。同一个程序中的多个Activity都由ActivityThread进行调用,Activity本身只是一个Java类而已,就像Rect、Trigle类一样,如果有人问“Rect类和Trigle类之间如何传递消息”,你会不会觉得有点奇怪?

  事实上,如果要在两个类中传递数据,方法可以有很多。

  方法一:可以先实例化某个类,获得该类的引用,当其他类需要该对象的内部数据时,可以直接通过该引用去访问该类的内部数据。

  方法二:对于A、B两个类之间,可以先实例化一个第三方类C,然后两个类都可以把需要传递的数据存入C中,或从C中取出。

  这些方法理论上都可以用在Activity类之间传递数据。然而,与普通类传递数据有所不同,普通类的实例化都是程序员显式完成的,而Activity类的实例化却是由Framework完成的,程序员只能使用startActivity()方法来告诉Framework去运行哪个Activity,这就意味着程序员不能得到Acitivity对象的引用,那么就不能直接访问该对象的内部数据。解决的办法是使用Activity.getApplication()函数,该函数能够返回一个Application对象,该Application对象在该程序中是唯一的,同一程序中的不同Activity调用该函数所返回的Application对象是相同的,该对象的名称可以在AndroidManifest.xml中指定。一旦获取了该Application对象,就可以借助该对象,在不同的Activity之间传递数据。

  除此之外,Framework本身也提供了标准的Activity之间传递数据的方法,即Intent类。该类作为startActivity()的参数,仅用于在启动Activity时传递给目标Activity,同时,如果调用startActivityForResult(),目标Activity在结束后,也会返回一个Intent对象给原Activity。

  另外,从设计理念的角度来看,Android认为,两个Activity如果要共享数据,可以通过Preference Storage或者文件、数据库进行,同时,在一般情况下,设备上只会有一个Activity在运行,因此,多个Activity之间传递数据也不是必需的。如果某个Activity需要在停止后还能处理某些数据,那么,该Activity似乎更应该被设计为一个后台的Thread或者一个Service,无论是Thread还是Service都很容易获得其引用。