Android从启动到程序运行整个过程的整理

1Android是基于Linux的一个操做系统,它能够分为五层,下面是它的层次架构图,能够记一下,由于后面应该会总结到SystemServer这些Application Framework层的东西java

Android的五层架构从上到下依次是:应用层,应用框架层,库层,运行时层,Linux内核层。linux

而在Linux中,它的启动能够归为一下几个流程: 
Boot Loader——>初始化内核——>。。。。。。 
当初始化内核以后,就会启动一个至关重要的祖先进程,也就是init进程,在Linux中全部的进程都是由init进程直接或间接fork出来的。
android

而对于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是一个抽象类,下面是它的注释信息,摘自源码。

  1. /**
  2. * Interface to global information about an application environment. This is
  3. * an abstract class whose implementation is provided by
  4. * the Android system. It
  5. * allows access to application-specific resources and classes, as well as
  6. * up-calls for application-level operations such as launching activities,
  7. * broadcasting and receiving intents, etc.
  8. */
  9. 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这个操做了,它并非显示内容,而是把内容入队列。

  1. /**
  2. * Show the view for the specified duration.
  3. */
  4. public void show() {
  5. if (mNextView == null) {
  6. throw new RuntimeException("setView must have been called");
  7. }
  8. INotificationManager service = getService();
  9. String pkg = mContext.getOpPackageName();
  10. TN tn = mTN;
  11. tn.mNextView = mNextView;
  12. try {
  13. service.enqueueToast(pkg, tn, mDuration);
  14. } catch (RemoteException e) {
  15. // Empty
  16. }
  17. }
复制代码

Handler的内存泄露

对于Handler来讲,若是咱们直接在AndroidStudio中建立一个非静态内部类Handler,那么Handler这一大片的区域会被AS标记为黄色,这个应该不少人都遇到过吧。其实是由于这样设置会形成内存泄露,由于每个非静态内部类都会持有一个外部类的引用,那么这里也就产生了一个内存泄露的可能点,若是当Activity被销毁时没有与Handler解除,那么Handler仍然会持有对该Activity的引用,那么就形成了内存泄露。

解决方案 
使用static修饰Handler,这样也就成了一个静态内部类,那么就不会持有对外部类的引用了。而这个时候就能够在Handler中建立一个WeakReference(弱引用)来持有外部的对象。只要外部解除了与该引用的绑定,那么垃圾回收器就会在发现该弱引用的时候马上回收掉它。

垃圾回收

关于垃圾回收的相关总结看我以前的博客,传送门:JVM原理及底层探索

四种引用方式

上面扯到了弱引用,就再BB一下四种引用方式吧。

  • 强引用:垃圾回收器打死都不会回收掉一个强引用的,那怕是出现OOM也不会回收掉强引用,全部new出来的都是强引用。
  • 软引用:垃圾回收器会在内存不足的状况下回收掉软引用,若是内存充足的话不会理它
  • 弱引用:它跟软引用相似,可是它更脆弱,只要垃圾回收器一发现它,就会马上回收掉它。好比一个对象持有一个强引用和弱引用,当强引用被取消时,那么只要GC发现了它,就会马上回收掉。只是GC发现它的这个过程是不肯定的,有可能不会立刻发生,因此它可能还会多活一会,中间存在一个优先级。
  • 虚引用:它跟上面3种方式都不一样。我对虚引用的理解就是若是一个对象持有虚引用,那么就能够在被GC回收前进行一些设定好的工做等等。由于虚引用有个机制,由于虚引用必须和引用队列联合使用,当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就回在回收对象的内存前,把这个虚引用加入到与之关联的引用队列中。而程序若是判断到引用队列中已经加入了虚引用,那么就能够了解到被引用的对象立刻就要被垃圾回收了,这个时候就能够作些被回收以前的事情啦。

ClassLoader

类加载器按层次从顶层到下依次为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,同时实现了点击事件,长按事件等等的一个应用程序。

  1. public final class Launcher extends Activity
  2. implements View.OnClickListener,OnLongClickListener, LauncherModel.Callbacks,View.OnTouchListener
