NotePublic/Software/Development/System/Android/Android_如何配置_init.rc_中的开机启动...

30 KiB
Raw Blame History

Android 如何配置 init.rc 中的开机启动进程

开篇:为什么写这篇文章

先说下我自己的情况我是个普通的学生之前在学校一直做Android应用开发找实习的时候也一直想找相关的工作来到现在这家公司以后由于业务调整被领导安排去做底层开发本来我对底层的东西一无所知加上其实并不感兴趣其实一开始感觉还是很难的不过刚刚工作只有小孩子才在乎喜欢不喜欢成年人只在乎是否有利。我本着技多不压身的心态开始了底层开发学习之旅做Android底层开发的人其实相对应用开发来说少很多所以网上相关的文章和学习资料也是非常的有限我在最近两个月时间总算琢磨出点东西打算写这篇文章分享给大家给底层的世界填一分彩不过本人才疏学浅尚在学习当中如果有什么错误请大家不吝赐教。

本文到底是讲什么的

首先我先来解释一下本文到底讲什么的。用一句话来说本文讲解的主要内容是如何通过修改Android操作系统源码来配置一个自定义的开机启动进程。有些人也许会问这有什么用问的好一项实用的技术必然要有用处才会有价值。首先说明的是如果你的工作或项目只是做一个应用程序app那本文确实没有什么用处。但如果你的公司做的是Android系统开发或者本身就是一家做硬件的公司那本文可能就会有不少用处了举个例子假如你们公司做了一台搭载Android的嵌入式设备这台设备有某个特殊的传感器是一般手机没有的传感器属于硬件那想让硬件工作就必然有驱动程序现在我们想让这个传感器在设备一开机的时候就立刻启动那我们就需要知道如何配置一个Android的开机启动进程了。而本文正是讲这部分内容的。如果用最简单的一句话描述本文是讲什么的那其实只要文章题目的一句话但是其实涉及到的知识又多又杂加上本人也处在底层开发的探索阶段所以涉及到的知识我只讲到我们能用到的深度为止如果想深入学习我会附上其他博主对这些知识深入分析的文章的链接。

需要做哪些准备工作

首先在硬件上我希望你能有一块能搭载Android系统的嵌入式开发板比如我用的是Friendly-arm的NanoPi M3也许你会问一台普通的Android手机行不行这个说实话我没有试过但是市面上的手机有很多权限是被厂商限制的所以如果用手机来测试可能会发生很多迷之问题所以我建议还是使用一块开发板不一定非要和我的型号一样只要能跑Android就行。其次在软件上我希望你准备好了一份Android系统的源码关于如何获取Android的源码我这里就不讲了网上有很多文章讲这个你可以从Google官方的渠道获取如果你使用开发板一般开发板的提供商也会提供定制的Android源码那我们就直接从开发板的提供商的网站获取就行了。最后我希望你有一台装有mac OS或者Linux操作系统的电脑本文所开展的工作只支持以上两种系统不支持Windows其实我比较推荐unbuntu 14.04这也是我使用的开发板的官方文档所推荐的你当然也可以用Mac但是说实话我最开始开展工作时用的是搭载mac OS 10.12.02的Macbook pro 2016各种发生迷之错误其实按道理来说是可以解决的但是由于做底层开发的人本来就不多以及用Mac做底层开发的人就更少了所以网上关于解决这些问题的学习资料以及文章少之又少导致问题长时间无法解决浪费时间所以还是推荐使用ubuntu 14.04。

最后阅读本文需要哪些知识本文尽量讲的通俗且浅显易懂你不需要有任何底层开发的前置知识你只需要对Android系统有一个比较全面的认识就行你说你写App的水平不高这些都没关系都不会影响你阅读这篇文章。但是我希望你能了解最起码的Linux命令不用太复杂最基本的就行实在不行Windows的cmd命令总用过吧只要会用命令行工具进入硬盘下某个目录就行。

大体上有哪些知识点

大体上有哪些知识点,我画了以下一张图来描述:

大体上有哪些知识点

