系统架构与MediaPlayer框架

第11章 系统架构与MediaPlayer框架
本章将介绍Android系统架构和MediaPlayer框架。本书主要是讲解Android应用层知识的,在最后一章为何要讲解 Android 系统底层源码呢?其主要目的就是要告诉读者,作为一个Android开发者,了解Android系统底层源码是十分必要的。希望本章能起到过渡的作用:带领读者由应用层过渡到系统底层。由于篇幅有限,而系统源码的知识又太多,因此本章只会介绍系统架构和MediaPlayer框架的部分源码,并且不会拘泥于源码细节,旨在帮助读者学习一些入门知识。如果读者想要了解更多有关系统底层源码的知识,请阅读专门介绍系统源码的书籍。另外需要提醒大家注意的一点就是,阅读本章需要有一定的C/C++基础。
11.1 Android系统架构
Android系统架构分为5层,从上到下依次是应用层、应用框架层、系统运行库层、硬件抽象层和Linux内核层,如图11-1所示。
1.应用层
系统内置的应用程序以及非系统级的应用程序均属于应用层。其负责与用户进行直接交互,通常都是用Java进行开发的。
2.应用框架层(Java Framework)
应用框架层为开发人员提供了开发应用程序所需要的 API,我们平常开发应用程序都是调用这一层所提供的API,当然也包括系统的应用。这一层是用Java代码编写的,可以称为Java Framework。下面来看这一层所提供的主要组件,如表11-1所示。

图11-1 Android系统架构
在这里插入图片描述

表11-1 应用框架层提供的组件
在这里插入图片描述

在这里插入图片描述

续表

3.系统运行库层(Native)
从图11-1中可以看出,系统运行库层分为两部分,分别是C/C++程序库和Android运行时库。下面分别介绍它们。
(1)C/C++程序库
C/C++程序库能被Android系统中的不同组件所使用,并通过应用程序框架为开发者提供服务。表11-2列出了主要的C/C++程序库。
表11-2 主要的C/C++程序库
在这里插入图片描述

(2)Android运行时库
从图11-1中可以看出,运行时库又分为核心库和ART(Android 5.0系统之后,Dalvik虚拟机被ART取代)。核心库提供了Java语言核心库的大多数功能,这样开发者可以使用Java语言来编写 Android 应用。相较于JVM,Dalvik虚拟机(以下简称 DVM)是专门为移动设备定制的,其允许在有限的内存中同时运行多个虚拟机的实例,并且每一个 DVM 应用作为一个独立的 Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。而替代DVM的ART机制与DVM不同。在DVM下,应用每次运行的时候,字节码都需要通过即时编译器转换为机器码,这会使得应用的运行效率降低;而在ART环境中,应用在第一次安装的时候,字节码就会预先编译成机器码并存储在本地,这样应用每次运行时就无须执行编译了,运行效率也会大大提升。
4.硬件抽象层(HAL)
硬件抽象层是位于操作系统内核与硬件电路之间的接口层,其目的在于将硬件抽象化。为了保护硬件厂商的知识产权,它隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,可在多种平台上进行移植。从软硬件测试的角度来看,软硬件的测试工作都可分别基于硬件抽象层来完成,这使得软硬件测试工作的并行进行成为可能。通俗来讲,就是将控制硬件的动作放在硬件抽象层中。
5.Linux内核层
Android 的核心系统服务基于 Linux 内核,在此基础上添加了部分 Android 专用的驱动。系统的安全性、内存管理、进程管理、网络协议栈和驱动模型等都依赖于该内核。Android系统的5层架构就讲到这里,了解以上知识对我们以后分析系统源码有很大的帮助。
11.2 Android系统源码目录
在学习MediaPlayer框架源码之前,我们要先了解Android系统源码目录,以便为后期源码学习打下基础。关于源码的阅读,你可以访问http://androidxref.com/来阅读系统源码。当然,最好是将源码下载下来,下载源码可以使用清华大学开源软件镜像站提供的 Android 镜像:https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/。如果觉得麻烦也可以查找国内的网盘进行下载,笔者推荐使用该百度网盘地址下载:http://pan.baidu.com/s/1ngsZs,它提供了多个Android版本的源码下载。
11.2.1 整体结构
各个版本的源码目录基本是类似的,如果是编译后的源码目录,则会多增加一个 out 文件夹,用来存储编译产生的文件。Android 7.0的根目录结构说明如表11-3所示。
表11-3 系统根目录结构
在这里插入图片描述

续表
在这里插入图片描述

从表 11-3 中可以看出,系统源码分类清晰,内容庞大且复杂,11.4 节要讲的 MediaPlayer框架也只是frameworks目录中很小的一部分。接下来分析packages中的内容,也就是应用层部分。
11.2.2 应用层部分
应用层位于整个 Android 系统的最上层,开发者开发的应用程序以及系统内置的应用程序都在应用层。源码根目录中的packages目录对应着系统应用层。它的目录结构如表11-4所示。
表11-4 packages目录
在这里插入图片描述