复制代码


当咱们点击一个APP的图标时,会调用Launcher内部的startActivitySafely()方法,而这个方法则会进行两件事,一个是启动目标activity,另外一个功能就是捕获异常ActivityNotFoundException,也就是常见的“找不到activity,是否已经在androidmenifest文件中注册?”。而在startActivity方法中,通过一系列的转换最终会调用到startActivityForResult这个方法。

  1. @Override
  2. public void startActivity(Intent intent, @Nullable Bundle options) {
  3. if (options != null) {
  4. startActivityForResult(intent, -1, options);
  5. } else {
  6. // Note we want to go through this call for compatibility with
  7. // applications that may have overridden the method.
  8. startActivityForResult(intent, -1);
  9. }
  10. }
复制代码

因此实际上,我对整个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方法:

  1. case MotionEvent.ACTION_DOWN:
  2. intercepted = false;
  3. break;
  4. case MotionEvent.ACTION_MOVE:
  5. if(父类容器须要) {
  6. intercepted = true;
  7. } else {
  8. intercepted = false;
  9. }
  10. break;
  11. case MotionEvent.ACTION_UP:
  12. intercepted = false;
  13. break;
  14. return intercepted;
复制代码

这里有一点须要注意,ACTION_DOWN事件父类容器就必须返回false,由于若是父类容器拦截了的话,后面的Move等全部事件都会直接由父类容器处理,就没法传给子元素了。UP事件也要返回false,由于它自己来讲没有太多的意义,可是对于子元素就不一样了,若是拦截了,那么子元素的onClick事件就没法触发。

内部拦截法 
这种方法指的是父容器不拦截任什么时候间,全部的事件都传递给子元素,若是子元素须要此事件就直接消耗掉,不然就交给父容器进行处理。它须要配合requestDisallowInterceptTouchEvent方法才能正常工做。咱们须要重写子元素的dispatch方法

  1. case MotionEvent.ACTION_DOWN:
  2. parent.requestDisallowInterceptTouchEvent(true);
  3. break;
  4. MotionEvent.ACTION_MOVE:
  5. if(父容器须要此类点击事件) {
  6. parent.requestDisallowInterceptTouchEvent(false);
  7. }
  8. break;
  9. 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方法,一个读取一个设置。

  1. /**
  2. * Returns the value of this variable for the current thread. If an entry
  3. * doesn't yet exist for this variable on this thread, this method will
  4. * create an entry, populating the value with the result of
  5. * {@link #initialValue()}.
  6. *
  7. * @return the current value of the variable for the calling thread.
  8. */
  9. @SuppressWarnings("unchecked")
  10. public T get() {
  11. // Optimized for the fast path.
  12. Thread currentThread = Thread.currentThread();
  13. Values values = values(currentThread);
  14. if (values != null) {
  15. Object[] table = values.table;
  16. int index = hash & values.mask;
  17. if (this.reference == table[index]) {
  18. return (T) table[index + 1];
  19. }
  20. } else {
  21. values = initializeValues(currentThread);
  22. }
  23. return (T) values.getAfterMiss(this);
  24. }
  25. /**
  26. * Sets the value of this variable for the current thread. If set to
  27. * {@code null}, the value will be set to null and the underlying entry will
  28. * still be present.
  29. *
  30. * @param value the new value of the variable for the caller thread.
  31. */
  32. public void set(T value) {
  33. Thread currentThread = Thread.currentThread();
  34. Values values = values(currentThread);
  35. if (values == null) {
  36. values = initializeValues(currentThread);
  37. }
  38. values.put(this, value);
  39. }
复制代码