这么一看一目了然我们需要将我们自己写的程序示例中我用的是C语言将它配置给init.rc这个文件然后通过编写Android.mk来对它进行编译描述什么是编译描述呢我们可以这么理解只要我们写的这个程序的所在目录下有Android.mk文件它就可以和Android源码一样用make命令编译然后我们要通过SEAndroid给我们的程序授权否则程序会因为权限问题而无法执行以上这些都是在Android源码中进行修改的最后我们将源码编译刷机运行你亲自编译的Android操作系统就可以运行在你的开发板上同时你的C程序也可以跟随Android系统的启动而实现开机启动。

Android源码目录结构介绍

我相信读者手里已经拿到一份Android源码了无论它是从哪里获得的哪怕是同学同事拷贝给你的。我的源码版本是5.1.1源码版本对本文的影响不大只要版本不是太老就行。我们先来对Android源码做个简要的介绍。打开源码的文件夹我们会发现有很多目录让我们眼花缭乱确实Android系统是一个非常复杂的软件它必须面面俱到所以这里面的源码覆盖了方方面面想要全部把它看完是不可能的我相信有不少人看过Linux内核源码光是Linux内核的源码可能就能让一个人一生都无法穷尽而Linux内核只是Android的最底层可想而知Android的源码数量有多么庞大所以我在这里只说几个我们最常用的目录以及本文会涉及到的目录。

不少人做Android都是做应用开发做应用开发入门以后很多人都想要进阶这时候就会买一些进阶的书这些进阶的书很多都涉及Android源码分析比如比较火的《Android开发艺术探索》《Andoird源码设计模式解析与实战》等等我们刚学Android的时候就知道Android系统分为四层我们开发的应用处于Application层而Application层的下面一层是framework层想做好应用开发了解一下下层机制是必不可少的所以我刚才说的源码分析的书籍所讲解的源码都在framework层大家其实也能发现这些书里所讲的源码都是java代码。那打开Android源码目录我们能看到一个framework文件夹所以framework层的源码就在这个文件夹下你进去找找可以找到不少应用开发时常用的API。

上面说的是闲话现在我来介绍一个等一下会多次使用的一个目录——device点开这个目录我们会看到很多手机品牌的名字比如htc啊moto啊samsung啊等等。这是什么意思呢Android系统会运行在各种品牌的手机上但是各种品牌的手机在硬件上都会有差异因此很多厂商都会对源码进行定制以修改它的某些部分来配合自家硬件的特性比如我有某个传感器别的厂商没有我的摄像头比较特殊运行起来比较复杂这些都属于硬件差异。由于我使用的Friendly-arm提供的源码所以我可以在这个目录下看到一个friendly-arm的文件夹里面是针对friendly-arm的开发板定制的一些代码等会儿我们会多次访问这里。

还有一些等下我们会常用的目录比如systemexternalout等等这些我们等会儿用到的时候再说。

如何在源码中添加自己的可执行文件

我们如果想要一个属于自己的开机启动进程那首先就要一个我们自己编写的程序了一般来说在实际项目中这个程序就是我们想要开机启动的驱动正如文章开头所说的那样但是在我们这个例子中我就不搞那么复杂了写一个最简单的C语言程序让它作为我们的开机启动进程。我给这个程序命名为loop也就是循环的意思代码如下

#include<stdio.h>

int main()
{
    int i=0;
    for(i;i<100;i++)
    {
        sleep(180);
        printf("I am a process\n");
    }
    return 0;
}

怎么样是不是非常简单任何有任何语言编程基础的人都能看懂我们设置了一个循环每次在执行循环体的内容前将主线程休眠180秒一共循环100次而循环的内容就是输出一条语句。我为什么要这么做主要是因为如果这个程序一下子就执行完成这个进程就死掉了那我们就不能在命令行终端中看到这个进程所以每次循环的时候我都会让它休眠180秒这样算下来这个进程理论上可以保活5小时够长了手速再慢的人也能在5小时内用命令行终端登入开发板然后输入命令查看当前活动的进程了。

现在我们要把这个写好的.c文件放到Android源码目录下进入Android源码目录找到vendor文件夹然后新建一个自己的文件夹我给它起名叫“while-process”我们就把它放在这个里面。那么如何才能让它和源码一起编译呢这时就涉及到了一个知识点——Android.mk。

如何编写自己的Android.mk

