首页 > 开发 > Android > 正文

Android从启动到程序运行发生的事情

2016-04-17 22:38:56  来源:极客头条

  转载请注明出处
博客地址:http://blog.csdn.net/JonsTank2013/article/details/51118563
作者:李中权
前言   好久没有写博客了,瞬间感觉好多学了的东西不进行一个自我的总结与消化总归变不成自己的。通过博客可能还可以找到一些当初在学习的时候没有想到的问题。想了半天,从大二上学期自学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();        }        INotificationManager service = getService();        String pkg = mContext.getOpPackageName();        TN tn = mTN;        tn.mNextView = mNextView;        try {            service.enqueueToast(pkg, tn, mDuration);        } catch (RemoteException e) {                    }    } Handler的内存泄露   对于Handler来说,如果我们直接在AndroidStudio中创建一个非静态内部类Handler,那么Handler这一大片的区域会被AS标记为黄色,这个应该很多人都遇到过吧。实际上是因为这样设置会造成内存泄露,因为每一个非静态内部类都会持有一个外部类的引用,那么这里也就产生了一个内存泄露的可能点,如果当Activity被销毁时没有与Handler解除,那么Handler仍然会持有对该Activity的引用,那么就造成了内存泄露。
  解决方案
使用static修饰Handler,这样也就成了一个静态内部类,那么就不会持有对外部类的引用了。而这个时候就可以在Handler中创建一个WeakReference(弱引用)来持有外部的对象。只要外部解除了与该引用的绑定,那么垃圾回收器就会在发现该弱引用的时候立刻回收掉它。
垃圾回收   关于垃圾回收的相关总结看我之前的博客,传送门:JVM原理及底层探索
四种引用方式   上面扯到了弱引用,就再BB一下四种引用方式吧。
强引用:垃圾回收器打死都不会回收掉一个强引用的,那怕是出现OOM也不会回收掉强引用,所有new出来的都是强引用。 软引用:垃圾回收器会在内存不足的情况下回收掉软引用,如果内存充足的话不会理它 弱引用:它跟软引用类似,但是它更脆弱,只要垃圾回收器一发现它,就会立刻回收掉它。比如一个对象持有一个强引用和弱引用,当强引用被取消时,那么只要GC发现了它,就会立刻回收掉。只是GC发现它的这个过程是不确定的,有可能不会马上发生,所以它可能还会多活一会,中间存在一个优先级。 虚引用:它跟上面3种方式都不同。我对虚引用的理解就是如果一个对象持有虚引用,那么就可以在被GC回收前进行一些设定好的工作等等。因为虚引用有个机制,因为虚引用必须和引用队列联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就回在回收对象的内存前,把这个虚引用加入到与之关联的引用队列中。而程序如果判断到引用队列中已经加入了虚引用,那么就可以了解到被引用的对象马上就要被垃圾回收了,这个时候就可以做些被回收之前的事情啦。   类加载器按层次从顶层到下依次为Boorsrtap ClassLoader(启动类加载器),Extension ClassLoader(拓展类加载器),ApplicationClassLoader(应用程序类加载器)
  判断两个类是否是同一个类就是看它们是否是由同一个类加载器加载而来。
  这里就需要介绍一下双亲委派模式了:
双亲委派模式的意思就是:除了启动类加载器之外,其余的加载器都需要指定一个父类的加载器,当需要加载的时候会先让父类去试着加载,如果父类无法加载也就是找不到这个类的话就会让子类去加载
  好处:防止内存中出现多份同样的字节码
  比如类A和类B都要加载system类,如果不是委托的话,类A就会加载一份,B也会加载一份,那么就会出现两份SYstem字节码
如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下,如果A用这个已经加载了的话会直接返回内存中的system而不需要重新加载。那么就只会存在一份
延迟加载的应用:单例模式   对于Java来说,类是需要使用到时才会加载,这里也就出现了一个延迟加载的效果。而在延迟加载的时候,会默认保持同步。这也就产生了一种单例模式的方式,具体的看我之前的博客:设计模式_单例模式
  我觉得在android所有的创建单例模式方法中里延迟加载方式是最好吧,虽然枚举比延迟加载更好,effiective java中也很推荐,但是并不怎么适用于Android,Android里枚举的消耗是static的两倍,延迟加载的话只要我们在使用延迟加载方式时做好反序列化的返回值readResolve()准备就好了。