摘自源码 
首先研究它的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。

  1. Message next() {
  2. // Return here if the message loop has already quit and been disposed.
  3. // This can happen if the application tries to restart a looper after quit
  4. // which is not supported.
  5. final long ptr = mPtr;
  6. if (ptr == 0) {
  7. return null;
  8. }
  9. int pendingIdleHandlerCount = -1; // -1 only during first iteration
  10. int nextPollTimeoutMillis = 0;
  11. for (;;) {
  12. if (nextPollTimeoutMillis != 0) {
  13. Binder.flushPendingCommands();
  14. }
  15. nativePollOnce(ptr, nextPollTimeoutMillis);
  16. synchronized (this) {
  17. // Try to retrieve the next message. Return if found.
  18. final long now = SystemClock.uptimeMillis();
  19. Message prevMsg = null;
  20. Message msg = mMessages;
  21. if (msg != null && msg.target == null) {
  22. // Stalled by a barrier. Find the next asynchronous message in the queue.
  23. do {
  24. prevMsg = msg;
  25. msg = msg.next;
  26. } while (msg != null && !msg.isAsynchronous());
  27. }
  28. if (msg != null) {
  29. if (now < msg.when) {
  30. // Next message is not ready. Set a timeout to wake up when it is ready.
  31. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
  32. } else {
  33. // Got a message.
  34. mBlocked = false;
  35. if (prevMsg != null) {
  36. prevMsg.next = msg.next;
  37. } else {
  38. mMessages = msg.next;
  39. }
  40. msg.next = null;
  41. if (DEBUG) Log.v(TAG, "Returning message: " + msg);
  42. msg.markInUse();
  43. return msg;
  44. }
  45. } else {
  46. // No more messages.
  47. nextPollTimeoutMillis = -1;
  48. }
  49. // Process the quit message now that all pending messages have been handled.
  50. if (mQuitting) {
  51. dispose();
  52. return null;
  53. }
  54. // If first time idle, then get the number of idlers to run.
  55. // Idle handles only run if the queue is empty or if the first message
  56. // in the queue (possibly a barrier) is due to be handled in the future.
  57. if (pendingIdleHandlerCount < 0
  58. && (mMessages == null || now < mMessages.when)) {
  59. pendingIdleHandlerCount = mIdleHandlers.size();
  60. }
  61. if (pendingIdleHandlerCount <= 0) {
  62. // No idle handlers to run. Loop and wait some more.
  63. mBlocked = true;
  64. continue;
  65. }
  66. if (mPendingIdleHandlers == null) {
  67. mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
  68. }
  69. mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
  70. }
  71. // Run the idle handlers.
  72. // We only ever reach this code block during the first iteration.
  73. for (int i = 0; i < pendingIdleHandlerCount; i++) {
  74. final IdleHandler idler = mPendingIdleHandlers[i];
  75. mPendingIdleHandlers[i] = null; // release the reference to the handler
  76. boolean keep = false;
  77. try {
  78. keep = idler.queueIdle();
  79. } catch (Throwable t) {
  80. Log.wtf(TAG, "IdleHandler threw exception", t);
  81. }
  82. if (!keep) {
  83. synchronized (this) {
  84. mIdleHandlers.remove(idler);
  85. }
  86. }
  87. }
  88. // Reset the idle handler count to 0 so we do not run them again.
  89. pendingIdleHandlerCount = 0;
  90. // While calling an idle handler, a new message could have been delivered
  91. // so go back and look again for a pending message without waiting.
  92. nextPollTimeoutMillis = 0;
  93. }
  94. }
复制代码

源码有点长,总结一下就是: 
next方法它是一个死循环,若是消息队列中没有消息,那么next方法就会一直阻塞在这里,当有新的消息来的时候,next方法就会返回这条信息并将其从单链表中移除。