这里简单介绍下如何编译Android源码等下还会详细介绍。编译Android源码简单来说就是做一大堆准备工作然后在命令行工具中使用make命令进行编译make命令会在源码目录中遍历所有目录找到里面的Android.mk文件然后根据Android.mk文件的内容编译当前目录下的代码。所以说我们可以理解Android.mk配置文件是一个编译指南。关于Android.mk文件所能扩展出来的知识点也是非常的多这里我也只介绍一些我们最需要和最基本的东西。首先你需要随便找一个Android.mk文件然后用记事本之类的软件打开它。

首先这行代码必须有“LOCAL_PATH:= $(call my-dir)”这行表示所要编译的内容在本Android.mk文件所在的目录下。

然后Android.mk文件中可以指定多个编译模块每个编译模块都是以include (CLEAR_VARS)开始以include (BUILD_XXX)结束。然后在这两代码中间我们找到LOCAL_SRC_FILES:= \xxx后面的xxx就表示你要编译的源文件比如我刚才写的程序叫loop这里就写loop.c我们还会看到LOCAL_MODULE:= xxx这里表示编译的模块的名字在这里我们把xxx换成loop。由于我们这个程序非常简单所以更多的属性在这里就不介绍了感兴趣的可以去下面这篇文章去查阅

你如果不知道怎么创建Android.mk文件最简单的方法就是随便找一个Android.mk文件拷贝过来然后删掉些对你没用的语句增加些对你有用的语句然后改改某些语句的值或者变量名就可以用了如果编译失败就按照提示进行修改多试几次就可以了。

开始编译Android源码

由于讲解了Android.mk所以我这里就先讲一下Android源码的编译不过其实这是最后几步才要做的事情但是先编译也是有好处的因为第一次编译的时候时间会非常的长你可以一边让它编译着一边了解下面的知识点。当你后面的工作全部完成以后再次编译的时候只要你不清空之前的编译结果它就会进行增量编译也就是只编译修改过的地方这样只需要几分钟就能完成了。

Android源码怎么在ubuntu下编译这个网上的文章有很多我这里简要的说明一下。如果你使用的是开发板请找到开发板对应的官方文档然后严格按照文档中的教程一步一步来一般不会遇到什么问题。比如说我用的开发板的文档中给出的步骤大致如下

  1. 搭建编译环境。这里需要一堆支持包,你可以先不用管这些支持包是什么,按照如下命令安装即可:

    sudo apt install bison g++-multilib git gperf libxml2-utils make python-networkx zip sudo apt install flex libncurses5-dev zlib1g-dev gawk minicom

  2. 使用命令行工具的cd命令进入Android源码目录然后依次执行以下三个命令

    source build/envsetup.sh lunch aosp_nanopi3-userdebug make -j8

    这三行命令第一行是设置编译环境第二行是选择编译方案也就是我刚才所说的选择厂商定制的方案可以看到nanopi3是我的开发板的型号所以就选择这个方案第三行是开始编译后面的-j8代表的是使用8线程同时编译使用几个线程同时编译要看你的电脑配置一般来说和你电脑的处理器有关。例如你的电脑装有四核处理器每个核有两个线程那你就可以使用j8也就是4*2。选对同时编译的线程数量合适编译的速度就能成倍增长。如果你是第一次编译源码最慢的话时间可能长达一晚上之久如果你之前成功编译过那这次编译就是增量编译系统只会编译你修改过的地方很人性化只需要几分钟。

    编译过程中也许会遇到一些问题从而停止编译比如你无权操作某些文件等等这些都会在命令行工具中有英文提示看着提示改就行了这里的提示的英文也不复杂。建议在执行命令前先进入root用户也就是先使用su命令。 编译成功以后我们先进入Android源码目录然后进入以下这个目录out/target/product/nanopi3还是那句话根据你选择的编译方案即你使用的开发板型号不同目录会有区别。进入这个目录下面以后我们能看到很多个img镜像文件。

  3. 现在我们把我们编译好的系统刷到我们的开发板上在这之前先准备一张足够大的SD卡然后用读卡器把SD卡连接到电脑上。

    我使用的方式是使用刷机脚本来刷。首先执行以下两条命令:

    git clone https://github.com/friendlyarm/sd-fuse_s5p6818.git cd sd-fuse_s5p6818

    意思就是时候用git下载这个刷机工具然后进入这个刷机工具的目录。执行以下两条命令

    su ./fusing.sh /dev/sdx

    这两条命令的意思是首先进入root用户如果你刚才已经进入root用户了可以不用执行su这条命令下面一行就是开始刷写其中sdx是你的sd卡的设备名请把sdx换成你的设备名比如我的sd卡叫做sdb就把sdx换成sdb。

    刷写成功后我们把卡插入开发板然后用USB线把开发板和电脑连接然后执行命令

    adb remount adb shell

    这时我们就已经使用命令行工具登入到开发板了现在我们要找到我们写的loop程序。比如我把它放在了system/bin目录下使用cd命令进入这个目录然后使用ls命令就可以看到它存在如果想要执行它就使用./命令loop这个程序就执行了执行以后我们能看到每三分钟就可以在命令行终端中看到它输出一行语句但是我们需要确保这个loop这个进程确实存在那我们就需要用ps -Z命令查看它但是当前这个命令行工具窗口正在运行程序无法使用命令所以我们就新建一个命令行终端窗口像刚才一样使用adb shell命令登入开发板然后使用命令ps -Z。就能看到它正在运行了。

    编译Android源码这一块不是我们本讲的重点这一块没有什么概念需要理解就是一些操作步骤的流水账我写的比较简单中间难免有疏漏会造成一些小问题如果读者出现了问题可以根据具体问题去网上搜索或者给我留言网上关于如何编译Android源码如何编译的文章是非常多的不过我还是建议你参照你使用的开发板的官方文档严格按照上面的步骤一步一步来这样发生不必要的问题的概率会小一些。