继续断点2   上面BB了太多其他的,现在有点缓不过来,下次自己看自己博客的时候会不会都被自己的思路带得乱七八糟的。
  上面的时候我们就已经完成了整个Android系统的开机以及初始化。接下来就可以B一下从点击APP图标开始到APP内部程序运行起来的流程了。
  当我们点击屏幕时,触摸屏的两层电极会连接在一起,也就产生了一个电压(具体的我忘了,书上有,图找不到了),当产生电压的时候,就可以通过对应的驱动把当前按压点的XY坐标传给上层,这里也就是操作系统。操作系统在获取到XY值的时候,就会对按压点的范围进行一个判断,如果确定按压点处于一个APP图标或者是Button等等的范围中时,操作系统也就会认为用户当前已经点击了这个东西,启动对应的监听。
  而当系统判断我们点击的是APP图标时,该App就由Launcher开始启动了【断点3】
Launcher   Launcher是一个继承自Activity,同时实现了点击事件,长按事件等等的一个应用程序。
public final class Launcher extends Activity        implements View.OnClickListener,OnLongClickListener, LauncherModel.Callbacks,View.OnTouchListener   当我们点击一个APP的图标时,会调用Launcher内部的startActivitySafely()方法,而这个方法则会进行两件事,一个是启动目标activity,另一个功能就是捕获异常ActivityNotFoundException,也就是常见的“找不到activity,是否已经在androidmenifest文件中注册?”。而在startActivity方法中,经过一系列的转换最终会调用到startActivityForResult这个方法。
@Override    public void startActivity(Intent intent, @Nullable Bundle options) {        if (options != null) {            startActivityForResult(intent, -1, options);        } else {                                    startActivityForResult(intent, -1);        }    }   所以实际上,我对整个Android的界面是这样理解的:
当系统完成初始化以及各种服务的创建之后,就会启动Launcher这个应用程序(它也是继承自Activity的,包含自己对应的xml布局文件),然后再把各种图标按照一个正常APP布局的方式放在上面,当我们点击APP图标时,也就相当于在Launcher这个APP应用程序中通过startActivity(在底层最后会转为startActivityForResult)来启动这个APP。简单的讲,我觉得就是一个主要的APP(Launcher)里面启动了其他的功能APP,例如QQ、微信这些。【个人理解,如果以后发现不对再修改】
Android中点击事件的处理   当我们手指按下时,Android是如何处理点击事件的呢?如何确定是让哪一个控件来处理呢?
简单一句话:层层传递-冒泡的方式处理
举个例子:现在公司来了个小项目,老板一看分配给经理做,经理一看分配给小组长,小组长一看好简单,分配给组员。如果在这个传递过程中(也就是还为分配到最底部时),某一层觉得我来负责这个比较好的话就会拦截掉这个消息,然后把它处理了,下面的就收不到有消息的这个通知。如果一直到了底层的话,组员如果能完成,就完成它。如果不能完成,那么就报告给组长,说组长我做不来,边学边做要影响进度。组长一看我也做不来,就给经理,经理一看我也不会,就给老板。这样也就一层层的传递了。
总结一下就是消息从上到下依次传递,如果在传递的过程中被拦截了就停止下传。如果没有被拦截,就一直传递到底部,如果底部不能够消耗该消息,那么就又一层层的返回来,返给上层,直到被消耗或者是到达最顶层。
  在Android中,存在三个重要的方法:
dispathTouchEvent(MotionEvent ev) onInterceptTouchEvent(MotionEvent ev) onTouchEvent(MotionEvent ev)   第一个方法负责事件的分发,它的返回值就是表示是否消耗当前事件。
第二个方法是用于判断是否拦截该消息,如果当前View拦截了某个时间,那么在同一个事件序列中,此方法不会被再次调用。返回结果表示是否拦截当前事件
第三个方法就是处理事件。返回结果表示是否消耗当前事件,如果不小号,则在同一时间序列中,当前View无法再次接收到事件。
  对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,调用它的dispath方法。如果这个ViewGroup的onIntercept方法返回true就表示它要拦截当前事件,false就表示不拦截,这个时候事件就会继续传递给子元素,接着调用子元素的dispath方法,直到被处理。