从表11-4的目录结构中可以发现,packages目录存放着系统核心应用程序、第三方的应用程序和输入法等。这些应用都是运行在系统应用层的,因此packages目录对应着系统的应用层。
11.2.3 应用框架层部分
应用框架层是系统的核心部分,其一方面向上提供接口给应用层调用,另一方面向下与C/C++程序库以及硬件抽象层等进行衔接。应用框架层的主要实现代码在/frameworks/base和/frameworks/av目录下,其中/frameworks/base目录结构如表11-5所示。
表11-5/frameworks/base目录
在这里插入图片描述

11.2.4 C/C++程序库部分
系统运行库层(Native)中的 C/C++程序库的类型繁多,功能强大,C/C++程序库并不完全在一个目录中。这里给出几个常用且比较重要的C/C++程序库所在的目录位置,如表11-6所示。
表11-6 C/C++程序库部分
在这里插入图片描述

讲完C/C++程序库部分,剩下的部分我们在表11-3中已经给出:Android运行时库的代码放在 art/目录中。硬件抽象层的代码放在 hardware/目录中,这是手机厂商改动最大的一部分,根据手机终端所采用的硬件平台会有不同的实现。
11.3 Source lnsights使用
Source Insights是阅读系统源码的必备利器,它是Windows平台下的软件。首先我们来新建源码工程。通过菜单项Project→New Project即可指定源码的目录,可以添加整个Android系统源码,也可以只把/frameworks/base目录添加到工程中,以后再根据需要添加其他目录。这里我们只添加/frameworks/base目录的代码,如图11-2所示。
在这里插入图片描述

图11-2 导入项目
选择/frameworks/base目录后点击“Add Tree 按钮。base目录的代码就添加到源码工程中。
1.定位文件
Source Insights 的定位文件功能十分强大,我们只需要知道源码文件名就可以轻松地找到它,比如我们要找MediaPlayer.java,只要在文件搜索框中输入“MediaPlayer 即可,如图11-3所示。
2.全局搜索
Source Insights的另一个好用功能就是全局搜索,默认快捷键为CTRL+/,或者点击最上面工具栏中类似R的图标,弹出的搜索框如图11-4所示。在Search In的输入选项中我们可以自定义搜索的范围。

图11-3 定位文件
图11-4 全局搜索
在这里插入图片描述

当然,Source Insights的功能不止以上几种。相信随着使用次数的增多,你就会熟练掌握它的大部分功能,这里就不过多介绍了。
11.4 MediaPlayer框架
在11.1节中我们学习了系统架构,为了更好地理解系统架构,最好的办法就是读系统源码,这需要从应用层开始分析,一直到 Native 层(又可称为 Native Framework 层)。本节介绍MediaPlayer框架的Java Framework层、JNI层和Native层。
11.4.1 Java Framework层的MediaPlayer分析
Java Framework层也就是应用框架层,后文简称为Java层。Android系统默认的音乐播放器Music,在packages/apps/Music目录中。在MediaPlaybackService.java的OnCreate方法中创建了MultiPlayer,如下所示:
packages/apps/Music/src/com/android/music/MediaPlaybackService.java
在这里插入图片描述

MultiPlayer是MediaPlaybackService的内部类,它实现了对MediaPlayer的调用,如下所示:
packages/apps/Music/src/com/android/music/MediaPlaybackService.java
在这里插入图片描述

在 MultiPlayer 的 setDataSourceImpl 方法中调用了 MediaPlayer 的 reset、setDataSource 和prepare等方法。这样系统应用就调用了应用框架层的MediaPlayer。我们来查看MediaPlayer的prepare方法,如下所示:
frameworks/base/media/java/android/media/MediaPlayer.java
在这里插入图片描述

prepare方法中调用了_prepare()方法:
private native void_prepare() throws IOException,IllegalStateException;
我们会发现_prepare方法前面有一个native修饰符,这表明_prepare是一个native方法,表示这个方法会由非Java层实现,也就是后文讲到的JNI层来实现。
11.4.2 JNl层的MediaPlayer分析
JNI也是用Native语言编写的,它属于Native层。但是为了便于学习和分析,本书将与JNI相关的代码划分到JNI层,JNI层是Native层的一部分。在分析JNI层的MediaPlayer代码前,首先要简单介绍 JNI。JNI是Java Native Interface的缩写,通过JNI可以让Java方法与Native方法相互调用,其中Native方法一般是用C/C++编写的。因此,JNI就是连接Java层和Native层的桥梁。要使用JNI需要先加载JNI库,在MediaPlayer.java中有如下代码:
frameworks/base/media/java/android/media/MediaPlayer.java
在这里插入图片描述

