写给Android App开发人员看的Android底层知识合集(1-8)

写给Android App开发人员看的Android底层知识合集(1-8)

转自包老师:http://www.cnblogs.com/Jax/p/6864103.html

写给Android App开发人员看的Android底层知识(1)

 (一)引言
早在我还是Android菜鸟的时候,有很多技术我都不太明白,也都找不到答案,比如apk是怎么安装的,比如资源是怎么加载的。
再比如说,每本书都会讲AIDL,但我却从来没用过。四大组件也是这个问题,我只用过Activity,其它三个组件,不但没用过,甚至连它们是做什么的,都不是很清楚。
      之所以这样,是因为我一直从事的是电商类App开发工作,对于这类App,基本就是由列表页和详情页组成的,所以我们每天面对的是Activity,会写这两类页面,把网络底层封装的足够强大就够了。
绝大多数App开发人员,都是如此。
但直到接触Android的插件化编程和热修复技术,才发现只掌握上述这些技术是远远不够的。
 
(二)还是引言
市场上有很多介绍Android底层的书籍,网上也有很多文章,但大都是给ROM开发人员看的,动辄贴出几页代码,不适合App开发人员去阅读学习。
我曾经在微信中问过老罗和老邓,你们写的书为什么我们App开发人员看不懂啊,他们就呵呵了,跟我说,他们的书就是写给ROM开发人员看的。
于是,这几年来,我一直在寻找这样一类知识,App开发人员看了能有助于他们更好的编写App程序,而又不需要知道太多这门技术底层的代码实现。
 
这类知识分为两种。
      一种是知道概念即可,就比如说Zygote,其实App开发人员是不需要了解Zygote的,知道有这么个东西是“孕育天地”的就够了,类似的还有SurfaceFlinger、WMS这些概念。
       还有一种是需要知道内部原理,就比如说Binder。关于Binder的介绍铺天盖地,但对于我们App开发人员,需要了解的是它的架构模型,只要有Client和Server,以及SM就足够了。
      四大组件的底层通信机制都是基于Binder的,我们需要知道每个组件中,分别是哪些类扮演了Binder Client,哪些类扮演了Binder Server。知道这些概念,有助于我们App开发人员进行插件化编程。
 
(三)目录
我这个系列的文章,已经写好了下面的内容,会在接下来的每天发布一篇,共计8篇,看了这8篇文章,就可以迈进Android插件化的大门了。
   Binder
   AIDL
   AMS
   Activity
   Service
   ContentProvider
   匿名共享内存
   BroadcastReceiver
   PMS及App安装过程
 
Android底层知识,还应该包括以下内容,但是和插件化关系不大,也不是我擅长的领域,所以我只列出了大纲,没有继续写下去:
   View和ViewGroup
   Message、Looper和Handler
   权限管理
   Android SDK工具内部原理
 
有兴趣的同学,可以按照我这个思路继续写下去,记得,一,少贴代码。多画图,二,一定要有趣。
 
       接下来就详细讲那些App开发人员需要知道的Android底层知识。
 
       (三)Binder
       Binder是为了解决跨进程通信。
关于Binder的文章实在是太多了,每篇文章都能从Java层讲到C++层,App开发人员其实是没必要了解这么多内容的。我们看对App开发有用的几个点:
1)首先,Binder分为Client和Server两个进程。
注意,Client和Server是相对的。谁发消息,谁就是Client,谁接收消息,谁就是Server。
      举个例子,两个进程A和B之间使用Binder通信,进程A发消息给进程B,那么这时候A是Binder Client,B是Binder Server;进程B发消息给进程A,那么这时候B是Binder Client,A是Binder Server——其实这么说虽然简单了,但还是不太严谨,我们先这么理解着。
 
2)其次,我们看下面这个图(摘自田维术的博客),基本说明白了Binder的组成解构:
 
 
 
图中的IPC就是进程间通信的意思。
图中的ServiceManager,负责把Binder Server注册到一个容器中。
      有人把ServiceManager比喻成电话局,存储着每个住宅的座机电话,还是很恰当的。张三给李四打电话,拨打电话号码,会先转接到电话局,电话局的接线员查到这个电话号码的地址,因为李四的电话号码之前在电话局注册过,所以就能拨通;没注册,就会提示该号码不存在。
      对照着Android Binder机制,对着上面这图,张三就是Binder Client,李四就是Binder Server,电话局就是ServiceManager,电话局的接线员在这个过程中做了很多事情,对应着图中的Binder驱动
 
3)接下来我们看Binder通信的过程,还是摘自田维术博客的一张图:
 
 
      
       注:图中的SM也就是ServiceManager。
 
       我们看到,Client想要直接调用Server的add方法,是不可以的,因为它们在不同的进程中,这时候就需要Binder来帮忙了。
首先是Server在SM这个容器中注册。
其次,Client想要调用Server的add方法,就需要先获取Server对象, 但是SM不会把真正的Server对象返回给Client,而是把Server的一个代理对象返回给Client,也就是Proxy。
然后,Client调用Proxy的add方法,SM会帮他去调用Server的add方法,并把结果返回给Client。
 
       以上这3步,Binder驱动出了很多力,但我们不需要知道Binder驱动的底层实现,涉及到C++的代码了——把有限的时间去做更有意义的事情。
 
       App开发人员对Binder的掌握,这些内容就足够了。
 
       是时候总结一波了:
学习Binder,是为了更好的理解AIDL,基于AIDL模型,进而了解四大组件的原理。
理解了Binder,再看AMS和四大组件的关系,就像是Binder的两个进程Server和Client通信。
 
       (四)AIDL
       AIDL是Binder的延伸。一定要先看懂我前面介绍的Binder,再来看AIDL。要按顺序阅读。
       Android系统中很多系统服务都是aidl,比如说剪切板。举这个例子,是为了让App开发人员知道AIDL无处不在,和我们距离非常近。
 
       AIDL中需要知道下面几个类:
   IBinder
   IInterface
   Binder
   Proxy
   Stub
 
       当我们自定义一个aidl文件时(比如MyAidl.aidl,里面有一个sum方法),Android Studio会帮我们生成一个类文件MyAidl.java,如下图所示:
 
 
   
