继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android性能优化典范

闲筝听雨
关注TA
已关注
手记 14
粉丝 7
获赞 31

Android 性能优化是每个面试官必问的问题,最近也在进行该方面的总结。来自作者小红军storm,希望对大家有一定的帮助。
参考文章

目录

  1. 内存优化
  2. UI优化
  3. 网络优化
  4. 启动优化

1、内存优化

1.1、解决所有的内存泄漏

1.1.1、内存泄漏:

堆上分配的对象已经不会再使用,但是GC收集器无法对其进行回收,此对象被强应用所引用 。

1.1.2、GC收集器原理:

可达性算法:从GCRoot对象为起点,向下搜索,可到达的对象是称为GC可达,GC收集器不会回收,不可到达的对象称为不GC不可达,是GC收集器回收的对象。

GCRoot对象:
(1)、虚拟机栈(栈帧找那个的局部变量表)中的对象;
(2)、方法区中类静态变量引用的对象;
(3)、方法区中常量引用的对象。

1.1.3、常见的内存泄漏实例

(1)、单例造成的内存泄漏

package com.example.testdemo;

import android.content.Context;

public class AppManager {
	private static AppManager instance;

	private static Context mContext;

	private AppManager(Context context) {
		this.mContext = context;
	}

	public static AppManager getInstance(Context context) {
		if (instance == null) {
			instance = new AppManager(context);
		}

		return instance;
	}

}

单例类的实例是静态的,如果需要其构造方法需要传递一个context,如果传入的是 Activity 的 Context,当传入 Activity 就会造成泄漏了。
解决方法:this.context = context.getApplicationContext();// 使用Application 的context。
(2)、内部类造成的内存泄漏

package com.example.testdemo;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends Activity {

	private static TestResource mTestResource = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		if (mTestResource == null) {
			mTestResource = new TestResource();
		}
	}

	class TestResource {

	}
}

在Activity中有一个内部类,如果创建了这个内部类的静态对象,Activity关闭的时候,由于内部类会持有外部类的引用,内部类静态对象会持有外部类Activity的引用,导致Activity发生内存泄漏。
解决方法:将该内部类设为静态内部类,如果该内部类需要持有外部类的引用,则使用软引用/弱引用,在使用外部类的引用之前需进行空判断。
(3)、异步线程造成的内存泄漏

package com.example.testdemo;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	Runnable runnable = new Runnable() {
		
		@Override
		public void run() {
			
		}
	};
}

在Activity中有一个开启一个线程执行一个runnable,这个runnable是内部类就会持有外部类Activity的引用,如果线程的生命周期比Activity的生命周期长,就不会导致Activity内存泄漏。
解决方法:同上
(4)、Handler 造成的内存泄漏

package com.example.testdemo;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

public class MainActivity extends Activity {

	private final Handler mHandler = new Handler() {

		public void handleMessage(Message msg) {

		};

	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mHandler.postDelayed(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub

			}
		}, 1000 * 6);
		finish();
	}

}

在Activity中声明一个内部类handler,当使用这个handler发送一个延迟消息时,此消息执行前,Activity关闭会造成内存泄漏。
解决方法:同上
(5)资源未关闭造成内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

1.1.4、内存泄漏检测方案

LeakCanary原理:

  • LeakCanary在一个Fragment或者Activity onDestory的时候,创建一个弱引用
    KeyedWeakReference 到要被监控的对象。

  • 然后在后台线程检查引用是否被清除,如果没有,调用GC。

  • 如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。

  • 在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。

  • 得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。

  • HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。

1.2、避免内存抖动

内存抖动代表频繁GC,会占用程序执行时间,造成页面卡顿等性能问题。

1.2.1、避免内存抖动的方式:

  1. 避免在onDraw中创建Paint、Bitmap对象等;
  2. 避免在循环中创建临时对象;
  3. 避免在scrollListener中创建对象。

1.2.2、内存抖动检测方法

使用Android profiler 进行观察,如果发下内存剧增剧减则是GC时间,可查看GC时间段的内存,找出重复创建的大量对象,进行优化(例子:在recycleview中获取距离屏幕的距离时,创建大量对象)。

1.3、图片优化

  1. 网络图片:使用网络裁剪服务,获取适当的图片加载。
  2. 本地图片:使用Tinypng深度压缩,必要时进行裁剪。
  3. 使用web格式代替png、jpeg格式。
  4. 使用Fresco对图片进行管理。

1.3.1、fresco对内存的管理:

  1. 在5.0以下系统,Bitmap缓存位于ashmem,这样Bitmap对象的创建和释放将不会引发GC,更少的GC会使你的APP运行得更加流畅。5.0及其以上系统,相比之下,内存管理有了很大改进,所以Bitmap缓存直接位于Java的heap上。
  2. 当应用在后台运行时,该内存会被清空。

1.4、使用优化的数据结构

1.4.1 集合的遍历操作:

private void for1(ArrayList<Model> list){
        long start = System.currentTimeMillis();
        int len = list.size();
        for(int i= 0;i < len;i++){
            Model model = list.get(i);
            model.getName();
            model.getType();
        }
        long end = System.currentTimeMillis();
        Log.d(TAG,"---for1 spend:" + (end - start) + "ms");
    }

    private void for2(ArrayList<Model> list){
        long start = System.currentTimeMillis();
        for(int i= 0;i < list.size();i++){
            Model model = list.get(i);
            model.getName();
            model.getType();
        }
        long end = System.currentTimeMillis();
        Log.d(TAG,"---for2 spend:" + (end - start) + "ms");
    }

    private void foreach(ArrayList<Model> list){
        long start = System.currentTimeMillis();
        for(Model m:list){
            Model model = m;
            model.getName();
            model.getType();
        }
        long end = System.currentTimeMillis();
        Log.d(TAG,"---foreach spend:" + (end - start) + "ms");
    }

    private void iterator(ArrayList<Model> list){
        long start = System.currentTimeMillis();
        Iterator<Model> it = list.iterator();
        while (it.hasNext()){
            Model model = it.next();
            model.getName();
            model.getType();
        }
        long end = System.currentTimeMillis();
        Log.d(TAG,"---iterator spend:" + (end - start) + "ms");
    }

     D/CollectionActivity: ---for1 spend:65ms
     D/CollectionActivity: ---for2 spend:77ms
     D/CollectionActivity: ---foreach spend:117ms
     D/CollectionActivity: ---iterator spend:118ms

计算发现:for1方法的便利速度最快,而改进for循环和Iterator遍历速度明显不如普通for循环速度,而普通for循环中若将list的长度声明为临时变量使用效果更佳。所以在使用ArrayList进行便利操作时尽量采用for1()方法的结构。

1.4.2 SpareArray替代HashMap< Integer,Object>

SpareArray是Android提供的类似Map结构的集合,为什么用SpareArray替代HashMap.我们先看一下SpareArray的结构:

public SparseArray(int initialCapacity) {
        if (initialCapacity == 0) {
            mKeys = EmptyArray.INT;
            mValues = EmptyArray.OBJECT;
        } else {
            mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
            mKeys = new int[mValues.length];
        }
        mSize = 0;
    }

SpaseArray使用int类型做为key值,而HashMap的key值则必须为Integer,这样就导致在使用HashMap时创建了很多Integer对象。

1.4.3 ArrayList、LinkedList、Vector比较

ArrayList:线性不安全,可变数组,随机访问很快,删除非头尾元素慢
LinkedList:线性不安全,双链表,随机访问很慢,增删操作很快,不耗费多余资源
Vector:线性安全,可变数组,由于线性安全,所以操作效率低于ArrayList,但内存方面优于ArrayList。

ArrayList与Vector内存占用不同是由于两者的扩容机制不同造成的:
ArrayList:存储元素超过容器大小时,扩大1.5倍

@Override public boolean add(E object) {
        Object[] a = array;
        int s = size;
        if (s == a.length) {
            Object[] newArray = new Object[s +
                    (s < (MIN_CAPACITY_INCREMENT / 2) ?
                     MIN_CAPACITY_INCREMENT : s >> 1)];
            System.arraycopy(a, 0, newArray, 0, s);
            array = a = newArray;
        }
        a[s] = object;
        size = s + 1;
        modCount++;
        return true;
    }

Vector:按需扩容,每次加1,(elementCount++)

@Override
    public synchronized boolean add(E object) {
        if (elementCount == elementData.length) {
            growByOne();
        }
        elementData[elementCount++] = object;
        modCount++;
        return true;
    }

由此比较,在内存方面Vector表现比ArrayList要好。

2、UI优化

  1. 分析布局,减少布局嵌套或者替换消耗性能少的布局
  2. 使用include+merge减少布局层级
  3. 使用viewstub提供按需加载

3、网络优化

3.1、网络速度:

正常一条网络请求需要经过的流程是这样:

  1. DNS 解析,请求DNS服务器,获取域名对应的 IP 地址;
  2. 与服务端建立连接,包括 tcp 三次握手,安全协议同步流程;
  3. 连接建立完成,发送和接收数据,解码数据。

这里有明显的三个优化点:
3.1、IP直连或者HTTPDNS;
3.2、开启 keep-alive进行连接复用;
3.3、减少请求和返回数据的大小;
(1)、请求和返回数据做Gzip压缩;
(2)、精简数据格式;
(3)、按需加载图片(图片裁剪,也可按网络状态返回不同分辨率的图片);
3.4、数据缓存;
3.5、请求打包;(如埋点统计)
3.6、协议优化;(如使用优化的的更好的http2,请求头压缩)
3.7、断点续传。

4、启动优化

4.1、异步加载一:Application中加入异步线程
4.2、异步加载二:MainActivity中加入异步线程
4.3、 延迟加载功能:首屏绘制完成之后加载
4.4、动态加载布局:主布局文件优化
4.5、主布局文件深度优化
4.6、 功能代码深度优化

打开App,阅读手记
1人推荐
发表评论
随时随地看视频慕课网APP

热门评论

很有用,感谢!

查看全部评论