• 前言:最近在研究MediaCodec,u盘坏了,之前下载的源码也就自然没有了,真是个难过的事情,不能因为没有源码了,就阻挡我的计划了呀,于是就是今天一文,在线看Android源码,加之此前很多人也有向我留言,如何看a

    请尊重分享成果,转载请注明出处,本文来自逆流的鱼yuiop,原文链接:http://blog.csdn.net/hejjunlin/article/details/53454514

    前言:最近在研究MediaCodec,u盘坏了,之前下载的源码也就自然没有了,真是个难过的事情,不能因为没有源码了,就阻挡我的计划了呀,于是就是今天一文,在线看Android源码,加之此前很多人也有向我留言,如何看android系统的源码,今天来敲黑板,划重点了。

    不管是做Android底层开发,还是做应用层开发,在Android这条路上走下去,必须对于Android源码够了解,如某一个功能,某一个机制,或者某一个API,它到底是如何实现的,需要 Read the fucking source code,才能知其然,知其所以然。下面几种在线看源码的方式:

    1,http://androidxref.com

    2,http://www.grepcode.com/

    3,Android SDK Search插件

    来说说这三种方式的体验,http://androidxref.com,这个同步官方源码,最新的7.0,上面也已经有了,我也比较喜欢这个,该有的功能应用尽有,第2个的话,如图,还停留在5.1阶段,也找不到我想要的,于是果断放弃了。Android SDK Search也还行。下面重点介绍Android SDK Search 插件和androidxref。

    http://www.grepcode.com/

    这里写图片描述

    Android SDK Search

    在 Android 开发或者源码分析时,一般会去查看 Google 官方提供的 Android API,如果希望深入了解 API 实现的细节,当然最好的方法就是去看源代码了。对于在线 API 文档,如果在查看时,还能够查看到源代码那当然是最好不过的。在 Chrome 浏览器下,可以安装 Android SDK Search 扩展程序来达到这个目的,该扩展程序在 Chrome网上应用店截图如下所示。安装完该扩展程序后,再次搜索 Android API 后,会在相应的搜索结果后增加一个 view source链接。例如搜索 Activity 类,结果如图 4 所示。点击 view source链接,就可以打开 Activity类的源代码,如图所示,从网页中可以看出其实是链接到了 Google 的 Android 源码的 Git

    这里写图片描述

    这里写图片描述

    这里写图片描述

    AndroidXRef

    AndroidXRef(http://androidxref.com/)项目提供 Android 源码的交叉索引,可以快速的搜索符合特定条件的 Android 源代码,后台是基于 OpenGrok 引擎,OpenGrok是一个快速,便于使用的源码搜索引擎与对照引擎,它能够帮助我们快速的搜索、定位、对照代码树。AndroidXRef 提供了完整的 Android kernel源码的索引。在 AndroidXRef 主页的右侧显示了可用的源码版本,如图所示,
    点击相应的源码版本链接

    这里写图片描述

    就可以进入对应的源码分支,如图所示,我进入6.0的最新分支:

    这里写图片描述

    本文来自逆流的鱼yuiop,原文链接:http://blog.csdn.net/hejjunlin/article/details/53454514

    上图右侧提供在特定目录下搜索,例如在分析 Framework源码时,可以选择 frameworks 目录,这样可以减少搜索范围,缩短搜索时间,能够更加精确地定位到需要的源代码。左侧是提供了一些搜索的条件,其意义如下(在指定的 Project(s)下):

    • Full Search: 进行全文搜索,会匹配所有的单词、字符串、标识符以及数字等,例如在frameworks 下通过 Full Search 搜索”mediacodec“

    这里写图片描述

    点击”search“,会显示所有包含mediacodec字符(忽略大小写)的结果,即使是注释也会显示出来,如下图,点击对应的链接会打开包含mediacodec所在的文件夹

    这里写图片描述

    点击左侧文件名如 TrackDecoder.java链接会打开包含mediacodc字符串文件;

    这里写图片描述

    其他依次类推,点击右侧每一行的搜索结果链接同样会打开相相应的文件内容;

    • Definition:搜索符号定义相关的代码,例如搜索 ondraw 函数的定义,显示结果如图

    这里写图片描述

    提示你是否要找onDraw,onChildDraw等方法,我们点击onDraw,立刻跳转到如下页面:

    这里写图片描述

    从搜索结果可以看到,所有onDraw方法的类都会列出来,到第6页,找到我们熟悉的TextureView,点击进去

    这里写图片描述

    • Symbol:搜索符号,例如可以搜索类中的成员变量等,下图显示了通过 Symbol 搜索FEATURE_NO_TITLE的结果

    这里写图片描述
    平时我们在this.requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏,这个FEATURE_NO_TITLE就是在Window.java中的,
    本文来自逆流的鱼yuiop,原文链接:http://blog.csdn.net/hejjunlin/article/details/53454514

    • File Path:搜索源码文件名中包含给定字符串的文件,例如想要搜索文件名包含mediacodec的源码文件,则可以在 File Path 中填入 mediacodec 进行搜索,结果如下所示

    这里写图片描述

    这刚好就是我相要的,比起在IDE中一个一个看,这里这个功能不得不说6的飞起。

    • History:这个几乎没有用,用处肯定也不大,我尝试过各种搜索。

    上面只是单一的搜索,如果是多个条件呢?这里依然满足,
    如搜索mediacodec.cpp中的start函数,如图所示:

    这里写图片描述

    由于AndroidXRef 使用的是 OpenGrok 引擎,因此还支持一些其他的搜索方式:在【http://androidxref.com/6.0.0_r5/help.jsp】,写的相当详细,感兴趣的同学可以看看。

    本文来自逆流的鱼yuiop,原文链接:http://blog.csdn.net/hejjunlin/article/details/53454514

    第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。


    这里写图片描述

    如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易

    展开全文
  • 技术博客终于可以恢复正常的更新速度了,原因是我编写的进阶书籍的初稿已经完成,窃以为它将会是Android应用书籍中最有深度的一本,可以说是《Android开发艺术探索》的姊妹篇。在这本书的最后一章我会分析Android...

    前言

    技术博客终于可以恢复正常的更新速度了,原因是我编写的进阶书籍的初稿已经完成,窃以为它将会是Android应用书籍中最有深度的一本,可以说是《Android开发艺术探索》的姊妹篇。在这本书的最后一章我会分析Android底层源码,但是毕竟是一本Android应用开发的书,对于底层源码阅读也只能带大家入个门。因此,在博客中我会新开一个“系统源码解析”的系列,该系列的开篇就是本篇文章。

    1.Android系统架构

    Android系统架构分为五层,从上到下依次是应用层、应用框架层、系统运行库层、硬件抽象层和Linux内核层。
    这里写图片描述

    应用层

    系统内置的应用程序以及非系统级的应用程序都是属于应用层。负责与用户进行直接交互,通常都是用Java进行开发的。

    应用框架层(Java Framework)

    应用框架层为开发人员提供了可以开发应用程序所需要的API,我们平常开发应用程序都是调用的这一层所提供的API,当然也包括系统的应用。这一层的是由Java代码编写的,可以称为Java Framework。下面来看这一层所提供的主要的组件。

    名称 功能描述
    Activity Manager(活动管理器) 管理各个应用程序生命周期以及通常的导航回退功能
    Location Manager(位置管理器) 提供地理位置以及定位功能服务
    Package Manager(包管理器) 管理所有安装在Android系统中的应用程序
    Notification Manager(通知管理器) 使得应用程序可以在状态栏中显示自定义的提示信息
    Resource Manager(资源管理器) 提供应用程序使用的各种非代码资源,如本地化字符串、图片、布局文件、颜色文件等
    Telephony Manager(电话管理器) 管理所有的移动设备功能
    Window Manager(窗口管理器) 管理所有开启的窗口程序
    Content Providers(内容提供器) 使得不同应用程序之间可以共享数据
    View System(视图系统) 构建应用程序的基本组件

    表1

    系统运行库层(Native)

    系统运行库层分为两部分,分别是C/C++程序库和Android运行时库。下面分别来介绍它们。

    1.C/C++程序库

    C/C++程序库能被Android系统中的不同组件所使用,并通过应用程序框架为开发者提供服务,主要的C/C++程序库如下表2所示。

    名称 功能描述
    OpenGL ES 3D绘图函数库
    Libc 从BSD继承来的标准C系统函数库,专门为基于嵌入式Linux的设备定制
    Media Framework 多媒体库,支持多种常用的音频、视频格式录制和回放。
    SQLite 轻型的关系型数据库引擎
    SGL 底层的2D图形渲染引擎
    SSL 安全套接层,是为网络通信提供安全及数据完整性的一种安全协议
    FreeType 可移植的字体引擎,它提供统一的接口来访问多种字体格式文件

    表2

    2.Android运行时库

    运行时库又分为核心库和ART(5.0系统之后,Dalvik虚拟机被ART取代)。核心库提供了Java语言核心库的大多数功能,这样开发者可以使用Java语言来编写Android应用。相较于JVM,Dalvik虚拟机是专门为移动设备定制的,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。而替代Dalvik虚拟机的ART 的机制与Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。

    硬件抽象层(HAL)

    硬件抽象层是位于操作系统内核与硬件电路之间的接口层,其目的在于将硬件抽象化,为了保护硬件厂商的知识产权,它隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,可在多种平台上进行移植。 从软硬件测试的角度来看,软硬件的测试工作都可分别基于硬件抽象层来完成,使得软硬件测试工作的并行进行成为可能。通俗来讲,就是将控制硬件的动作放在硬件抽象层中。

    Linux内核层

    Android 的核心系统服务基于Linux 内核,在此基础上添加了部分Android专用的驱动。系统的安全性、内存管理、进程管理、网络协议栈和驱动模型等都依赖于该内核。
    Android系统的五层架构就讲到这,了解以上的知识对以后分析系统源码有很大的帮助。

    2.Android系统源码目录

    我们要先了解Android系统源码目录,为后期源码学习打下基础。关于源码的阅读,你可以访问http://androidxref.com/来阅读系统源码。当然,最好是将源码下载下来。下载源码可以使用清华大学开源软件镜像站提供的Android 镜像:https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/ 。如果觉得麻烦也可以查找国内的网盘进行下载,推荐使用该百度网盘地址下载:http://pan.baidu.com/s/1ngsZs,它提供了多个Android版本的的源码下载。

    整体结构

    各个版本的源码目录基本是类似,如果是编译后的源码目录会多增加一个out文件夹,用来存储编译产生的文件。Android7.0的根目录结构说明如下表所示。

    Android源码根目录 描述
    abi 应用程序二进制接口
    art 全新的ART运行环境
    bionic 系统C库
    bootable 启动引导相关代码
    build 存放系统编译规则及generic等基础开发包配置
    cts Android兼容性测试套件标准
    dalvik dalvik虚拟机
    developers 开发者目录
    development 应用程序开发相关
    device 设备相关配置
    docs 参考文档目录
    external 开源模组相关文件
    frameworks 应用程序框架,Android系统核心部分,由Java和C++编写
    hardware 主要是硬件抽象层的代码
    libcore 核心库相关文件
    libnativehelper 动态库,实现JNI库的基础
    ndk NDK相关代码,帮助开发人员在应用程序中嵌入C/C++代码
    out 编译完成后代码输出在此目录
    packages 应用程序包
    pdk Plug Development Kit 的缩写,本地开发套件
    platform_testing 平台测试
    prebuilts x86和arm架构下预编译的一些资源
    sdk sdk和模拟器
    system 底层文件系统库、应用和组件
    toolchain 工具链文件
    tools 工具文件
    Makefile 全局Makefile文件,用来定义编译规则

    表3

    从表3可以看出,系统源码分类清晰,并且内容庞大且复杂。接下来分析packages中的内容,也就是应用层部分。

    应用层部分

    应用层位于整个Android系统的最上层,开发者开发的应用程序以及系统内置的应用程序都是在应用层。源码根目录中的packages目录对应着系统应用层。它的目录结构如表4所示。

    packages目录 描述
    apps 核心应用程序
    experimental 第三方应用程序
    inputmethods 输入法目录
    providers 内容提供者目录
    screensavers 屏幕保护
    services 通信服务
    wallpapers 墙纸

    表4

    从目录结构可以发现,packages目录存放着系统核心应用程序、第三方的应用程序和输入法等等,这些应用都是运行在系统应用层的,因此packages目录对应着系统的应用层。

    应用框架层部分

    应用框架层是系统的核心部分,一方面向上提供接口给应用层调用,另一方面向下与C/C++程序库以及硬件抽象层等进行衔接。 应用框架层的主要实现代码在/frameworks/base和/frameworks/av目录下,其中/frameworks/base目录结构如表5所示。

    /frameworks/base目录 描述 /frameworks/base目录 描述
    api 定义API cmds 重要命令:am、app_proce等
    core 核心库 data 字体和声音等数据文件
    docs 文档 graphics 图形图像相关
    include 头文件 keystore 和数据签名证书相关
    libs location 地理位置相关库
    media 多媒体相关库 native 本地库
    nfc-extras NFC相关 obex 蓝牙传输
    opengl 2D/3D 图形API packages 设置、TTS、VPN程序
    sax XML解析器 services 系统服务
    telephony 电话通讯管理 test-runner 测试工具相关
    tests 测试相关 tools 工具
    wifi wifi无线网络

    表5

    C/C++程序库部分

    系统运行库层(Native)中的 C/C++程序库的类型繁多,功能强大,C/C++程序库并不完全在一个目录中,这里给出几个常用且比较重要的C/C++程序库所在的目录位置。

    目录位置 描述
    bionic/ Google开发的系统C库,以BSD许可形式开源。
    /frameworks/av/media 系统媒体库
    /frameworks/native/opengl 第三方图形渲染库
    /frameworks/native/services/surfaceflinger 图形显示库,主要负责图形的渲染、叠加和绘制等功能
    external/sqlite 轻量型关系数据库SQLite的C++实现

    表6

    讲完 C/C++程序库部分,剩下的部分我们在表3已经给出:Android运行时库的代码放在art/目录中。硬件抽象层的代码在hardware/目录中,这一部分是手机厂商改动最大的一部分,根据手机终端所采用的硬件平台会有不同的实现。


    公众号末尾1.1.jpg

    展开全文
  • 经过两年的时间,终于完成对Android系统的研究了。Android是一个博大精深的系统,老罗不敢说自己精通了(事实上最讨厌的就是说自己精通神马神马的了,或者说企业说要招聘精通神马神马的人才),但是至少可以说打通了...

            经过两年的时间,终于完成对Android系统的研究了。Android是一个博大精深的系统,老罗不敢说自己精通了(事实上最讨厌的就是说自己精通神马神马的了,或者说企业说要招聘精通神马神马的人才),但是至少可以说打通了整个Android系统,从最上面的应用层,一直到最下面的Linux内核,炼就的是一种内功修养。这篇文章和大家一起分享这两年研究Android系统的历程,以此感谢大家一直以来的支持和鼓励。

    《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

            以下是本文的提纲:

            1. 理念

            2. 里程碑

            3. 看过的书

            4. 研究过的内容

            5. 将来要做的事情

            它们涵盖了老罗这两年一直想要和大家分享的内容。好了,不说废话了,直入主题。

            一. 理念

            这里说的理念是说应该带什么样的心态去研究一个系统。古人说书中自的颜如玉,书中自有黄金屋,我想说代码里也有颜如玉和黄金屋,所以老罗希望大家都能“Read The Fucking Source Code”。再者,对于优秀的开源项目来说,不去读一下它的源代码,简直就是暴殄天物啊。那么,读代码有什么好处呢?太多了,除了可以学到别人的优秀代码、架构之外,最重要的是,我们能从中找到答案,从而可以解决自己项目上的燃眉之急。

            我们在项目中碰到问题的时候,通常第一反应都是到网上去搜索答案。但是有时候有些问题,网络并不能给出满意的答案。这时候就千万不要忘了你所拥有的一个大招——从代码中找答案!当然,从代码中找答案说起来是轻松,但是等到直正去找时,可能就会发现云里雾里,根本不知道那些代码在说什么东东,甚至连自己想要看的源代码文件都不知道在哪里。这就要求平时就要养成读代码的习惯,不要临时抱佛脚。有时候临时抱佛脚是能解决问题,但是千万不能抱着这种侥幸心里,掌握一门技术还是需要踏踏实实地一步一步走。

            胡克其实在牛顿之前,就发现了万有引力定律,并且推导出了正确的公式。但由于数学不好,他只能勉强解释行星绕日的圆周运动,而没有认识到支配天体运行的力量是“万有”的。后来数学狂人牛顿用微积分圆满地解决了胡克的问题,并且把他提出的力学三条基本定律推广到了星系空间,改变了自从亚里士多德以来公认的天地不一的旧观点,被科学界奉为伟大的发现。胡克大怒,指责牛顿剽窃了他的成果。牛顿尖酸刻薄的回敬:是啊,我他妈还真是站在巨人的肩膀上呢!

            我们有理由相信像牛顿、乔布斯之类的狂人,不用站在巨人的肩膀上也能取得瞩目的成就。但是,我们不是牛顿,也不是乔布斯,所以在看代码之前,还是找一些前人总结的资料来看看吧。拿Android系统来说,你在至少得懂点Linux内核基础吧!所以在看Android源代码之前,先找些Linux内核的经典书籍来看看吧,骚年!后面老罗会推荐一些书籍给大家。

            另外,我们知道,现在的互联网产品,讲究的是快速迭代。Android系统自第一个版本发布以来,到现在已经经历了很多版本呢?那么我们应该如何去选择版本来阅读呢?一般来说,就是选择最新的版本来阅读了。不过随着又有新版本的源代码的发布,我们所看的源代码就会变成旧版本。这时候心里就会比较纠结:是应该继续看旧的代码,还是去追新版本的代码呢?就当是看连续剧,一下子跳到前面去,可能就不知道讲什么了。其实版本就算更新得再快,基础的东西也是不会轻易变化的。我们看代码时,要抱着的一个目的就是弄懂它的骨架和脉络。毕竟对于一个系统来说,它是有很多细节的,我们无法在短时间把它们都完全吃透。但是主要我们掌握了它的骨架和脉络,以后无论是要了解它的什么细节,都可以很轻轻地找到相关的源文件,并且可以很容易进入主题。

            坦白说,对于Android系统,很多细节我也不了解。所以有时候你们可以看到,在博客文章后面的评论上,有些同学问的一些比较具体的问题,我是没有回复的。一来是我不懂,二来是我也没有时间去帮这些同学去扒代码来看。这也是在文章一开头,我就说自己没有精通Android系统的原因。但是请相信,主要你熟悉Android系统的代码,并且有出现问题的现场,顺藤摸瓜跟着代码走下去,并且多一点耐心和细心,是可以解决问题的!

            关于Android版本的问题,相信大家都知道我现在的文章都是基于2.3来写的。很多同学都说我out了,现在都4.2了,甚至4.3或者5.0都要出来了,还在看2.3。我想说的是,主要你掌握了它的骨架和脉络,无论版本上怎么变化,原理都是一样的,这就是以不变应万变之道。因此,我就一直坚持研究2.3,这样可以使得前前后后研究的东西更连贯一致,避免分散了自己的精力。如果还有疑问的话,后面我讲到Android的UI架构时,就会简单对比一下4.2和2.3的不同,其实就会发现,基本原理还是一样的!

            说到Android系统的骨架和脉络,也有同学抱怨我的文章里面太多代码细节了,他们希望我可以抽象一下,用高度概括的语言或者图像来勾勒出每一个模块的轮廓。我想说的是,如果你不看代码,不了解细节,即使我能够用概括的语言或者图像来勾勒出这样的轮廓出来,也许这个轮廓只有我才能看得懂。

            我在真正开始看Android系统的源代码之前,也是有这样的想法,希望能有一张图来清楚地告诉我Android系统的轮廓,例如,HAL为什么要将驱动划分成用户空间和内核空间两部分,为什么说Binder是所有IPC机制效率最高的。我确实是从网上得到抽象的资料来解释这两个问题,但是这些资料对我来说,还是太抽象了,以至于我有似懂非懂的感觉,实际上就是不懂!就是因为这样,激发了我要从代码中找答案的念头!现在当我回过头来这些所谓抽象的轮廓时,我就清楚地知道它讲的是什么了。

            所以古人云“天将降大任于斯人也,必先苦其心志,劳其筋骨,饿其体肤”是有道理的,因为只有亲身经历过一些磨难后得到的东西才是真实的!

            好了,关于理念的问题,就完了,这里再做一下总结:

            1. 从代码中找答案——Read The Fucking Source Code。

            2. 以不变应万变——坚持看一个版本的代码直至理清它的骨架和脉络。

            二. 里程碑

            研究Android 2.3期间,主要是经历了以下五个时间点,如图1所示:


    图1 研究Android 2.3的里程碑

             从2011年06月21日第一篇博客文章开始,到2013年06月03日结束对Android 2.3的研究,一共是差不多两年的时间,一个从无到有的过程。其中,最痛苦的莫过于是2011年12月下旬到2012年06月12日这6个多月的时间里面,整理了2011年12月下旬前的所有博客文章,形成了《Android系统源代码情景分析》一书,并且最终在2012年10月下旬正式上市。

            总的来说,就是在两年的时间里面,获得了以下的两个产出: 

            1. 《老罗的Android之旅》博客专栏93篇文章,1857224次访问,4156条评论,13440积分,排名154。

            2. 《Android系统源代码情景分析》一书3大篇16章,830页,1570000字。

            以上产出除了能帮助到广大的网友之外,也让自己理清了Android系统的骨架和脉络。这些骨架和脉络接下来再总结。2013年06月03日之后,将何去何从?接下来老罗也会简单说明。

            三. 看过的书 

            在2011年06月21日开始写博客之前,其实已经看过不少的书。在2011年06月21日之后,也一边写博客一边看过不少的书。这个书单很长,下面我主要分类列出一些主要的和经典的。

            语言:

            《深度探索C++对象模型》,对应的英文版是《Inside C+++ Object Model》

            程序编译、链接、加载:

            《链接器和加载器》,对应的英文版是《Linker and Loader》

            《程序员的自我修养:链接、装载和库》

            操作系统:

            《Linux内核设计与实现》,对应的英文版是《Linux Kernel Development》

            《深入理解Linux内核》,对应的英文版是《Understanding the Linux Kernel》

            《深入Linux内核架构》,对应的英文版是《Professional Linux Kernel Architecture》

            《Linux内核源代码情景分析》

             网络:

            《Linux网络体系结构:Linux内核中网络协议的设计与实现》,对应的英文版是《The Linux Networking Architecture: Design and Implementation of Network Protocols in the Linux Kernel》

            《深入理解LINUX网络技术内幕》,对应的英文版是《 Understanding Linux Network Internals》

            设备驱动:

            《Linux设备驱动程序》,对应的英文版是《Linux Device Drivers》

            《精通Linux设备驱动程序开发》,对应的英文版是《Essential Linux Device Drivers》

            虚拟机:

            《Java SE 7虚拟机规范》

            《深入Java虚拟机》,对应的英文版是《Inside the Java Virtual Machine》

            《Oracle JRockit: The Definitive Guide》

            嵌入式:

            《嵌入式Linux开发》,对应的英文版是《Embedded Linux Primer》

            《构建嵌入式Linux系统》,对应的英文版是《Building Embedded Linux Systems》

            ARM体系架构:

            《ARM嵌入式系统开发:软件设计与优化》,对应的英文版是《ARM System Developer's Guide: Designing and Optimizing System Software》

            综合:

           《深入理解计算机系统》,对应的英文版是《Computer Systems: A Programmer's Perspective》

            上面介绍的这些书,都是属于进阶级别的,所以要求要有一定的语言基础以及操作系统基础。此外,对于看书,老罗有一些观点,供大家参考:

            1. 书不是要用的时候才去看的,要养成经常看书、终身学习的习惯。

            2. 不要只看与目前自己工作相关的书,IT技术日新月异,三五年河东,三五年河西。

            3. 书看得多了,就会越看越快,学习新的东西时也越容易进入状态。

            对于Android应用开发,力推官方文档:

            http://developer.android.com/training/index.html

            http://developer.android.com/guide/components/index.html

            http://developer.android.com/tools/index.html

            四. 研究过的内容

            整个博客的内容看似松散,实际上都是有组织有计划的,目标是打通整个Android系统,从最上面的应用层,到最下面的Linux内核层。简单来说,博客的所有文章可以划分为“三横三纵”,如图2所示:


    图2 Android系统研究之“三横三纵”

            接下来,老罗就分别描述这三条横线和纵线,并且给出对应的博客文章链接。

            1. 准备 -- Preparation -- 横线

            主要就是:

           (1)通过阅读相关的书籍来了解Linux内核和Android应用基础知识

             Android学习启动篇

           (2)搭建好Android源代码环境

             在Ubuntu上下载、编译和安装Android最新源代码

             在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)

             如何单独编译Android源代码中的模块

             制作可独立分发的Android模拟器

           (3)Android系统有很多C++代码,这些C++代码用到了很多智能指针,因此有必要了解一下Android系统在C/C++ Runtime Framework中提供的智能指针

             Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析

             2. 专用驱动 -- Proprietary Drivers -- 横线

             这些专用驱动就是指Logger、Binder和Ashmem,它们整个Android系统的基石:

            (1)Logger

              浅谈Android系统开发中LOG的使用

             Android日志系统驱动程序Logger源代码分析

             Android应用程序框架层和系统运行库层日志系统源代码分析

             Android日志系统Logcat源代码简要分析

            (2)Binder

              Android进程间通信(IPC)机制Binder简要介绍和学习计划

             浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路

             浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路

             Android系统进程间通信(IPC)机制Binder中的Server启动过程源代码分析

             Android系统进程间通信(IPC)机制Binder中的Client获得Server远程接口过程源代码分析

             Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析

            (3)Ashmem

              Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划

              Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析

              Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析

              Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析

            3. 硬件抽象层 -- HAL -- 纵线

            硬件抽层象最适合用作Android系统的学习入口,它从下到上涉及到了Android系统的各个层次:

             Android硬件抽象层(HAL)概要介绍和学习计划

             在Ubuntu上为Android系统编写Linux内核驱动程序

             在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序

             在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序

             在Ubuntu为Android硬件抽象层(HAL)模块编写JNI方法提供Java访问硬件服务接口

             在Ubuntu上为Android系统的Application Frameworks层增加硬件访问服务

             在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务

            4. 应用程序组件 -- Application Component -- 纵线

            应用程序组件是Android系统的核心,为开发者提供了贴心的服务。应用程序组件有四种,分别是Activity、Service、Broadcast Receiver和Content Provider。围绕应用程序组件,又有应用程序进程、消息循环和安装三个相关模块。

           (1)Activity

             Android应用程序的Activity启动过程简要介绍和学习计划

             Android应用程序启动过程源代码分析

             Android应用程序内部启动Activity过程(startActivity)的源代码分析

             Android应用程序在新的进程中启动新的Activity的方法和过程分析

             解开Android应用程序组件Activity的"singleTask"之谜

           (2)Service

             Android系统在新进程中启动自定义服务过程(startService)的原理分析

             Android应用程序绑定服务(bindService)的过程源代码分析

           (3)Broadcast Receiver

             Android系统中的广播(Broadcast)机制简要介绍和学习计划

             Android应用程序注册广播接收器(registerReceiver)的过程分析

             Android应用程序发送广播(sendBroadcast)的过程分析

           (4)Content Provider

             Android应用程序组件Content Provider简要介绍和学习计划

             Android应用程序组件Content Provider应用实例

             Android应用程序组件Content Provider的启动过程源代码分析

             Android应用程序组件Content Provider在应用程序之间共享数据的原理分析

             Android应用程序组件Content Provider的共享数据更新通知机制分析

           (5)进程

             Android系统进程Zygote启动过程的源代码分析

             Android应用程序进程启动过程的源代码分析

           (6)消息循环

             Android应用程序消息处理机制(Looper、Handler)分析

             Android应用程序键盘(Keyboard)消息处理机制分析

             Android应用程序线程消息循环模型分析

           (7)安装

             Android应用程序安装过程源代码分析

             Android系统默认Home应用程序(Launcher)的启动过程源代码分析

            5. 用户界面架构 -- UI -- 纵线

            大家对老罗现在还在写Android 2.3的UI架构意见最大,认为已经过时了。老罗认为持有这种观点的人,都是没有经过认真思考的。老罗承认,从Android 4.0开始,UI部分发生了比较大的变化。但是请注意,这些变化都是在Android  2.3的UI架构基础之上进行的,也就是说,Android  2.3的UI架构并没有过时。你不能说Android 4.0在Android  2.3之上增加了一些feature,就说Android  2.3过时了。

            下面这张是从Android官网拿过来的最新UI渲染流程图,也就是4.2的UI渲染流程图


    图2 Android 4.2的UI渲染流程

            从这张图可以看出关于Android的UI架构的三条主线:

          (1)每一个Window的Surface都怎样渲染的?不管怎么样,最后渲染出来的都是一个Buffer,交给SurfaceFlinger合成到Display上。

          (2)SurfaceFlinger是怎样合成每一个Window的Surface的?

          (3)WindowManamgerService是怎么样管理Window的? 

            第(1)和第(2)两个点在2.3和4.2之间有变化,主要是因为增加了GPU的支持,具体就表现为Window的Surface在渲染的时候使用了GPU,而SurfaceFlinger在合成每一个Window的Surface的时候,也使用了GPU或者Overlay和Blitter这些硬件加速,但是主体流程都没有变,也就是说,Window的Surface渲染好之后,最终依然是交给SurfaceFlinger来合成。此外,虽然我还没有开始看4.2的代码,但是可以看得出,4.2里面的HWComposer,只不过是封装和抽象了2.3就有的Overlay和Blitter,而SurfaceTexture的作用与2.3的SurfaceComposerClient、SurfaceControl也是类似的。第(3)点基本上就没有什么变化,除非以后要支持多窗口。

            通过上述对比,只想强调一点:Android 2.3的UI架构并没有过时,是值得去研究的,并且在2.3的基础上去研究4.2的UI架构,会更有帮助。

            仁者见仁,智者见智,Android 2.3的UI架构的说明就到此为止,接下来它的分析路线,都是围绕上述三个点来进行的。

            首先是以开机动画为切入点,了解Linux内核里面的驱动:

            Android系统的开机画面显示过程分析

            FB驱动抽象了显卡,上面的用户空间程序就是通过它来显示UI的。

            HAL层的Gralloc模块对FB驱动进行了封装,以方便SurfaceFlinger对它进行访问:

            Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析

            SurfaceFlinger负责合成各个应用程序窗口的UI,也就是将各个窗口的UI合成,并且通过FB显示在屏幕上。在对SurfaceFlinger进行分析之前,我们首先了解应用程序是如何使用的它的:

            Android应用程序与SurfaceFlinger服务的关系概述和学习计划

            Android应用程序与SurfaceFlinger服务的连接过程分析

            Android应用程序与SurfaceFlinger服务之间的共享UI元数据(SharedClient)的创建过程分析

            Android应用程序请求SurfaceFlinger服务创建Surface的过程分析

            Android应用程序请求SurfaceFlinger服务渲染Surface的过程分析

            万事俱备,可以开始分析SurfaceFlinger了:

            Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划

            Android系统Surface机制的SurfaceFlinger服务的启动过程分析

            Android系统Surface机制的SurfaceFlinger服务对帧缓冲区(Frame Buffer)的管理分析

            Android系统Surface机制的SurfaceFlinger服务的线程模型分析

            Android系统Surface机制的SurfaceFlinger服务渲染应用程序UI的过程分析

            SurfaceFlinger操作的对象是应用程序窗口,因此,我们要掌握应用程序窗口的组成:

            Android应用程序窗口(Activity)实现框架简要介绍和学习计划

            Android应用程序窗口(Activity)的运行上下文环境(Context)的创建过程分析

            Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析

            Android应用程序窗口(Activity)的视图对象(View)的创建过程分析

            Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析

            Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析

            Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析

            应用程序窗口是由WindowManagerService进行管理的,并且也是WindowManagerService负责提供窗口信息给SurfaceFlinger的,因此,我们最后分析WindowManagerService:

            Android窗口管理服务WindowManagerService的简要介绍和学习计划

            Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析

            Android窗口管理服务WindowManagerService对窗口的组织方式分析

            Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析

            Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析

            Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析

            Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析

            Android窗口管理服务WindowManagerService切换Activity窗口(App Transition)的过程分析

            Android窗口管理服务WindowManagerService显示窗口动画的原理分析

            上述内容都研究清楚之后,Android系统的UI架构的骨架就清晰了。但是前面所研究的应用程序窗口还是太抽象了,我们有必要研究一下那些组成应用程序窗口内容的UI控件是怎么实现的,以TextView和SurfaceView为代表:

            Android控件TextView的实现原理分析

            Android视图SurfaceView的实现原理分析

            最后,分析Android系统的UI架构,怎能不提它的资源管理框架?它有效地分离了代码和UI:

            Android资源管理框架(Asset Manager)简要介绍和学习计划

            Android应用程序资源的编译和打包过程分析

            Android应用程序资源管理器(Asset Manager)的创建过程分析

            Android应用程序资源的查找过程分析

            分析这里,Android系统的UI架构就分析完成了,看出什么门道来没有?是的,我们以开机动画为切入点,从Linux内核空间的FB驱动,一直分析到用户空间中HAL层模块Gralloc、C/C++ Runtime Framework层的SurfaceFlinger、Java Runtime Framework层的WindowMangerService、Window、Widget,以及资源管理框架,从下到上,披荆斩棘。

            6. Dalvik虚拟机 -- 横线

            Android系统的应用程序及部分应用程序框架是使用Java语言开发的,它们运行在Dalvik虚拟机之上,还有另外一部分应用唾弃框架在使用C/C++语言开发的。使用Java语言开发的应用程序框架老罗称之为Java Runtime Framework,而使用C/C++语言开发的应用程序框架老罗称之为C/C++ Runtime Framework,它们被Dalvik虚拟机一分为二。通过前面的学习,其实我们都已经了解Android系统的Java Runtime Framework和C/C++ Runtime Framework,因此,我们最后将注意力集中在Dalvik虚拟机上:

            Dalvik虚拟机简要介绍和学习计划

            Dalvik虚拟机的启动过程分析

            Dalvik虚拟机的运行过程分析

            Dalvik虚拟机JNI方法的注册过程分析

            Dalvik虚拟机进程和线程的创建过程分析

            学习完成“三横三纵”这六条主线之后,我们就可以自豪地说,从上到下地把Android系统打通,并且将它的骨架和脉络也理清了!

            对于“准备”、“专用驱动”、“HAL”、“应用程序组件”这四条主线,老罗极力推荐大家看《Android系统源代码情况分析》一书,内容比博客文章要系统、详细很多,不说其它的,就单单是讲Binder进程间通信机制的那一章,就物超所值。在《Android系统源代码情景分析》一书中,老罗最引以为豪的就是讲Binder进程间通信机制的第5章,网上或者其它书上绝对是找不到这么详尽的分析资料。

            五. 将来要做的事情

            接下来要做的主要是三件事情:

            1. 继续研究Android系统

            本来以为前段时间的Google I/O会发布Android 4.3或者5.0,然后老罗就以最新发布的版本为蓝本来进行研究。既然没有发布新版本,那么就只有以现在的最新发布版本4.2为蓝本进行研究了。如前所述,4.2与2.3相比,最大的不同之处是默认增加了GPU支持,因此,老罗在接下来的一段时间里,将着重研究4.2的GPU支持。

            2. 停止博客更新

            这两年投入在博客上的精力太多了,博客上的文章基本上熬夜熬出来的。大多数时候,一个话题要花一个星期左右的时间来看代码,然后再花四个星期左右的时间将文章写出来。本来是这样计划的,依靠《Android系统源代码情景分析》一书的销量,可以在经济上得到一定的回报,然后可以继续在博客上投入,直至把4.x版本的GPU支持写完,最后再整理出一本关于Android系统UI架构的书。但是最近询问了一下书的销量,差强人意,达不到预期目标。由于没有形成良性循环,因此没有办法,只好停止博客更新。老罗需要把精力投入在其它事情上,希望大家谅解!

            3. 仍然会持续地进行一些小分享

            主要是一些随笔分享,这些分享主要会发布在微博或者QQ群上面,那里也方便一些和大家进行讨论。此外,老罗也乐意和大家进行一些线下分享,主要针对企业或者单位组织的沙龙、活动和会议等,同时也可以单独地针对企业内部进行分享。不过前提当然是举办方对《老罗的Android之旅》专栏或者《Android系统源代码情景分析》一书的内容感兴趣,并且邀请老罗去参加。

            如果需要邀请老罗去参加分享,可以通过微博或者邮箱和老罗联系,非常感谢大家两年以来的支持!

            邮箱:shyluo@gmail.com

            微博:http://weibo.com/shengyangluo

            

    展开全文
  • Android - 系统启动过程 计算机是如何启动的? 首先熟悉一些概念,计算机的硬件包括:CPU,内存,硬盘,显卡,显示器,键盘鼠标等其他输入输出设备。 所有的软件(比如:操作系统)都是存放在硬盘上,程序执行时...

    Android - 系统启动过程

    相关系列

    计算机是如何启动的?

    首先熟悉一些概念,计算机的硬件包括:CPU,内存,硬盘,显卡,显示器,键盘鼠标等其他输入输出设备。 所有的软件(比如:操作系统)都是存放在硬盘上,程序执行时需要将程序从硬盘上读取到内存中然后加载到 CPU 中来运行。 当我们按下开机键时,此时内存中什么都没有,,因此需要借助某种方式,将操作系统加载到内存中,而完成这项任务的就是 BIOS。

    • 引导阶段

    BIOS: Basic Input/Output System(基本输入输出系统),在 IBM PC 兼容系统上,是一种业界标准的固件接口(来自维基百科)。 BIOS 一般是主板芯片上的一个程序,计算机通电后,第一件事就是读取它。

    BIOS 程序首先检查计算机硬件能否满足运行的基本条件,这叫做"硬件自检"(Power-On Self-Test),缩写为 POST。 如果硬件出现问题,主板会发出不同含义的蜂鸣,启动中止。 如果没有问题,屏幕就会显示出 CPU,内存,硬盘等信息。

    硬件自检完成后,BIOS 把控制权转交给下一阶段的启动程序。 这时 BIOS 需要知道,下一阶段的启动程序到底存放在哪一个设备当中。 也就是说 BIOS 需要有一个外部存储设备的排序,排在前面的设备就是优先转交控制权的设备。 这种排序叫做启动排序,也就是我们平时进入 BIOS 界面时能看到的 Boot Sequence。

    如果我们没有进行特殊操作的话,那么 BIOS 就会按照这个启动顺序将控制权交给下一个存储设备。 我们在使用 U 盘光盘之类的装系统时就是在这里将启动顺序改变了,将本来要移交给硬盘的控制权交给了 U 盘或者光盘。

    第一存储设备被激活后,计算机读取该设备的第一个扇区,也就是读取最前面的 512 个字节。 如果这 512 个字节的最后两个字节是 0x55 和 0xAA ,表明这个设备可以用于启动;如果不是,表明设备不能用于启动,控制权于是被转交给“启动顺序”中的下一个设备。

    这最前面的 512 个字节,就叫做"主引导记录"(Master boot record,缩写为 MBR)。 主引导记录 MBR 是位于磁盘最前边的一段引导代码。它负责磁盘操作系统对磁盘进行读写时分区合法性的判别、分区引导信息的定位,它由磁盘操作系统在对硬盘进行初始化时产生的。 硬盘的主引导记录 MBR 是不属于任何一个操作系统的,它先于所有的操作系统而被调入内存,并发挥作用,然后才将控制权交给主分区内的操作系统,并用主分区信息表来管理硬盘。

    MBR 只有512个字节,放不了太多东西。 它的主要作用是,告诉计算机到硬盘的哪一个位置去找操作系统。 我们找到可用的 MBR 后,计算机从 MBR 中读取前面 446 字节的机器码之后,不再把控制权转交给某一个分区,而是运行事先安装的"启动管理器"(boot loader),由用户选择启动哪一个操作系统。

    • 加载内核阶段

    选择完操作系统后,控制权转交给操作系统,操作系统的内核首先被载入内存。

    以 Linux 系统为例,先载入 /boot 目录下面的 kernel。 内核加载成功后,第一个运行的程序是 /sbin/init。 它根据配置文件(Debian 系统是 /etc/initab )产生 init 进程。 这是 Linux 启动后的第一个进程,pid 进程编号为 1,其他进程都是它的后代。

    然后,init 线程加载系统的各个模块,比如:窗口程序和网络程序,直至执行 /bin/login 程序,跳出登录界面,等待用户输入用户名和密码。

    至此,全部启动过程完成。

    Android 手机的启动过程

    Android 系统虽然也是基于 Linux 系统的,但是由于 Android 属于嵌入式设备,并没有像 PC 那样的 BIOS 程序。 取而代之的是 Bootloader —— 系统启动加载器。 它类似于 BIOS,在系统加载前,用以初始化硬件设备,建立内存空间的映像图,为最终调用系统内核准备好环境。 在 Android 里没有硬盘,而是 ROM,它类似于硬盘存放操作系统,用户程序等。 ROM 跟硬盘一样也会划分为不同的区域,用于放置不同的程序,在 Android 中主要划分为一下几个分区:

    • /boot:存放引导程序,包括内核和内存操作程序
    • /system:相当于电脑c盘,存放Android系统及系统应用
    • /recovery:恢复分区,可以进入该分区进行系统恢复
    • /data:用户数据区,包含了用户的数据:联系人、短信、设置、用户安装的程序
    • /cache:安卓系统缓存区,保存系统最常访问的数据和应用程序
    • /misc:包含一些杂项内容,如系统设置和系统功能启用禁用设置
    • /sdcard:用户自己的存储区,可以存放照片,音乐,视频等文件

    这里写图片描述

    那么 Bootloader 是如何被加载的呢?跟 PC 启动过程类似,当开机通电时首先会加载 Bootloader,Bootloader 回读取 ROM 找到操作系统并将 Linux 内核加载到 RAM 中。

    当 Linux 内核启动后会初始化各种软硬件环境,加载驱动程序,挂载根文件系统,Linux 内核加载的最后阶段会启动执行第一个用户空间进程 init 进程。

    init 进程

    这里写图片描述

    init 是 Linux 系统中用户空间的第一个进程(pid=1),Kernel 启动后会调用 /system/core/init/Init.cpp 的 main() 方法。

    • Init.main()

    首先初始化 Kernel log,创建一块共享的内存空间,加载 /default.prop 文件,解析 init.rc 文件。

    init.rc 文件

    init.rc 文件是 Android 系统的重要配置文件,位于 /system/core/rootdir/ 目录中。 主要功能是定义了系统启动时需要执行的一系列 action 及执行特定动作、设置环境变量和属性和执行特定的 service。

    init.rc 脚本文件配置了一些重要的服务,init 进程通过创建子进程启动这些服务,这里创建的 service 都属于 native 服务,运行在 Linux 空间,通过 socket 向上层提供特定的服务,并以守护进程的方式运行在后台。

    通过 init.rc 脚本系统启动了以下几个重要的服务:

    • servic_emanager:启动 binder IPC,管理所有的 Android 系统服务
    • mountd:设备安装 Daemon,负责设备安装及状态通知
    • debuggerd:启动 debug system,处理调试进程的请求
    • rild:启动 radio interface layer daemon 服务,处理电话相关的事件和请求
    • media_server:启动 AudioFlinger,MediaPlayerService 和 CameraService,负责多媒体播放相关的功能,包括音视频解码
    • surface_flinger:启动 SurfaceFlinger 负责显示输出
    • zygote:进程孵化器,启动 Android Java VMRuntime 和启动 systemserver,负责 Android 应用进程的孵化工作

    在这个阶段你可以在设备的屏幕上看到 “Android” logo 了。

    以上工作执行完,init 进程就会进入 loop 状态。

    service_manager 进程

    ServiceManager 是 Binder IPC 通信过程中的守护进程,本身也是一个 Binder 服务。ServiceManager 进程主要是启动 Binder,提供服务的查询和注册。

    具体过程详见 Binder:Android Binder 进程间通讯

    surface_flinger 进程

    SurfaceFlinger 负责图像绘制,是应用 UI 的和兴,其功能是合成所有 Surface 并渲染到显示设备。SurfaceFlinger 进程主要是启动 FrameBuffer,初始化显示系统。

    media_server 进程

    MediaServer 进程主要是启动 AudioFlinger 音频服务,CameraService 相机服务。负责处理音频解析播放,相机相关的处理。

    Zygote 进程

    fork 创建进程过程:

    Zygote 进程孵化了所有的 Android 应用进程,是 Android Framework 的基础,该进程的启动也标志着 Framework 框架初始化启动的开始。

    Zygote 服务进程的主要功能:

    • 注册底层功能的 JNI 函数到虚拟机
    • 预加载 Java 类和资源
    • fork 并启动 system_server 核心进程
    • 作为守护进程监听处理“孵化新进程”的请求

    当 Zygote 进程启动后, 便会执行到 frameworks/base/cmds/app_process/App_main.cpp 文件的 main() 方法。

    App_main.main() //设置进程名,并启动 AppRuntime。
    AndroidRuntime::start() //创建 Java 虚拟机,注册 JNI 方法,调用 ZygoteInit.main() 方法。
    ZygoteInit.main()   //为 Zygote 注册 socket,预加载类和资源,启动 system_server 进程。
    

    然后 Zygote 进程会进入 loop 状态,等待下次 fork 进程。

    system_server 进程

    system_server 进程 由 Zygote 进程 fork 而来。接下来看下 system_server 启动过程。

    //首先会调用 ZygoteInit.startSystemServer() 方法
    ZygoteInit.startSystemServer()  
    //fork 子进程 system_server,进入 system_server 进程。
    
    ZygoteInit.handleSystemServerProcess()  
    //设置当前进程名为“system_server”,创建 PathClassLoader 类加载器。
    
    RuntimeInit.zygoteInit()    
    //重定向 log 输出,通用的初始化(设置默认异常捕捉方法,时区等),初始化 Zygote -> nativeZygoteInit()。
    
    nativeZygoteInit()  
    //方法经过层层调用,会进入 app_main.cpp 中的 onZygoteInit() 方法。
    
    app_main::onZygoteInit()// 启动新 Binder 线程。
    
    applicationInit()   
    //方法经过层层调用,会抛出异常 ZygoteInit.MethodAndArgsCaller(m, argv), ZygoteInit.main() 会捕捉该异常。
    
    ZygoteInit.main()   
    //开启 DDMS 功能,preload() 加载资源,预加载 OpenGL,调用 SystemServer.main() 方法。
    
    SystemServer.main() 
    //先初始化 SystemServer 对象,再调用对象的 run() 方法。
    
    SystemServer.run()  
    //准备主线程 looper,加载 android_servers.so 库,该库包含的源码在 frameworks/base/services/ 目录下。
    

    system_server 进程启动后将初始化系统上下文(设置主题),创建系统服务管理 SystemServiceManager,然后启动各种系统服务:

    startBootstrapServices(); // 启动引导服务
    //该方法主要启动服务 ActivityManagerService,PowerManagerService,LightsService,DisplayManagerService,PackageManagerService,UserManagerService。
    //设置 ActivityManagerService,启动传感器服务。
    
    startCoreServices();      // 启动核心服务
    //该方法主要
    //启动服务 BatteryService 用于统计电池电量,需要 LightService。
    //启动服务 UsageStatsService,用于统计应用使用情况。
    //启动服务 WebViewUpdateService。
    
    startOtherServices();     // 启动其他服务
    //该方法主要启动服务 InputManagerService,WindowManagerService。
    //等待 ServiceManager,SurfaceFlinger启动完成,然后显示启动界面。
    //启动服务 StatusBarManagerService,
    //准备好 window, power, package, display 服务:
    //	- WindowManagerService.systemReady()
    //	- PowerManagerService.systemReady()
    //	- PackageManagerService.systemReady()
    //	- DisplayManagerService.systemReady()
    

    所有的服务启动完成后会注册到 ServiceManager。
    ActivityManagerService 服务启动完成后,会进入 ActivityManagerService.systemReady(),然后启动 SystemUI,WebViewFactory,Watchdog,最后启动桌面 Launcher App。

    最后会进入循环 Looper.loop()。

    ActivityManagerService 启动

    启动桌面 Launcher App 需要等待 ActivityManagerService 启动完成。我们来看下 ActivityManagerService 启动过程。

    ActivityManagerService(Context) 
    //创建名为“ActivityManager”的前台线程,并获取mHandler。
    //通过 UiThread 类,创建名为“android.ui”的线程。
    //创建前台广播和后台广播接收器。
    //创建目录 /data/system。
    //创建服务 BatteryStatsService。
    
    ActivityManagerService.start()  //启动电池统计服务,创建 LocalService,并添加到 LocalServices。
    
    ActivityManagerService.startOtherServices() -> installSystemProviders()
    //安装所有的系统 Provider。
    
    ActivityManagerService.systemReady()
    //恢复最近任务栏的 task。
    //启动 WebView,SystemUI,开启 Watchdog,启动桌面 Launcher App。
    //发送系统广播。
    

    启动桌面 Launcher App,首先会通过 Zygote 进程 fork 一个新进程作为 App 进程,然后创建 Application,创建启动 Activity,最后用户才会看到桌面。

    完整启动过程

    这里写图片描述

    参考资料

    其他系列

    Gradle 系列

    更多文章:

    这是我博客长期更新的项目,欢迎大家 Star。
    https://github.com/jeanboydev/Android-ReadTheFuckingSourceCode

    我的公众号

    欢迎你「扫一扫」下面的二维码,关注我的公众号,可以接受最新的文章推送,有丰厚的抽奖活动和福利等着你哦!?

    qrcode_android_besos_black_512.png

    如果你有什么疑问或者问题,可以 点击这里 提交 issue,也可以发邮件给我 jeanboy@foxmail.com

    同时欢迎你 Android技术进阶:386463747 来一起交流学习,群里有很多大牛和学习资料,相信一定能帮助到你!

    展开全文
  • 引言:这篇文章以WiFi举例,介绍了Android系统网络架构。其内容包含:网络链路的连接和注册、网络有效性检测和网络优选、Android系统网络防火墙和几种场景下的网络策略等,文章的最后也列举了几种常见的无法上网原因...

    引言:这篇文章以WiFi举例,介绍了Android系统网络架构。其内容包含:网络链路的连接和注册、网络有效性检测和网络优选、Android系统网络防火墙和几种场景下的网络策略等,文章的最后也列举了几种常见的无法上网原因供大家参考。

    一. 基本结构

    1.1 类图

    1.2 WifiService

    WifiManager中公开API的具体实现,提供了WiFi打开与关闭、配置和扫描、连接和断开等方法,其中也包含了对调用者的权限检查,如开关WiFi需要"Manifest.permission.CHANGE_WIFI_STATE"权限等。外部调用方式为:

    WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
    

    1.3 WifiStateMachine

    状态机: 状态机是一种用于表示有限个状态以及在这些状态之间转移和动作行为的数学模型。状态机描述了对象在它生命周期内所经历的状态序列,以及在不同状态下如何响应外部事件。使用状态机可以省去代码中一堆的"if-else"判断,这样不仅易于管理,同时也使代码结构更加清晰,易于阅读。

    WifiStateMachine是一个状态机,用于管理WiFi驱动加载、扫描、连接、获取IP、漫游等各个状态。基本的状态如图:("→"的起始端为父状态,终端为子状态;外部消息可以在子→父状态中流动,子状态不处理的消息交由父状态处理)

    各个状态的描述:

    State Description
    DefaultState 初始状态,WiFi 开关没有打开,驱动没有加载。当处于其他状态时,消息由子状态上行可用于日志打印。
    ConnectModeState ConnectModeStateWiFi开关已经打开,驱动已经加载,native中wpa_supplicant已经启动。此时可以进行扫描和连接的操作。
    进入该状态时发送 “android.net.wifi.WIFI_STATE_CHANGED” 广播。
    L2ConnectedState L2是"Level 2"的意思,代表OSI网络模型中的2层即数据链路层。这个状态代表数据链路已经建立完成。
    进入该状态时发送"android.net.wifi.STATE_CHANGE"广播,连接状态是CONNECTING。
    ObtainingIpState DHCP获取IP过程时的状态。
    ConnectedState 已连接状态,当链路建立完成且DHCP配置IP完成后会进入该状态。
    进入该状态时发送"android.net.wifi.STATE_CHANGE"广播,连接状态是CONNECTED。
    RoamingState 漫游状态。如果附近的两个热点名字(ssid)相同,且网络质量达到一定差异化时,系统就会进入漫游状态,连接到另一个热点。
    DisconnectingState 断开中状态。从断开发起到断开成功,处于该状态。
    进入该状态会发送 “android.net.wifi.STATE_CHANGE” 广播,连接状态是DISCONNECTING。
    DisconnectedState 已断开状态。进入该状态时会发送"android.net.wifi.STATE_CHANGE" 广播,连接状态是DISCONNECTED。

    adb连接状态下可以使用 “adb shell dumpsys wifi” 来查看连接WiFi的信息和连接的详细过程,如:

    上一次扫描结果:

    Latest scan results:
        BSSID              Frequency  RSSI    Age      SSID                                 Flags
      80:8d:b7:62:da:12       5765    -52    1.330+   Bytedance Inc                     [WPA2-EAP-CCMP][ESS]
      80:8d:b7:62:da:15       5765    -53   94.471    Bytedance AD                      [WPA2-EAP-CCMP][ESS]
      80:8d:b7:62:da:14       5765    -53   94.471    jiyunhudong                       [WPA2-EAP-CCMP][ESS]
      80:8d:b7:62:da:13       5765    -53   94.471    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]
      80:8d:b7:63:12:d4       5805    -71   94.471    jiyunhudong                       [WPA2-EAP-CCMP][ESS]
      80:8d:b7:63:12:d5       5805    -71   94.469    Bytedance AD                      [WPA2-EAP-CCMP][ESS]
      80:8d:b7:63:12:d2       5805    -71   94.469+   Bytedance Inc                     [WPA2-EAP-CCMP][ESS]
      80:8d:b7:63:12:d3       5805    -71   94.469    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]
      80:8d:b7:62:da:02       2412    -54   94.469    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]
      80:8d:b7:63:43:d2       5260    -81   94.469+   Bytedance Inc                     [WPA2-EAP-CCMP][ESS]
      80:8d:b7:63:43:d3       5260    -81   94.468    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]
      80:8d:b7:63:43:d0       5260    -81   94.468                                      [WPA2-PSK-CCMP][ESS]
      80:8d:b7:63:43:d4       5260    -81   94.468    jiyunhudong                       [WPA2-EAP-CCMP][ESS]
      80:8d:b7:63:43:d5       5260    -81   94.468    Bytedance AD                      [WPA2-EAP-CCMP][ESS]
      80:8d:b7:62:df:73       5300    -87   94.468    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]
      80:8d:b7:60:26:50       5260    -87   94.468                                      [WPA2-PSK-CCMP][ESS]
      80:8d:b7:60:26:52       5260    -86   94.468+   Bytedance Inc                     [WPA2-EAP-CCMP][ESS]
      80:8d:b7:60:26:54       5260    -86   94.467    jiyunhudong                       [WPA2-EAP-CCMP][ESS]
      80:8d:b7:60:26:53       5260    -86   94.467    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]
      80:8d:b7:60:26:55       5260    -86   94.467    Bytedance AD                      [WPA2-EAP-CCMP][ESS]
      80:8d:b7:62:da:11       5765    -53   94.467    Bytedance Guest                   [ESS]
      80:8d:b7:63:12:d1       5805    -71   94.467    Bytedance Guest                   [ESS]
      80:8d:b7:62:da:01       2412    -53   94.467    Bytedance Guest                   [ESS]
      80:8d:b7:63:43:d1       5260    -80   94.467    Bytedance Guest                   [ESS]
      80:8d:b7:63:05:22       2437    -68   94.467    Bytedance Guest                   [ESS]
      80:8d:b7:60:26:51       5260    -87   94.466    Bytedance Guest                   [ESS]
      80:8d:b7:63:38:f3       5200    -73   94.466    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]
    

    一次L2连接成功的过程:

     rec[34]: time=04-08 21:36:05.811 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENT
                                      rt=34877/34863 29 0 SSID: Bytedance Inc BSSID: 00:00:00:00:00:00 nid: 1 state: ASSOCIATING
     rec[35]: time=04-08 21:36:05.910 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENT
                                      rt=34976/34962 30 0 SSID: Bytedance Inc BSSID: 00:00:00:00:00:00 nid: 1 state: ASSOCIATED
     rec[38]: time=04-08 21:36:06.055 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENT
                                      rt=35121/35108 48 0 SSID: Bytedance Inc BSSID: 80:8d:b7:62:da:12 nid: 1 state: FOUR_WAY_HANDSHAKE
     rec[39]: time=04-08 21:36:06.062 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENT
                                      rt=35128/35114 49 0 SSID: Bytedance Inc BSSID: 80:8d:b7:62:da:12 nid: 1 state: GROUP_HANDSHAKE
     rec[40]: time=04-08 21:36:06.066 processed=ConnectModeState org=DisconnectedState dest=ObtainingIpState what=147459(0x24003) !NETWORK_CONNECTION_EVENT
                                      rt=35132/35118 1 0 80:8d:b7:62:da:12 nid=1 "Bytedance Inc"-WPA_EAP
     rec[41]: time=04-08 21:36:06.096 processed=ConnectModeState org=ObtainingIpState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENT
                                      rt=35163/35149 52 0 SSID: Bytedance Inc BSSID: 80:8d:b7:62:da:12 nid: 1 state: COMPLETED
    

    1.4 ConnectivityService

    ConnectivityService(简称CS)是Android系统中的网络连接大管家,所有类型(如WiFi、Telephony、Ethernet等)的网络都需要注册关联到CS并提供链路请求接口。CS主要提供了以下几个方面的功能:

    • 网络有效性检测(NetworkMonitor)
    • 网络评分与选择(NetworkFactory、NetworkAgent、NetworkAgentInfo)
    • 网口、路由、DNS等参数配置(netd)
    • 向系统及三方提供网络申请接口(ConnectivityManager)

    启动方式:

    // SystemServer.java
    try {
        connectivity = new ConnectivityService(
            context, networkManagement, networkStats, networkPolicy);
        ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
                    /* allowIsolated= */ false,
            DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
        networkStats.bindConnectivityManager(connectivity);
        networkPolicy.bindConnectivityManager(connectivity);
    } catch (Throwable e) {
        reportWtf("starting Connectivity Service", e);
    }
    

    外部调用方式:

    ConnectivityManager connectivityManager = 
                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    

    adb连接状态下可以通过"adb shell dumpsys connectivity"来查看系统当前所有的网络信息以及网络检测等关键日志:

    Current Networks:
      NetworkAgentInfo{ ni{[type: WIFI[], state: CONNECTED/CONNECTED, reason: (unspecified), extra: "Bytedance Inc", roaming: false, failover: false, isAvailable: true]}  network{100}
      lp{{InterfaceName: wlan0 LinkAddresses: [fe80::5a44:98ff:fef8:74e2/64,10.95.43.48/21,]  Routes: [fe80::/64 -> :: wlan0,10.95.40.0/21 -> 0.0.0.0 wlan0,0.0.0.0/0 -> 10.95.40.1 wlan0,]
      DnsAddresses: [10.2.0.2,10.1.0.2,] Domains: bytedance.net MTU: 0 TcpBufferSizes: 524288,1048576,2097152,262144,524288,1048576 HttpProxy: [10.95.40.10] 8888 xl= }}
      nc{[ Transports: WIFI Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN&VALIDATED LinkUpBandwidth>=1048576Kbps LinkDnBandwidth>=1048576Kbps SignalStrength: -54]}
      Score{60}  everValidated{true}  lastValidated{true}  created{true} lingering{false} explicitlySelected{false} acceptUnvalidated{false} everCaptivePortalDetected{false}
      lastCaptivePortalDetected{false} }
        Requests:
          NetworkRequest [ id=1, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN] ]
          NetworkRequest [ id=3, legacyType=-1, [] ]
          NetworkRequest [ id=4, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
          NetworkRequest [ id=6, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
          NetworkRequest [ id=7, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
          NetworkRequest [ id=8, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
          NetworkRequest [ id=9, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
        Lingered:
    
    Network Requests:
      Listen from uid/pid:10126/8357 for NetworkRequest [ id=7, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
      Request from uid/pid:1000/2048 for NetworkRequest [ id=1, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN] ]
      Listen from uid/pid:1000/3122 for NetworkRequest [ id=3, legacyType=-1, [] ]
      Listen from uid/pid:10126/8357 for NetworkRequest [ id=9, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
      Listen from uid/pid:10126/8357 for NetworkRequest [ id=8, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
      Listen from uid/pid:10126/7970 for NetworkRequest [ id=6, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
      Listen from uid/pid:10126/7873 for NetworkRequest [ id=4, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
    

    1.5 NetworkFactory

    系统中的网络工厂,也是CS向链路网络请求的统一接口。Android系统启动之初,数据和WiFi就通过WifiNetworkFactory和TelephonyNetworkFactory将自己注册到CS中,方便CS迅速响应网络请求。

    NetworkFactory继承自Handler,并通过AsyncChannel(对Messenger的一种包装,维护了连接的状态,本质上使用Messenger)建立了CS和WifiStateMachine之间的单向通信:

    // NetworkFactory.java
    public void register() {
        if (DBG) log("Registering NetworkFactory");
        if (mMessenger == null) {
            // 创建以自己为Handler的Messenger并传递给CS
            // 之后CS就能够使用Messenger通过Binder的形式与WifiStateMachine线程通信
            mMessenger = new Messenger(this);
            ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG);
        }
    }
    

    CS通过NetworkFactory和WifiStateMachine单向通信:

    对于AsyncChannel可以参考之前整理的一篇博客:
    https://blog.csdn.net/qq_14978113/article/details/80701588

    1.6 NetworkAgent

    链路网络的代理,是CS和链路网络管理者(如WifiStateMachine)之间的信使,在L2连接成功后创建。通过NetworkAgent,WifiStateMachine可以向CS:

    • 更新网络状态 NetworkInfo(断开、连接中、已连接等)
    • 更新链路配置 LinkProperties(本机网口、IP、DNS、路由信息等)
    • 更新网络能力 NetworkCapabilities(信号强度、是否收费等)

    CS可以向WifiStateMachine:

    • 更新网络有效性(即NetworkMonitor的网络检测结果)
    • 禁止自动连接
    • 由于网络不可上网等原因主动断开网络

    因此,NetworkAgent提供了CS和WifiStateMachine之间双向通信的能力。原理类似NetworkFactory,也是使用了AsyncChannel和Messenger。

    CS和WifiStateMachine通过NetworkAgent进行双向通信:

    1.7 NetworkMonitor

    在链路网络注册到CS,并且所有网络配置信息都已经向netd完成了配置,此时就会开始进行网络诊断,具体诊断的任务交给NetworkMonitor。

    NetworkMonitor也是一个状态机,包含以下几种基本状态:

    State Description
    DefaultState 初始状态。接收CS网络诊断命令消息后触发诊断;接收用户登录网络消息
    MaybeNotifyState 通知用户登录。接收诊断后发送的"CMD_LAUNCH_CAPTIVE_PORTAL_APP"消息,startActivity显示登录页面
    EvaluatingState 诊断状态。进入时发送"CMD_REEVALUATE"消息,接收 “CMD_REEVALUATE” 消息并执行网络诊断过程
    CaptivePortalState 登录状态。进入时发送"CMD_LAUNCH_CAPTIVE_PORTAL_APP"消息显示登录页面,
    发送10分钟延迟的"CMD_CAPTIVE_PORTAL_RECHECK"消息进行再次诊断
    ValidatedState 已验证状态。进入时发送"EVENT_NETWORK_TESTED"通知CS网络诊断完成。
    EvaluatingPrivateDnsState 私密DNS验证状态。Android Pie验证私密DNS推出。

    1.8 NetworkPolicyManagerService

    NetworkPolicyManagerService(简称NPMS)是Android系统的网络策略管理者。NPMS会监听网络属性变化(是否收费,metered)、应用前后台、系统电量状态(省电模式)、设备休眠状态(Doze),在这些状态发生改变时,为不同名单内的网络消费者配置不同的网络策略。

    启动方式:

    // SystemServer.java
    try {
        networkPolicy = new NetworkPolicyManagerService(context, mActivityManagerService,
                networkManagement);
        ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
    } catch (Throwable e) {
        reportWtf("starting NetworkPolicy Service", e);
    }
    

    外部调用方式:

    NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(this);
    

    网络策略的基本目的:

    • 在收费网络的情况下省流量
    • 最大可能性的省电
    • 防止危险流量进入

    网络策略中几个重要的名单:

    NameList Description
    mUidFirewallStandbyRules 黑名单,针对前后台应用。此名单中的APP默认REJECT,可配置ALLOW。
    mUidFirewallDozableRules 白名单,针对Doze。此名单中的APP在Doze情况下默认ALLOW。
    mUidFirewallPowerSaveRules 白名单,针对省电模式(由Battery服务提供)。此名单中的APP在省电模式下默认ALLOW,但在Doze情况下仍然REJECT。

    NPMS对网络策略进行统一管理和记录,并配合netd和iptables/ip6tables工具,达到网络限制的目的。

    adb连接状态下可以使用"adb shell dumpsys netpolicy"来查看当前的网络策略:

    System ready: true
    Restrict background: false
    Restrict power: false
    Device idle: false
    Metered ifaces: {}
    Network policies:
      NetworkPolicy{template=NetworkTemplate: matchRule=MOBILE, matchSubscriberIds=[460078...] cycleRule=RecurrenceRule{
                    start=2018-01-11T00:00+08:00[Asia/Shanghai] end=null period=P1M} warningBytes=2147483648
                    limitBytes=-1 lastWarningSnooze=-1 lastLimitSnooze=-1 lastRapidSnooze=-1 metered=true inferred=true}
      NetworkPolicy{template=NetworkTemplate: matchRule=MOBILE, matchSubscriberIds=[460021...] cycleRule=RecurrenceRule{
                    start=2018-01-11T00:00+08:00[Asia/Shanghai] end=null period=P1M} warningBytes=2147483648
                    limitBytes=-1 lastWarningSnooze=-1 lastLimitSnooze=-1 lastRapidSnooze=-1 metered=true inferred=true}
    power save whitelist (except idle) app ids:
      UID=1000: true
      UID=1001: true
      UID=2000: true
      UID=10006: true
      UID=10008: true
      UID=10013: true
      UID=10021: true
    Power save whitelist app ids:
      UID=1000: true
      UID=1001: true
      UID=2000: true
      UID=10013: true
      UID=10021: true
    Default restrict background whitelist uids:
      UID=10013
      UID=10021
      UID=12810021
    

    1.9 NetworkManagementService

    Android SystemServer不具备直接配置和操作网络的能力,所有的网络参数(网口、IP、DNS、Router等)配置,网络策略执行都需要通过netd这个native进程来实际执行或者传递给Kernel来执行。

    而NetworkManagementService(简称NMS)就是SystemServer中其他服务连接netd的桥梁。

    NMS和netd之间通信的方式有两种:Binder 和 Socket。为什么不全使用Binder?原因在于Android老版本上像 vold、netd 这种native进程和SystemServer通信的方式都是使用的Socket,目前高版本上也在慢慢的Binder化,提升调用速度。

    SystemServer和netd之间的数据流向图:

    adb连接状态下可以使用 “adb shell dumpsys network_management” 查看NMS和netd之前通过socket传递的信息记录:

    04-09 15:09:25.609 - SND -> {1331 network create 101}
    04-09 15:09:25.609 - RCV <- {200 1331 success}
    04-09 15:09:25.610 - SND -> {1332 network interface add 101 wlan0}
    04-09 15:09:25.616 - SND -> {1333 traffic wmmer enable}
    04-09 15:09:25.701 - RCV <- {200 1332 success}
    04-09 15:09:25.702 - SND -> {1334 network route add 101 wlan0 fe80::/64}
    04-09 15:09:25.706 - RCV <- {200 1333 command succeeeded}
    04-09 15:09:25.707 - SND -> {1335 traffic limitter enable}
    04-09 15:09:25.707 - RCV <- {200 1334 success}
    04-09 15:09:25.708 - SND -> {1336 network route add 101 wlan0 10.95.40.0/21}
    04-09 15:09:25.757 - RCV <- {200 1335 command succeeeded}
    04-09 15:09:25.757 - SND -> {1337 traffic updatewmm 10014 1}
    04-09 15:09:25.757 - RCV <- {200 1336 success}
    04-09 15:09:25.758 - SND -> {1338 network route add 101 wlan0 0.0.0.0/0 10.95.40.1}
    04-09 15:09:25.758 - RCV <- {200 1337 command succeeeded}
    04-09 15:09:25.759 - SND -> {1339 traffic whitelist 10014 add}
    04-09 15:09:25.759 - RCV <- {200 1338 success}
    04-09 15:09:25.761 - RCV <- {200 1339 command succeeeded}
    04-09 15:09:25.762 - SND -> {1340 resolver setnetdns 101 bytedance.net 10.2.0.2 10.1.0.2 240c::6666 114.114.114.114}
    

    1.10 netd

    为了保障各个功能的正常运行,Android系统中有非常多的守护进程(Daemon)。为了保证系统起来后各项功能都已经ready,这些daemon进程跟随系统的启动而启动,而且一般比system_server进程先启动。如存储相关的vold、电话相关的rild、以及网络相关netd等。

     root@virgo:/ # ps |grep -E "netd|vold|rild|system_server"
     root      253   1     10268  2464  __sys_trac b6d0a824 S /system/bin/vold
     root      330   1     30600  2884  binder_thr b6c47ac8 S /system/bin/netd
     radio     332   1     59132  11124 __sys_trac b6dba824 S /system/bin/rild
     radio     566   1     57844  11024 __sys_trac b6e9a824 S /system/bin/rild
     system    2048  348   1925344 248952 sys_epoll_ b6ca999c S system_server
    

    init.svc.netd进程由init进程启动,netd.rc 如下:

    service netd /system/bin/netd
        class main
        socket netd stream 0660 root system
        socket dnsproxyd stream 0660 root inet
        socket mdns stream 0660 root system
        socket fwmarkd stream 0660 root inet
        onrestart restart zygote
        onrestart restart zygote_secondary
    

    netd作为Android系统的网络守护者,主要有以下方面的职能:

    • 处理接收来自Kernel的UEvent消息(包含网络接口、带宽、路由等信息),并传递给Framework
    • 提供防火墙设置、网络地址转换(NAT)、带宽控制、网络设备绑定(Tether)等接口
    • 管理和缓存DNS信息,为系统和应用提供域名解析服务

    1.11 wpa_supplicant

    与netd一样,也是Android系统的一个daemon进程,与netd不同的是,它只有在WiFi开启的情况下才会启动,在WiFi关闭的时候会随之关闭。wpa_supplicant向Framework提供了WiFi配置、连接、断开等接口。

    wpa_supplicant比Android的历史要早,在很多其他平台上也被广泛利用,他增加了对更多RFC协议的支持,这也是Google最初选择它的原因。但从Android近几个版本来看,Google还是希望弱化wpa_supplicant,并将其功能迁移至Framework或者其他daemon进程中。Android 8.0发生的几个改变:

    • 与system_server的通信从原来的Socket通信改成了HIDL,提高了速度、便于system分区自升级
    • 扫描的功能迁移到了system/wificond中,弱化wpa_supplicant

    启动方式:

    service wpa_supplicant /system/vendor/bin/hw/wpa_supplicant -g@android:wpa_wlan0
        interface android.hardware.wifi.supplicant@1.0::ISupplicant default
        interface android.hardware.wifi.supplicant@1.1::ISupplicant default
        socket wpa_wlan0 dgram 660 wifi wifi
        class main
        disabled
        oneshot
    

    wpa_supplicant和Framework通信:

    二. 注网过程

    Android系统网络注册过程很复杂,涉及到的模块也非常多。主要可以分为以下几个步骤:

    1. WiFi热点扫描,获取扫描结果
    2. 配置WiFi验证信息,已配置完可忽略
    3. 数据链路层L2连接(包含Associate、FourWay-Handshake、Group-Handshake等过程)
    4. DHCP通过UDP的方式获取IP、Gateway、DNS等网络信息
    5. 配置Interafce、IP、DNS、Router到netd

    2.1 WiFi链路连接

    以自动连接为例:

    扫描流程:

    在Android系统中,WiFi扫描的方式主要有三种:

    1. 前台扫描:亮屏状态下且在WiFi Settings页面,每10s发起一次扫描
    2. 后台扫描:亮屏状态下且不在WiFi Settings页面,扫描间隔呈二进制指数退避,退避:interval * (2^n),最短间隔为20s,最长间隔为160s
    3. PNO扫描:灭屏状态下只扫描已保存的网络。最小间隔min=20s,最大间隔max=20s*3

    在Android 8.0 以后,为了解决多种扫描类型带来的冗杂,Google推出了 WifiScanningService,在其中维护了3个状态机分别应用上述3种扫描:WifiSingleScanStateMachine 、WifiBackgroundScanStateMachine、WifiPnoScanStateMachine。

    AP选择流程:

    假如设备中保存了多个可以上网的网络,并且当前都可以被扫描到,系统如何保证连接上最佳(网络质量最高、用户最想要连接)的网络呢?

    Ans:WifiNetworkSelector提供了AP优选的能力,影响优选的因素有:

    1. 是否被用户由于无法上网而UnWanted,进入了禁止自动连接的黑名单
    2. 信号是否过弱,2.4GHz下不低于-80dBm,5GHz下不低于-77dBm
    3. 其他因素一致情况下,5GHz比2.4GHz享有 40 分加成
    4. 上次用户主动选择的AP享有最高 480 分加成,根据时长递减
    5. 根据信号衰减值(rssi)计算信号分加成(rssi + 85)* 4
    6. 与当前连接的AP一致享有 24 分加成
    7. 非开放网络享有 80 分加成

    连接流程:

    Android WiFi的连接过程主要分为 链路连接 和 DHCP获取IP 两个过程。(如果使用的是静态IP则不需要进行DHCP)

    WiFi链路连接:

    (图片来自:https://blog.csdn.net/QQ474111624/article/details/86620579

    1. 认证:对于WPA-PSK、WPA2-PSK类型网络使用密码(Pre-shared key)进行认证;对于EAP类型(PEAP、TTLS、PWD、TLS)则根据具体的加密方法需要身份、密码、证书等进行认证。

    2. 关联:由STA向AP发出关联请求,AP回应关联请求。STA和AP建立关联后,后续数据报文的收发则只能与关联的AP进行。

      注:对于开放类型的网络,这时候链路就已经连理成功了。

    3. 四路握手:PTK(Pairwise Transient Key,成对临时密钥,用于加密单播数据流的加密密钥)的生成、交换、安装。

    4. 组握手:GTK(Group Temporal Key, 组临时密钥,用于加密广播和组播数据流的加密密钥)的生成、交换、安装。


    DHCP获取IP: 动态主机设置协议(Dynamic Host Configuration Protocol,DHCP)是一个局域网内的网络协议,使用UDP协议工作,主要用于内部网或网络服务供应商自动分配IP地址。DHCP流程:

    1. DHCP DISCOVER:DHCP客户机发送有限广播请求IP。(0.0.0.0:68 → 255.255.255.255:67)

    2. DHCP OFFER:DHCP服务器响应。在收到客户机的DHCP请求后,DHCP服务器从IP地址池中找出合法可用的IP地址填入DHCP OFFER报文中并发送有限广播给客户机。(192.168.1.1:67 → 255.255.255.255:68)

    3. DHCP REQUEST:DHCP客户机选择IP。DHCP客户机从接收到的DHCP OFFER消息中选择IP地址,并发送DHCP REQUEST有限广播到所有的DHCP服务器,表明它接受提供的内容。(0.0.0.0:68 → 255.255.255.255:67)

    4. DHCP ACK:DHCP服务器确认租约。(192.168.1.1:67 → 255.255.255.255:68)


    Android系统中为DHCP创建的协议族为IPPROTO_UDP的Socket:

    // DhcpClient.java
    private boolean initUdpSocket() {
        final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP);
        try {
            // UDP数据报
            mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
            Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName);
            // 广播
            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
            // Inet4Address.ANY: 0.0.0.0
            // DhcpPacket.DHCP_CLIENT: 68
            Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
            NetworkUtils.protectFromVpn(mUdpSock);
        } catch(SocketException|ErrnoException e) {
            Log.e(TAG, "Error creating UDP socket", e);
            return false;
        } finally {
            TrafficStats.setThreadStatsTag(oldTag);
        }
        return true;
    }
    

    2.2 WiFi注网

    WiFi、Data、Ethernet等类型链路网络注册到CS,并将Interface、IP、DNS、Router等网络属性设置到netd中的过程称为Android系统的注网过程。

    前面已经提到WifiStateMachine和CS之间是通过WifiNetworkAgent使用AsyncChannel来进行双向通信的,这里不再赘述。

    注网流程图:

    网络属性设置到netd的Socket通信记录可以通过 “adb shell dumpsys network_management” 来查看。如前文所说,Framework和netd通信有Socket和Binder两种方式,在网络注册这个过程中,DNS的设置在Android O版本前后发生了变化,O以前的版本使用Socket,而O以后的版本使用的是Binder,如下所示:

    Android M:

    04-09 15:09:25.609 - SND -> {1331 network create 101}
    04-09 15:09:25.609 - RCV <- {200 1331 success}
    04-09 15:09:25.610 - SND -> {1332 network interface add 101 wlan0}
    04-09 15:09:25.701 - RCV <- {200 1332 success}
    04-09 15:09:25.702 - SND -> {1334 network route add 101 wlan0 fe80::/64}
    04-09 15:09:25.707 - RCV <- {200 1334 success}
    04-09 15:09:25.708 - SND -> {1336 network route add 101 wlan0 10.95.40.0/21}
    04-09 15:09:25.757 - RCV <- {200 1336 success}
    04-09 15:09:25.758 - SND -> {1338 network route add 101 wlan0 0.0.0.0/0 10.95.40.1}
    04-09 15:09:25.759 - RCV <- {200 1338 success}
    // 使用socket的方式,在"dumpsys network_management"中有该记录
    04-09 15:09:25.762 - SND -> {1340 resolver setnetdns 101 bytedance.net 10.2.0.2 10.1.0.2 240c::6666 114.114.114.114}
    

    Android O:

    2019-04-09T17:38:48.692 - SND -> {149271 network create 314}
    2019-04-09T17:38:48.692 - RCV <- {200 149271 success}
    2019-04-09T17:38:48.878 - SND -> {149277 network interface add 314 wlan0}
    2019-04-09T17:38:48.963 - RCV <- {200 149277 success}
    2019-04-09T17:38:48.965 - SND -> {149278 network route add 314 wlan0 fe80::/64}
    2019-04-09T17:38:48.966 - RCV <- {200 149278 success}
    2019-04-09T17:38:48.968 - SND -> {149279 network route add 314 wlan0 10.95.48.0/21}
    2019-04-09T17:38:48.969 - RCV <- {200 149279 success}
    2019-04-09T17:38:48.969 - SND -> {149280 network route add 314 wlan0 0.0.0.0/0 10.95.48.1}
    2019-04-09T17:38:48.970 - RCV <- {200 149280 success}
    // 没有setnetdns的记录,因为这个过程使用了Binder的通信方式
    

    2.3 连接状态及相关广播

    以WiFi举例,对于应用开发者来说,有3个网络相关的广播比较重要:

    Class Broadcast Description
    WifiManager.java android.net.wifi.WIFI_STATE_CHANGED WiFi开关状态的改变(打开、打开中、关闭、关闭中)
    WifiManager.java android.net.wifi.STATE_CHANGE WiFi连接状态的改变(已连接、连接中、已断开、断开中),“已连接” 不代表此时可以上网。
    ConnectivityManager.java android.net.conn.CONNECTIVITY_CHANGE 网络(不只是WiFi)连接状态的改变(连接、断开),“已连接” 代表此时可以上网。

    这三个广播都是粘性广播,通过Context.sendStickyBroadcast来发送,因此应用在注册该广播时,如果之前有发送过该广播,就一定会收到一次广播通知。

    Note:不要通过 “android.net.wifi.STATE_CHANGE” 来判断是否可以上网,因为链路成功后是不能代表此时可以支持上网的,需要等待CS配置完成并发送 “android.net.conn.CONNECTIVITY_CHANGE” 的广播后才可能上网。


    三. 网络优选/评分

    3.1 网络有效性检测

    每种类型的链路网络在L2连接上并注册到CS中时,CS都会为其匹配一个NetworkMonitor,用于进行网络有效性检测。NetworkMonitor将检测结果反馈给CS,CS会根据结果进行以下过程:

    • 标记和提示用户网络有效性状态
    • 提示用户进入网络二次登录操作(针对Portal类型网络,如机场WiFi)
    • 为网络进行评分,并进行网络切换,提供最优网络

    触发网络有效性检测的时机:

    • 链路连接上并且Interface/IP/Router/DNS等配置成功后触发检测
    • 网络检测不通过时延时触发检测
    • Portal类型网络登录后触发检测
    • 三方APP通过CS的reportNetworkConnectivity接口反馈网络有效性触发检测

    检测方式实际就是通过HTTP请求下面域名进行的:

    // NetworkMonitor.java
    private static final String DEFAULT_HTTPS_URL     = "https://www.google.com/generate_204";
    private static final String DEFAULT_HTTP_URL      =
            "http://connectivitycheck.gstatic.com/generate_204";
    private static final String DEFAULT_FALLBACK_URL  = "http://www.google.com/gen_204";
    private static final String DEFAULT_OTHER_FALLBACK_URLS =
            "http://play.googleapis.com/generate_204";
    

    网络有效性检测的原理:

    1. DNS验证:使用"Network.getAllByName(host)"进行DNS解析,成功则验证通过,抛出"UnknownHostException"异常则说明验证失败。

    2. HTTP验证:使用HttpURLConnection访问generate_204网站(访问成功会返回204的response code),该网站一般使用Google提供的"http://connectivitycheck.gstatic.com/generate_204",各大手机厂商也会进行定制,防止被墙导致诊断失误。HTTP验证会有3种结果,根据response code确定:

    • code=204:返回值由generate_204网站返回,网络验证通过
    • 200<=code<=399:返回值由路由器网关返回,一般会携带redirect url,网络需要登录
    • code不在上述范围内:无法上网
    • 抛出"IOException",无法上网

    网络有效性检测的主要流程: (下图描述了某个需要二次登录认证网络的有效性检测过程)

    3.2 评分机制

    CS中注册的网络可能不只一种,同时,CS也能够向Data和WiFi提供的NetworkFactory请求链路网络。多种网络共存时,就存在优先选择的问题,CS通过分数统计的方式来进行网络择优。评分的影响因素有:

    1. 链路网络存在初始分数:WiFi默认为60分,Data默认为50分
    2. 链路网络根据信号衰减rssi更新初始分数
    3. 用户强行选择的网络(不可上网但用户主动连接)默认100分
    4. 网络是否可以上网,不可上网则减去40分
    // NetworkAgentInfo.java
    private int getCurrentScore(boolean pretendValidated) {
        if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
            // 用户主动选择,一般是摄像机、车载WiFi等设备
            // 直接返回100分
            return ConnectivityConstants.MAXIMUM_NETWORK_SCORE;
        }
    
        // currentScore为链路网络的初始分数,受rssi影响
        int score = currentScore;
        if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty()) 
        {
            // 不可上网,减去40分
            score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY;
        }
        if (score < 0) score = 0;
        return score;
    }
    

    当前的分数影响到网络选择,以WiFi和Data举例,

    如果当前连接Data:由于score小于WiFI的默认分数60,向WifiNetworkFactory请求网络,并保持当前网络直到WiF连接重新触发网络验证、评分和网络选择。

    如果当前连接WiFi:

    1. score > 50:保持使用WiFi,如果Data连接着且没有"针对性"(NetworkRequest中存在"TRANSPORT_CELLULAR"选项)请求,则断开Data网络
    2. score < 50:保持使用WiFi,并向TelephonyNetworkFactory请求Data网络,Data连上后重新触发网络验证、评分和网络选择

    注明:Android O上开始在CS中默认保留一个 “TRANSPORT_CELLULAR” 的mDefaultMobileDataRequest,除非用户主动关闭数据网络,否则将一直保持数据链路的连接状态,方便在WiFi状态不佳时进行WiFi和Data之间的快速切换。


    四. 网络策略/防火墙

    为了达到省电/省流量/拦截等目的,Android系统会在多种场景下(Doze、Powersave、前后台等)根据配置进行网络流量限制。

    4.1 Netfilter和iptables

    Android基于Linux内核,而Linux则使用Netfilter这个"钩子"在内核的IP协议栈中去hook各个阶段的数据包,根据预先制定的包过滤规则,定义哪些数据包可以接收,哪些数据包需要丢弃或者拒绝。

    iptables/ip6tables:iptables/ip6tables是用户层的一个工具,用户层使用iptables/ip6tables通过socket的系统调用方式(setsockopt、getsockopt)获取和修改Netfilter需要的包过滤规则,是用户层和内核层Netfilter之间交互的工具。(iptables用于IPv4,ip6tables用于IPv6)

    Netfilter和iptables是Linux网络防火墙中重要的组成部分。Netfilter的工作流程:

    (图片来自:http://blog.chinaunix.net/uid-23069658-id-3160506.html

    收到的每个数据包都从(1)进来,经过路由判决,如果是发送给本机的就经过(2),然后往协议栈的上层继续传递;否则,如果该数据包的目的地不是本机,那么就经过(3),然后顺着(5)将该包转发出去。Netfilter在 PRE_ROUTING、LOCAL_IN、LOCAL_OUT、FORWARD、POST_ROUTING 这5个阶段分别设置回调函数(hook函数),对每一个进出的数据包进行检测。


    q:为什么不只在PRE_ROUTING和POST_ROUTING这两个入口和出口设置数据包检测?

    ans:一方面,这两个阶段处于网络层(IP层)协议栈中,这时候不会拆解TCP/UDP等传输层协议的头部信息,如果需要对更上层协议内容(如端口等)进行过滤,在这两个阶段显然不行;
    另一方面,这两个阶段协议栈不知道这个数据包是需要转发给谁,是转发到下一跳还是传递给上层协议栈,如果是需要传递给上层应用,就更不知道需要传递给哪个应用了。但这些信息在LOCAL_IN和LOCAL_OUT这两个阶段是明确的(明确了传输层协议类型、源IP/目的IP、源端口/目的端口,确定了一条连接),这样过滤应用的报文就成为了可能。


    Netfilter主要有3个模块和3张表:

    1. 包过滤子模块:对应filter表,能够对数据包进行过滤,DROP/REJECT/RETURN/ACCEPT
    2. NAT子模块:对应nat表,能够实现网络地址转换(这个在运营商服务主机中很常用,路由器中其实也运用了该功能,如你手机的外网IP是120.52.148.57,但内网IP是192.168.1.100,这个时候就需要进行网络地址转换)
    3. 数据报修改和跟踪模块:对应mangle表,能够对数据包打上或者判断mark标记,也可以修改数据报中的其它内容(如IP协议头部的tos等)。

    应用层通过iptables工具修改filter、nat和mangle这三张表来控制Netfilter的行为。


    iptables和Netfilter交互方式:

    iptables的源码在/external/iptables目录下,编译完成后,iptables在系统中是一个可执行的bin文件,位于/system/bin目录下:

    root@virgo:/ # ls -lZ system/bin |grep -E "iptables|ip6tables"
    -rwxr-xr-x root     shell             u:object_r:system_file:s0 ip6tables
    lrwxr-xr-x root     shell             u:object_r:system_file:s0 ip6tables-restore -> ip6tables
    lrwxr-xr-x root     shell             u:object_r:system_file:s0 ip6tables-save -> ip6tables
    -rwxr-xr-x root     shell             u:object_r:system_file:s0 iptables
    lrwxr-xr-x root     shell             u:object_r:system_file:s0 iptables-restore -> iptables
    lrwxr-xr-x root     shell             u:object_r:system_file:s0 iptables-save -> iptables
    

    iptables和Netfilter通信使用的是sockopt的系统调用方式,通过setsockopt和getsockopt在参数中传递对应命令值来进行修改和查询:

    (图片来自:http://blog.chinaunix.net/uid-23069658-id-3160506.html

    内核中定义了iptables sockopt的相关命令值:

    // ./include/uapi/linux/netfilter_ipv4/ip_tables.h
    /*
     * New IP firewall options for [gs]etsockopt at the RAW IP level.
     * Unlike BSD Linux inherits IP options so you don't have to use a raw
     * socket for this. Instead we check rights in the calls.
     *
     * ATTENTION: check linux/in.h before adding new number here.
     */
    #define IPT_BASE_CTL        64
    
    // 修改ip tables规则
    #define IPT_SO_SET_REPLACE  (IPT_BASE_CTL)
    // 加入流量计数器
    #define IPT_SO_SET_ADD_COUNTERS (IPT_BASE_CTL + 1)
    #define IPT_SO_SET_MAX      IPT_SO_SET_ADD_COUNTERS
    
    // 获取ip tables某种类型的表信息
    #define IPT_SO_GET_INFO         (IPT_BASE_CTL)
    // 获取ip tables规则信息
    #define IPT_SO_GET_ENTRIES      (IPT_BASE_CTL + 1)
    #define IPT_SO_GET_REVISION_MATCH   (IPT_BASE_CTL + 2)
    #define IPT_SO_GET_REVISION_TARGET  (IPT_BASE_CTL + 3)
    #define IPT_SO_GET_MAX          IPT_SO_GET_REVISION_TARGET
    

    以iptables获取某个表的规则信息为例:

    // ./external/iptables/libiptc/libiptc.c
    struct xtc_handle *TC_INIT(const char *tablename)
    {
            // 表所有信息数据结构,包含info和规则等
            struct xtc_handle *h;
            // 表基本信息数据结构
            STRUCT_GETINFO info;
            unsigned int tmp;
            socklen_t s;
            int sockfd;
    retry:
            iptc_fn = TC_INIT;
            //...
            sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);
            //...
            s = sizeof(info);
    
            // 把tablename复制到info中,用于告知Netfilter查询的是哪张表
            strcpy(info.name, tablename);
            
            // 使用getsockopt的系统调用方式,其中IPT命令为SO_GET_INFO,对应内核
            // 中定义的IPT_SO_GET_INFO,调用完成后,表信息通过info参数返回
            if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) {
                    close(sockfd);
                    return NULL;
            }
    
            if ((h = alloc_handle(info.name, info.size, info.num_entries))
                == NULL) {
                    close(sockfd);
                    return NULL;
            }
    
            /* Initialize current state */
            h->sockfd = sockfd;
            h->info = info;
    
            h->entries->size = h->info.size;
    
            tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;
    
            // 使用getsockopt的系统调用方式,其中IPT命令为SO_GET_ENTRIES,对应内核
            // 中定义的IPT_SO_GET_ENTRIES,调用完成后,规则信息通过h->entries参数返回
            if (getsockopt(h->sockfd, TC_IPPROTO, SO_GET_ENTRIES, h->entries,
                           &tmp) < 0)
                    goto error;
    
            if (parse_table(h) < 0)
                    goto error;
    
            CHECK(h);
            return h;error:
            TC_FREE(h);
            /* A different process changed the ruleset size, retry */
            if (errno == EAGAIN)
                    goto retry;
            return NULL;
    }
    

    当然,native层并不需要这么复杂的去操作ip tables,这些都已经被iptables工具封装好了。系统中如netd这些native进程甚至我们在root shell下使用iptables命令就可以操作,如使用"iptables -t filter -L"查看filter表信息:

    root@virgo:/ # iptables -t filter -L
    Chain INPUT (policy ACCEPT)
    target     prot opt source               destination
    bw_INPUT   all  --  anywhere             anywhere
    fw_INPUT   all  --  anywhere             anywhere
    tc_limiter  all  --  anywhere             anywhere
    
    Chain FORWARD (policy ACCEPT)
    target     prot opt source               destination
    oem_fwd    all  --  anywhere             anywhere
    fw_FORWARD  all  --  anywhere             anywhere
    bw_FORWARD  all  --  anywhere             anywhere
    natctrl_FORWARD  all  --  anywhere             anywhere
    
    Chain OUTPUT (policy ACCEPT)
    target     prot opt source               destination
    wmsctrl_OUTPUT  tcp  --  anywhere             anywhere
    DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
    DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
    DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
    DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
    DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
    DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
    DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
    DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
    oem_out    all  --  anywhere             anywhere
    fw_OUTPUT  all  --  anywhere             anywhere
    

    这个过程其实就是fork了iptables子进程并执行了其main函数,并且携带了"-t filter -L"等args参数。


    注明:Google、各大ODM及手机厂商都会配置很多包过滤规则来进行定制化,因此iptables的操作会很频繁,每次fork都会占用比较大的时间资源;并且为了保证并发访问修改内核的ip tables规则时的安全性,iptables中其实是有文件锁(#define XT_LOCK_NAME "/system/etc/xtables.lock")存在的,这样就又存在排队等待。这个过程比较耗时甚至可能还会引起上层的系统watchdog。

    Google在Android O上做了优化:netd中fork出一个iptables-restore进程并且保持它的存活,每次需要时都通过socket的方式将命令发送给该子进程,并且在执行连续执行命令时做了优化,尽可能保证一次查询一次修改。大大优化了效率。如下是系统中的iptables-restore进程,他的父进程是netd:

    HWEML:/ $ ps -A |grep -E "iptables|netd"
    root           569     1 2163632   4320 0                   0 S netd
    root          9071   569   13040   2788 0                   0 S iptables-restore
    

    4.2 前后台网络策略

    前面介绍NetworkPolicyManagerService时提到了一个mUidFirewallStandbyRules数组名单,这里面缓存了后台需要限制上网的uid黑名单。

    NameList Description
    mUidFirewallStandbyRules 黑名单,针对前后台应用。此名单中的APP默认REJECT,可配置ALLOW。
    mUidFirewallDozableRules 白名单,针对Doze。此名单中的APP在Doze情况下默认ALLOW。
    mUidFirewallPowerSaveRules 白名单,针对省电模式(由Battery服务提供)。此名单中的APP在省电模式下默认ALLOW,但在Doze情况下仍然REJECT。

    可以使用"adb shell dumpsys network_management"来查看mUidFirewallStandbyRules名单:

    root@virgo:/ # dumpsys network_management
    UID firewall standby chain enabled: true
    UID firewall standby rule: [10055:2,10104:2,10108:2,10111:2,10116:2,10123:2,10125:2,10126:2,10127:2]
    

    前后台网络策略最终通过filter表中的fw_standby这个名单来控制,该名单与mUidFirewallStandbyRules名单保持一致:

    root@virgo:/ # iptables -t filter -L fw_standby
    Chain fw_standby (2 references)
    target     prot opt source               destination
    DROP       all  --  anywhere             anywhere             owner UID match u0_a55
    DROP       all  --  anywhere             anywhere             owner UID match u0_a104
    DROP       all  --  anywhere             anywhere             owner UID match u0_a108
    DROP       all  --  anywhere             anywhere             owner UID match u0_a111
    DROP       all  --  anywhere             anywhere             owner UID match u0_a116
    DROP       all  --  anywhere             anywhere             owner UID match u0_a123
    DROP       all  --  anywhere             anywhere             owner UID match u0_a125
    DROP       all  --  anywhere             anywhere             owner UID match u0_a126
    DROP       all  --  anywhere             anywhere             owner UID match u0_a127
    RETURN     all  --  anywhere             anywhere
    

    fw_stanby这条chain是黑名单,Netfilter会将数据包的信息与该名单规则(UID匹配)一条条匹配,匹配到就会执行DROP操作,也就是丢弃数据包;如果所有的名单规则都未匹配,则匹配最后一条没有限定条件的规则,执行RETURN操作,也就是放行数据包。"2 references"表示被另外两条chain(fw_INPUT和fw_OUTPUT)引用,只有链接到Netfilter直接操作的chain上时该名单才能够生效。


    注明:只有在非充电情况下fw_standy这条chain才会生效,也就是被fw_INPUT和fw_OUTPUT这两条chain引用,否则fw_standy就会显示"0 references"。可以通过 “adb shell dumpsys battery unplug” 来取消USB充电,然后使用 “adb shell iptables -t filter -L fw_standby” 来查看。


    4.3 Doze下网络策略

    Doze下的网络策略由NetworkPolicyManagerService中的mUidFirewallDozableRules控制,对应filter表中的fw_dozable chain,这是个白名单,符合名单中任何一条UID规则的数据包都会被放行,否则匹配到最后一条默认规则,被丢弃。这个白名单也是可配置的,将一些关键应用(如微信、QQ等需要在休眠时也能接收消息)配置在其中,防止Doze情况下这些应用无法上网,影响用户使用。

    正常情况下,fw_dozable这条chain不会被使用(0 references):

    root@virgo:/ # iptables -t filter -L fw_dozable
    Chain fw_dozable (0 references)
    target     prot opt source               destination
    RETURN     all  --  anywhere             anywhere             owner UID match 0-9999
    RETURN     all  --  anywhere             anywhere             owner UID match radio
    RETURN     all  --  anywhere             anywhere             owner UID match finddevice
    RETURN     all  --  anywhere             anywhere             owner UID match u0_a0
    RETURN     all  --  anywhere             anywhere             owner UID match u0_a1
    RETURN     all  --  anywhere             anywhere             owner UID match u0_a2
    RETURN     all  --  anywhere             anywhere             owner UID match u0_a3
    RETURN     all  --  anywhere             anywhere             owner UID match u0_a4
    RETURN     all  --  anywhere             anywhere             owner UID match u0_a5
    

    当系统进入Doze模式时,fw_dozable就会被使用并且Add到fw_INPUT和fw_OUTPUT中(2 references):

    Chain fw_dozable (2 references)
    target     prot opt source               destination
    RETURN     all  --  anywhere             anywhere             owner UID match 0-9999
    RETURN     all  --  anywhere             anywhere             owner UID match radio
    RETURN     all  --  anywhere             anywhere             owner UID match finddevice
    RETURN     all  --  anywhere             anywhere             owner UID match u0_a0
    RETURN     all  --  anywhere             anywhere             owner UID match u0_a1
    RETURN     all  --  anywhere             anywhere             owner UID match u0_a2
    RETURN     all  --  anywhere             anywhere             owner UID match u0_a3
    ....
    DROP       all  --  anywhere             anywhere
    

    Netfilter将数据包与fw_dozable中的名单一条条匹配,当UID符合规则时,则RETURN,也就是放行;如果数据包的归属者UID都不满足fw_dozable中的规则,则执行最后一条默认的DROP规则,数据包被丢弃。


    注:可以使用 "adb shell dumpsys deviceidle force-idle deep"来进入Doze模式

    root@virgo:/ # dumpsys deviceidle force-idle deep
    Now forced in to deep idle mode
    

    五. 无法上网原因

    最后,简单罗列几种可能导致无法上网的原因:

    1. WiFi网络未验证(portal网络),访问时路由器会重定向到二次登录网址
    2. 运营商服务器或代理服务器问题,无法连接到外网
    3. DNS服务器问题,导致DNS解析失败
    4. 系统时间不正常,导致证书失效,SSL/TLS握手失败,HTTPS无法上网
    5. TCP连接长时间无数据收发,达到NAT超时时间,网络运营商切断TCP连接,导致长连接失效(push心跳间隔应小于NAT超时时间)
    6. 应用进入了后台且在mUidFirewallStandbyRules黑名单中,数据包被DROP
    7. 系统进入省电模式且应用不在mUidFirewallPowerSaveRules白名单中,数据包被DROP
    8. 系统进入Doze且应用不在mUidFirewallDozableRules白名单中,数据包被DROP
    展开全文
  • 前言 从毕业后到做开发已经两年多了,刚开始学习...不再那么迷茫,说来也惭愧,干这么长时间了,竟然对Android操作系统一无所知,我相信打开这篇博客的人也是跟我一样好奇,233 介绍 你们知道android系...
  • Android系统基础介绍

    2017-07-28 21:56:51
    Android系统基础介绍 <深入理解Android内核设计思想>读书笔记 1.android系统特点 开发与扩展性 android最大的特点是开放性,虽然android遵循的是Apache开源软件许可证,和Linux遵从的GPL不同 合理的分层架构 现在...
  • 转载请标明出处:【顾林海的博客】 init进程启动过程 init进程概述 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Android系统基于Linux,init进程是Android系统中用户空间的第一个进程,进程号...
  • 经过两年的时间,终于完成对Android系统的研究了。Android是一个博大精深的系统,老罗不敢说自己精通了(事实上最讨厌的就是说自己精通神马神马的了,或者说企业说要招聘精通神马神马的人才),但是至少可以说打通了...
  • android 本地日志打印 提示 博主:章飞_906285288 博客地址:http://blog.csdn.net/qq_29924041 转载请注明出处 前言   离上一篇博客大概有快四个月没有更新了,当然这段时间其实是遇到了一些事情,...
  • 在手机上去实现一些动画效果算是件比较炫酷的事情,因此Android系统在一开始的时候就给我们提供了两种实现动画效果的方式,逐帧动画(frame-by-frame animation)和补间动画(tweened animation)。逐帧动画的工作原理很...
  • 参考以下博客: http://blog.csdn.net/mad1989/article/details/38109689/ http://blog.csdn.net/liu_zhen_wei/article/details/12559277 http://blog.csdn.net/soslinken/article/details/49587497 ...
  • 一、 实现思路安卓应用在读取资源时是由AssetManager和Resources两个类来实现的。Resouce类是先根据ID来找到资源文件名称,然后再将该文件名称交给AssetManager来打开文件。我们主题开发的核心思路就是在应用读取...
  • android blog
  • 我们知道,在一个APK文件中,除了有代码文件之外,还有很多资源文件。这些资源文件是通过Android...在本文中,我们就详细分析XML资源文件的编译和打包过程,为后面深入了解Android系统的资源管理框架打下坚实的基础。
  • 今天给大家分享一款开源的博客系统,说是迄今为止我在Github上找到的最好、最全,最容易上手的博客系统一点都不为过。原谅我没有太多文化,只能用“卧槽”两字来表达我内心的喜欢。今天也顺便分享给大家,群众的眼睛...
  • 大家好,今天我们继续来学习Android 8.0系统的适配。 之前我们已经讲到了,Android 8.0系统最主要需要进行适配的地方有两处:应用图标和通知栏。在上一篇文章当中,我们学习了Android 8.0系统应用图标的适配,那么...
  • 写完上一篇博客,发现了这么一篇文章跟自己上一篇有些类似,还没来得及仔细看,先转来...自定义Android系统级权限组 摘要: Android安全模型基于Linux的权限管理,使用沙箱隔离机制将每个应用的进程资源隔离。A
  • 本文已在我的公众号hongyangAndroid原创首发。... 本文出自张鸿洋的博客 本文已在我的公众号hongyangAndroid原创首发,文章合集。 公众号后台很多关注者给我留言,想学习直播相关技术,但是无从下手,其实我也非直
  • Android系统中自带的图标&一些预定义样式&参考颜色值 博客分类:  android动画&效果 AndroidPHPBlog  Android系统中自带了很多图标,我们的程序可以方便使用。  Android™ 1.5 android.R....
1 2 3 4 5 ... 20
收藏数 32,245
精华内容 12,898