MyAidl.java这个生成文件中,包括MyAidl接口,以及Stub和Proxy两个实现了MyAidl接口的类,其中Stub是定义在MyAidl接口中的,而Proxy则定义在Stub类中。
      我曾经很不理解,为什么不是生成3个文件,一个接口,两个类,清晰明了。都放在一个文件中,这是导致很多人看不懂AIDL的一个门槛。其实Android这么设计是有道理的。当有多个AIDL类的时候,Stub和Proxy类就会重名,把它们放在各自的AIDL接口中,就必须MyAidl.Stub这样去使用,就区分开了。
 
       对照这张图,我们继续来分析,Stub的sum方法是怎么调用到Proxy的sum方法?然后又调用另一个进程的sum方法的?
       起决定意义的是Stub的asInterface方法和onTransact方法。其实这个图没有画全,把完整的Binder Server也画上,就应该是这样:
 
 
 
       1)先从Client看起,对于AIDL的使用者,我们这么写程序:
MyAidl.Stub.asInterface(某IBinder对象).sum(1, 2);         //最好在执行sum方法前判空。
 
       asInterface方法的作用是判断参数——也就是IBinder对象,和自己是否在同一个进程:
   是,则直接转换、直接使用,接下来就跟Binder跨进程通信无关啦;
   否,则把这个IBinder参数包装成一个Proxy对象,这时调用Stub的sum方法,间接调用Proxy的sum方法。
return new MyAidl.Stub.Proxy(obj);
      
       2)Proxy在自己的sum方法中,会使用Parcelable来准备数据,把函数名称、函数参数都写入_data,让_reply接收函数返回值。最后使用IBinder的transact方法,把数据就传给Binder的Server端了。
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); //这里的mRemote就是asInterface方法传过来的obj参数
      
 
———————我是Binder分界线—————-
 
       3)Server则是通过onTransact方法接收Client进程传过来的数据,包括函数名称、函数参数,找到对应的函数,这里是sum,把参数喂进去,得到结果,返回。
       所以onTransact函数经历了读数据-->执行要调用的函数-->把执行结果再写数据的过程。
 
       下一篇文章要介绍的四大组件的原理,我们都可以对照着AIDL的这张图来看,比如说,四大组件的启动和后续流程,都是在和ActivityManagerService(简称AMS)来来回回的通信,四大组件给AMS发消息,四大组件就是Binder Client,而AMS就是Binder Server;AMS发消息通知四大组件,那么角色就互换。
      
       那么四大组件中,比如说Activity,又是哪个类扮演了Stub的角色,哪个类扮演了Proxy的角色呢?这也是我下一篇文章要介绍的,包括AMS、四大组件各自的运行原理。
 
       好戏即将开始。
写给Android App开发人员看的Android底层知识(2)
(五)AMS
如果站在四大组件的角度来看,AMS就是Binder中的Server。
      AMS全称是ActivityManagerService,看字面意思是管理Activity的,但其实四大组件都归它管。估计是Android底层开发人员先写了ActivityManagerService用来管理Activity,后来写Service、Receiver、CP的时候发现代码都差不多,于是就全都用ActivityManagerService,但是却忘记改名字了——我也是猜的,纯属八卦。
 
由此而说到了插件化,我记得16年和Lody、张勇、林光亮一起吃夜宵的时候,我当时问了困惑已久的两个问题:
1)App的安装过程,为什么不把apk解压缩到本地,这样读取图片就不用每次从apk包中读取了——这个问题,我们放到PMS那一节再详细说。
2)为什么Hook永远是在Binder Client端,也就是四大组件这边,而不是在AMS那一侧进行Hook。
 
这里要说清楚第二个问题。就拿Android剪切板举例吧。前面说过,这也是个Binder服务。
      AMS要负责和所有App的四大组件进行通信,也真够他忙的。如果在一个App中,在AMS层面把剪切板功能给篡改了,那会导致Android系统所有的剪切板功能被篡改——这就是病毒了,如果是这样的话,Android系统早就死翘翘了。所以Android系统不允许我们这么做。
      我们只能在AMS的另一侧,Client端,也就是四大组件这边做篡改,这样即使我们把剪切板功能篡改了,也只影响篡改代码所在的App,在别的App中,剪切板功能还是正常的。
 
关于AMS我们就说这么多,下面介绍四大组件时,会反复提到四大组件和AMS的跨进程通信。
 
(六)Activity 第1讲
      对于做App的开发人员而言,Activity是四大组件中用的最多的,也是最复杂的,我这里只讲Activity的启动和通信原理。还有一些相关的概念,比如说View、Looper、Intent、Resource,我以后另起章节来介绍。
 
      注:我对四大组件的分析,都是基于罗升阳的那本分析Android底层的书,我把其中罗列的大部分代码都删掉了,只保留那些对App开发人员有用的一些代码片段和一些关键类名,并融入了我对四大组件的理解。
 
 
1)首先要搞清,App是怎么启动的。
      在手机屏幕上点击某个App的Icon,假设就是斗鱼App吧,这个App的首页(或引导页)就出现在我们面前了。这个看似简单的操作,背后经历了Activity和AMS的反反复复的通信过程。
      首先要搞清楚,在手机屏幕上点击App的icon快捷图标,此时手机屏幕就是一个Activity,而这个Activity所在的App,业界称之为Launcher。Launcher是手机系统厂商提供的,类似小米华为这样的手机,比拼的就是谁的Launcher绚丽和人性化。
      Launcher这个App,其实和我们做的各类应用类App没有什么不同,我们大家用过华为、小米之类的手机,预装App以及我们下载的各种App,都显示在Launcher上,每个App表现为一个Icon。Icon多了可以分页,可以分组,此外,Launcher也会发起网络请求,调用天气的数据,显示在屏幕上,所谓的人性化界面。
还记得我们在开发一款App时,在Manifest文件中是怎么定义默认启动Activity的么?如下所示:
 

 
而Launcher中为每个App的icon提供了启动这个App所需要的Intent信息,如下所示(比如说斗鱼的包名是):
action:android.intent.action.MAIN
category: android.intent.category.LAUNCHER
cmp: 斗鱼的包名+ 首页Activity名
 
这些信息是App安装(或Android系统启动)的时候,PackageManagerService从斗鱼的apk包的manifest文件中读取到的。
 
所以点击icon就启动了斗鱼App中的首页。
 
