杂谈——Android从启动到程序运行发生的事情
前言
好久没有写博客了,瞬间感觉好多学了的东西不进行一个自我的总结与消化总归变不成自己的。通过博客可能还可以找到一些当初在学习的时候没有想到的问题。想了半天,从大二上学期自学Android以来还没有对Android从启动到程序运行期间进行一个完整的归纳,刚好最近又学到了一些新东西,那就以这篇博客为媒介,总结一下从Android启动到程序运行期间发生的所有事吧。包括什么ClassLoader, JVM,IPC, 消息处理机制要是总结到了就顺带BB一下。但是这里就不包含很多细节了,比如为什么PMS内部为什么要这么构造,好处是什么,如果我来设计的话我会怎么设计啊这种暂时就不总结了,因为我觉得以我现在的水平还有学习精力来说把这些细节都一个个的弄清楚有点没抓住重点。现阶段还是先能够了解整个流程,有个大局观才是最重要的。至于以后如果有需要或者是有精力的时候再一个个的突破。
发现本文的错误或者遗漏后会立刻更改
在正式开始之前还是忍不住想要BB一下最近参加的京东笔试,被坑得有点憋屈。憋屈啥勒,被编译器坑了。这次京东的笔试说实话感觉真的好简单,真的没有什么技术上的难点,但是尼玛编程题把我坑了。提前一个小时把代码在本地编译器上编译完成并通过,当时心里还有些小激动,一提交,在线编译器说得不到指定结果,尼玛,顿时整个人都斯巴达了。最开始的时候还以为是自己本身代码的Bug,后来顺着思路又理了几遍,完全没问题啊,又自己创了几个新的输入也都能够运行,返回正常结果。整个人都是崩溃的,在这上面花了20多分钟时候不经意间瞥了一下左边的样例输入和输出,哦豁,这下全懂了。
因为我没有很多这种参加在线笔试的经验,也没在网上怎么刷题,所以在样例输入和输出那里掺杂了一些自己想当然的想法。
题目要求的样例输入是一直输入,有两种情况,一种情况返回No,一种情况返回Yes并返回对应的结果。是要求连续输入的,也就是你在输入的时候我至少要用一个数组或者是List、Map来保存你的输入。当检测到输入为空也就是直接按了回车的同时就开始运行,然后再一次性的打印出结果。我不知道啊,第一次看这种样例输入输出,一看以为只要能返回就好了,然后就是分开做的,输入错的就返回No,输入对的就返回Yes和结果,并不能够一起输入及返回。而这个时候时间又过了好多了,改代码的话整个代码的架构都要变,时间上完全来不及。这笔试要是编程题错了那估计是没戏了。
这其实也怪自己吧,怨不得别的,只好等下次了,只是这次的题真的简单,错过了好可惜,毕竟还是非常想进京东锻炼锻炼的,就算进不了去体验京东的面试,知道哪里有不足也是好的。
正式开始
上面BB了这么多,也是超过了我的预料,这里就正式开始这篇博客了。
首先,我们知道,Android是基于Linux的一个操作系统,它可以分为五层,下面是它的层次架构图,可以记一下,因为后面应该会总结到SystemServer这些Application Framework层的东西
Android的五层架构从上到下依次是应用层,应用框架层,库层,运行时层以及Linux内核层。
而在Linux中,它的启动可以归为一下几个流程:
Boot Loader-》初始化内核-》。。。。。。
当初始化内核之后,就会启动一个相当重要的祖先进程,也就是init进程,在Linux中所有的进程都是由init进程直接或间接fork出来的。
而对于Android来说,前面的流程都是一样的,而当init进程创建之后,会fork出一个Zygote进程,这个进程是所有Java进程的父进程。我们知道,Linux是基于C的,而Android是基于Java的(当然底层也是C)。所以这里就会fork出一个Zygote Java进程用来fork出其他的进程。【断点1】
总结到了这里就提一下之后会谈到的几个非常重要的对象以及一个很重要的概念。
ActivityManagerServices(AMS):它是一个服务端对象,负责所有的Activity的生命周期,ActivityThread会通过Binder与之交互,而AMS与Zygote之间进行交互则是通过Socket通信(IPC通信在之后会总结到) ActivityThread:它也就是我们俗称的UI线程/主线程,它里面存在一个main()方法,这也是APP的真正入口,当APP启动时,就会启动ActivityThread中的main方法,它会初始化一些对象,然后开启消息循环队列(之后总结),之后就会Looper.loop死循环,如果有消息就执行,没有就等着,也就是事件驱动模型(edt)的原理。 ApplicationThread:它实现了IBinder接口,是Activity整个框架中客户端和服务端AMS之间通信的接口,同时也是ActivityThread的内部类。这样就有效的把ActivityThread和AMS绑定在一起了。 Instrumentation:这个东西我把它理解为ActivityThread的一个工具类,也算是一个劳动者吧,对于生命周期的所有操作例如onCreate最终都是直接由它来执行的。Android系统中的客户端和服务器的概念
在Android系统中其实也存在着服务器和客户端的概念,服务器端指的就是所有App共用的系统服务,比如上面的AMS,PackageManagerService等等,这些系统服务是被所有的App共用的,当某个App想要实现某个操作的时候,就会通知这些系统服务。
继续断点1
当Zygote被初始化的时候,会fork出System Server进程,这个进程在整个的Android进程中是非常重要的一个,地位和Zygote等同,它是属于Application Framework层的,Android中的所有服务,例如AMS, WindowsManager, PackageManagerService等等都是由这个SystemServer fork出来的。所以它的地位可见一斑。
而当System Server进程开启的时候,就会初始化AMS,同时,会加载本地系统的服务库,创建系统上下文,创建ActivityThread及开启各种服务等等。而在这之后,就会开启系统的Launcher程序,完成系统界面的加载与显示。【断点2】
Context总结
Context是一个抽象类,下面是它的注释信息,摘自源码。
/**
* Interface to global information about an application environment. This is
* an abstract class whose implementation is provided by
* the Android system. It
* allows access to application-specific resources and classes, as well as
* up-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {
从上面的这段话可以简单理解一下,Context是一个关于应用程序环境的全局变量接口,通过它可以允许去获得资源或者类,例如启动Activity,广播,intent等等。
我的理解:Context的具体实现是Application, Activity,Service,通过Context能够有权限去做一些事情,其实我觉得就是一个运行环境的问题。
需要注意的地方
Android开发中由于很多地方都包含了Context的使用,因此就必须要注意到内存泄露或者是一些可能会引起的问题。
例如在Toast中,它的Context就最好设置为Application Context,因为如果Toast在显示东西的时候Activity关闭了,但是由于Toast仍然持有Activity的引用,那么这个Activity就不会被回收掉,也就造成了内存泄露。
Toast的相关总结
上面举例的时候举到了Toast,其实Toast也是很有意思的一个东西,它的show方法其实并不是显示一个东西这么简单。
Toast实际上是一个队列,会通过show方法把新的任务加入到队列当中去,列队中只要存在消息就会弹出来使用,而队列的长度据说默认是40个(这是网上搜出来的,我在源码中没找到对应的设置,感觉也没啥必要就没找了)。
所以这里就要注意一下show这个操作了,它并不是显示内容,而是把内容入队列。
/**
* Show the view for the specified duration.
*/
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
Handler的内存泄露
对于Handler来说,如果我们直接在AndroidStudio中创建一个非静态内部类Handler,那么Handler这一大片的区域会被AS标记为黄色,这个应该很多人都遇到过吧。实际上是因为这样设置会造成内存泄露,因为每一个非静态内部类都会持有一个外部类的引用,那么这里也就产生了一个内存泄露的可能点,如果当Activity被销毁时没有与Handler解除,那么Handler仍然会持有对该Activity的引用,那么就造成了内存泄露。
解决方案
使用static修饰Handler,这样也就成了一个静态内部类,那么就不会持有对外部类的引用了。而这个时候就可以在Handler中创建一个WeakReference(弱引用)来持有外部的对象。只要外部解除了与该引用的绑定,那么垃圾回收器就会在发现该弱引用的时候立刻回收掉它。
四种引用方式
上面扯到了弱引用,就再BB一下四种引用方式吧。
强引用:垃圾回收器打死都不会回收掉一个强引用的,那怕是出现OOM