在静态代码块中加载JNI库,并调用native_init方法。关于native_init方法,后文会进行分析。这里加载的名为"media_jni"的 JIN 库指的是 libmedia.so。MediaPlayer 的 JNI 层的代码在frameworks/base/media/jni/android_media_MediaPlayer.cpp中。我们需要通过JNI调用Native方法,那么是不是应该有一个结构来保存Java层Native方法与JNI层方法的对应关系?答案是肯定的,这个结构就是android_media_MediaPlayer.cpp中定义的JNINativeMethod数组,代码如下所示:
frameworks/base/media/jni/android_media_MediaPlayer.cpp
在这里插入图片描述

在这里插入图片描述

从上面来看,JNINativeMethod数组有3个参数。
• 第一个参数,比如"_prepare",它是Java 层Native 方法的名称。在Java层调用Native方法,交由JNI层来实现。
• 第二个参数是 Java 层 Native 方法的参数和返回值。其中()中的字符代表参数,后面的字母则代表返回值。
• 第三个参数是Java层Native方法对应的JNI层的方法,比如_prepare方法对应JNI层的方法为android_media_MediaPlayer_prepare。而通过JNI层的方法就可以调用Native层相应的方法。
前面在MediaPlayer.java的静态代码块中调用了native_init方法,这个方法是一个Native方法,查询JNINativeMethod数组,它对应的是android_media_MediaPlayer_native_init方法,如下所示:
frameworks/base/media/jni/android_media_MediaPlayer.cpp
在这里插入图片描述

上面代码注释1处的代码是JNI层调用Java层,获取MediaPlayer对象。在注释2处的代码获取了Java层的postEventFromNative方法。可以看出native_init做了初始化操作,获取了上下文对象和一些方法。从上述代码可知,JNI层可以调用Java层的代码;换句话来说,就是Native层是可以通过JNI层来调用Java层代码的。下面介绍Java层通过JNI层来调用Native层的代码。_prepare对应的JNI层的方法为android_media_MediaPlayer_prepare,代码如下所示:
frameworks/base/media/jni/android_media_MediaPlayer.cpp
在这里插入图片描述

在上面代码注释1处得到MediaPlayer的强指针,接着在注释2处的process_media_player_call方法中,调用 mp->prepare()来调用 MediaPlayer 的 prepare 方法。也就是通过 JNI 层的android_media_MediaPlayer_prepare 方法调用了 Native 层的 prepare 方法。因此,通过JNINativeMethod数组间接实现了Java层与Native层的关联,如图11-5所示。
在这里插入图片描述

图11-5 Java Framework层与Native层的关联
11.4.3 Native层的MediaPlayer分析
MediaPlayer的Native层整体是一个C/S(Client/Server)架构,Client端和Server端是运行在两个进程中的,它们之间是通过Binder机制来进行通信的。Client端MediaPlayer对应的动态库是libmedia.so,Server端MediaPlayerService对应的动态库为libmediaservice.so。首先我们分析Client端的实现。
1.Client端分析
MediaPlayer Client 端的功能实现定义的头文件为 mediaplayer.h,相应的源文件为mediaplayer.cpp。我们接着来查看mediaplayer.cpp的prepare方法,如下所示:
frameworks/av/media/libmedia/mediaplayer.cpp
在这里插入图片描述

prepare方法会调用prepareAsync_l方法:
frameworks/av/media/libmedia/mediaplayer.cpp
在这里插入图片描述

在上面代码注释1处调用了mPlayer的prepareAsync方法,那么mPlayer指的是什么呢?我们带着这个问题来查看mediaplayer.cpp的setDataSource方法,如下所示:
frameworks/av/media/libmedia/mediaplayer.cpp
在这里插入图片描述

在这里插入图片描述

在上面代码注释1处通过getMediaPlayerService方法得到IMediaPlayerService指针。关于getMediaPlayerService方法,在后面的Server端分析中会讲到。IMediaPlayerService指针指向的就是MediaPlayer的Service端:MediaPlayerService。又在注释2处通过IMediaPlayerService的create方法得到IMediaPlayer指针。通过IMediaPlayer指针就可以调用MediaPlayerService所提供的各种功能。接下来看注释3处的attachNewPlayer方法:
frameworks/av/media/libmedia/mediaplayer.cpp
在这里插入图片描述

attachNewPlayer方法将player赋值给mPlayer。因此,前面遗留下的问题得到了解决:mPlayer指的就是IMediaPlayer指针,调用mPlayer的prepareAsync方法其实就是调用MediaPlayerService的prepareAsync方法。
2.Server端分析
在多媒体架构中除了C/S架构中的Client和Server,还有一个全局的ServiceManager,它用来管理系统中的各种Service。Client、Server和ServiceManager的关系如图11-6所示。
在这里插入图片描述

