Android作为移动设备,不管是内存还是cpu性能都受到一定限制。无法向pc那样具有超大的内存和高性能的cpu。意味着Android过多的使用内存会oom,过多的使用cpu资源(一般是大量耗时任务)会造成手机卡顿,甚至ANR。因此Android的性能优化及其重要。
性能优化中还有个重要的问题就是内存泄漏,内存泄漏并不会导致程序功能异常,但是他会导致Android程序内存占用过大。这回提高oom的概率。
要点
一、布局优化
布局优化的核心思想:尽量减少布局文件的层级。道理很简单嵌套层级越简单越好。嵌套层级少了,安卓的绘制工作就少了。性能自然提高了。
1、如何进行布局优化?
- 删除无用的控件和层级
- 有选择使用性能较低的viewGroup
比如LinearLayout和RelativeLayout选择时,优先选择LinearLayout。因为RelativeLayout的功能更复杂。LinearLayout和FramLayout都是简单高级的ViewGroup。
ps:当然布局嵌套时,我们可以使用RelativeLayout或者ConstraintLayout来减少布局嵌套层级。
2、布局优化的另一种手段
- 采用include标签
主要是布局复用 - 采用merge标签
这个标签一般和include联合使用,减少布局层级 - 采用ViewStub标签
延迟加载提高程序的性能
(1)include
item布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
主activity中复用item:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
tools:context=".MainActivity"
android:orientation="vertical">
<!-- include 简单使用-->
<include layout="@layout/item"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</LinearLayout>
将一个指定的布局文件加载到当前布局文件中。避免写一些重复的布局(如上我们复用了item布局的功能,这样就不用在当前的布局中重写一遍item功能的代码了)
注意:
1、include标签只支持android:ayout开头的属性和id属性。(background等是不支持的)
2、如果为include标签指定了id属性,同时被包含布局根元素也指定了id,以include指定为准
3、如果为include指定其他android:ayout属性,则android:ayout_width和android:ayout_height也必须执定,否则android:ayout无法生效。
(2)merge
这个标签一般和include联合使用,减少布局层级。
如上include的代码例子,我们使用include时item布局时,item的主布局是线性垂直,而主activity中也是线性垂直,这时,我们发现item中的LinearLayout就是多余的。这时我们就可以使用merge
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</merge>
很简单 吧item的Linearlayout改为merge即可,这样系统绘制时,就把这层多余的Linearlayout优化了。(include 引用merge时便忽略merge了)
(3)ViewStub
ViewStub继承了view,他非常轻量级且宽高都是0,本身不参与任何绘制和布局过程。他的意义就是延时加载,在需要的时候加载。
栗子
主activity:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
tools:context=".MainActivity">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="测试数据"
android:gravity="center"
android:textSize="20sp"
android:textColor="@color/colorAccent"/>
<Button
android:onClick="netError"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="模拟没网"/>
<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/ll_error"
android:layout="@layout/net_error"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
net_error 布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/ll_error"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="延迟加载"
android:gravity="center"
android:textSize="20sp"
android:textColor="@color/colorAccent"/>
</LinearLayout>
调用:
public void netError(View view) {
findViewById(R.id.stub_import).setVisibility(View.VISIBLE);
// 或者
// ViewStub viewStub = findViewById(R.id.stub_import);
// viewStub.inflate();
}
点击按钮
如上:viewstub
1、 android:inflatedId="@+id/ll_error"为要加载的布局的根布局id
2、 android:layout="@layout/net_error" 要加载的布局
3、代码中调用显示
ps:viewstub目前不支持 merge标签
参考文章:
ViewStub–使用介绍
Android 使用View Gone 与 ViewStub的区别
二、绘制优化
绘制优化是指view的onDraw方法要避免执行大量操作。这里这里主要体现在两个方面:
1、onDraw中不要创建新的局部对象,这是因为onDraw方法可能会被频繁调用,这样就会在一瞬间产生大量临时对象这不仅占用更多的内存还会导致系统频繁的gc,降低程序指向效率。
2、onDraw方法只能给不要做耗时任务,也不能执行成千上万次耗时操作。尽管每次循环都很轻量,但是大量的循环任然抢占cpu的时间片,这会造成view的绘制不流畅。
ps:google官方给出的性能优化典范标准中,view绘制频率保证60fps是最佳的,这就要求每帧的绘制时间不超过16ms(16ms=1000/60),虽然程序很难保证16ms,但是尽量降低onDraw方法的复杂度总是切实有效的。
三、内存泄漏优化
内存泄漏优化包括两方面,一方面是尽量避免写出内存泄漏的代码,另一方面通过相关的工具找出潜在内存泄漏。
1、静态变量导致内存泄漏
常见场景:
这将导致activity无法正常销毁,静态变量引用了他,如上new view时持有Activity的实例。由于static修饰的成员为类所有,生命周期较长,即使activity执行了onDestroy,仍有对象持有他的引用,因此该activity不会被释放。
解决方案:
(1)应用尽量避免static成员变量引用资源耗费过多的实例,比如context。
(2)Context尽量使用ApplicationContext,这个Context的生命周期较长,引用他不会出现内存泄漏问题。
(3)使用WeakReference代替强引用。案例参考:handler去除警告
2、单例模式导致内存泄漏
静态变量导致内存泄漏太过明显,相信我们都会避免,而单例带来的内存泄漏是我们容易忽略的。
栗子:
/**
* Create by SunnyDay on 2019/04/21
*/
interface OnDataListener {
}
package com.example.administrator.performanceoptimizing;
import java.util.ArrayList;
import java.util.List;
/**
* Create by SunnyDay on 2019/04/21
*/
public class TestManager {
private List<OnDataListener> mOnDataListenerList = new ArrayList<>();
private static class SingleTonHolder {
public static final TestManager INSTANCE = new TestManager();
}
private TestManager() {
}
public static TestManager getIntance(){
return SingleTonHolder.INSTANCE;
}
// 注册操作
void registerListener(OnDataListener onDataListener) {
if (!mOnDataListenerList.contains(onDataListener)) {
mOnDataListenerList.add(onDataListener);
}
}
// 解注册
void unRegisterListener(OnDataListener onDataListener) {
mOnDataListenerList.remove(onDataListener);
}
}
我们在mainActivity使用时:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注册操作
TestManager.getIntance().registerListener(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 解注册 忘记写了。。。。。
}
由于缺少解注册而导致内存泄漏,原因是activity对象被单例所持有,不解注册时,这个对象的生命周期和Application一致了。当activity销毁时,Application还在运行时,activity的对象还无法释放。
3、属性动画导致的内存泄漏
安卓3.0开始google提供了属性动画,属性动画有一类无限循环的动画如果在activity中播放了此动画没有在ondeStroy中cancel就会内存泄漏。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
animator = ObjectAnimator
.ofFloat(new Button(this),"rotation",0,360)
.setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 忘记了 没写。。。。。
// animator.cancel();
}
原因:动画一直循环,view一直持有activity的引用。得不到释放。
4、线程导致内存溢出(内部类持有外部类引用得不到释放时)
这个问题也就是内部类持有外部类引用得不到释放时问题。
栗子:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
while(true){
// 大量耗时工作
}
}
}).start();
}
我们知道内部类是持有外部类的引用的,如果内部类中做一些让内部类生命周期较长的工作(如上死循环),这样外部类对象就会被内部类一直引用得不到销毁。
解决:
(1)将内部类改为静态(非静态内部类持有外部类对象的强引用,静态内部类不拥有)
(2)使用弱引用保存外部类实例
综上四点:我们发现持有activity的引用得不到释放这种情况较多。
OOM优化
1、图片过大导致oom(一般是Bitmap)
解决:
(1)缩放图片 参考:Bitmap的加载和Cache
(2)对图片采用软引用,及时地进行 recyle()操作
SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap);
if(bitmap != null){
if(bitmap.get() != null && !bitmap.get().isRecycled()){
bitmap.get().recycle();
bitmap = null;
}
}
2、界面切换导致 OOM
有时候我们会发现这样的问题,横竖屏切换 N 次后 OOM 了。
解决:
(1)看看页面布局当中有没有大的图片,比如背景图之类的。去除 xml 中相关设置,改在程序中设置背景图(放在 onCreate()方法中)在 Activity destory 时注意,drawable.setCallback(null); 防止 Activity 得不到及时的释放。
Drawable drawable = getResources().getDrawaImageView imageView = new ImageView(this);
imageView.setBackgroundDrawable(drawable);
(2) 在页面切换时尽可能少地重复使用一些代码
比如:重复调用数据库,反复使用某些对象等等… 常见的内存使用不当的情况
3、查询数据库没有关闭游标
程序中经常会进行查询数据库的操作,但是经常会有使用完毕 Cursor 后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排查带来困难和风险。
4、构造 Adapter 时,没有使用缓存的 convertView(Listview GridView使用)
五、响应速度优化和ANR日志分析
响应速度优化核心思想是避免在主线程做耗时操作,activity5秒,BroadCastRexeiver10秒没响应就会ANR。当进程发生anr后系统会在data/anr目录下创建traces.txt来记录anr日志。
栗子:
我们可以模拟anr 然后(如下代码)查看文件分析log
adb pull data/anr/traces.txt
六、内存泄漏分析
参考:MAT在Android Studio3.x版本上的使用
小结
性能优化文章参考推荐:Android应用开发性能优化完全分析
The end
参考:
<安卓开发艺术>
<安卓面试宝典>by 阳哥