布局优化
UI渲染机制
- 画面流畅需要帧数为60帧每秒
- Android通过VSYNC信号触发对UI的绘制,其间隔时间是1000ms/60=16ms(即1000ms内显示60帧画面的单位时间)
- 故需在16ms之内完成绘制才可以保证画面的流畅
- 否则会造成丢帧,如一次绘制耗时20ms,当16ms时系统发出VSYNC信号还未绘制完,下一个帧就会被丢弃,直到下次信号才开始绘制,导致16*2ms内都显示同一帧画面(即卡顿)
利用 开发者选项 / Profile GPU Rendering / On srceen as bars 工具可检查UI渲染时间
- 蓝色:测量绘制Display List的时间
- 红色:OPenGL渲染Display List所需要的时间
- 黄色:CPU等待GPU处理的时间
- 绿色横线:VSYNC时间16ms,需尽量将所有条形图控制在绿线下
避免Overdraw
过度绘制会浪费CPU、GPU资源,如系统会默认绘制Activity的背景,若再绘制一个重叠背景则Overdraw了
利用 开发者选项 / Enable GPU Overdraw工具可通过颜色判断Ovedraw的次数,颜色越深表示绘制次数越多
优化布局层级
对View的测量、布局和绘制都是通过对View树的深度遍历来操作
- 当布局层级少时,可以使用LinearLayout代替RelativeLayout,前者功能比较简单
- 当布局层级多时,建议View树的高度不超过10层,可用RelativeLayout替换嵌套的LinearLayout降低高度
利用<include>重用Layout
如下为一个共性的TextView,将宽高设为0可让开发者在使用时对其赋值
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center"
android:textSize="30sp"
android:text="common ui">
</TextView>
通过include的layout属性引用
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="@layout/common_text"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
<merge>
如果<include>中的布局和外层布局方向一致
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<include layout="@layout/vertical"/>
</LinearLayout>
可以使用<merge>合并,避免多出一个LinearLayout
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="one" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="two" />
</merge>
使用<ViewStub>实现View的延迟加载
如查看的内容只有在点击某个按钮时才会加载、如网络异常界面没必要在界面初始化时加载
- ViewSub只会在显示时才去渲染布局
- setVisibility(View.GONE)在初始化布局树的时候就已经添加布局
如下为要加载的view_stub_item.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher">
</ImageView>
主布局中ViewStub通过layout属性引用布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/vis"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="visible" />
<Button
android:id="@+id/inflate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="inflate" />
<ViewStub
android:id="@+id/viewStub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/view_stub_item" />
</LinearLayout>
获取到ViewStub后,可利用setVisibility(View.VISIBLE)或inflate()加载,后者可返回View
public class MainActivity extends AppCompatActivity {
ViewStub viewStub;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewStub = (ViewStub) findViewById(R.id.viewStub);
findViewById(R.id.vis).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewStub.setVisibility(View.VISIBLE);
}
});
findViewById(R.id.inflate).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
View inflateView = viewStub.inflate();
ImageView imageView = (ImageView) inflateView.findViewById(R.id.img);
}
});
}
}
Hierarchy View
通过SDK/Tool/monitor.bat,打开Android Device Monitor(打开先需关闭AS),再运行程序,布局如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
在Hierarchy View,我们可以看到三层的LinearLayout且没有分支,说明是冗余的
选择某个View,点击右上角的"Obtain layout time…",可获取布局测量、摆放、绘制的时间,绿黄红颜色表示绘制效率的好中差
ANR
出现ANR后,会在/data/anr 生成日志文件
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button anr = findViewById(R.id.brn_anr);
anr.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Toast.makeText(MainActivity.this, "onclick", Toast.LENGTH_SHORT).show();
}
});
}
}
如上,按键事件超过5s未结束,触发ANR,在生成log中可看到是由于线程在Sleeping
还有一种比较常见的ANR,如下
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button anr = findViewById(R.id.brn_anr);
anr.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
testANR();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
initView();
Log.d(TAG, "onClick: ");
}
});
}
private synchronized void initView() {
}
private synchronized void testANR() {
try {
Thread.sleep(30*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
虽然按键事件开启了线程,但调用的方法使用了同一个锁对象,导致后续的initView()方法被阻塞
如上initView()方法正在等待锁对象MainActivity,其被Thread14持有,再看tid=14的线程,其正在Sleeping
内存优化
获取内存信息
在开发者模式中找到内存相关信息
Profiler
可利用AS底部的Profiler实时监控程序的内存使用情况
- 当内存泄漏时,内存会持续增高
- 当发生GC时,内存会突然减少
TraceView
TraceView是一个可视化性能调查工具,用于分析TraceView log,可利用Debug类在onCreate()开启监听,onDestroy()时结束监听
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Debug.startMethodTracing();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onDestroy() {
super.onDestroy();
Debug.stopMethodTracing();
}
}
生成的dmtrace.trace存放在以下路径
此外,还可以通过Android Device Monitor的start method profiling
- Trace:跟着方法执行的全部过程,资源消耗大
- Sample:按照指定频率抽样调查,需要较长执行时间获取准备样本
用Android Device Monitor打开生成的Log,上半部分显示方法执行时间的时间轴,每一行代表一个线程,不同颜色的方块代表下面不同的执行方法
下半部分为具体方法在所处时间段内的各种类型时间及所占百分比
- Incl CPU Time——某方法占用的CPU时间
- Excl CPU Time——某方法本身(不包括子方法)占用的CPU时间
- Incl Real Time——某方法真正执行时间
- Excl Real Time——某方法本身(不包括子方法)真正执行时间
- Calls+RecurCalls——调用次数+递归回调次数
如果Incl CPU Time时间长,但Calls+RecurCalls次数少,则应该优化方法
MAT(Memory Analyzer Tool)
点击Android Device Monitor的Update Heap更新堆数据,在Heap标签点击Cause GC,再点击Dump HPROF File保存文件
利用Sdk\platform-tools\hprof-conv.exe转换格式
打开MAT选择Open Dump File
文章来源:https://www.toymoban.com/news/detail-418851.html
- Histogram:查看内存中每个对象的数量、大小和名称
- Dominator Tree:按照对象大小进行排序,并显示对象之间的引用结构,可找出大对象
dumpsys
dumpsys 可以列出系统相关的信息和服务状态文章来源地址https://www.toymoban.com/news/detail-418851.html
命令 | 功能 |
---|---|
activity | Activity栈信息 |
meminfo | 内存信息 |
procstats | 内存状态 |
package | 包信息 |
到了这里,关于Android中级——性能优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!