2)启动App哪有那么简单
前面介绍的只是App启动的一个最简单的概述。
仔细看,我们会发现,Launcher和斗鱼是两个不同的App,他们位于不同的进程中,它们之间的通信是通过Binder完成的——这时候AMS出场了。
 
仍然以启动斗鱼App为例子,整体流程是:
Launcher通知AMS,要启动斗鱼App,而且指定要启动斗鱼的哪个页面(也就是首页)。
AMS通知Launcher,好了我知道了,没你什么事了,同时,把要启动的首页记下来。
Launcher当前页面进入Paused状态,然后通知AMS,我睡了,你可以去找斗鱼App了。
AMS检查斗鱼App是否已经启动了。是,则唤起斗鱼App即可。否,就要启动一个新的进程。AMS在新进程中创建一个ActivityThread对象,启动其中的main函数。
斗鱼App启动后,通知AMS,说我启动好了。
AMS翻出之前在第二步存的值,告诉斗鱼App,启动哪个页面。
斗鱼App启动首页,创建Context并与首页Activity关联。然后调用首页Activity的onCreate函数。
 
至此启动流程完成,分成两部分,1-3步,Launcher和AMS相互通信,而后面几步,斗鱼App和AMS相互通信。
 
这会牵扯一堆类进来,列举如下,在接下来的分析中,我们都会遇到:
   Instrumentation
   ActivityThread
   H
   LoadedApk
   AMS
   ActivityManagerNative和ActivityManagerProxy
   ApplicationThread和ApplicationThreadProxy
 
 
第1阶段 Launcher通知AMS
 

 
这是我根据老罗那本书中对Activity的分析,自己手绘的UML图,一共七张,也就是Activity启动所经历的七个阶段。建议各位读者也亲自手绘一遍,从而就加深理解。
 
   第1步、第2步
      从上图中我们看到,点击Launcher上的斗鱼App的icon快捷图标,这时会调用Launcher的startActivitySafely方法,其实还是会调用Activity的startActivity方法,intent中带着要启动斗鱼App所需要的关键信息,如下所示:
      action = “android.intent.action.MAIN”
      category = “android.intent.category.LAUNCHER”
      cmp = “com.douyu.activity.MainActivity”
 
      第3行代码是我猜的,就是斗鱼App在Mainfest文件中指定为首页的那个Activity。这样,我们终于明白,为什么在Mainfest中,给首页指定action和category了。在app的安装过程中,会把这个信息“记录”在Launcher的斗鱼启动快捷图标中。关于App的安装过程,我会在后面的文章详细介绍。
 
      startActivity这个方法,如果我们看它的实现,会发现它调来调去,经过一系列startActivity的重载方法,最后会走到startActivityForResult方法。
 
      
 
      我们知道startActivityForResult需要两个参数,一个是intent,另一个是code,这里code是-1,表示Launcher才不关心斗鱼的App是否启动成功了呢。
      
 
      第3步: startActivityForResult
      Activity内部会保持一个对Instrumentation的引用,但凡是做过App单元测试的同学,对这个类都很熟悉,称之为仪表盘。
      在startActivityForResult方法的实现中,会调用Instrumentation的execStartActivity方法。
 

 
看到这里,我们发现有个mMainThread变量,这是一个ActivityThread类型的变量。
      这个家伙的来头可不小。
ActivityThread,就是主线程,也就是UI线程,它是在App启动时创建的,它代表了App应用程序。
      啥?ActivityThread代表了App应用程序,那Application类岂不是被架空了?其实,Application对我们App开发人员来说也许很重要,但是在Android系统中还真的没那么重要,他就是个上下文。Activity不是有个Context上下文吗?Application就是整个ActivityThread的上下文。
ActivityThread则没有那么简单了。它里面有main函数。
 
      我们知道大部分程序都有main函数,比如java、C#,远了不说,iPhone App用到的Objective-C,也有main函数,那么Android的main函数藏在哪里?就在ActivityThread中,如下所示,代码太多,我只截取了一部分
 

      又有人会问?不是说谁写的程序,谁就要提供main函数,作为入口吗?但Android App却不是这样的。Android App的main函数,在ActivityThread里面,而这个类是Android系统提供的底层类,不是我们提供的。
       所以这就是Andoid有趣的地方。Android App的入口是Mainifest中定义默认启动Activity。这是由Android AMS与四大组件的通信机制决定的。
 
       最近在看吴越版的西游记,就发现这个西天取经为啥用了十几年啊?因为这师徒四个取经路上爱管闲事,所以耽搁了很久。我这篇文章也是如此,经常讲着讲着就跑题了,再这么写下去,不知道要写到啥时候,所以我们一路向西,径直往前走,再遇到奇怪的类,先不要理它。
 
      在回到代码来,这里要传递2个很重要的参数:
   通过ActivityThread的getApplicationThread方法取到一个Binder对象,它的类型为ApplicationThread,它代表着Launcher所在的App进程。
   mToken,这也是个Binder对象,它代表了Launcher这个Activity,这里也通过Instrumentation传给AMS,AMS一查电话簿,就知道是谁向AMS发起请求了。
      
      这两个参数是伏笔,传递给AMS,以后AMS想反过来通知Launcher,就能通过这两个参数,找到Launcher。
 
 
 
       第4步,Instrumentation的execStartActivity方法
       Instrumentation绝对是Adnroid测试团队的最爱,因为它可以帮我们启动Activity。
 
       回到我们的App启动过程来,在Instrumentation的execStartActivity方法中,
 

 
我理解这就是一个透传,Activity把数据借助Instrumentation,传递给ActivityManagerNative,没太多有趣的内容,就不多讲了。
 
      第5步:AMN的getDefault方法
      ActivityManagerNative,简称AMN。这个类后面会反复用到。
      AMN通过getDefault方法,从ServiceManager中取得一个名为activity的对象,然后把它包装成一个ActivityManagerProxy对象(简称AMP),AMP就是AMS的代理对象。
      备注1:ServiceManager是一个容器类。
      备注2:  AMN的getDefault方法返回类型为IActivityManager,而不是AMP。IActivityManager是一个实现了IInterface的接口,里面定义了四大组件所有的生命周期。
      AMN和AMP都实现了IActivityManager接口,AMS继承自AMN(好乱),那么对照着前面AIDL的UML,就不难理解了:
 

 