滑动冲突   顺带总结一下滑动冲突的解决吧
View的滑动冲突一般可以分为三种:
外部滑动和内部滑动方向不一致 外部滑动方向和内部滑动方向一致 嵌套上面两种情况   比如说一个常见的,外部一个ListView,里面一个ScrollView。这个时候该怎么解决呢?其实这里想到了ViewPager,它里面实际上是解决了滑动冲突的,可以借鉴一下它的。
  滑动处理规则
一般来说,我们可以根据用户手指滑动的方向以及角度来判断用户是要朝着哪个方向去滑动。而很多时候还可以根据项目的需求来指定一套合适的滑动方案。
  外部拦截法
这种方法就是指所有的点击时间都经过父容器的拦截处理,如果父容器需要此时间就拦截,如果不需要此事件就不拦截。通过重写父容器的onInterceptTouchEvent方法:
case MotionEvent.ACTION_DOWN:    intercepted = false; break; case MotionEvent.ACTION_MOVE: if(父类容器需要) {    intercepted = true; } else {    intercepted = false; } break; case MotionEvent.ACTION_UP:    intercepted = false; break; return intercepted;   这里有一点需要注意,ACTION_DOWN事件父类容器就必须返回false,因为如果父类容器拦截了的话,后面的Move等所有事件都会直接由父类容器处理,就无法传给子元素了。UP事件也要返回false,因为它本身来说没有太多的意义,但是对于子元素就不同了,如果拦截了,那么子元素的onClick事件就无法触发。
  内部拦截法
这种方法指的是父容器不拦截任何时间,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理。它需要配合requestDisallowInterceptTouchEvent方法才能正常工作。我们需要重写子元素的dispatch方法
case MotionEvent.ACTION_DOWN:    parent.requestDisallowInterceptTouchEvent(true); break; MotionEvent.ACTION_MOVE:    if(父容器需要此类点击事件) {    parent.requestDisallowInterceptTouchEvent(false);    } break; return super.dispatchTouchEvent(event);   这种方法的话父类容器需要默认拦截除了ACTION_DOWN以外的其他时间,这样当子元素调用request方法的时候父元素才能继续拦截所需的事件。
  其他的
如果觉得上面两个方式太复杂,看晕了,其实也可以自己根据项目的实际需要来指定自己的策略实现。例如根据你手指按的点的位置来判断你当前触碰的是哪个控件,以此来猜测用户是否是要对这个控件进行操作。如果点击的是空白的地方,就操作外部控件即可。
  【等有时间了就把ViewPager的处理总结一下,挺重要的】
继续断点3 当我们点击桌面的APP图标时,Launcher进程会采用Binder的方式向AMS发出startActivity请求 AMS在接收到请求之后,就会通过Socket向Zygote进程发送创建进程的请求 Zygote进程会fork出新的子进程(APP进程) 之后APP进程会再向AMS发起一次请求,AMS收到之后经过一系列的准备工作再回传请求。 APP进程收到AMS返回的请求后,会利用Handler向主线程发送LAUNCH_ACTIVITY消息 主线程在收到消息之后,就创建目标Activity,并回调onCreate()/onStart()/onResume()等方法,UI渲染结束后便可以看到App主界面
【断点4】 Handler/Looper/Message Queue/ThreadLocal机制   Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑
  虽然MessageQueue叫做消息队列,但是实际上它内部的存储结构是单链表的方式。由于Message只是一个消息的存储单元,它不能去处理消息,这个时候Looper就弥补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待(机制等会介绍)。而对于Looper来说,存在着另外的一个很重要的概念,就是ThreadLocal。
ThreadLocal   ThreadLocal它并不是一个线程,而是一个可以在每个线程中存储数据的数据存储类,通过它可以在指定的线程中存储数据,数据存储之后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到该线程的数据。
举个例子,多个线程通过同一个ThreadLocal获取到的东西是不一样的,就算有的时候出现的结果是一样的(偶然性,两个线程里分别存了两份相同的东西),但他们获取的本质是不同的。
  那为什么有这种区别呢?为什么要这样设计呢?
先来研究一下为什么会出现这个结果。
在ThreadLocal中存在着两个很重要的方法,get和set方法,一个读取一个设置。