init.rc介绍

我先来做个名词解释什么是init.rc那就要从什么是init说起。init是由Android的Linux内核启动的第一个第一个进程这个进程非常特殊它的PID永远是1并且这个进程是不会死亡的如果它死亡内核就会崩溃。init进程启动后会fork出很多及其重要的系统进程比如我们做应用开发的时候都耳熟能详的zygote进程我们所有的应用程序的进程都由zygote拉起。解释完了init进程我们再说init.rcinit.rc是一个规定init进程行为和动作的配置文件。init进程可以做哪些事情都由它规定。关于init.rc的详细介绍大家可以参考这篇文章

我们这里只对init.rc做一个简单的介绍init.rc文件中只包含两种声明on和service我们可以把on称为行为把service称为服务这里的服务和应用开发中四大组件中的服务以及通过context.getSystemService()所得到的系统服务都不是一个东西我一直不知道该怎么给它起名姑且叫它init服务。service声明了服务以及服务的各种行为。我们标题中说的开机启动进程就是这里的init服务。service只定义服务但不能让服务做任何事情如果你需要服务能够产生启动或者停止等相关动作你就需要on每个on下面的有各种命令其中就包括很多对init服务的操作。这里要提到的是我们要修改的init.rc文件在device/friendly-arm/nanopi3目录下也就是厂商定制的版本如果你使用的是别的开发板可以去相应的目录找找。我们来看看init.rc中on和service两个典型的定义

on early-init
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_score_adj -1000
    # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls.
    write /sys/fs/selinux/checkreqprot 0
    # Set the security context for the init process.
    # This should occur before anything else (e.g. ueventd) is started.
    setcon u:r:init:s0
    # Set the security context of /adb_keys if present.
    restorecon /adb_keys

    start ueventd
    # create mountpoints
    mkdir /mnt 0775 root system

    ......

service ueventd /sbin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0

#后面的一行是注释这个不用我多说。我们看到里面有很多东西感觉很晕现在我们只重点关注几行我们看到on下面有一行是“start ueventd”而下面service的名字也是ueventd这表明什么我想大家都猜到了那就是在early-init这个on启动了ueventd这个service。对on就是这样启动sercice的当然on还有例如restart以及stop等等其它对service的操作分别是让service停止并重新启动以及停止不过on并不只是为service而生的它还有许多其它的命令在这里我就不详细介绍了大家可以去网上搜索相关文章或者看这本书《构建嵌入式Android系统》。

我们看完了on再看看serviceservice我也只是简单介绍一下service关键字声明了你要定义一个service而ueventd就是这个service的名字至于后面的目录则是这个service对应的可执行文件在系统中的位置。注意这里是说在系统中的位置也就是在开发板运行你的Android源码编译的系统后的目录而不是源码的目录至于Android源码的编译等下再讲。接下来我们可以看到service下面也有很多东西这里我们不叫它们命令了叫属性或者参数也许会比较好其中比较重要的是class core表示这个service属于core这个classclass我们不需要深入去管它只要把它理解成一组service的集合就行至于后面的属性等下我们开始配置service的时候再说。