而这个时候勒Looper就等着的,它也是一直循环循环,不停地从MessageQueue中查看是否有新消息,若是有新消息就会马上处理,不然就会一直阻塞在那里。而对于Looper来讲,它是只能建立一个的,这个要归功与它的prepare方法。

  1. /** Initialize the current thread as a looper.
  2. * This gives you a chance to create handlers that then reference
  3. * this looper, before actually starting the loop. Be sure to call
  4. * {@link #loop()} after calling this method, and end it by calling
  5. * {@link #quit()}.
  6. */
  7. public static void prepare() {
  8. prepare(true);
  9. }
  10. private static void prepare(boolean quitAllowed) {
  11. if (sThreadLocal.get() != null) {
  12. throw new RuntimeException("Only one Looper may be created per thread");
  13. }
  14. sThreadLocal.set(new Looper(quitAllowed));
  15. }
复制代码


从这里咱们就能够看出该prepare方法会首先检测是否已经存在looper了,若是不存在,就建立一个新的;若是存在,就抛出异常。 
而以后使用Looper.loop()就能够开启消息循环了。

  1. /**
  2. * Run the message queue in this thread. Be sure to call
  3. * {@link #quit()} to end the loop.
  4. */
  5. public static void loop() {
  6. final Looper me = myLooper();
  7. if (me == null) {
  8. throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  9. }
  10. final MessageQueue queue = me.mQueue;
  11. // Make sure the identity of this thread is that of the local process,
  12. // and keep track of what that identity token actually is.
  13. Binder.clearCallingIdentity();
  14. final long ident = Binder.clearCallingIdentity();
  15. for (;;) {
  16. Message msg = queue.next(); // might block
  17. if (msg == null) {
  18. // No message indicates that the message queue is quitting.
  19. return;
  20. }
  21. // This must be in a local variable, in case a UI event sets the logger
  22. Printer logging = me.mLogging;
  23. if (logging != null) {
  24. logging.println(">>>>> Dispatching to " + msg.target + " " +
  25. msg.callback + ": " + msg.what);
  26. }
  27. msg.target.dispatchMessage(msg);
  28. if (logging != null) {
  29. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
  30. }
  31. // Make sure that during the course of dispatching the
  32. // identity of the thread wasn't corrupted.
  33. final long newIdent = Binder.clearCallingIdentity();
  34. if (ident != newIdent) {
  35. Log.wtf(TAG, "Thread identity changed from 0x"
  36. + Long.toHexString(ident) + " to 0x"
  37. + Long.toHexString(newIdent) + " while dispatching to "
  38. + msg.target.getClass().getName() + " "
  39. + msg.callback + " what=" + msg.what);
  40. }
  41. msg.recycleUnchecked();
  42. }
  43. }
复制代码

从这里面咱们能够看到它也是个死循环,会不停的调用queue.next()方法来获取信息,若是没有,就return,若是有就处理。

注意 
固然了,这里有一个很重要的点,通常可能会忘,那就是在子线程中若是手动为其建立了Looper,那么在全部的事情完成之后应该调用quit方法来终止消息循环,不然这个子线程就会一直处于等待状态,而若是退出Looper以后,这个线程就会马上终止,因此建议不须要使用的时候终止Looper。

Handler 
上面总结了Looper和MessageQueue,这里就对Handler进行一个总结吧。它的工做主要包含消息的发送和接受过程,消息的发送能够经过post的一系列方法以及send的一系列方法来实现,post的一系列方法最终是经过send的一系列方法来实现的。 
实际上它发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper在收到消息以后就会开始处理了。最后由Looper交给Handler处理(handleMessage()方法)。

IPC通讯

上面总结完了Android的消息处理机制,那么就顺带总结一下IPC通讯吧,毕竟上面提到过那么屡次Binder和Socket。

资料:为何Android要采用Binder做为IPC机制? 
知乎上面的回答至关的好,这个博主对系统底层也是很有钻研,学习。

这里就结合上面的知乎回答以及加上《Linux程序设计》还有一本Linux内核剖析(书名忘了可是讲得真的很是好),掺杂一些我的的理解。

进程的定义 
UNIX标准把进程定义为:“一个其中运行着一个或多个进程的地址控件和这些线程所须要的系统资源”。目前,能够简单的把进程看作正在运行的程序。