第6步,AMP的startActivity方法
看到这里,你会发现AMP的startActivity方法,和AIDL的Proxy方法,是一模一样的,写入数据到另一个进程,也就是AMS,然后等待AMS返回结果。
 
至此,第一阶段的工作就做完了。
 
后续流程请参加下一篇文章。
写给Android App开发人员看的Android底层知识(3)
       (七)App启动流程第2篇
 
       书接上文,App启动一共有七个阶段,上篇文章篇幅所限,我们只看了第一阶段,接下来讲剩余的六个阶段,仍然是拿斗鱼App举例子。
 
       简单回顾一下第一阶段的流程,就是Launcher向AMS发送一个跨进程通信,通过AMN/AMP,告诉AMS,我要启动斗鱼App。
 
       画一个图,描述一下启动App所经历的7个阶段:
 

第2阶段 AMS处理Launcher传过来的信息
 

 
这个阶段主要是Binder的Server端在做事情。因为我们是没有机会修改Binder的Server端逻辑的,所以这个阶段看起来非常枯燥,我尽量说的简单些。
首先Binder,也就是AMN/AMP,和AMS通信,肯定每次是做不同的事情,就比如说这次Launcher要启动斗鱼App,那么会发送类型为START_ACTIVITY——TRANSACTION的请求给AMS,同时会告诉AMS要启动哪个Activity。
AMS说,好,我知道了,然后它会干一件很有趣的事情,就是检查斗鱼App中的Manifest文件,是否存在要启动的Activity。如果不存在,就抛出Activity not found的错误,各位做App的同学对这个异常应该再熟悉不过了,经常写了个Activity而忘记在Manifest中声明了,就报这个错,就是因为AMS在这里做检查。不管是新启动一个App的首页,还是在App内部跳转到另一个Activity,都会做这个检查。
但是Launcher还活着啊,所以接下来AMS会通知Launcher,哥们儿没你什么事了,你“停薪留职”吧。那么AMS是通过什么途径告诉Launcher的呢?
 
       前面讲过,Binder的双方进行通信是平等的,谁发消息,谁就是Client,接收的一方就是Server。Client这边会调用Server的代理对象。
       对于从Launcher发来的消息,通过AMS的代理对象AMP,发送给AMS。
 
       那么当AMS想给Launcher发消息,又该怎么办呢?前面不是把Launcher以及它所在的进程给传过来了吗?它在AMS这边保存为一个ActivityRecord对象,这个对象里面有一个ApplicationThreadProxy,单单从名字看就出卖了它,这就是一个Binder代理对象。它的Binder真身,也就是ApplicationThread。
       站在AIDL的角度,来画这张图,是这样的:
 

所以结论是,AMS通过ApplicationThreadProxy发送消息,而App端则是通过ApplicationThread来接收这个消息。
 
      第3阶段 Launcher去休眠,然后通知AMS,我真的已经“停薪留职”了,没有吃空饷
 

ApplicationThread(简称APT),它和ApplicationThreadProxy(简称ATP)的关系,我们在第三阶段已经介绍过了。
      APT接收到来自AMS的消息后,就调用ActivityThread的sendMessage方法,向Launcher的主线程消息队列发送一个PAUSE_ACTIVITY消息。
      前面说过,ActivityThread就是主线程(UI线程)
 
      看到下面的代码是不是很亲切?
 

发送消息是通过一个名为H的Handler类的完成的,这个H类的名字真特么有个性,想记不住都难。
      做App的同学都知道,继承自Handler类的子类,就要实现handleMessage方法,这里是一个switch…case语句,处理各种各样的消息,PAUSE_ACTIVITY消息只是其中一种,由此也能预见到,AMS给Activity发送的所有消息,以及给其它三大组件发送的所有消息,都从H这里经过,为什么要强调这一点呢,既然四大组件都走这条路,那么这里就可以做点手脚,从而做插件化技术,这个我们以后介绍插件化技术的时候会讲到。
 

H对于PAUSE_ACTIVITY消息的处理,如上面的代码,是调用ActivityThread的handlePauseActivity方法。这个方法干两件事:
   ActivityThread里面有一个mActivities集合,保存当前App也就是Launcher中所有打开的Activity,把它找出来,让它休眠。
   通过AMP通知AMS,我真的休眠了。
 
      你可能会找不到H和APT这两个类文件,那是因为它们都是ActivityThread的内嵌类。
 
      至此,Launcher的工作完成了。你可以看到在这个过程中,各个类都起到了什么作用。芸芸众生,粉墨登场:
   APT
   ActivityThread
   H
 
      第4阶段 AMS启动新的进程
      接下来又轮到AMS做事了,你们会发现我不太喜欢讲解AMS的流程,甚至都不画UML图,因为这部分逻辑和App开发人员关系不是很大,我尽量说的简单一些,把流程说清楚就好。
 
      AMS接下来要启动斗鱼App的首页,因为斗鱼App不在后台进程中,所以要启动一个新的进程。这里调用的是Process.start方法,并且指定了ActivityThread的main函数为入口函数。
 

第5阶段 新的进程启动,以ActivityThread的main函数作为入口

启动新进程,其实就是启动一个新的App。
 
      在启动新进程的时候,为这个进程创建ActivityThread对象,这就是我们耳熟能详的主线程(UI线程)。
      创建好UI线程后,立刻进入ActivityThread的main函数,接下来要做2件具有重大意义的事情:
      1)创建一个主线程Looper,也就是MainLooper。看见没,MainLooper就是在这里创建的。
      2)创建Application。记住,Application是在这里生成的。
 
- - - - - - - - - - - - - -华丽的分割线 开始- - - - - - - - - - - - - - -
 
      App开发人员对Application非常熟悉,因为我们可以在其中写代码,进行一些全局的控制,所以我们通常认为Application是掌控全局的,其实Application的地位在App中并没有那么重要,它就是一个Context上下文,仅此而已。
      App中的灵魂是ActivityThread,也就是主线程,只是这个类对于App开发人员是访问不到的——使用反射倒是可以修改这个类的一些行为。
 