正式开始在init.rc中配置service

一上午已经过去了,不知不觉我已经写了这么多,但是重点才刚刚要开始。

我们先来理一理我们现在通过上文的讲解已经得到了什么。我们已经编写了一个自己的C语言程序loop并且把它放在了Android的源码中Android的源码也编译好了如何把编译好的Android系统刷到开发板上并启动我们也已经学会了。但是最重要的目的还没实现那就是让loop这个可执行文件开机就可以被启动。而现在我们正要做的就是这件事。 首先打开Android源码目录进入device/friendly-arm/nanopi3文件夹下然后打开inti.rc文件我们正式开始配置。

首先定义一个service还记得service是怎么定义的吗我这里定义的语句如下service qya system/bin/loop。相信不难理解吧我们定义了一个服务叫qya它对应的程序是system/bin目录下的loop。这些上面都讲了。然后我在这个service下面增加几个配置属性

service qya /system/bin/loop
    class main
    console
    oneshot

其中console表示服务需要并运行在控制台oneshot表示服务只运行一次在退出时将服务设置为禁用。你可以根据你的需要来增加这些属性我写的两个属性并不一定都是必须的。《构建嵌入式Android系统》这本书的第六章对这些属性参数介绍的非常详细如果网上找不到相应的文章可以拿这本书看看。

也许有人会发现class main这是个什么东西好像没讲然而我并不是忘了我在这里再详细讲讲。我之前讲过start命令是在on中启动serice但是通览整个init.rc文件我们会发现直接使用start命令启动service的情况非常少我之前也用这种方式试过几次但都未能成功。所以我在仔细阅读和查询资料后发现大多数inti进程fork出来的开机启动进程都是用另外一种方式来启动。我们来看看下面一个on行为的定义

on property:vold.decrypt=trigger_restart_min_framework
    class_start main

我在这里再简单说一下onon分为两种第一种一共有7个它们是一定会随inti进程的启动而执行的比如我们上面介绍init.rc的时候展示的early-init正是这7个on中的第一个。而第二种on则是在满足某些特定条件时才会启动的比如我们这里的这个on就是第二种。我们看到它下面有一条class_start main命令而我们的service下面第一个属性正是class main。所以可以理解了这个class_start main命令是启动main下面所有service。通览init.rc文件我们会发现属于main的service非常多所以我们的service也就搭一趟便车挤上main的队伍。

什么是SELinux/SEAndroid

看起来我们最后一步已经做了把我们的loop程序成功添加到init.rc的文件中。你以为这样你就成功了吗图样图森破我们将会面临本文从开头到现在为止最大的挑战。我们的进程会被SEAndroid这个东西禁止掉从而你无论怎么ps -Z你也不会看到它的存在。那么SEAndroid到底是个什么东西如果你想深入研究它那可得好好花上一段时日了我在这里给出两位大神的系列文章专门分析什么是SEAndroid。这两位大神分别是罗升阳前辈和阿拉神农前辈两人讲解SEAndroid的文章地址如下

  1. http://blog.csdn.net/luoshengyang/article/details/35392905
  2. http://blog.csdn.net/innost/article/details/19299937/

其中罗升阳老师的文章比较长分析的比较理论有助于你全面而细致的了解SEAndroid。而阿拉神农的文章则更加实用让你快速能看懂这个东西但是理论上并没有罗老师的深大家可以各取所需。

我在这里越俎代庖的稍微讲一下什么是SEAndroid。老习惯一句话Android的系统安全机制。它来源于Linux系统中的SELinux。关于它们的历史我这里也都不讲了总之SEAndroid是这样管理权限的凡是任何想要运行的进程想要做任何事情都必须在安全策略配置文件中赋予权限如果没有声明某个权限那它就没有这个权限。要理解其实很容易做应用开发的时候我们常常需要在AndroidManifest.xml文件中赋予应用权限。比如如果你的应用想要读写磁盘数据那你就要写permission语句赋予它读写磁盘的语句如果你的应用想要访问网络那你就需要写一条关于网络的permission语句以准许它访问网络。SEAndroid也是类似的东西你的进程想要干什么你就得给它写一个.te文件然后在文件中使用allow语句赋予它权限不同的是SEAndroid的安全策略文件.te比AndroidManifest.xml可是难写多了。

