我是靠谱客的博主 慈祥小鸽子,最近开发中收集的这篇文章主要介绍由浅到深,深入分析APP卡顿和优化,android开发基础教程,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {

ViewManager wm = a.getWindowManager();
//获得WindowManager,实际是WindowManagerImpl

wm.addView(decor, l);
//添加视图

wm.updateViewLayout(decor, l);
//需要刷新的时候会走这里

}

从上面可以看到,当Activity执行onResume()的时候就会添加视图,或者刷新视图。需要解释一点:WindowManager实现了ViewManager接口。

如图中所说,WindowManagerGlobal调用addView()的时候会把DecorView添加到它维护的数组中去,并且会创建另一个关键且极其重要的ViewRootImpl(这个必须要专门讲一下)类型的对象,并且也会把它存到一个数组中维护。
WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {

root = new ViewRootImpl(view.getContext(), display);
//重要角色登场
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
//保存起来维护
mParams.add(wparams);

root.setView(view, wparams, panelParentView);
//设置必要属性view是DecorView,panelParentView是PhoneWindow

}

可以看出ViewRootImpl是在Activity执行onResume()的时候才被创建的,并且此时才把DecorView传进去让它管理。

知识点:WindowManager是在onCreate()时被创建。它对窗口的管理能力实际是通过WindowManagerGlobal实现的。在onResume()是视图才通过WindowManager被添加到窗口上。

4.ViewRootImpl
ViewRootImpl能够和系统的WindowManagerService进行交互,并且管理着DecorView的绘制和窗口状态。非常的重要。赶紧在图中找到对应位置吧!

ViewRootImpl并不是一个View,而是负责管理视图的。它配合系统来完成对一个Window内的视图树的管理。从图中也可以看到,它持有了DecorView的引用,并且视图树它是视图树绘制的起点。因此,ViewRootImpl会稍微复杂一点,需要我们更深入的去了解,在图中我标出了它比较重要的组成Surface和Choreographer等都会在后面提到。

到此,我们已经一起把第一张图撸了一遍了,现在童鞋们因该对Android视图框架有了大致的了解。下面将更进一步的去了解Android的绘制机制。

###二.App总是卡顿到底是什么原因?
下面将会详细的讲解为什么我们设置的视图能够被绘制到屏幕上?这中间究竟隐藏着怎样的离奇?看完之后,你自然就能够从根源知道为什么你的App会那么卡,以及开始有思路着手解决这些卡顿。

同样用一张图来展示这个过程。由于Android绘制机制确实有点复杂,所以第一眼看到的时候你的内心中可能蹦腾了一万只草泥马????。不要怕!我们从源头开始,一点一点的梳理这个看似复杂的绘制机制。为什么说看似复杂呢?因为这个过程只需要几分钟。Just Do It!

1.CPU、GPU是搞什么鬼的?

整天听到CPU、GPU的,你知道他们是干什么的吗?这里简单的提一下,帮助理解后面的内容。

在Android的绘制架构中,CPU主要负责了视图的测量、布局、记录、把内容计算成Polygons多边形或者Texture纹理,而GPU主要负责把Polygons或者Textture进行Rasterization栅格化,这样才能在屏幕上成像。在使用硬件加速后,GPU会分担CPU的计算任务,而CPU会专注处理逻辑,这样减轻CPU的负担,使得整个系统效率更高。

2.RefreshRate刷新率和FrameRate帧率

RefreshRate刷新率是屏幕每秒刷新的次数,是一个与硬件有关的固定值。在Android平台上,这个值一般为60HZ,即屏幕每秒刷新60次。

FrameRate帧率是每秒绘制的帧数。通常只要帧数和刷新率保持一致,就能够看到流畅的画面。在Android平台,我们应该尽量维持60FPS的帧率。但有时候由于视图的复杂,它们可能就会出现不一致的情况。

如图,当帧率小于刷新率时,比如图中的30FPS < 60HZ,就会出现相邻两帧看到的是同一个画面,这就造成了卡顿。这就是为什么我们总会说,要尽量保证一帧画面能够在16ms内绘制完成,就是为了和屏幕的刷新率保持同步。

下面将会介绍Android是如何来确保刷新率和帧率保持同步的。

3.Vsync(垂直同步)是什么?

你可能在游戏的设置中见过Vsync,开启它通常能够提高游戏性能。在Android中,同样使用Vsync垂直同步来提高显示性能。它能够使帧率FrameRate和硬件的RefreshRate刷新强制保持一致。

3.1HWComposer与Vsync不得不说的事
看图啦看图啦。首先在最左边我们看到有个叫HWComposer的类,这是一个c++编写的类。它Android系统初始化时就被创建,然后开始配合硬件产生Vsync信号,也就是图中的HW_Vsync信号。当然它不是一直不停的在产生,这样会导致Vsync信号的接收者不停的接收到绘制、渲染命令,即使它们并不需要,这样会带来严重的性能损耗,因为进行了很多无用的绘制。所以它被设计设计成能够唤醒和睡眠的。这使得HWComposer在需要时才产生Vsync信号(比如当屏幕上的内容需要改变时),不需要时进入睡眠状态(比如当屏幕上的内容保持不变时,此时屏幕每次刷新都是显示缓冲区里没发生变化的内容)。