- - - - - - - - - - - - - - -华丽的分割线 结束- - - - - - - - - - - - - - -
                                                                                                                                                                                                                              
      创建新App的最后,就是告诉AMS,我启动好了,同时把自己的ActivityThread对象发送给AMS,从此以后,AMS的电话簿中就多了这个新的App的登记信息,AMS以后向这个App发送消息,就通过这个ActivityThread对象。
 
     第6阶段 AMS告诉新App启动哪个Activity
     AMS把传入的ActivityThread对象,转为一个ApplicationThread对象,用于以后和这个App跨进程通信。还记得APT和ATP的关系吗?这就又回到第2阶段的那张关系图了。
 
     还记得第1阶段,Launcher发送给AMS要启动斗鱼App的哪个Activity吗?这个信息被AMS存下来了。
     那么在第6阶段,AMS从过去的记录中翻出来要启动哪个Activity,然后通过ATP告诉App。
 
     第7阶段 启动斗鱼首页Activity
 

      毕其功于一役,尽在第7阶段。这是最后一步。
 
      App,这个Binder的另一端,通过APT接收到AMS的消息,仍然是在H的handleMessage方法的switch语句中处理,只不过,这次消息的类型是LAUNCH_ACTIVITY:
 

 
      ActivityClientRecord是什么?这是AMS传递过来的要启动的Activity。
      还是这里,我们仔细看那个getPackageInfoNoCheck方法,这个方法会提取Apk中的所有资源,然后设置给r的packageInfo属性。这个属性的类型很有名,叫做LoadedApk。各位记住这里,这个地方可以干坏事,也是插件化技术渗入的一个点。
 
      在H的这个分支中,又反过来回调ActivityThread的handleLaunchActivity方法,你要是觉得很绕那就对了。其实我一直觉得,ActivityThread和H合并成一个类也没问题。
 

      重新看一下这个过程,每次都是APT执行ActivityThread的sendMessage方法,在这个方法中,把消息拼装一下,然后扔个H的swicth语句去分析,来决定要执行ActivityThread的那个方法。每次都是这样,习惯就好了。
 
      handleLaunchActivity方法都做哪些事呢?
      1)通过Instrumentation的newActivity方法,创建出来要启动的Activity实例。
      2)为这个Activity创建一个上下文Context对象,并与Activity进行关联。
      3)通过Instrumentation的callActivityOnCreate方法,执行Activity的onCreate方法,从而启动Activity。看到这里是不是很熟悉很亲切?
 
      至此,App启动完毕。这个流程是经过了很多次握手, App和ASM,频繁的向对方发送消息,而发送消息的机制,是建立在Binder的基础之上的。
 
下一篇文章,我们讲Context家族。
写给Android App开发人员看的Android底层知识(4)
(八)App内部的页面跳转
 
      在介绍完App的启动流程后,我们发现,其实就是启动一个App的首页。
 
      接下来我们看App内部页面的跳转。
 
      从ActivityA跳转到ActivityB,其实可以把ActivityA看作是Launcher,那么这个跳转过程,和App的启动过程就很像了。
      有了前面的分析基础,会发现,这个过程不需要重新启动一个新的进程,所以可以省略App启动过程中的一些步骤,流程简化为:
      1)ActivityA向AMS发送一个启动ActivityB的消息。
      2)AMS保存ActivityB的信息,然后通知App,你可以休眠了(onPaused)。
      3)ActivityA进入休眠,然后通知AMS,我休眠了。
      4)AMS发现ActivityB所在的进程就是ActivityA所在的进程,所以不需要重新启动新的进程,所以它就会通知App,启动ActivityB。
      5)App启动ActivityB。
 
      不想看上述文字的,看我画的这个图:
 

 
整体流程我就不多说了,和上一篇文章介绍的App启动流程是基本一致的。
 
      以上的分析,仅限于ActivityA和ActivityB在相同的进程中,如果在Manifest中指定这两个Activity不在同一个进程中,那么就又是另一套流程了,但是整体流程大同小异。
 
 
      (九)Context家族史
      Activity和Service都有Context,这三个类,还有Application,其实是亲戚一家子。
 

 
Activity因为有了一层Theme,所以中间有个ContextThemeWrapper,相当于它是Service和Application的侄子。
 
      ContextWrapper只是一个包装类,没有任何具体的实现,真正的逻辑都在ContextImpl里面。
 
一个应用包含的Context个数:Service个数+Activity个数+1(Application类本身对应一个Context对象)。
 
应用程序中包含多个ContextImpl对象,而其内部变量mPackageInfo指向同一个PackageInfo对象。
 
- - - - - - - - - - - - - 华丽的分割线,以下是例子- - - - - - - - - - - - - - - - - - - -
 
      我们就拿Activity举例子,看看Activity和Context的联系和区别。
 
      我们知道,跳转到一个新的Activity要这么写:
 

 
我们还知道,也可以在Activity中使用getApplicationContext方法获取Context上下文信息,然后使用Context 的startActivity方法,启动一个新的Activity:
 

 
这二者的区别是什么?我们画个图,就看明白了:
 

 
 
      因为Context的startActivity方法,我看了在ContextImpl中的源码实现,仍然是从ActivityThread中取出Instrumentation,然后执行execStartActivity方法,这和使用Activity的startActivity方法的流程是一样的。
 
      还记得我们前面分析的App启动流程么?在第五阶段,创建App进程的时候,先创建的ActivityThread,再创建的Application。Application的生命周期是跟着整个App走的。
      而getApplicationContext得到的Context,就是从ActivityThread中取出来的Application对象,所以这个Context上下文,使用时要当心,容易引起内存泄露。
 

 
 