进程都会被分配一个惟一的数字编号,咱们成为PID(也就是进程标识符),它一般是一个取值范围从2到32768的正整数。当进程被启动时,系统将按顺序选择下一个未被使用的数字做为PID,当数字已经回绕一圈时,新的PID从新从2开始,数字1通常是为init保留的。在进程中,存在一个本身的栈空间,用于保存函数中的局部变量和控制函数的调用与返回。进程还有本身的环境空间,包含专门为这个进程创建的环境变量,同时还必需要维护本身的程序计数器,这个计数器用来记录它执行到的位置,即在执行线程中的位置。 
在Linux中能够经过system函数来启动一个进程

守护进程 
这里就须要提到一个守护进程了,这个在全部的底层中常常都会被提到。 
在linux或者unix操做系统中在系统引导的时候会开启不少服务,这些服务就叫作守护进程。为了增长灵活性,root能够选择系统开启的模式,这些模式叫作运行级别,每一种运行级别以必定的方式配置系统。 守护进程是脱离于终端而且在后台运行的进程。守护进程脱离于终端是为了不进程在执行过程当中的信息在任何终端上显示而且进程也不会被任何终端所产生的终端信息所打断。 
守护进程经常在系统引导装入时启动,在系统关闭时终止。若是想要某个进程不由于用户或终端或其余的变化而受到影响,那么就必须把这个进程变成一个守护进程

防止手机服务后台被杀死 
是否是在手机的设置界面看当前正在运行的服务时会发现有的APP不止存在一个服务?有的APP后台存在两个,有的存在三个?有的流氓软件也会这么设置,这样的话就能够一直运行在后台,用户你关也关不了(倒不是说全部这么设置的都是流氓软件,由于有的软件须要保持一个长期的后台在线,这是由功能决定的)。

这里有两种方法(可能还有更多,这里只总结我了解的):

  • 第一种方法就是利用android中service的特性来设置,防止手机服务后台被杀死。经过更改onStartCommand方法的返回值,将service设置为粘性service,那么当service被kill的时候就会将服务的状态返回到最开始启动的状态,也就是运行的状态,因此这个时候也就会再次从新运行。可是须要注意一点,这个时候的intent值就为空了,获取的话须要注意一下这一点。
  • 第二种就是fork出一个C的进程,由于在Linux中,子类进程在父类被杀死销毁的时候不会随之杀死,它会被init进程领养。因此也就可使用这一个方法,利用主进程fork出一个C进程在后台运行,一旦检测到服务被杀死(检测的方式多种,可以使用观察者模式,广播,轮询等等),就重启服务便可

IPC通讯 
上面总结了进程的相关基础,这里就开始总结一下进程间通讯(IPC 
)的问题了。 
如今Linux现有的全部IPC方式:

  • 管道:在建立时分配一个page大小的内存,缓存区大小有限
  • 消息队列:信息复制两次,额外的cpu消耗,不适合频繁或信息量大的通讯
  • 共享内存:无需复制,共享缓冲区直接附加到进程虚拟地址控件,速度是在全部IPC通讯中最快的。可是进程间的同步问题操做系统没法实现,必须由各进程利用同步工具解决。
  • Socket:做为更通用的接口,传输效率低,主要用于不通机器或跨网络的通讯
  • 信号量:常做为一种锁机制。
  • 信号:不适用于信息交换,更适用于进程件中断控制,例如kill process

到了这里,就有了问题,为何在Linux已经存在这么多优良的IPC方案时,Android还要采起一种新的Binder机制呢? 
猜想:我以为Android采用这种新的方式(固然也大面积的同时使用Linux的IPC通讯方式),最多两个缘由:

  • 推广时手机厂商自定义ROM底层的保密性或者公司之间的关系。
  • 在某些状况下更适合手机这种低配置,对效率要求极高,用户体验极其重要的设备

资料

