?
一、绘制原理
cpu负责计算显示内容 GPU负责棚格化(UI元素绘制到屏幕上)
16ms发出VSync信号触发UI渲染 大多数的Android设备屏幕刷新频率:60Hz
二、优化工具
(1)Systrace a) 关注Frames b) 正常:绿色原点,丢帧:黄色或红色 c) Alerts栏
(2)Layout Inspector a)AndroidStudio自带工具
b)查看视图层次结构
(3)Choreographer a)是一个类库,android自带的。
b)可以获取FPS,具备实时性
c)可以带到线上。
Choregrapher.getInstance().postFrameCallback
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
Log.e("TAG", "starTime=" + starTime + ", frameTimeNanos=" + frameTimeNanos + ", frameDueTime=" + (frameTimeNanos - starTime) / 1000000);
}
});
输出
starTime=1757438563915168, frameTimeNanos=1757438654798396, frameDueTime=90
三、布局加载原理
从setContentView开始
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
public abstract void setContentView(@LayoutRes int resId);
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
LayoutInflater是真正对resId加载,进入inflate方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
...
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
@NonNull
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
...
return impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
...
}
loadXmlResourceParser加载指定文件的 XML 解析器。返回XmlResourceParser。 回到LayoutInflater的inflate方法,找到createViewFromTag方法,根据标签创建view,进入tryCreateView方法
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
mPrivateFactory只用于fragment标签, 如果view == null
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
进入createView方法,里面是反射创建View
if (constructor == null) {
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
if (mFilter != null) {
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
...
try {
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
 (1)布局文件读取慢:IO过程 (2)创建View慢:反射(比new慢3倍) LayoutInflater.Factory: LayoutInflater创建view的一个Hook(挂钩) 定制创建view的过程:例如全局替换自定义Textview等 LayoutInflater包含Factory和Factory2两个 Factory和Factory2:Factory2继承自Factory,并且多了一个参数parent
四 优雅获取界面布局耗时
背景:获取每个界面加载耗时 实现:覆写方法、手动埋点
Aop实现:
@Around("execution(* android.app.Activity.setContentView(..))")
public void getSetContentViewTime(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.toShortString();
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
LogUtils.i(name + " cost " + (System.currentTimeMillis() - time));
}
获取任一控件的耗时
低侵入性、使用LayoutInflater.Factory
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (TextUtils.equals(name, "TextView")) {
}
long time = System.currentTimeMillis();
View view = getDelegate().createView(parent, name, context, attrs);
LogUtils.i(name + " cost " + (System.currentTimeMillis() - time));
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
五、异步Inflate实战
1、优化布局加载:异步AsyncLayoutInflater 在WorkThread加载布局,(原生使用的办法是在UI线程中加载布局),(2)加载结束后,回调主线程。 优点:节约主线程的时间。 使用 AsyncLayoutInflater 类实现步骤:
implementation 'com.android.support:asynclayoutinflater:28.0.0'
setContentView(R.layout.activity_main);
new AsyncLayoutInflater(MainActivity.this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(@NonNull View view, int i, @Nullable ViewGroup viewGroup) {
setContentView(view);
mRecyclerView = findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
mRecyclerView.setAdapter(mNewsAdapter);
mNewsAdapter.setOnFeedShowCallBack(MainActivity.this);
}
});
?侧面缓解卡顿 使用了AsyncLayoutInflater就失去了向下兼容的特性,不能设置 LayoutInflater.Factory(自定义解决) 注意view中不能有依赖主线程的操作 这个方法只是缓解的作用,并没有从根本上解决(IO操作 和 反射)。
2、X2C介绍: 保留xml优点,解决性能问题(开发人员写xml,加载java代码),原理:通过APT编译期翻译xml为java代码 优点:本质上解决了性能问题,(不会IO操作,同时使用new创建) 缺点:引入新问题,不便于开发,可维护性差。(XML的优点是方便预览,维护性好) 部分属性Java不支持 失去了系统的兼容(AppCompat)如:TextView、ImageView系统在高版本向低版本有兼容 x2c使用:
依赖:
annotationProcessor 'com.zhangyue.we:x2c-apt:1.1.2'
implementation 'com.zhangyue.we:x2c-lib:1.0.6'
使用方式:
@Xml(layouts = "activity_main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
X2C.setContentView(MainActivity.this, R.layout.activity_main);
}
}
六、视图绘制优化实战
布局绘制回顾:
测量:确定大小(遍历视图树,确认viewgroup和view元素的大小) 布局:确定位置(遍历视图树,每个viewgroup根据测量阶段的大小确认自己的位置) 绘制:绘制视图(视图树中的每个对象都会创建一个canvas对象,向GPU发送绘制命令)
性能瓶颈:
每个阶段耗时 自顶向下的遍历 触发多次(如嵌套RelativeLayout)
减少布局层级和复杂度:
准则:减少view树层级、宽而浅,避免窄而深 ConstraintLayout:实现几乎完全扁平化布局、构建复杂布局性能更高、具有RelativeLayout和LinearLayout特性 其他:不嵌套使用elativeLayout、不在嵌套LinearLayout中使用weight、merge标签可减少一个层级(只能用于根view)
避免过度绘制
一个像素最好只被绘制一次、调试GPU过度绘制、蓝色可接受 方法: 去掉多余背景色,减少复杂shape使用 避免层级叠加(控件不要重叠) 自定义view使用clipRect屏蔽被遮盖View绘制
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDroids.length > 0 && mDroidCards.size() == mDroids.length) {
int i;
for (i = 0; i < mDroidCards.size() - 1; i++) {
mCardLeft = i * mCardSpacing;
canvas.save();
canvas.clipRect(mCardLeft,0,mCardLeft+mCardSpacing,mDroidCards.get(i).getHeight());
drawDroidCard(canvas, mDroidCards.get(i), mCardLeft, 0);
canvas.restore();
}
drawDroidCard(canvas, mDroidCards.get(mDroidCards.size() - 1),
mCardLeft + mCardSpacing, 0);
}
invalidate();
}
其他
ViewStub:高效占位符、延迟初始化(没有测量和布局的过程) onDraw中避免:创建大量对象、耗时操作
|