下一篇文章,我们就要迈进Service的世界了。
写给Android App开发人员看的Android底层知识(5)
     (十)Service
     Service有两套流程,一套是启动流程,另一套是绑定流程。我们做App开发的同学都应该知道。
 
     
     1)在新进程启动Service
       我们先看Service启动过程,假设要启动的Service是在一个新的进程中,分为5个阶段:
       1)App向AMS发送一个启动Service的消息。
       2)AMS检查启动Service的进程是否存在,如果不存在,先把Service信息存下来,然后创建一个新的进程。
       3)新进程启动后,通知AMS说我可以啦。
       4)AMS把刚才保存的Service信息发送给新进程
       5)新进程启动Service
 
     我们仔细看一下这5个阶段:
 
     第1阶段
 
     
 
     和Activity非常像,仍然是通过AMM/AMP把要启动的Service信息发送给AMS。
 
     第2阶段
     AMS检查Service是否在Manifest中声明了,没声明会直接报错。
 
     AMS检查启动Service的进程是否存在,如果不存在,先把Service信息存下来,然后创建一个新的进程。
 
     在AMS中,每个Service,都使用ServiceRecord对象来保存。
 
     第3阶段
     Service所在的新进程启动的过程,就和前面介绍App启动时的过程差不多。
 
     新进程启动后,也会创建新的ActivityThread,然后把ActivityThread对象通过AMP传递给AMS,告诉AMS,新进程启动成功了。
 
     第4阶段
     AMS把传进来的ActivityThread对象改造为ApplicationThreadProxy,也就是ATP,通过ATP,把要启动的Service信息发送给新进程。
 
     第5阶段
 
     
 
     新进程通过ApplicationThread接收到AMS的信息,和前面介绍的启动Activity的最后一步相同,借助于ActivityThread和H,执行Service的onCreate方法。在此期间,为Service创建了Context上下文对象,并与Service相关联。
 
     需要重点关注的是ActivityThread的handleCreateService方法,
 
     
 
     你会发现,这段代码和前面介绍的handleLaunchActivity差不多,都是从PMS中取出包的信息packageInfo,这是一个LoadedApk对象,然后获取它的classloader,反射出来一个类的对象,在这里反射的是Service。
     四大组件的逻辑都是如此,所以我们要做插件化,可以在这里做文章,换成插件的classloader,加载插件中的四大组件。
 
     至此,我们在一个新的进程中启动了一个Service。
 
    2)启动统一进程的Service
     如果是在当前进程启动这个Service,那么上面的步骤就简化为:
       1)App向AMS发送一个启动Service的消息。
       2)AMS例行检查,比如Service是否声明了,把Service在AMS这边注册。AMS发现要启动的Service就是App所在的Service,就通知App启动这个Service。
       3)App启动Service。
 
     我们看到,没有了启动新进程的过程。
 
    3)在同一进程绑定Service
     如果是在当前进程绑定这个Service呢?过程是这样的:
       1)App向AMS发送一个绑定Service的消息。
       2)AMS例行检查,比如Service是否声明了,把Service在AMS这边注册。AMS发现要启动的Service就是App所在的Service,就先通知App启动这个Service,然后再通知App,对Service进行绑定操作。
       3)App收到AMS第1个消息,启动Service,
       4)App收到AMS第2个消息,绑定Service,并把一个Binder对象传给AMS
       5)AMS把接收到的Binder对象,发送给App
       6)App收到Binder对象,就可以使用了。
 
     你也许会问,都在一个进程,App内部直接使用Binder对象不就好了,其实吧,要考虑不在一个进程的场景,代码又不能写两份,两套逻辑,所以就都放在一起了,即使在同一个进程,也要绕着AMS走一圈。
 
    第1阶段:App向AMS发送一个绑定Service的消息。
 
     
 
     第4阶段:处理第2个消息
 
     
     第5阶段和第6阶段:
 
     这一步是要仔细说的,因为AMS把Binder对象传给App,这里没用ATP和APT,而是用到了AIDL来实现,这个AIDL的名字是IServiceConnection。
 
     
 
     ServiceDispatcher的connect方法,最终会调用ServiceConneciont的onServiceConnected方法,这个方法我们就很熟悉了。App开发人员在这个方法中拿到connection,就可以做自己的事情了。
 
      好了,关于Service的底层知识,我们就全都介绍完了。当你再去编写一个Service时,是否感觉对这个组件理解的更透彻了呢?
 
     下一篇我们聊一聊BroadcastReceiver。
写给Android App开发人员看的Android底层知识(6)
(十一)BroadcastReceiver
BroadcastReceiver,也就是广播,简称Receiver。
 
     很多App开发人员表示,从来没用过Receiver。其实吧,对于音乐播放类App,用Service和Receiver还是蛮多的,如果你用过QQ音乐,App退到后台,音乐照样播放不会停止,这就是你写的Service在后台起作用。
     在前台的Activity,点击停止按钮,就会给后台Service发送一个Receiver,通知它停止播放音乐;点击播放按钮,仍然是发送这个Receiver,只是携带的值变了,所以Service收到请求后播放音乐。
     反过来,后台Service播放每播放完一首音乐,接下来准备播放下一首音乐的时候,就会给前台Activity发Receiver,让Activity显示下一首音乐的名称。