对于Binder来讲,存在着如下的优点:

  • 性能角度:Binder的数据拷贝只须要一次,而管道、消息队列、Socket都须要2次,而共享内存是一次都不须要拷贝,所以Binder的性能仅次于共享内存
  • 稳定性来讲:Binder是基于C/S架构的,也就是Client和Server组成的架构,Client端有什么需求,直接发送给Server端去完成,架构清晰,分工明确。而共享内存的实现方式复杂,须要充分考虑访问临界资源的并发同步问题,不然可能会出现死锁等问题。从稳定性来讲,Binder的架构优于共享内存。
  • 从安全的角度:Linux的传统IPC方式的接收方没法得到对方进程可靠的UID(用户身份证实)/PID(进程身份证实),从而没法鉴别对方身份,而Android是一个对安全性能要求特别高的操做系统,在系统层面须要对每个APP的权限进行管控或者监视,对于普通用户来讲,绝对不但愿从App商店下载偷窥隐射数据、后台形成手机耗电等问题。传统的Linux IPC无任何保护措施,彻底由上层协议来确保。而在Android中,操做系统为每一个安装好的应用程序分配了本身的UID,经过这个UID能够鉴别进程身份。同时Android系统对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略判断UID/PID是否知足访问权限。也就是说Binder机制对于通讯双方的身份是内核进行校验支持的。例如Socket方式只须要指导地址就能够链接,他们的安全机制须要上层协议来假设
  • 从语言角度:Linux是基于C的,而Android是基于Java的,而Binder是符合面向对象思想的。它的实体位于一个进程中,而它的引用遍及与系统的各个进程之中,它是一个跨进程引用的对象,模糊了进程边界,淡化了进程通讯的过程,整个系统仿佛运行于同一个面向对象的程序之中。
  • 从公司角度:Linux内核是开源的,GPL协议保护,受它保护的Linux Kernel是运行在内核控件,对于上层的任何类库、服务等只要进行系统调用,调用到底层Kernel,那么也必须遵循GPL协议。而对于Android来讲,Google巧妙地将GPL协议控制在内核控件,将用户控件的协议采用Apache-2.0协议(容许基于Android的开发商不向社区反馈源码)。

反射

刚才谈到Binder的时候提了一下效率的问题,那这里就不得不讲到反射了。

反射它容许一个类在运行过程当中得到任意类的任意方法,这个是Java语言的一个很重要的特性。它方便了程序员的编写,可是下降了效率。

实际上,对于只要不是特别大的项目(非Android),反射对于效率的影响微乎其微,而与之对比的开发成原本说就更划算了。 
可是,Android是一个用于手机的,它的硬件设施有限,咱们必需要考虑到它的这个因素,用户体验是最重要的。之前看到过国外的一项统计。在一个APP中的Splash中使用了反射,结果运行时间增长了一秒,这个已经算是很严重的效率影响了。

为何反射影响效率呢 
这里就须要提到一个东西,JIT编译器。JIT编译器它能够把字节码文件转换为机器码,这个是能够直接让处理器使用的,通过它处理的字节码效率提高很是大,可是它有一个缺点,就是把字节码转换成机器码的过程很慢,有的时候甚至还超过了不转换的代码效率(转换以后存在一个复用的问题,对于转换了的机器码,使用的次数越多就越值的)。所以,在JVM虚拟机中,也就产生了一个机制,把经常使用的、使用频率高的字节码经过JIT编译器转换,而频率低的就无论它。而反射的话则是直接越过了JIT编译器,无论是经常使用的仍是很是用的字节码一概没有通过JIT编译器的转化,因此效率就会低。 
而在Android里面,5.0以前使用的是Davlik虚拟机,它就是上面的机制,而在Android5.0以后Google使用了一个全新的ART虚拟机全面代替Davlik虚拟机。 
ART虚拟机会在程序安装时直接把全部的字节码所有转化为机器码,虽然这样会致使安装时间边长,可是程序运行的效率提高很是大。 
【疑问:那在Android5.0以后的系统上,反射会不会没影响了?因为如今作项目的时候更多考虑的是向下兼容,单独考虑5.0的状况尚未,等之后有需求或者是有机会的时候再深刻了解一下,之后更新】