/**
     * Returns the value of this variable for the current thread. If an entry
     * doesn't yet exist for this variable on this thread, this method will
     * create an entry, populating the value with the result of
     * {@link #initialValue()}.
     *
     * @return the current value of the variable for the calling thread.
     */
    @SuppressWarnings()
    public T get() {
        
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

 /**
     * Sets the value of this variable for the current thread. If set to
     * {@code null}, the value will be set to null and the underlying entry will
     * still be present.
     *
     * @param value the new value of the variable for the caller thread.
     */
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

  摘自源码
首先研究它的get方法吧,从注释上可以看出,get方法会返回一个当前线程的变量值,如果数组不存在就会创建一个新的。
这里有几个很重要的词,就是“当前线程”和“数组”。
这里提到的数组对于每个线程来说都是不同的,values.table,而values是通过当前线程获取到的一个Values对象,因此这个数组是每个线程唯一的,不能共用,而下面的几句话也更直接了,获取一个索引,再返回通过这个索引找到数组中对应的值。这也就解释了为什么多个线程通过同一个ThreadLocal返回的是不同的东西。
  那这里为什么要这么设置呢?翻了一下书,搜了一下资料:
ThreadLocal在日常开发中使用到的地方较少,但是在某些特殊的场景下,通过ThreadLocal可以轻松实现一些看起来很复杂的功能。一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑使用ThreadLocal。例如在Handler和Looper中。对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同的线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松的实现Looper在线程中的存取。如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定的Looper,这样就比较麻烦了,还需要一个管理类。 ThreadLocal的另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,就可能表现为函数调用栈比较深以及代码入口的多样性,这种情况下,我们又需要监听器能够贯穿整个线程的执行过程。这个时候就可以使用到ThreadLocal,通过ThreadLocal可以让监听器作为线程内的全局对象存在,在线程内通过get方法就可以获取到监听器。如果不采用的话,可以使用参数传递,但是这种方式在设计上不是特别好,当调用栈很深的时候,通过参数来传递监听器这个设计太糟糕。而另外一种方式就是使用static静态变量的方式,但是这种方式存在一定的局限性,拓展性并不是特别的强。比如有10个线程在执行,就需要提供10个监听器对象。 消息机制   上面提到了Handler/Looper/Message Queue,它们实际上是一个整体,只不过我们在开发中接触更多的是Handler而已,Handler的主要作用是将一个任务切换到某个指定的线程中去执行,而Android之所以提供这个机制是因为Android规定UI只能在主线程中进程,如果在子线程中访问UI就会抛出异常。
  为什么Android不允许在子线程访问UI
其实这一点不仅仅是对于Android,对于其他的所有图形界面现在都采用的是单线程模式。
因为对于一个多线程来说,如果子线程更改了UI,那么它的相关操作就必须对其他子线程可见,也就是Java并发中很重要的一个概念,线程可见性,Happen-before原则【下篇博客总结一下自己对Java并发的理解吧,挺重要的,总结完后再把传送门贴过来】而一般来说,对于这种并发访问,一般都是采用加锁的机制,但是加锁的机制存在很明显的问题:让UI访问间的逻辑变得复杂,同时效率也会降低。甚至有的时候还会造成死锁的情况,这个时候就麻烦了。
而至于究竟能不能够实现这种UI界面的多线程呢?SUN公司的某个大牛(忘了是谁,很久之前看的,好像是前副总裁)说:“行肯定是没问题,但是非常考技术,因为必须要考虑到很多种情况,这个时候就需要技术专家来设计。而这种设计出来的东西对于广大普通程序员来说又是异常头疼的,就算是实现了多线程,普通人用起来也是怨声载道的。所以建议还是单线程”。
死锁   顺带着BB一下死锁。
  死锁的四个必要条件
互斥条件:资源不能被共享,只能被同一个进程使用 请求与保持条件:已经得到资源的进程可以申请新的资源 非剥夺条件:已经分配的资源不能从相应的进程中被强制剥夺 循环等待条件:系统中若干进程组成环路,该环路中每个进程都在等待相邻进程占用的资源   举个常见的死锁例子:进程A中包含资源A,进程B中包含资源B,A的下一步需要资源B,B的下一步需要资源A,所以它们就互相等待对方占有的资源释放,所以也就产生了一个循环等待死锁。
  处理死锁的方法
忽略该问题,也就是鸵鸟算法。当发生了什么问题时,不管他,直接跳过,无视它。 检测死锁并恢复 资源进行动态分配 破除上面的四种死锁条件之一 继续消息机制   MessageQueue主要包含两个操作:插入和读取,读取操作本身会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。这也就是为什么使用的是一个单链表的数据结构来维护消息列表,因为它在插入和删除上比较有优势(把下一个连接的点切换一下就完成了)。
  而对于MessageQueue的插入操作来说,没什么可以看的,也就这样吧,主要需要注意的是它的读取方法next。

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG,  + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount = 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV7(context, window, callback);
        }
    }

  这里会根据SDK的等级来返回不同的东西,这样的话就不深究了,底层的话我撇了一下,应该原理和Activity是一样的,可能存在一些区别。这里就用Activity来谈谈它的setContentView方法做了什么事。
  在setContentView上面有段注释:
   Set the activity content from a layout resource.  The resource will be inflated, adding all top-level views to the activity.
  这里就介绍了它的功能,它会按照一个布局资源去设置Activity的内容,而这个布局资源将会被引入然后添加所有顶级的Views到这个Activity当中。