所以音乐播放器的原理,就是一个前后台Activity和Service互相发送和接收Receiver的过程。
 
     
 
     Receiver分静态广播和动态广播两种。
     在Manifest中声明的Receiver,是静态广播:
     
     
     
     在程序中手动写注册代码的,是动态广播:
 
     
 
     二者具有相同的功能,只是写法不同。既然如此,我们就可以把所有静态广播,都改为动态广播,这就省的在Manifest文件中声明了,避免了AMS检查。接下来你想到什么?对,Receiver的插件化解决方案,就是这个思路。
     接下来我们看Receiver是怎么和AMS打交道的,分为两部分,一是注册,二是发送广播。
     你只有注册了这个广播,发送这个广播时,才能通知到你,执行onReceive方法。
      
     我们就拿音乐播放器来举例,在Activity注册Receiver,在Service发送广播。Service播放下一首音乐时,会通知Activity修改当前正在播放的音乐名称。
 
     注册过程如下所示:
     1)在Activity中,注册Receiver,并通知AMS。
 
     
     这里Activity使用了Context提供的registerReceiver方法,然后通过AMN/AMP,把一个receiver传给AMS。
     在创建这个Receiver对象的时候,需要为receiver指定IntentFilter,这个filter就是Receiver的身份证,用来描述receiver。
 
     在Context的registerReceiver方法中,它会使用PMS获取到包的信息,也就是LoadedApk对象。
     就是这个LoadedApk对象,它的getReceiverDispatcher方法,将Receiver封装成一个实现了IIntentReceiver接口的Binder对象。
     我们就是将这个Binder对象和filter传递给AMS。
 
     只传递Receiver给AMS是不够的,当发送广播时,AMS不知道该发给谁啊?所以Activity所在的进程还要把自身对象也发送给AMS。
 
     2)AMS收到消息后,就会把上面这些信息,存在一个列表中,这个列表中保存了所有的Receiver。
     注意了,这里忙活半天,都是在注册动态receiver。
     静态receiver什么时候注册到AMS的呢?是在App安装的时候。PMS会解析Manifest中的四大组件信息,把其中的receiver存起来。
 
     动态receiver和静态receiver分别存在AMS不同的变量中,在发送广播的时候,会把两种receiver合并到一起,然后以此发送。其中动态的排在静态的前面,所以动态receiver永远优先于静态receiver收到消息。
 
 
     此外,Android系统每次启动的时候,也会把静态广播接收者注册到AMS。因为Android系统每次启动时,都会重新安装所有的apk,详细流程,我们会在后面PMS的相关章节看到。
 
     - - - - - - - - - - - - - - - 华丽的分界线------------------------
 
    发送广播的流程如下:
     1)在Service中,通过AMM/AMP,发送广播给AMS,广播中携带着Filter。
     2)AMS收到这个广播后,在receiver列表中,根据filter找到对应的receiver,可能是多个,把它们都放到一个广播队列中。最后向AMS的消息队列发送一个消息。
     当消息队列中的这个消息被处理时,AMS就从广播队列中找到合适的receiver,向广播接收者所在的进程发送广播。
     3)receiver所在的进程收到广播,并没有把广播直接发给receiver,而是将广播封装成一个消息,发送到主线程的消息队列中,当这个消息被处理时,才会把这个消息中的广播发送给receiver。
 
     我们下面通过图,仔细看一下这3个阶段:
     第1步,Service发送广播给AMS
 
     
     发送广播,是通过Intent这个参数,携带了Filter,从而告诉AMS,什么样的receiver能接受这个广播。
 
     第2步,AMS接收广播,发送广播。
     接收广播和发送广播是不同步的。AMS每接收到一个广播,就把它扔到广播发送队列中,至于发送是否成功,它就不管了。
     因为receiver分为无序receiver和有序receiver,所以广播发送队列也分为两个,分别发送这两种广播。
 
     AMS发送广播给客户端,这又是一个跨进程通信,还是通过ATP,把消息发给APT。因为要传递Receiver这个对象,所以它也是一个Binder对象,才可以传过去。我们前面说过,在把Receiver注册到AMS的时候,会把Receiver封装为一个IIntentReceiver接口的Binder对象。那么接下来,AMS就是把这个IIntentReceiver接口对象传回来。
 
     第3步,App处理广播
 
     
     这个流程描述如下:
     1)消息从AMS传到客户端,把AMS中的IIntentReceiver接口对象转为InnerReceiver对象,这就是receiver,这是一个AIDL跨进程通信。
     2)然后在ReceiverDispatcher中封装一个Args对象(这是一个Runnable对象,要实现run方法),包括广播接收者所需要的所有信息,交给ActivtyThread来发送
     3)接下来要走的路就是我们所熟悉的了,ActivtyThread把Args消息扔到H这个Hanlder中,向主线程消息队列发送消息。等到执行Args消息的时候,自然是执行Args的run方法。
     4)在Args的run方法中,实例化一个Receiver对象,调用它的onReceiver方法。
     5)最后,在Args的run方法中,随着Receiver的onReceiver方法调用结束,会通过AMN/AMP发送一个消息个AMS,告诉AMS,广播发送成功了。AMS得到通知后,就发送广播给下一个Receiver。
     注意:InnerReceiver是IIntentReceiver的stub,是Binder对象的接收端。
 
 
    广播的种类
     Android广播按发送方式分类有三种:无序广播、有序广播(OrderedBroadcast)和粘性广播(StickyBroadcast)。
 
     1)无序广播是最普通的广播。
     2)有序广播区别于无序广播,就在于它可以指定优先级。
这两种receiver存在AMS不同的变量中,可以认为是两个receiver集合,发送不同类别的广播。
     3)粘性广播是无序广播的一种。
     粘性广播,我们平常见的不多,但我说一个场景你就明白了,那就是电池电量。当电量小于20%的时候,就会提示用户。
     而获取电池的电量信息,就是通过广播来实现的。
     但是一般的广播,发完就完了。我们需要有这样一种广播,发出后,还能一直存在,未来的注册者也能收到这个广播,这种广播就是粘性广播。
 
     由于动态receiver只能在Activity的onCreate()方法调用时才能注册再接收广播,所以当程序没有运行就不能接受到广播;但是静态注册的则不依赖于程序是否处于运行状态。
 
     至此,关于广播的所有概念,就全都介绍完了,就连我都比较惊讶的是,我居然没贴什么代码。希望上述这两千多字,能引导App开发人员进入一个神奇的世界。
 
     下一篇文章,我们去看看ContentProvider。
写给Android App开发人员看的Android底层知识(7)
(十二)ContentProvider
 
(1)ContentProvider是什么?
ContentProvider,简称CP。
做App开发的同学,尤其是电商类App,对CP并不熟悉,对这个概念的最大程度的了解,也仅仅是建立在书本上,它是Android四大组件中的一个。
做系统管理类的App,比如说手机助手这种,有机会频繁使用CP。
而对于应用类App,数据通常存在服务器端,其它应用类App也想使用的时候,一般都是从服务器取数据,所以没机会使用到CP。
 
有时候我们会在自己的App中读取通信录或者短信的数据,这时候就需要用到CP了。通信录或者短信的数据,是以CP的形式提供的,我们在App这边,是使用方。
      对于做应用类App的同学,很少有机会自定义CP供其它App使用。
 
我们快速回顾一下在App中怎么使用CP。
 

 
1)定义CP的App1:
       在App1中定义一个CP的子类MyContentProvider,并在Manifest中声明,为此要在MyContentProvider中实现CP的增删改查四个方法:
 

 

 
2)使用CP的App2:
     在App2访问App1中定义的CP,为此,要使用到ContentResolver,它也提供了增删改查4个方法,用于访问App1中定义的CP:
 

 
首先我们看一下ContentResolver的增删改查这4个方法的底层实现,其实都是和AMS通信,最终调用App1的CP的增删改查4个方法,后面我们会讲到这个流程是怎么样的。
 
      其次,URI是CP的身份证,唯一标识。
      我们在App1中为CP声明URI,也就是authorities的值为baobao,那么在App2中想使用它,就在ContentResolver的增删改查4个方法中指定URI,格式为:
uri = Uri.parse("content://baobao/");
 
      接下来把两个App都进入debug模式,就可以从App2调试进入App1了,比如说,query操作。
 
     (2)CP的本质
      CP的本质是把数据存储在SQLite数据库中。
    
      各种数据源,有各种格式,比如短信、通信录,它们在SQLite中就是不同的数据表,但是对外界的使用者而言,就需要封装成统一的访问方式,比如说对于数据集合而言,必须要提供增删改查四个方法,于是我们在SQLite之上封装了一层,也就是CP。
 
     (3)匿名共享内存(ASM)
CP读取数据使用到了匿名共享内存,英文简称ASM,所以你看上面CP和AMS通信忙的不亦乐乎,其实下面别有一番风景。
      关于ASM的概念,它其实也是个Binder通信,我画个图哦,你们就明白了:
 

 
什么?还没看懂?那我再画一个类的交互关系图:
 

 
这里的CursorWindow就是匿名共享内存。
 
这个流程,简单来说是这样的:
1)Client内部有一个CursorWindow对象,发送请求的时候,把这个CursorWindow类型的对象传过去,这个对象暂时为空。
2)Server收到请求,搜集数据,填充到这个CursorWindow对象。
      3)Client读取内部的这个CursorWindow对象,获取到数据。
 
      由此可见,这个CursorWindow对象,就是匿名共享内存,这是同一块匿名内存。   
举个生活中的例子就是,你定牛奶,在你家门口放个箱子,送牛奶的人每天早上往这个箱子放一袋牛奶,你睡醒了去箱子里取牛奶。这个牛奶箱就是匿名共享内存。
 
 
 
     (4)CP与AMS的通信流程
      接下来我们看一下CP是怎么和AMS通信的。
能坚持看到这里的人,都不容易。我努力多贴图,不贴代码,即使有代码,也是App开发人员能看懂的代码。
还是拿App2想访问App1中定义的CP为例子。我们就看CP的insert方法。
 

 
     上面这5行代码,包括了启动CP和执行CP方法两部分,分水岭在insert方法,insert方法的实现,前半部分仍然是在启动CP,当CP启动后获取到CP的代理对象,后半部分是通过代理对象,调用insert方法。
 
整体的流程如下图所示:
 

 
1)App2发送消息给AMS,想要访问App1中的CP。
2)AMS检查发现,App1中的CP没启动过,为此新开一个进程,启动App1,然后获取到App1启动的CP,把CP的代理对象返回给App2。
      3)App2拿到CP的代理对象,也就是IContentProvider,就调用它的增删改查4个方法了,接下来就是使用ASM来传输数据或者修改数据了,也就是上面提到的CursorWindow这个类,取得数据或者操作结果即可,作为App的开发人员,不需要知道太多底层的详细信息,用不上。
 
至此,关于CP的介绍就结束了。下一篇文章,我们看一下App的安装流程,也就PMS。
写给Android App开发人员看的Android底层知识(8)
(十)PMS及App安装过程
       PMS,全称PackageManagerService,是用来获取Apk包的信息的。
 
       在前面分析四大组件与AMS通信的时候,我们介绍过,AMS总是会使用PMS加载包的信息,将其封装在LoadedApk这个类对象中,然后我们就可以从中取出在manifest声明的四大组件信息了。
 
       (一)
       在下载并安装App的过程,会把Apk存放在data/app目录下。
Apk是一个zip压缩包,在文件头会记录压缩包的大小,所以后续在文件尾巴就算是追加一部小电影,也不会对解压造成影响——木马其实就是这个思路,在可执行文件exe尾巴上挂一个木马病毒,执行exe的同时也会执行这个木马,然后你就中招了。
       我们可以把木马思想运用在Android多渠道打包上。在比较老的Android 4.4版本中,我们会在Apk尾巴上追加几个字节,来标记Apk的渠道。Apk启动的时候,从apk中的尾巴上读取这个渠道值。
       后来Google也发现这个安全漏洞了,在新版本的系统中,就会在Apk安装的时候,检查Apk的实际大小,看这个值与Apk的头部记录的压缩包大小,是否相等,不相等就会报错说安装失败。
      
       (二)
       我们继续说App的安装过程。Android系统使用PMS解析这个Apk中的manifest文件,包括:
   四大组件的信息,比如说,前面讲过的静态Receiver。比如说默认启动的Activity。
   分配用户Id和用户组Id。用户Id是唯一的,因为Android是一个Linux系统。用户组Id指的是各种权限,每个权限都在一个用户组中,比如读写SD卡,比如网络访问,分配了哪些用户组Id,就拥有了哪些权限。
 
       3)在Launcher生成一个icon,icon中保存着默认启动的Activity的信息。
 
       4)App安装过程的最后,是把上面这些信息记录在一个xml文件中,以备下次安装时再次使用。
 
       (三)
       其实,在Android手机系统每次启动的时候,都会使用PMS,把Android系统中的所有Apk都安装一遍,一共4个步骤,如下所示:
 

 
其中的第3步、第4步,和单独安装一个App的步骤是一样的。我们分析一下前两步:
      第1步,因为结束安装的时候,都会把安装信息保存在xml文件中,所以Android系统再次启动时,再次重新安装所有的Apk,就可以直接读取之前保存的xml文件了。
      第2步,从5个目录中读取并安装所有的apk。
 
      最后,回答前面提及的一个问题,为什么App安装时,不把它解压呢?直接从解压文件中读取资源文件比如图片是不是更快呢?其实并不是这样的,这部分逻辑需要到底层C++的代码去寻找,我没有具体看过,只是道听途说问过Lody,他是这么给我解释的:
      每次从apk中读取资源,并不是先解压再找图片资源,而是解析Apk中的Resource.arsc文件,这个文件中存储着资源的所有信息,包括资源在Apk中的地址、大小等等,按图索骥,从这个文件中快速找到相应的资源文件。这是一种很高效的算法。
      不解压Apk的好处,自然是节省空间。

 

猜你喜欢

转载自blog.csdn.net/qingdaohaishanhu/article/details/87884553
今日推荐