如图,Vsync的两个接收者,一个是SurfaceFlinger(负责合成各个Surface),一个是Choreographer(负责控制视图的绘制)。我们稍后再介绍,现在先知道它们是干什么的就行了。

3.2Vsync offset机制
为了提高效率,尽量减少卡顿,在Android 4.1时引入了Vsync机制,并在随后的4.4版本中加入Vsync offset偏移机制。

图1. 为4.1时期的Vsync机制。可以看到,当一个Vsync信号到来时,SurfaceFlinger和UI绘制进程会同时启动,导致它们竞争CPU资源,而CPU分配资源会耗费时间,着降低系统性能。同时当收到一个Vsync信号时,第N帧开始绘制。等再收到一个Vsync信号时,第N帧才被SurfaceFlinger合成。而需要显示到屏幕上,需要等都第三个Vsync信号。这是比较低效率。于是才有了图2. 4.4版本加入的Vsync offset机制。

图2. Google加入Vsync offset机制后,原本的HW_Vsync信号会经过DispSync会分成Vsync和SF_Vsync两个虚拟化的Vsync信号。其中Vsync信号会发送到Choreographer中,而SF_Vsync会发送到SurfaceFlinger中。理论上只要phase_app和phase_sf这两个偏移参数设置合理,在绘制阶段消耗的时间控制好,那么画面就会像图2中的前几帧那样有序流畅的进行。理想总是美好的。实际上很难一直维持这种有序和流畅,比如frame_3是比较复杂的一帧,它的绘制完成的时间超过了SurfaceFlinger开始合成的时间,所以它必须要等到下一个Vsync信号到来时才能被合成。这样便造成了一帧的丢失。但即使是这样,如你所见,加入了Vsync offset机制后,绘制效率还是提高了很多。

从图中可以看到,Vsync和SF_Vsync的偏移量分别由phase_app和phase_sf控制,这两个值是可以调节的,默认为0,可为负值。你只需要找到BoardConfig.mk文件,就可以对这两个值进行调节。

4.回到ViewRootImpl

前面介绍了几个关键的概念,现在我们回到ViewRootImpl中去,在图中找到ViewRootImpl的对应位置。

前面说过,ViewRootImpl控制着一个Window中的整个视图树的绘制。那它是如何进行控制的呢?一次绘制究竟是如何开始的呢?

在ViewRootImpl创建的时候,会获取到前面提到过过的一个关键对象Choreographer。Choreographer在一个线程中仅存在一个实例,因此在UI线程只有一个Choreographer存在。也就说,通常情况下,它相当于一个应用中的单例。

在ViewRootImpl初始化时,会实现一个Choreographer.FrameCallback(这是一个Choreographer中的内部类),并向Choreographer中post。顾名思义,FrameCallback会在每次接收到Vsync信号时被回调。
Choreographer.java

public interface FrameCallback {
public void doFrame(long frameTimeNanos);
//一旦注册到CallbackQueue中,那么
//每次Choreographer接收到Vsync信号时都会回调。
}

FrameCallback一旦被注册,那么每次收到Vsync信号时它都会被回调。利用它,我们可以实现会帧率的监听。
ViewRootImpl.java

//这个方法只有在ViewRootImpl初始化时才会被调用
private void profileRendering(boolean enabled) {

mRenderProfiler = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {

scheduleTraversals();
//请求一个Vsync信号,后面还会提到这个方法
mChoreographer.postFrameCallback(mRenderProfiler);
//每次回调时,重新将FrameCallback post到Choreographer中

}
};

mChoreographer.postFrameCallback(mRenderProfiler);
//将FrameCallback post到Choreographer中

}

上面代码出现了一个重要方法scheduleTraversals()。下面我们看看它究竟为何重要。
ViewRootImpl.java

void scheduleTraversals() {

mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//向Choreographer中post一个TraversalRunnable
//这又是一个十分重要的对象

}

可以看出scheduleTraversals()每次调用时会向Choreographer中post一个TraversalRunnable,它会促使Choreographer去请求一个Vsync信号。所以这个方法的作用就是用来请求一次Vsync信号刷新界面的。事实上,你可以看到,在invalidate()、requestLayout()等操作中,都能够看到它被调用。原因就是这些操作需要刷新界面,所以需要请求一个Vsync信号来出发新界面的绘制。
ViewRootImpl.java

final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
//开始遍历视图树,这意味着开始绘制一帧内容了
}
}

从图中可以看到,每当doTraversal()被调用时,一系列的测量、布局和绘制操作就开始了。在绘制时,会通过Surface来获取一个Canvas内存块交给DecorView,用于视图的绘制。整个View视图的内容都是被绘制到这个Canvas中。

5.Choreographer中的风起云涌

前面反复提到向Choreographer中post回调,那么post过去发生了些什么呢?从图中可以看到,所有的post操作最终都进入到postCallbackDelayedInternal()中。