继续断点4

刚才总结了Android的消息处理机制和IPC通讯,那么咱们主线程的消息处理机制是何时开始的呢?由于咱们知道在主线程中咱们是不须要手动调用Looper.prepare()和Looper.loop()的。

Android的主线程就是ActivityThread,主线程的入口方法是main方法,在main方法中系统会经过Looper.prepareMainLooper()来建立主线程的Looper以及MessageQueue,并经过Looper.loop来开启消息循环,因此这一步其实是系统已经为咱们作了,咱们就再也不须要本身来作。 
ActivityThread经过AppplicationThread和AMS进行进程件通讯,AMS以进程间通讯的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,而后ApplicationThread会向Handler发送消息,Handler收到消息后会将ApplicationThread中的逻辑切换到主线程中去执行,这个过程就是主线程的消息循环模型。

上面总结到了APP开始运行,依次调用onCreate/onStart/onResume等方法,那么在onCreate方法中咱们常用的setContentView和findViewById作了什么事呢?

Activity界面显示

首先,就考虑到第一个问题,也就是setContentView这个东西作了什么事,这里就要对你当前继承的Activity分类了,若是是继承的Activity,那么setContentView源码是这样的:

  1. /**
  2. * Set the activity content from a layout resource. The resource will be
  3. * inflated, adding all top-level views to the activity.
  4. *
  5. * @param layoutResID Resource ID to be inflated.
  6. *
  7. * @see #setContentView(android.view.View)
  8. * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
  9. */
  10. public void setContentView(@LayoutRes int layoutResID) {
  11. getWindow().setContentView(layoutResID);
  12. initWindowDecorActionBar();
  13. }
  14. /**
  15. * Set the activity content to an explicit view. This view is placed
  16. * directly into the activity's view hierarchy. It can itself be a complex
  17. * view hierarchy. When calling this method, the layout parameters of the
  18. * specified view are ignored. Both the width and the height of the view are
  19. * set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use
  20. * your own layout parameters, invoke
  21. * {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
  22. * instead.
  23. *
  24. * @param view The desired content to display.
  25. *
  26. * @see #setContentView(int)
  27. * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
  28. */
  29. public void setContentView(View view) {
  30. getWindow().setContentView(view);
  31. initWindowDecorActionBar();
  32. }
  33. /**
  34. * Set the activity content to an explicit view. This view is placed
  35. * directly into the activity's view hierarchy. It can itself be a complex
  36. * view hierarchy.
  37. *
  38. * @param view The desired content to display.
  39. * @param params Layout parameters for the view.
  40. *
  41. * @see #setContentView(android.view.View)
  42. * @see #setContentView(int)
  43. */
  44. public void setContentView(View view, ViewGroup.LayoutParams params) {
  45. getWindow().setContentView(view, params);
  46. initWindowDecorActionBar();
  47. }
复制代码


这里面存在着3个重载函数,而无论你调用哪个,最后都会调用到initWindowDecorActionBar()这个方法。 
而对于新的一个AppcompatActivity,这个Activity里面包含了一些新特性,如今我作的项目里基本都是使用AppcompatActivity代替掉原来的Activity,固然也并非必定的,仍是要根据项目的实际状况来选择。 
在AppcompatActivity中,setContentView是这样的:

  1. @Override
  2. public void setContentView(@LayoutRes int layoutResID) {
  3. getDelegate().setContentView(layoutResID);
  4. }
  5. @Override
  6. public void setContentView(View view) {
  7. getDelegate().setContentView(view);
  8. }
  9. @Override
  10. public void setContentView(View view, ViewGroup.LayoutParams params) {
  11. getDelegate().setContentView(view, params);
  12. }
复制代码