图11-6 Client、Server和ServiceManager的关系
从图11-6中可知,首先Server进程会注册一些Service到ServiceManager中,Client要使用某个Service,则需要先到ServiceManager查询Service的相关信息,然后根据Service的相关信息与Service所在的Server进程建立通信通路,这样Client就可以使用Service了。Android的多媒体服务是由一个叫作 MediaServer的服务进程提供的,它是一个可执行程序,在Android系统启动时,MediaServer也会被启动。它的入口函数如下所示:
frameworks/av/media/mediaserver/main_mediaserver.cpp
在这里插入图片描述

MediaServer需要向ServiceManager注册服务,在上面代码注释1处得到IServiceManager指针,这样我们就可以与另一个进程的ServiceManager进行通信。注释2处的代码用来初始化MediaPlayerService。我们来查看MediaPlayerService的instantiate方法,如下所示:
frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
在这里插入图片描述

上面的方法调用 IServiceManager 的 addService 方法,向 ServiceManager 添加一个名为media.player 的 MediaPlayerService 服务。这样 MediaPlayerService 就被添加到 ServiceManager中,MediaPlayer 就可以通过字符串"media.player"来查询 ServiceManager。那么,MediaPlayer是在哪里进行查询的呢?让我们再回到mediaplayer的setDataSource方法,如下所示:
frameworks/av/media/libmedia/mediaplayer.cpp

在这里插入图片描述
在这里插入图片描述

上面代码注释1处的getMediaPlayerService方法是在IMediaDeathNotifier中定义的,如下所示:
frameworks/av/media/libmedia/IMediaDeathNotifier.cpp
在这里插入图片描述

在上面代码注释 1 处得到 IServiceManager 指针,并在注释 2 处查询名称为"media.player"的服务。这样MediaPlayer就获取了MediaPlayerService的信息,通过信息就可以与在MediaServer进程中的MediaPlayerService进行通信了。当我们调用MediaPlayer的setDataSource方法时,会调用MediaPlayerService的setDataSource方法:
frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
在这里插入图片描述

在上面代码注释1处调用了MediaPlayerFactory的getPlayerType方法,以获取播放器的类型playerType,接着调用setDataSource_pre,如下所示:
frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
在这里插入图片描述

在setDataSource_pre方法中调用了createPlayer方法并传入playerType,createPlayer方法的代码如下所示:
frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
在这里插入图片描述

在上面代码注释1处调用了MediaPlayerFactory的createPlayer方法,代码如下所示:
frameworks/av/media/libmediaplayerservice/MediaPlayerFactory.cpp

在上面代码注释1处会调用factory的createPlayer方法来创建播放器。在Android 6.0中,MediaPlayerFactory 中有 3 个播放器 Factory,它们分别是 StagefrightPlayerFactory、NuPlayerFactory和TestPlayerFactory。其中,StagefrightPlayerFactory用于创建Stagefright框架。Stagefright 框架在 Android 2.0 中被引入系统以替代庞大复杂的 OpenCore 框架。但是由于Stagefright 存在严重漏洞,并被黑客所利用,因此在 Android 7.0 中,谷歌去掉了StagefrightPlayerFactory,也就是Stagefright框架。默认的播放器使用谷歌自己研发的NuPlayer框架。NuPlayerFactory的createPlayer方法如下所示:
frameworks/av/media/libmediaplayerservice/MediaPlayerFactory.cpp
在这里插入图片描述
在这里插入图片描述

createPlayer方法中会创建一个NuPlayerDriver类,也就是NuPlayer框架。MediaPlayer框架就讲到这里。本节所讲的MediaPlayer框架知识如图11-7所示。
在这里插入图片描述

图11-7 MediaPlayer框架
从图11-7中可以得知,如果我们调用MediaPlayer的一个方法比如setDataSource方法,这个方法调用就会经过JNI层和Native层,一直到调用NuPlayer的setDataSource方法。当然,这只是MediaPlayer框架的调用过程,再往下调用还会涉及NuPlayer框架、Audio系统、Surface系统和HAL层等。如果想要深入了解这些框架和系统,或者是其他系统源码,那就从现在开始学习系统源码吧。
11.5 本章小结
本章介绍了Android系统架构、系统源码目录,并以MediaPlayer框架为例讲解了如何阅读系统底层源码。其主要用意就是拓展读者的思路,提醒读者 Android 并非只有 Android 应用层开发,一个优秀的 Android 开发者也不应仅仅满足做一个“API 小王子”,系统底层源码还等着我们去探索呢。鉴于篇幅有限,本书并没有过多介绍系统底层源码,对此感兴趣的读者可以阅读一些有关Android系统底层源码的专业书籍

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值