在一个典型的显示系统中,通常包括CPU、GPU、display三个部分,其中CPU负责计算数据,把计算好的数据交给GPU,GPU对图形数据进行渲染,渲染好后放到buffer中存起来,而后display负责把buffer的数据呈现到屏幕上。java
在Android中也是如此,由CPU/GPU准备好数据,存入buffer,display每隔一段时间去buffer里取数据,而后显示出来android
display在Android中,读取的频率是固定的,为16ms。之因此是16ms是由于人眼与大脑之间的协做没法感知超过60fps的画面更新。
canvas
咱们在使用APP的时候,当界面出现卡顿不流畅的状况,是由于当前界面UI的处理超过了16ms,会占用下一个16ms,这就致使16ms*2都是同一帧,也就是“卡”了。缓存
了解了Android渲染机制后,咱们来分析app为何会超过16ms的重绘时间,一般有如下缘由:性能优化
一、布局层级不合理markdown
二、布局存在过分绘制app
针对上述状况,咱们接下来说述一些常见的监控手段和布局和优化手段异步
Layout Inspater是AndroidStudio自带的工具,用于分析布局层级ide
一、在链接的设备或模拟上运行你的应用工具
二、点击Tools > Layout Inspector
三、在出现的Choose Process对话框中,选择你想要检查的应用进程,而后点击OK。
四、默认状况下,Choose Process 对话框仅会为 Android Studio 中当前打开的项目列出进程,而且该项目必须在设备上运行。 若是想要检查设备上的其余应用,请点击 Show all processes。 若是正在使用已取得 root 权限的设备或者没有安装 Google Play 商店的模拟器,那么您会看到全部正在运行的应用。 不然,您只能看到能够调试的运行中应用。
View Tree:视图在布局中的层次结构
Screenshot:带每一个视图可视边界的设备屏幕截图。
Properties Table:选定视图的布局属性
其中,咱们最须要使用的是View Tree,像本案例中,设置页的每个栏目,由SettingItem包着RelativeLayout,其中SettingItem这个自定义View自己又继承了RelativeLayout,其实就多余了,能够用merge标签进行优化,下降层级
固然,最好使用约束布局,能够大大下降层级
从开发者模式中找到调试GPU过分绘制
功能开关,打开
这种过分绘制常见于background的设置
public class FPSMonitor implements Choreographer.FrameCallback, Runnable{
private HandlerThread mHandlerThread;
private long startTime = -1;
private long endTime = -1;
private final int MONITOR_TIME = 1000;
private Handler mWorkHandler;
private int mFpsCount;
@Override
public void doFrame(long frameTimeNanos) {
if (startTime == -1) {
startTime = frameTimeNanos;
}
mFpsCount++;
//超过一秒了,发消息到工做线程,计算帧率
long duration = (frameTimeNanos - startTime) / 1000000L;
if (duration >= MONITOR_TIME) {
endTime = frameTimeNanos;
mWorkHandler.post(this);
} else {
//没到一秒,设定下一帧监听
Choreographer.getInstance().postFrameCallback(this);
}
}
@Override
public void run() {
//计算帧率
long duration = (endTime - startTime) / 1000000L;
float frame = 1000.0f * mFpsCount / duration;
Log.i("Restart", "当前帧率: " + frame);
//开启下一秒的计算
start();
}
public void start() {
if (mHandlerThread == null) {
mHandlerThread = new HandlerThread("FPS Monitor Thread");
mHandlerThread.start();
mWorkHandler = new Handler(mHandlerThread.getLooper());
}
//重置计算值
startTime = -1;
endTime = -1;
mFpsCount = 0;
//设置帧绘制监听器
Choreographer.getInstance().postFrameCallback(this);
}
}
复制代码
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//注意:要在super.onCreate(savedInstanceState);以前调用才不会报错
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
long start = System.currentTimeMillis();
AppCompatDelegate delegate = getDelegate();
View view = delegate.createView(parent, name, context, attrs);
long duration = System.currentTimeMillis() - start;
Log.i("Restart", name + "的绘制耗时: " + duration);
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
}
复制代码
下面介绍一个最经常使用的场景:
//假定自定义View为RelativeLayout
public class LoginButton extends RelativeLayout {
。。。
}
//在xml标签中使用merge做为根标签,而不要再次使用RelativeLayout做为根标签,能够省去一个层级
复制代码
ViewStub的使用场景为当一个布局可能须要加载,也可能不须要的状况。假如布局delayInflateLayout可能不须要也可能须要。则能够用ViewStub标签,以下代码所示,在布局文件中使用。
<ViewStub android:id="@+id/contentPanel" android:inflatedId="@+id/inflatedStart" android:layout="@layout/delayInflateLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" />
复制代码
再在代码中控制是否真正加载它
//调用inflate则会真正的加载它,可是只能调用一次
viewStub.inflate();
复制代码
clipRect的功能能够理解为在一个大的画布中,用一些大小可变的矩形一个一个来裁切,在某一个矩形内,绘制想要绘制的图形,超出的不进行绘制,当咱们的app这样进行写自定义View的时候,能够避免view与view之间的叠加,从而产生同一个像素点被绘制屡次的状况,原理就是这样。举一个案例:
上述的三张图,相互重叠的状况,onDraw方法优化以下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(20, 120);
for (int i = 0; i < mCards.length; i++)
{
canvas.translate(120, 0);
canvas.save();
if (i < mCards.length - 1)
{
//裁剪画布,减小没必要要的绘制
canvas.clipRect(0, 0, 120, mCards[i].getHeight());
}
canvas.drawBitmap(mCards[i], 0, 0, null);
canvas.restore();
}
canvas.restore();
}
复制代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new AsyncLayoutInflater(this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
setContentView(R.layout.activity_main);
}
});
}
复制代码
使用AsyncLayoutInflater 的局限性:
全部构建的View中必须不能直接使用 Handler 或者是调用 Looper.myLooper(),由于异步线程默认没有调用 Looper.prepare ();
异步转换出来的 View 并无被加到 parent view中,必须手动添加;
AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;
同时缓存队列默认 10 的大小限制若是超过了10个则会致使主线程的等待;
开发Android应用程序时也不可能无限制的使用CPU和内存,若是对CPU和内存使用不当也会形成应用的卡顿和内存溢出等问题。所以,性能优化是每一个Android开发人员都应该去注意的,本文介绍了绘制相关的一些监控手段和常见的优化手段,但愿对你们有帮助~