Choreographer.java

private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {

synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
//将Callback添加到CallbackQueue[]中
if (dueTime <= now) {
scheduleFrameLocked(now);
//如果回调时间到了,请求一个Vsync信号
//在接收到后会调用doFrame()回调这个Callback。
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
//异步消息,避免被拦截器拦截
mHandler.sendMessageAtTime(msg, dueTime);
//如果还没到回调的时间,向FrameHandelr中发送
//MSG_DO_SCHEDULE_CALLBACK消息
}
}

}

上面这段代码会把post到Choreographer中的Callback添加到Callback[]中,并且当它因该被回调时,请求一个Vsync信号,在接收到下一个Vsync信号时回调这个Callback。如果没有到回调的时间,则向FrameHandler中发送一个MSG_DO_SCHEDULE_CALLBACK消息,但最终还是会请求一个Vsync信号,然后回调这个Callback。

现在来看看前面代码中调用的scheduleFrameLocked()是如何请求一个Vsync信号的。

private void scheduleFrameLocked(long now) {

//先判断当前是不是在UI线程
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
//是UI线程就请求一个Vsync信号
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
//不在UI线程向FrameHandler发送一个MSG_DO_SCHEDULE_VSYNC消息
//来请求一个Vsync信号
}
}

private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
//通过DisplayEventReceiver请求一个Vsync信号
//这是个恨角色,待会儿会聊聊它。
//MSG_DO_SCHEDULE_VSYNC消息也是通过调用这个方法请求Vsync信号的。
}

上面我们提到过,Choreographer在一个线程中只有一个。所以,如果在其它线程,需要通过Handler来切换到UI线程,然后再请求Vsync信号。

下面看看刚刚出场的mDisplayEventReceiver是个什么鬼?

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {

//这个方法用于接收Vsync信号
public void onVsync(){

Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
//这里并没有设置消息的类型
//其实就是默认为0,即MSG_DO_FRAME类型的消息
//它其实就是通知Choreographer开始回调CallbackQueue[]中的Callback了
//也就是开始绘制下一帧的内容了
}

//这个方法是在父类中的,写在这方便看
public void scheduleVsync() {

nativeScheduleVsync(mReceiverPtr);
//请求一个Vsync信号
}
}

这给类功能比较明确,而且很重要!

上面一直在说向FrameHandler中发消息,搞得神神秘秘的。接下来就来看看FrameHandler本尊。请在图中找到对应位置哦。

private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
//开始回调Callback,以开始绘制下一帧内容
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
//请求一个Vsync信号
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_C
ALLBACK:
//实际也是请求一个Vsync信号
doScheduleCallback(msg.arg1);
break;
}
}
}

FrameHandler主要在UI线程处理3种类型的消息。

  • MSG_DO_FRAME:值为0。当接收到一个Vsync信号时会发送该种类型的消息,然后开始回调CallbackQueue[]中的Callback。比如上面说过,在ViewRootImpl有两个重要的Callback,FrameCallback(请求Vsync并再次注册回调)和TraversalRunnable(执行doTraversal()开始绘制界面)频繁被注册。
  • MSG_DO_SCHEDULE_VSYNC:值为1。当需要请求一个Vsync消息(即屏幕上的内容需要更新时)会发送这个消息。接收到Vsync后,同上一步。
  • MSG_DO_SCHEDULE_CALLBACK:值为2。请求回调一个Callback。实际上会先请求一个Vsync信号,然后再发送MSG_DO_FRAME消息,然后再回调。

FrameHandler并不复杂,但在UI的绘制过程中具有重要的作用,所以一定要结合图梳理下这个流程。

6.SurfaceFlinger和Surface简单说

在介绍Vsync的时候,我们可能已经看到了,现在Android系统会将HW_VSYNC虚拟化为两个Vsync信号。一个是VSYNC,被发送给上面一直在讲的Choreographer,用于触发视图树的绘制渲染。另一个是SF_VSYNC,被发送给我接下来要讲的SurfaceFlinger,用于触发Surface的合成,即各个Window窗口画面的合成。接下来我们就简单的看下SurfaceFlinger和Surface。由于这部分基本是c++编写的,我着重讲原理。
流程。

6.SurfaceFlinger和Surface简单说

在介绍Vsync的时候,我们可能已经看到了,现在Android系统会将HW_VSYNC虚拟化为两个Vsync信号。一个是VSYNC,被发送给上面一直在讲的Choreographer,用于触发视图树的绘制渲染。另一个是SF_VSYNC,被发送给我接下来要讲的SurfaceFlinger,用于触发Surface的合成,即各个Window窗口画面的合成。接下来我们就简单的看下SurfaceFlinger和Surface。由于这部分基本是c++编写的,我着重讲原理。

最后

以上就是慈祥小鸽子为你收集整理的由浅到深,深入分析APP卡顿和优化,android开发基础教程的全部内容,希望文章能够帮你解决由浅到深,深入分析APP卡顿和优化,android开发基础教程所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(64)

评论列表共有 0 条评论

立即
投稿
返回
顶部