我这里对编写.te文件和上面Android.mk一样不做深入介绍。想详细了解如何编写.te文件就去参考阿拉神农前辈的文章。我这里只做一个简单的介绍。

我们随便来找一条.te文件中的allow语句allow netd proc:file write。这条语句是阿拉神农的文章中给出的示例他在文章详细分析了这条语句每个词是干什么的但我在这里也不多说还是老惯例一句话允许netd进程对proc type的file进行写(write)操作。

我们这里把这条语句换一下写成allow a b:c d。那意思就是允许a进程对c这个b类型的objec class进行d操作。是不是一下子就理解了不过这里的类型(type)是SEAndroid中的概念很难三言两语说清楚所以请看上面的文章而file也不是我们通常理解的文件而是一种object class表示一种可以被操作的对象比如除了file以外还有DirSocket等等由系统规定这里也不展开了。

到这里我再废话两句SEAndroid把操作系统中的东西分为两种能发起动作的进程以及只能被进程操作的文件而allow语句则就是规定允许谁对谁做什么的。其实SEAndroid的知识远不止这些还有例如MLS分级系统等等不过这里就不讨论了再提示一下想深入了解的看上面两篇文章吧

好的现在我们要开始真正动手了。首先进入Android源码目录然后进入external/sepolicy。还是和上面一样我也不知道怎么创建一个.te文件我们直接随便找个.te文件不过说是随便我们不能真的随便我们也要想一想怎么做成功率高记得我说过我们自定义的service是属于main这个class的吧那我们就找一个同样在main这个class下面的其他服务的.te文件来修改因为在同一个class中的进程所需的权限应该是相近的。

我们找到一个然后把它的名字改成loop.te。注意啊我们的service的名字叫qya但是我们要执行的程序叫loopSEAndroid是赋予进程权限而不是赋予service权限所以我们的.te名字叫loop.te而不是qya.te。.te文件中除了allow语句以外还有一些其它语句比如type等等它们是什么意思大家就自己去查一查吧。

我这里给出大家一个投机取巧的办法吗如果你不想现在了解SEAndroid那我就教你一个办法比如我的.te文件是复制uncrypt的那进到这个文件里面以后就把文件中所有写uncrypt的地方全部改成loop一般来说就可以了。如果编译的时候不通过你就仔细看错误提示我当时不通过的原因是有两行allow语句和domain.te文件中的neverallow语句相冲突看字面意思也能明白neverallow的意思是从不允许如果这里允许了一件事情那里又不允许这件事情势必发生冲突那我们只需要把我们的.te文件中的相关allow语句注释掉或者删掉就行了。

最后一步:重新编译刷机并运行

现在我们该修改的东西终于全部改完了那我们要做的就是重新编译怎么编译上面已经介绍过了由于这次只是增量编译所以只需几分钟就OK我们把它刷到开发板上然后给开发板插上USB线和电脑连接然后在命令行工具中执行两条命令

adb remount
adb shell

现在我们的命令行工具已经登入到开发板上了直接使用命令ps -Z我们就能在窗口中看到我们的进程正在后台运行了我们没有用./命令去执行loop所以它就是被init进程fork出来的。命令行窗口的截图如下所示

通过 PS 查看进程信息

我们可以看到最下面的我们最熟悉的进程zygote它的上面就是我们自定义的loop进程自此自定义int.rc开机启动service成功我们大功告成。

总结

按照惯例还是得总结一下。我们来理一理本文所涉及到的知识点编译Android源码Android.mk文件的编写修改Android源码init.rc介绍SEAndroid。每个知识点都是为了我们能成功运行自定义service而讲解所以讲解的深度其实都是不够的我在文中不少地方都已经贴出了详细介绍每个知识点的博文的地址供大家后续学习。加上本文篇幅也算比较长的了加上介绍的知识又多又杂难免有疏漏之处如果你按照本文的方法在配置过程中出现了失败的地方可以根据具体问题上网搜索只要坚持一定可以解决。Android底层的知识的特点就是多而杂所以做底层开发最需要的就是耐心祝所有走在底层开发的小伙伴都能学习顺利