=========================================================================
我们如果需要优化布局卡顿问题,首先最重要的就是:确定定量标准。
所以我们首先介绍几种获取布局文件加载耗时的方法。
常规获取
首先介绍一下常规方法:
val start = System.currentTimeMillis()
setContentView(R.layout.activity_layout_optimize)
val inflateTime = System.currentTimeMillis() - start
这种方法很简单,因为setContentView是同步方法,如果想要计算耗时,直接将前后时间计算相减即可得到结果了。
AOP(Aspectj,ASM)
上面的方式虽然简单,但是却不够优雅,同时代码有侵入性,如果要对所有Activity测量时,就需要在基类中复写相关方法了,比较麻烦了。
下面介绍一种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();
}
Log.i(“aop inflate”,name + " cost " + (System.currentTimeMillis() - time));
}
上面用的Aspectj,比较简单,上面的注解的意思是在setContentView方法执行内部去调用我们写好的getSetContentViewTime方法。
这样就可以获取相应的耗时。
我们可以看下打印的日志:
I/aop inflate: AppCompatActivity.setContentView(…) cost 69
I/aop inflate: AppCompatActivity.setContentView(…) cost 25
这样就可以实现无侵入的监控每个页面布局加载的耗时。
具体源码可见文末。
获取任一控件耗时
有时为了更精确的知道到底是哪个控件加载耗时,比如我们新添加了自定义View,需要监控它的性能。
我们可以利用setFactory2来监听每个控件的加载耗时。
首先我们来回顾下setContentView方法:
public final View tryCreateView(@Nullable View parent, @NonNull String name,
…
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
…
return view;
}
在真正进行反射实例化xml结点前,会调用mFactory2的onCreateView方法。
这样如果我们重写onCreateView方法,在其前后加上耗时统计,即可获取每个控件的加载耗时。
private fun initItemInflateListener(){
LayoutInflaterCompat.setFactory2(layoutInflater, object : Factory2 {
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
val time = System.currentTimeMillis()
val view = delegate.createView(parent, name, context, attrs)
Log.i(“inflate Item”,name + " cost " + (System.currentTimeMillis() - time))
return view
}
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
return null
}
})
}
如上所示:真正的创建View的方法,仍然是调用delegate.createView,我们只是其之前与之后做了埋点。
注意,initItemInflateListener需要在onCreate之前调用。
这样就可以比较方便地实现监听每个控件的加载耗时。
=========================================================================
布局加载慢的主要原因有两个,一个是IO,一个是反射。
所以我们的优化思路一般有两个:
-
侧面缓解(异步加载)。
-
根本解决(不需要IO,反射过程,如X2C,Anko,Compose等)。
AsyncLayoutInflater方案
AsyncLayoutInflater 是来帮助做异步加载 layout 的,inflate(int, ViewGroup, OnInflateFinishedListener) 方法运行结束之后 OnInflateFinishedListener 会在主线程回调返回 View;这样做旨在 UI 的懒加载或者对用户操作的高响应。
简单的说我们知道默认情况下 setContentView 函数是在 UI 线程执行的,其中有一系列的耗时动作:Xml的解析、View的反射创建等过程同样是在UI线程执行的,AsyncLayoutInflater就是来帮我们把这些过程以异步的方式执行,保持UI线程的高响应。
使用如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new AsyncLayoutInflater(AsyncLayoutActivity.this)
.inflate(R.layout.async_layout, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
setContentView(view);
}
});
// 别的操作
}
这样做的优点在于将UI加载过程迁移到了子线程,保证了UI线程的高响应。
缺点在于牺牲了易用性,同时如果在初始化过程中调用了UI可能会导致崩溃。
X2C方案
X2C是掌阅开源的一套布局加载框架。
它的主要是思路是在编译期,将需要翻译的layout翻译生成对应的java文件,这样对于开发人员来说写布局还是写原来的xml,但对于程序来说,运行时加载的是对应的java文件。
这就将运行时的开销转移到了编译时。
如下所示,原始xml文件:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=“10dp”>
<include
android:id=“@+id/head”
layout=“@layout/head”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerHorizontal=“true” />
<ImageView
android:id=“@+id/ccc”
style=“@style/bb”
android:layout_below=“@id/head” />
X2C 生成的 Java 文件:
public class X2C_2131296281_Activity_Main implements IViewCreator {
@Override
public View createView(Context ctx, int layoutId) {
Resources res = ctx.getResources();
RelativeLayout relativeLayout0 = new RelativeLayout(ctx);
relativeLayout0.setPadding((int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,10,res.getDisplayMetrics())),0,0,0);
View view1 =(View) new X2C_2131296283_Head().createView(ctx,0);
RelativeLayout.LayoutParams layoutParam1 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
view1.setLayoutParams(layoutParam1);
relativeLayout0.addView(view1);
view1.setId(R.id.head);
layoutParam1.addRule(RelativeLayout.CENTER_HORIZONTAL,RelativeLayout.TRUE);
ImageView imageView2 = new ImageView(ctx);
RelativeLayout.LayoutParams layoutParam2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,(int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,1,res.getDisplayMetrics())));
imageView2.setLayoutParams(layoutParam2);
relativeLayout0.addView(imageView2);
imageView2.setId(R.id.ccc);
layoutParam2.addRule(RelativeLayout.BELOW,R.id.head);
return relativeLayout0;
}
}
使用时如下所示,使用X2C.setContentView替代原始的setContentView即可。
// this.setContentView(R.layout.activity_main);
X2C.setContentView(this, R.layout.activity_main);
X2C优点
- 在保留xml的同时,又解决了它带来的性能问题。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
最后
Android学习是一条漫长的道路,我们要学习的东西不仅仅只有表面的 技术,还要深入底层,弄明白下面的 原理,只有这样,我们才能够提高自己的竞争力,在当今这个竞争激烈的世界里立足。
人生不可能一帆风顺,有高峰自然有低谷,要相信,那些打不倒我们的,终将使我们更强大,要做自己的摆渡人。
我把自己这段时间整理的Android最重要最热门的学习方向资料放在了我的GitHub,里面还有不同方向的自学编程路线、面试题集合/面经、及系列技术文章等。
外链图片转存中…(img-skwTAjh3-1710765513423)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-ylROpRpZ-1710765513423)]
最后
Android学习是一条漫长的道路,我们要学习的东西不仅仅只有表面的 技术,还要深入底层,弄明白下面的 原理,只有这样,我们才能够提高自己的竞争力,在当今这个竞争激烈的世界里立足。
人生不可能一帆风顺,有高峰自然有低谷,要相信,那些打不倒我们的,终将使我们更强大,要做自己的摆渡人。
我把自己这段时间整理的Android最重要最热门的学习方向资料放在了我的GitHub,里面还有不同方向的自学编程路线、面试题集合/面经、及系列技术文章等。
资源持续更新中,欢迎大家一起学习和探讨。