同样的3个重载函数,只是里面没有了上面的那个init方法,取而代之的是一个getDelegate().setContentView,这个delegate从字面上能够了解到它是一个委托的对象,源码是这样的:

  1. /**
  2. * @return The {@link AppCompatDelegate} being used by this Activity.
  3. */
  4. @NonNull
  5. public AppCompatDelegate getDelegate() {
  6. if (mDelegate == null) {
  7. mDelegate = AppCompatDelegate.create(this, this);
  8. }
  9. return mDelegate;
  10. }
  11. 而在AppCompatDelegate.Create方法中,则会返回一个颇有意思的东西:
  12. /**
  13. * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}.
  14. *
  15. * @param callback An optional callback for AppCompat specific events
  16. */
  17. public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
  18. return create(activity, activity.getWindow(), callback);
  19. }
  20. private static AppCompatDelegate create(Context context, Window window,
  21. AppCompatCallback callback) {
  22. final int sdk = Build.VERSION.SDK_INT;
  23. if (sdk >= 23) {
  24. return new AppCompatDelegateImplV23(context, window, callback);
  25. } else if (sdk >= 14) {
  26. return new AppCompatDelegateImplV14(context, window, callback);
  27. } else if (sdk >= 11) {
  28. return new AppCompatDelegateImplV11(context, window, callback);
  29. } else {
  30. return new AppCompatDelegateImplV7(context, window, callback);
  31. }
  32. }
复制代码

这里会根据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方法中,有一段代码:

  1. /**
  2. * Creates a new ActionBar, locates the inflated ActionBarView,
  3. * initializes the ActionBar with the view, and sets mActionBar.
  4. */
  5. private void initWindowDecorActionBar() {
  6. Window window = getWindow();
  7. // Initializing the window decor can change window feature flags.
  8. // Make sure that we have the correct set before performing the test below.
  9. window.getDecorView();
  10. if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
  11. return;
  12. }
  13. mActionBar = new WindowDecorActionBar(this);
  14. mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
  15. mWindow.setDefaultIcon(mActivityInfo.getIconResource());
  16. mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
  17. }
复制代码




注意上面的window.getDecoreView()方法的注释,该方法会设置一些window的标志位,而当这个方法执行完以后,就不再能更改了,这也就是为何不少第三方SDK设置window的标志位时必定要求要在setContentView方法前调用。

findViewById

咱们经过一个findViewById方法能够实现对象的绑定,那它底层到底是怎么实现的呢?

findViewById根据继承的Activity类型的不一样也存在着区别,老规矩,仍是以Activity的来。

  1. /**
  2. * Finds a view that was identified by the id attribute from the XML that
  3. * was processed in {@link #onCreate}.
  4. *
  5. * @return The view if found or null otherwise.
  6. */
  7. @Nullable
  8. public View findViewById(@IdRes int id) {
  9. return getWindow().findViewById(id);
  10. }
复制代码




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

  1. /**
  2. * Finds a view that was identified by the id attribute from the XML that
  3. * was processed in {@link android.app.Activity#onCreate}. This will
  4. * implicitly call {@link #getDecorView} for you, with all of the
  5. * associated side-effects.
  6. *
  7. * @return The view if found or null otherwise.
  8. */
  9. @Nullable
  10. public View findViewById(@IdRes int id) {
  11. return getDecorView().findViewById(id);
  12. }
复制代码




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

  1. /**
  2. * Look for a child view with the given id. If this view has the given
  3. * id, return this view.
  4. *
  5. * @param id The id to search for.
  6. * @return The view that has the given id in the hierarchy or null
  7. */
  8. @Nullable
  9. public final View findViewById(@IdRes int id) {
  10. if (id < 0) {
  11. return null;
  12. }
  13. return findViewTraversal(id);
  14. }
  15. /**
  16. * {@hide}
  17. * @param id the id of the view to be found
  18. * @return the view of the specified id, null if cannot be found
  19. */
  20. protected View findViewTraversal(@IdRes int id) {
  21. if (id == mID) {
  22. return this;
  23. }
  24. return null;
  25. }
复制代码

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

相关文章
相关标签/搜索