这是个啥意思勒。
下面从网上扒了一张图:
这里写图片描述
这里是整个Activity的层级,最外面一层是我们的Activity,它包含里面的所有东西。
再上一层是一个PhoneWindow,这个PhoneWindow是由Window类派生出来的,每一个PhoneWindow中都含有一个DecorView对象,Window是一个抽象类。
再上面一层就是一个DecorView,我理解这个DecorView就是一个ViewGroup,就是装View的。
而在DecoreView中,最上面的View就是我们的TitleActionBar,下面就是我们要设置的content。所以在上面的initWindowDecorActionBar就能猜到是什么意思了吧。
  而在initWindowDecorActionBar方法中,有一段代码:

/**
     * Creates a new ActionBar, locates the inflated ActionBarView,
     * initializes the ActionBar with the view, and sets mActionBar.
     */
    private void initWindowDecorActionBar() {
        Window window = getWindow();

        
        
        window.getDecorView();

        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
            return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }

  注意上面的window.getDecoreView()方法的注释,该方法会设置一些window的标志位,而当这个方法执行完之后,就再也不能更改了,这也就是为什么很多第三方SDK设置window的标志位时一定要求要在setContentView方法前调用。
findViewById   我们通过一个findViewById方法可以实现对象的绑定,那它底层究竟是怎么实现的呢?
  findViewById根据继承的Activity类型的不同也存在着区别,老规矩,还是以Activity的来。

/**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link #onCreate}.
     *
     * @return The view if found or null otherwise.
     */
    @Nullable
    public View findViewById(@IdRes int id) {
        return getWindow().findViewById(id);
    }

  从源码来看,findViewById也是经过了一层层的调用,它的功能如同它上面的注释一样,通过一个view的id属性查找view,这里也可以看到一个熟悉的getWindow方法,说明findViewById()实际上Activity把它也是交给了自己的window来做

/**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link android.app.Activity#onCreate}.  This will
     * implicitly call {@link #getDecorView} for you, with all of the
     * associated side-effects.
     *
     * @return The view if found or null otherwise.
     */
    @Nullable
    public View findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

  而在这里面,又调用了getDecorView的findViewById()方法,这也相当于是一个层层传递的过程,因为DecorView我理解为就是一个ViewGroup,而当运行getDecorView().findViewById()方法时,就会运行View里面的findViewById方法。它会使用这个被给予的id匹配子View的Id,如果匹配,就返回这个View,完成View的绑定

/**
     * Look for a child view with the given id.  If this view has the given
     * id, return this view.
     *
     * @param id The id to search for.
     * @return The view that has the given id in the hierarchy or null
     */
    @Nullable
    public final View findViewById(@IdRes int id) {
        if (id < 0) {
            return null;
        }
        return findViewTraversal(id);
    }

    /**
     * {@hide}
     * @param id the id of the view to be found
     * @return the view of the specified id, null if cannot be found
     */
    protected View findViewTraversal(@IdRes int id) {
        if (id == mID) {
            return this;
        }
        return null;
    }

 

 最后总结一下(Activity中),findViewById的过程是这样的:
Activity -> Window -> DecorView -> View
参考