关于技术准备,主要是包含下面几项:
项目经历
Java基础
设计模式
算法
网络相关知识
Android基础
Android源码
Android性能优化
开源库源码、
题型
一、事件分发流程
http://blog.csdn.net/mingtiannihao623/article/details/78005303
二、Android性能优化之视图篇(渲染机制)
http://blog.csdn.net/applicaton/article/details/52252788
- Android系统每隔16ms重新绘制一次activity,也就是说你的app必须在16ms内完成屏幕刷新的所有逻辑操作,这样才能达到60帧/s。而用户一般所看到的卡顿是由于Android的渲染性能造成的。
- 如果某个绘制操作超过了16ms用了24ms这时候用户看同一张图片花了32ms而不是16ms,用户会感到卡顿,这种现象我们叫-丢帧
- android 的渲染主要分为两个组件,1.CPU 2.GPU由这两者共同完成在屏幕上绘制 。CPU在图像绘制之前向GPU输入基础指令集主要是多边形与纹理也就是图片,也就是说在屏幕绘制UI对象的时候都需要在CPU中转化成多边形再传递GPU进行格栅化操作,cpu将对象转换为多边形耗时 同样上传到GPU也耗时所以我们要减少对象转换次数以及上传数据的次数,幸运的是OpenGL-ES API允许数据上传到GPU进行数据保存,当下一次绘制按钮的时候只要在CPU的存储器里引用它,所以渲染性能的优化就是尽快的上传数据到GPU尽可能长的在不修改数据的条件下保存数据 虽然android 系统已经完成的大部分的优化但是还有一个问题造成了性能的影响–>过度绘制(OverDraw,屏幕上的某个像素点在同一帧的时间内绘制了多次 )。
- 如何清除过度绘制的两种方法
1.从视图中清除那些不必要的背景和图片使他们不会再最终渲染图像中显示
(1).将activity中的主题背景置为空减少与xml中的背景色覆盖getwindow().setBackGroundDrawable(null);
(2).去掉不必要的布局背景图片
2.对视图中重叠的区域进行定义从而降低CPU -GPU的消耗
(1).避免绘制在最终图片中不显示的UI组件称之为剪辑,这是系统完成的但是不适合在自定义View中,我们可以通过canvas.clipRect通过定义可见边界避免绘制被遮挡住的部分
(2).也可以通过canvas.quickReject判断给定区域是否在剪辑区域之外
3. layout刷新避免复杂的层级结构嵌套
4. ViewStub称之为“延迟化加载”,在多数情况下,程序无需显示ViewStub所指向的布局文件,只有在特定的某些较少条件下,此时ViewStub所指向的布局文件才需要被inflate,且此布局文件直接将当前ViewStub替换掉,具体是通过viewStub.infalte()或viewStub.setVisibility(View.VISIBLE)来完成;
5. Merge标签可以干掉一个view层级。Merge的作用很明显,但是也有一些使用条件的限制。有两种情况下我们可以使用Merge标签来做容器控件。第一种子视图不需要指定任何针对父视图的布局属性,就是说父容器仅仅是个容器,子视图只需要直接添加到父视图上用于显示就行。另外一种是假如需要在LinearLayout里面嵌入一个布局(或者视图),而恰恰这个布局(或者视图)的根节点也是LinearLayout,这样就多了一层没有用的嵌套,无疑这样只会拖慢程序速度。而这个时候如果我们使用merge根标签就可以避免那样的问题。另外Merge只能作为XML布局的根标签使用,当Inflate以开头的布局文件时,必须指定一个父ViewGroup,并且必须设定attachToRoot为true。
(作者:LooperJing
链接: http://www.jianshu.com/p/9ac245657127
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。)
三、Android View绘制和显示原理简介
http://blog.csdn.net/zhangcanyan/article/details/52817989
Android应用程序调用SurfaceFlinger服务把经过测量、布局和绘制后的Surface渲染到显示屏幕上。
SurfaceFlinger:Android系统服务,负责管理Android系统的帧缓冲区,即显示屏幕。
Surface:Android应用的每个窗口对应一个画布(Canvas),即Surface,可以理解为Android应用程序的一个窗口。
Android应用程序的显示过程包含了两个部分(应用侧绘制、系统侧渲染)、两个机制(进程间通讯机制、显示刷新机制)
1. 应用侧
一个Android应用程序窗口里面包含了很多UI元素,这些UI元素是以树形结构来组织的,即它们存在着父子关系,其中,子UI元素位于父UI元素里面,因此,在绘制一个Android应用程序窗口的UI之前,我们首先要确定它里面的各个子UI元素在父UI元素里面的大小以及位置。确定各个子UI元素在父UI元素里面的大小以及位置的过程又称为测量过程和布局过程。因此,Android应用程序窗口的UI渲染过程可以分为测量、布局和绘制三个阶段。测量、布局没有太多要说的,这里要着重说一下绘制。Android目前有两种绘制模型:基于软件的绘制模型和硬件加速的绘制模型(从Android 3.0开始全面支持) 在基于软件的绘制模型下,CPU主导绘图,视图按照两个步骤绘制:测量:递归(深度优先)确定所有视图的大小(高、宽)
布局:递归(深度优先)确定所有视图的位置(左上角坐标)
绘制:在画布canvas上绘制应用程序窗口所有的视图
- 1.让View层次结构失效
2.绘制View层次结构
当应用程序需要更新它的部分UI时,都会调用内容发生改变的View对象的invalidate()方法。无效(invalidation)消息请求会在View对象层次结构中传递,以便计算出需要重绘的屏幕区域(脏区)。然后,Android系统会在View层次结构中绘制所有的跟脏区相交的区域。不幸的是,这种方法有两个缺点:1.绘制了不需要重绘的视图(与脏区域相交的区域)
- 2.掩盖了一些应用的bug(由于会重绘与脏区域相交的区域)
注意:在View对象的属性发生变化时,如背景色或TextView对象中的文本等,Android系统会自动的调用该View对象的invalidate()方法。
在基于硬件加速的绘制模式下,GPU主导绘图,绘制按照三个步骤绘制:
- 让View层次结构失效
- 记录、更新显示列表
- 绘制显示列表
这种模式下,Android系统依然会使用invalidate()方法和draw()方法来请求屏幕更新和展现View对象。但Android系统并不是立即执行绘制命令,而是首先把这些View的绘制函数作为绘制指令记录一个显示列表中,然后再读取显示列表中的绘制指令调用OpenGL相关函数完成实际绘制。另一个优化是,Android系统只需要针对由invalidate()方法调用所标记的View对象的脏区进行记录和更新显示列表。没有失效的View对象则能重放先前显示列表记录的绘制指令来进行简单的重绘工作。
使用显示列表的目的是,把视图的各种绘制函数翻译成绘制指令保存起来,对于没有发生改变的视图把原先保存的操作指令重新读取出来重放一次就可以了,提高了视图的显示速度。而对于需要重绘的View,则更新显示列表,以便下次重用,然后再调用OpenGL完成绘制。
硬件加速提高了Android系统显示和刷新的速度,但它也不是万能的,它有三个缺陷:
- 兼容性(部分绘制函数不支持或不完全硬件加速,参见文章尾)
- 内存消耗(OpenGL API调用就会占用8MB,而实际上会占用更多内存)
- 电量消耗(GPU耗电)
2.系统侧
Android应用程序在图形缓冲区中绘制好View层次结构后,这个图形缓冲区会被交给SurfaceFlinger服务,而SurfaceFlinger服务再使用OpenGL图形库API来将这个图形缓冲区渲染到硬件帧缓冲区中。
3.进程间通讯机制
Android应用程序为了能够将自己的UI绘制在系统的帧缓冲区上,它们就必须要与SurfaceFlinger服务进行通信,
Android应用程序与SurfaceFlinger服务是运行在不同的进程中的,因此,它们采用某种进程间通信机制来进行通信。由于Android应用程序在通知SurfaceFlinger服务来绘制自己的UI的时候,需要将UI数据传递给SurfaceFlinger服务,例如,要绘制UI的区域、位置等信息。一个Android应用程序可能会有很多个窗口,而每一个窗口都有自己的UI数据,因此,Android系统的匿名共享内存机制就派上用场了。
每一个Android应用程序与SurfaceFlinger服务之间,都会通过一块匿名共享内存来传递UI数据,但是单纯的匿名共享内存在传递多个窗口数据时缺乏有效的管理,所以匿名共享内存就被抽象为一个更上流的数据结构SharedClient。
在每个SharedClient中,最多有31个SharedBufferStack,每个SharedBufferStack都对应一个Surface,即一个窗口。这样,我们就可以知道为什么每一个SharedClient里面包含的是一系列SharedBufferStack而不是单个SharedBufferStack:一个SharedClient对应一个Android应用程序,而一个Android应用程序可能包含有多个窗口,即Surface。从这里也可以看出,一个Android应用程序至多可以包含31个窗口。
每个SharedBufferStack中又包含了N个缓冲区(<4.1 N=2; >=4.1 N=3),即显示刷新机制中即将提到的双缓冲和三重缓冲技术。
4.显示刷新机制
一般我们在绘制UI的时候,都会采用一种称为“双缓冲”的技术。双缓冲意味着要使用两个缓冲区(SharedBufferStack中),其中一个称为Front Buffer,另外一个称为Back Buffer。UI总是先在Back Buffer中绘制,然后再和Front Buffer交换,渲染到显示设备中。理想情况下,这样一个刷新会在16ms内完成(60FPS),下图就是描述的这样一个刷新过程(Display处理前Front Buffer,CPU、GPU处理Back Buffer)。
为何第1个16ms段内,CPU/GPU没有及时处理第2帧数据?原因很简单,CPU可能是在忙别的事情,不知道该到处理UI绘制的时间了。可CPU一旦想起来要去处理第2帧数据,时间又错过了!
为解决这个问题,Android 4.1中引入了VSYNC,这类似于时钟中断。
为什么CPU不能在第二个16ms处开始绘制工作呢?原因就是只有两个Buffer(Android 4.1之前)。如果有第三个Buffer的存在,CPU就能直接使用它,而不至于空闲。出于这一思路就引出了三重缓冲区(Android 4.1)。
四、View的绘制流程
http://blog.csdn.net/mingtiannihao623/article/details/78005442
五、动画的原理
http://blog.csdn.net/yelbosh/article/details/7750500
Android 平台提供了两类动画,一类是 Tween 动画(补间动画),即通过对场景里的对象不断做图像变换 ( 平移、缩放、旋转 ) 产生动画效果;第二类是 Frame 动画,即顺序播放事先做好的图像,跟电影类似。
Android 动画框架是建立在 View 的级别上的,在 View 类中有一个接口 startAnimation 来使动画开始,startAnimation 函数会将一个 Animation 类别的参数传给 View,这个 Animation 是用来指定我们使用的是哪种动画,现有的动画有平移,缩放,旋转以及 alpha 变换等。
每一个窗口就是一棵 View 树,下面以我们写的 android_tabwidget_tutorial.doc 中的 tab 控件的窗口为例,通过 android 工具 hierarchyviewer 得到的窗口 View Tree,RootView 只有一个孩子就是 DecorView,这里整个 View Tree 都是 DecorView 的子 View,它们是从 android1.5/frameworks/base/core/res/res/layout/screen_title.xml 这个 layout 文件 infalte 出来的,感兴趣的读者可以参看 frameworks\policies\base\phone\com\android\internal\policy\Imp\PhoneWindow.java 中 generateLayout 函数部分的代码。
标题窗口以下部分的 FrameLayou 就是为了让程序员通过 setContentView 来设置用户需要的窗口内容。因为整个 View 的布局就是一棵树,所以绘制的时候也是按照树形结构遍历来让每个 View 进行绘制。ViewRoot.java 中的 draw 函数准备好 Canvas 后会调用 mView.draw(canvas),其中 mView 就是调用 ViewRoot.setView 时设置的 DecorView。然后看一下 View.java 中的 draw 函数:
递归的绘制整个窗口需要按顺序执行以下几个步骤:
- 绘制背景;
- 如果需要,保存画布(canvas)的层为淡入或淡出做准备;
- 绘制 View 本身的内容,通过调用 View.onDraw(canvas) 函数实现
- 绘制自己的孩子(通常也是一个 view 系统),通过 dispatchDraw(canvas) 实现,参看 ViewGroup.Java 中的代码可知,dispatchDraw -> drawChild -> child.draw(canvas) 这样的调用过程被用来保证每个子 View 的 draw 函数都被调用,通过这种递归调用从而让整个 View 树中的所有 View 的内容都得到绘制。在调用每个子 View 的 draw 函数之前,需要绘制的 View 的绘制位置是在 Canvas 通过 translate 函数调用来进行切换的,窗口中的所有 View 是共用一个 Canvas 对象
- 如果需要,绘制淡入淡出相关的内容并恢复保存的画布所在的层(layer)
- 绘制修饰的内容(例如滚动条),这个可知要实现滚动条效果并不需要 ScrollView,可以在 View 中完成的,不过有一些小技巧
当一个 ChildView 要重画时,它会调用其成员函数 invalidate() 函数将通知其 ParentView 这个 ChildView 要重画,这个过程一直向上遍历到 ViewRoot,当 ViewRoot 收到这个通知后就会调用上面提到的 ViewRoot 中的 draw 函数从而完成绘制。View::onDraw() 有一个画布参数 Canvas, 画布顾名思义就是画东西的地方,Android 会为每一个 View 设置好画布,View 就可以调用 Canvas 的方法,比如:drawText, drawBitmap, drawPath 等等去画内容。每一个 ChildView 的画布是由其 ParentView 设置的,ParentView 根据 ChildView 在其内部的布局来调整 Canvas,其中画布的属性之一就是定义和 ChildView 相关的坐标系,默认是横轴为 X 轴,从左至右,值逐渐增大,竖轴为 Y 轴,从上至下,值逐渐增大 ,
Android 动画就是通过 ParentView 来不断调整 ChildView 的画布坐标系来实现的,下面以平移动画来做示例,见下图 4,假设在动画开始时 ChildView 在 ParentView 中的初始位置在 (100,200) 处,这时 ParentView 会根据这个坐标来设置 ChildView 的画布,在 ParentView 的 dispatchDraw 中它发现 ChildView 有一个平移动画,而且当前的平移位置是 (100, 200),于是它通过调用画布的函数 traslate(100, 200) 来告诉 ChildView 在这个位置开始画,这就是动画的第一帧。如果 ParentView 发现 ChildView 有动画,就会不断的调用 invalidate() 这个函数,这样就会导致自己会不断的重画,就会不断的调用 dispatchDraw 这个函数,这样就产生了动画的后续帧,当再次进入 dispatchDraw 时,ParentView 根据平移动画产生出第二帧的平移位置 (500, 200),然后继续执行上述操作,然后产生第三帧,第四帧,直到动画播完。
以平移动画为例子来说明动画的产生过程,这其中又涉及到两个重要的类型,Animation 和 Transformation,这两个类是实现动画的主要的类,Animation 中主要定义了动画的一些属性比如开始时间、持续时间、是否重复播放等,这个类主要有两个重要的函数:getTransformation 和 applyTransformation,在 getTransformation 中 Animation 会根据动画的属性来产生一系列的差值点,然后将这些差值点传给 applyTransformation,这个函数将根据这些点来生成不同的 Transformation,Transformation 中包含一个矩阵和 alpha 值,矩阵是用来做平移、旋转和缩放动画的,而 alpha 值是用来做 alpha 动画的(简单理解的话,alpha 动画相当于不断变换透明度或颜色来实现动画),以上面的平移矩阵为例子,当调用 dispatchDraw 时会调用 getTransformation 来得到当前的 Transformation,
用户可以定义自己的动画类,只需要继承 Animation 类,然后重载 applyTransformation 这个函数。对动画来说其行为主要靠差值点来决定的,比如,我们想开始动画是逐渐加快的或者逐渐变慢的,或者先快后慢的,或者是匀速的,这些功能的实现主要是靠差值函数来实现的,Android 提供了 一个 Interpolator 的基类,你要实现什么样的速度可以重载其函数 getInterpolation,在 Animation 的 getTransformation 中生成差值点时,会用到这个函数。
从上面的动画机制的分析可知某一个 View 的动画的绘制并不是由他自己完成的而是由它的父 view 完成
六、底层如何给上层信号
底层到上层的数据传递过程:
1、Android的硬件抽象层读取相应的串口设备的数据:
static int uart_read_drv(struct uart_control_device_t *dev, char *buf, int count)
{
int count1 = 0;
count1 = read(fd, buf, count);
if(count1 < 0)
{
perror("read");
}
return 0;
}
2、 Android的JNI层经过回调之后,可以通过自定义的相应的结构体方法读取到硬件抽象层的数据,并反馈到上层:
static jchar uart_read(JNIEnv* env, jobject thiz) {
char s_buf[100]={0};
if (sUartDevice) {
sUartDevice->uart_read_hal(sUartDevice, s_buf, 1);
}else{
LOGI("sUartDevice is null");
}
if (s_buf == 0)
LOGI("JNI: Lost info");
else
{
LOGI("***********JNI:info length is : %d", strlen(s_buf));
}
return s_buf[0];
}
3、Android的JNI和上层函数的对应描述:
static const JNINativeMethod gMethods[] = {
….
{ "_uart_data_update",
"()C",
(void*)uart_read },
….
};
4、Android的Framework层定义相应的本地接口方法:
private static native char _uart_data_update();
通过类的方法进行封装:
public class Uart {
static {
System.loadLibrary(“uart_runtime”);
Log.i(“**********************Java Service”, “loadLibrary sucess”);
}
public Uart(){
_init();
}
public char GpsDataUpdate() {
char s = _uart_data_update();
return s;
}
private static native boolean _init();
private static native char _uart_data_update();
}
}
5、.Java上层App进行调用,并获得数据:
static Uart uartuart = new Uart();
char s = uartuart.GpsDataUpdate();
七、编译打包的过程
http://blog.csdn.net/zhangmiaoping23/article/details/51177590
http://www.cnblogs.com/sjm19910902/p/6416022.html
(1)这个过程的输入是什么?(2)这个过程的输出是什么?(3)这个过程使用了什么工具
第一步:打包资源文件,生成R.java文件
【输入】Resource文件(就是工程中res中的文件)、Assets文件(相当于另外一种资源,这种资源Android系统并不像对res中的文件那样优化它)、AndroidManifest.xml文件(包名就是从这里读取的,因为生成R.java文件需要包名)、Android基础类库(Android.jar文件)
【输出】打包好的资源(一般在Android工程的bin目录可以看到一个叫resources.ap_的文件就是它了)、R.java文件(在gen目录中,大家应该很熟悉了)
【工具】aapt工具,它的路径在${ANDROID_SDK_HOME}/platform-tools/aapt(如果你使用的是Windows系统,按惯例路径应该这样写:%ANDROID_SDK_HOME%\platform-tools\aapt.exe,下同)。
第二步:处理AIDL文件,生成对应的.java文件(当然,有很多工程没有用到AIDL,那这个过程就可以省了)
【输入】源码文件、aidl文件、framework.aidl文件
【输出】对应的.java文件
【工具】aidl工具
第三步:编译Java文件,生成对应的.class文件
【输入】源码文件(包括R.java和AIDL生成的.java文件)、库文件(.jar文件)
【输出】.class文件
【工具】javac工具
第四步:把.class文件转化成Davik VM支持的.dex文件
【输入】 .class文件(包括Aidl生成.class文件,R生成的.class文件,源文件生成的.class文件),库文件(.jar文件)
【输出】.dex文件
【工具】dx工具
第五步:打包生成未签名的.apk文件
【输入】打包后的资源文件、打包后类文件(.dex文件)、libs文件(包括.so文件,当然很多工程都没有这样的文件,如果你不使用C/C++开发的话)
【输出】未签名的.apk文件
【工具】apkbuilder工具
第六步:对未签名.apk文件进行签名
【输入】未签名的.apk文件
【输出】签名的.apk文件
【工具】jarsigner
第七步:对签名后的.apk文件进行对齐处理(不进行对齐处理是不能发布到Google Market的)
【输入】签名后的.apk文件
【输出】对齐后的.apk文件
【工具】zipalign工具
八、Android有多个资源文件夹,应用在不同分辨率下是如何查找对应文件夹下的资源的,描述整个过程
drawable目录
我们经常会给应用程序切几套图片,放在drawable-mdpi、drawable-hdpi、drawable-xhdpi等目录下面。当应用在设备对应dpi目录下没有找到某个资源时,遵循“先高再低”原则,然后按比例缩放图片:
比如,当前为hdpi设备,并且只有以下几个目录,则drawable的寻找顺序为:
hdpi->xhdpi->xxhdpi->mdpi,如果在xxhdpi中找到目标图片,则压缩0.5倍来使用,如果在mdpi中找到图片,则放大1.5倍来使用。
values目录
values目录用来放置colors.xml,dimens.xml,strings.xml等,也可以根据屏幕密度设置特定的values目录让满足设定的设备进行加载,比如values-mdpi、values-hdpi、values-xhdpi、values-xxhdpi等等,然后每个目录放置一个demins.xml,使不同分辨率的设备应用不同的尺寸设置。当应用设备在当前dpi对应目录的demins.xml中没有找到目标条目时,采用“就近匹配”原则:
比如,当前为hdpi设备,并且只有以下几个目录,则values的寻找顺序为:
hdpi->xhdpi->mdpi->values,即先向上级dpi目录查找,再向下级dpi目录查找,最后一路向下查找到values目录,都会直接应用这个值与当前设备的密度来计算最终的尺寸。也就是说,如果我们同样在values-xhdpi和values下写 length=10dp
那么在hdpi设备上得到的都是15px
在xhdpi设备上得到的都是20px
把希望在任何设备上视觉大小都一样的尺寸都放置在values目录下并且只放置这一份,其他需要有变化的尺寸则放置在对应目录下即可。
如果当前设备为xhdpi-1184x800,当前目录有values-xhdpi-1184x800,values-xhdpi-1184x960,values-xhdpi-1184x720,android的寻找顺序则是:
xhdpi-1184x800->values-xhdpi-1184x720->values-xhdpi
只向低于自己分辨率的目录下寻找,直到values-xhdpi,如果依然没有找到,按照之前的顺序继续进行。
也就是说,对于同dpi的多台不同分辨率平板设备,如果布局足够通用,我们可以只针对最低分辨率设计dimens即可,上面的例子中,则是values-xhdpi-1184x720。
九、ANR的原理(回答主线程5秒阻塞是不行的,要读源码)
http://www.cnblogs.com/android-blogs/p/5718302.html
http://blog.csdn.net/a332324956/article/details/77800315
那么哪些场景会造成ANR呢?
- Service Timeout:服务在20s内未执行完成;
- BroadcastQueue Timeout:比如前台广播在10s内执行完成
- ContentProvider Timeout:内容提供者执行超时
- inputDispatching Timeout: 输入事件分发超时5s,包括按键分发事件的超时。
出发时机:
1. Service Timeout
http://gityuan.com/images/ams/service_lifeline.jpg
在整个startService过程,从进程角度看服务启动过程
- Process A进程:是指调用startService命令所在的进程,也就是启动服务的发起端进程,比如点击桌面App图标,此处Process A便是Launcher所在进程。
- system_server进程:系统进程,是java framework框架的核心载体,里面运行了大量的系统服务,比如这里提供ApplicationThreadProxy(简称ATP),ActivityManagerService(简称AMS),这个两个服务都运行在system_server进程的不同线程中,由于ATP和AMS都是基于IBinder接口,都是binder线程,binder线程的创建与销毁都是由binder驱动来决定的,每个进程binder线程个数的上限为16。
- Zygote进程:是由init进程孵化而来的,用于创建Java层进程的母体,所有的Java层进程都是由Zygote进程孵化而来;
- Remote Service进程:远程服务所在进程,是由Zygote进程孵化而来的用于运行Remote服务的进程。主线程主要负责Activity/Service等组件的生命周期以及UI相关操作都运行在这个线程; 另外,每个App进程中至少会有两个binder线程 ApplicationThread(简称AT)和ActivityManagerProxy(简称AMP),当然还有其他线程,这里不是重点就不提了。
图中涉及3种IPC通信方式:Binder、Socket以及Handler,在图中分别用3种不同的颜色来代表这3种通信方式。一般来说,同一进程内的线程间通信采用的是 Handler消息队列机制,不同进程间的通信采用的是binder机制,另外与Zygote进程通信采用的Socket。
启动流程:
- Process A进程采用Binder IPC向system_server进程发起startService请求;
- system_server进程接收到请求后,向zygote进程发送创建进程的请求;
- zygote进程fork出新的子进程Remote Service进程;
- Remote Service进程,通过Binder IPC向sytem_server进程发起attachApplication请求;
- system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向remote Service进程发送scheduleCreateService请求;
- Remote Service进程的binder线程在收到请求后,通过handler向主线程发送CREATE_SERVICE消息;
到此,服务便正式启动完成。当创建的是本地服务或者服务所属进程已创建时,则无需经过上述步骤2、3,直接创建服务即可。
AMS中的mHandler收到SERVICE_TIMEOUT_MSG消息时触发:
在Service所在进程attach到system_server进程的过程中会调用realStartServiceLocked(),调用bumpServiceExecutingLocked()发送delay消息(SERVICE_TIMEOUT_MSG),
- 对于前台服务,则超时为SERVICE_TIMEOUT,即timeout=20s;
- 对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT,即timeout=200s;
当没有超时则调用移除函数,启动service 完成,当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程调用 mServices.serviceTimeout((ProcessRecord)msg.obj);主要是获取并发送超时serviceRecord信息;
2. BroadcastQueue Timeout
广播:http://gityuan.com/2016/06/04/broadcast-receiver/
1. BroadcastReceiver分为两类:
- 静态广播接收者:通过AndroidManifest.xml的标签来申明的BroadcastReceiver;
- 动态广播接收者:通过AMS.registerReceiver()方式注册的BroadcastReceiver, 不需要时记得调用unregisterReceiver();
2.广播发送方式可分为三类:
类型 方法 ordered sticky
普通广播 sendBroadcast false false
有序广播 sendOrderedBroadcast true false
Sticky广播 sendStickyBroadcast false true
3. 广播注册registerReceiver():
默认将当前进程的主线程设置为scheuler. 再向AMS注册该广播相应信息, 根据类型选择加入mParallelBroadcasts或mOrderedBroadcasts队列.
4. 广播发送processNextBroadcast():根据不同情况调用不同的处理过程:
- 如果是动态广播接收者,则调用deliverToRegisteredReceiverLocked处理;
- 如果是静态广播接收者,且对应进程已经创建,则调用processCurBroadcastLocked处理;
- 如果是静态广播接收者,且对应进程尚未创建,则调用startProcessLocked创建进程。
流程图:
静态注册的receivers始终采用串行方式来处理(processNextBroadcast); 动态注册的registeredReceivers处理方式是串行还是并行方式, 取决于广播的发送方式(processNextBroadcast)。
静态注册的广播往往其所在进程还没有创建,而进程创建相对比较耗费系统资源的操作,所以 让静态注册的广播串行化,能防止出现瞬间启动大量进程的喷井效应。
ANR时机:只有串行广播才需要考虑超时,因为接收者是串行处理的,前一个receiver处理慢,会影响后一个receiver;并行广播 通过一个循环一次性向所有的receiver分发广播事件,所以不存在彼此影响的问题,则没有广播超时;
串行广播超时情况
1:某个广播总处理时间 > 2* receiver总个数 * mTimeoutPeriod, 其中mTimeoutPeriod,前台队列默认为10s,后台队列默认为60s;
2:某个receiver的执行时间超过mTimeoutPeriod;
BroadcastQueue中的mHandler收到BROADCAST_TIMEOUT_MSG消息时触发
在发送广播过程中会执行scheduleBroadcastsLocked方法来处理相关的广播,然后会调用到processNextBroadcast方法来处理下一条广播。
当广播处理时间超时,则强制结束这条广播调用
broadcastTimeoutLocked(false);
3. ContentProvider Timeout
appNotRespondingViaProvider()
Timeout时间20s
调用链:
ContentProviderClient.NotRespondingRunnable.run
ContextImpl.ApplicationContentResolver.appNotRespondingViaProvider
ActivityThread.appNotRespondingViaProvider
AMP.appNotRespondingViaProvider
AMS.appNotRespondingViaProvider
4. inputDispatching Timeout
在native层InputDispatcher.cpp中经过层层调用,从native层com_android_server_input_InputManagerService调用到java层InputManagerService。
[-> InputManagerService.java]
private long notifyANR(InputApplicationHandle inputApplicationHandle,
InputWindowHandle inputWindowHandle, String reason) {
return mWindowManagerCallbacks.notifyANR(
inputApplicationHandle, inputWindowHandle, reason);
}
mWindowManagerCallbacks为InputMonitor对象
if (appWindowToken != null && appWindowToken.appToken != null) {
boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(reason);
if (! abort) {
return appWindowToken.inputDispatchingTimeoutNanos;
}
} else if (windowState != null) {
//AMP经过binder,最终调用到AMS【见小节2.4.3】
long timeout = ActivityManagerNative.getDefault().inputDispatchingTimedOut(
windowState.mSession.mPid, aboveSystem, reason);
if (timeout >= 0) {
return timeout * 1000000L; //转化为纳秒
}
}
5.最终调用的处理ANR函数
if (anrMessage != null) {
//当存在timeout的service,则执行appNotResponding【见小节3.1】
mAm.appNotResponding(proc, null, null, false, anrMessage);
}
[-> ActivityManagerService.java]
{
//记录ANR
//输出ANR到main log.
//dump栈信息
//输出各个进程的CPU使用情况
//输出CPU负载
//发送signal 3来dump栈信息
//将anr信息添加到dropbox
//弹出ANR对话框
}
主要发送ANR, 则会输出
- 各个进程的CPU使用情况;
- CPU负载;
- IOWait;
- traces文件
导致ANR常见情形:
- I/O阻塞
- 网络阻塞;
- onReceiver执行时间超过10s;
- 多线程死锁
避免ANR:
- UI线程尽量只做跟UI相关的工作
- 耗时的工作()比如数据库操作,I/O,网络操作),采用单独的工作线程处理
- 用Handler来处理UIthread和工作thread的交互