Android开发—Android性能优化大全

一、App启动优化

为什么要做App的启动优化?

网页端存在的一个定律叫8秒定律:即指用户访问一个网站时,如果等待打开的时间超过8秒,超过70%的用户将会放弃等待。

同样的,移动端也有一个8秒定律:如果一个App的启动时间超过8秒或有明显的卡顿,80%的用户将会退出应用并对程序员进行口吐芬芳。当然这是我瞎编的,但却不代表是不存在的。最起码肯定会影响App在市场上的评分,进而让更多的用户在对比过程中选择竞品。

知道了启动优化的重要性,那么接下来我们就来分析下如何优化App的启动,本文内容主要分为以下三部分:

  • 分析优化方向
  • 相关数据测量
  • 启动优化技巧

1.分析优化方向

App应用主要有三种启动状态:冷启动、热启动和温启动。

(1)冷启动:耗时最长,也是主要的优化点;(恋爱前的女人)

冷启动前,系统主要做了三件事:

  • 加载并启动应用。
  • 在启动后立即显示应用的空白启动窗口。
  • 创建应用进程。

创建应用进程后:

  • 创建应用对象。
  • 启动主线程。
  • 创建主 Activity。
  • 扩充视图。
  • 布局屏幕。
  • 执行初始绘制

(2)热启动:耗时最短,将activity从后台带到前台;(热恋中的女人)
(3)温启动:耗时较长,重走了Actiivty的生命周期。(结婚后的女人)

从应用的启动状态中,我们可以分析得出,剥除系统本身的任务动作外(这部分我们是无法进行操作修改的),其实我们的启动优化方向主要就是:Application和Activity的生命周期、主视图的布局优化。

2. 相关数据测量

优化App的启动速度前,我们得先获取App的一些启动数据,根据这些数据才能准确找到优化的点,才能对优化后的操作做一个准确的评估。(下面的相关代码我将会拿之前的一个旧项目来做演示,一是更贴近实际开发情况,比demo更加直观;二是顺手给优化了,何乐而不为呢?)

2.1 获取启动时间

adb命令法:adb shell am start -S -W packagename/activity(含包名)

在这里插入图片描述

  • ThisTime:最后一个 Activity 启动时间;
  • TotalTime:所有 Activity 启动耗时(这里只启动了一个 MainActivity);
  • WaitTime:AMS 启动 Activity 的总耗时;

adb 命令虽然简单好用,但还是有不少缺点的:

  • 只能线下使用,而在实际开发过程中,用户的启动时间才是最好的参考指标;
  • 非精确的时间,这里只是显示了 Activity 启动完毕的时间,但对于用户的直观体验来说,只有首页的数据展示出来,才算是真正的启动完成。

2.2 手动打点法

我们先定义一个LaunchTimer类,用来记录启动时间:

public class LaunchTimer { 
	private static long mTime; 
	
	//开启时间 
	public static void startTime() { 
		mTime = System.currentTimeMillis(); 
	}
	
	//结束时间 
	public static void endTime() { 
		LoggerManager.d("启动时间:" + (System.currentTimeMillis() - mTime)); 
	} 
}

在Application类的attachBaseContext()方法中打入开始启动时间点:

protected void attachBaseContext(Context base) { 
	super.attachBaseContext(base); 
	LaunchTimer.startTime(); 
}

在我们的首页第一条数据展示成功后打入结束时间点:
(ps:网上很多文章都在onwindowfocuschanged()方法中打入结束时间,其实这个方法只是首帧时间,并不代表我们的页面数据等全部展示出来了。我们做优化,还是得以用户的实际体验来作为参考价值,不能仅仅KPI化)

//是否已经记录启动时间 
private boolean mIsRecord = false; 

@Override 
protected void convert(BaseViewHolder helper, final HomeListBean.DataBean item) { 

	if (helper.getPosition() == 1 && !mIsRecord) { 
		mIsRecord = true; 
		final View contentView = helper.getView(R.id.home_item_rl); 
		//监听第一条数据的绘制完成时间 	
		contentView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 
			@Override 
			public boolean onPreDraw() {
				contentView.getViewTreeObserver().removeOnPreDrawListener(this); 
				LaunchTimer.endTime(); 
				return true; 
			} 
		}); 
	} 
}

运行我们的代码,可以看到启动时间是3111毫秒,正常来说,是会比用adb命令打出的时间要长点。
在这里插入图片描述
手动打点的好处:

  • 可以线上使用,统计真实用户的启动时间
  • 时间准确,结合用户真实体验,参考价值更高

2.3其他优化分析工具

我经常使用的启动优化工具主要有Traceview(官方文档)和systrace(官方文档),Traceview虽然比较全
面,但性能消耗太大,这里不做过多介绍,有兴趣的朋友可以自行查看官方文档,这里主要介绍systrace这个工具(使用前需得先安装python)。

先打点:

public void onCreate() { 
	super.onCreate(); 
	//使用兼容的TraceCompat打入开始点 
	TraceCompat.beginSection("AppBegin"); 
	if (instance == null) { 
		instance = this; 
	}
	if (IS_DEBUG_ABLE) { 
		initLogger(); 
	}
	initBugly(); 
	Tiny.getInstance().init(this);
	//初始化tiny图片压缩工具 
	initJPush(); 
	initSkin(); 
	RichText.initCacheDir(this);
	//设置缓存 
	initFragmentation(); 
	MMKV.initialize(this); 
	TraceCompat.endSection(); 
	//使用兼容的TraceCompat记录结束点 
	TraceCompat.endSection(); 
}

安装App,使用systrace命令:python systrace.py -b 32768 -t 10 -a packagename -ooutputfile.html sched gfx view wm am app (命令执行过程中点击启动App)

运行操作后,打开我们的html文件,可以看到我们app的启动相关数据:

命令和命令项
在这里插入图片描述
运行操作后,打开我们的html文件,可以看到我们app的启动相关数据:
在这里插入图片描述
图中红圈部分是我们需要注意的地方,AppBegin就是我们打点的代表区间,可以看到这段区间时间是732.127毫秒。最下面有两个数值,一个是WallDuration,这个就是我们代码的执行时间,另一个
CPUDuration是我们的CPU执行时间。

为什么两个会有时间差异呢?打个比方:CPU在执行代码的时候,遇到一些需要等待回调的数据才能继续往下执行情况的时候,CPU会处在等待情景,这个时候是不计算CPU执行时间,只有等回调数据回来了,再往下执行时,才算是调用了CPU的资源。

所以这里也点明了我们的优化方向之一:就是如何更好的利用CPU的资源。

PS:其实这段的数据主要都是启动时间的数据,但在实际开发中,我们可能还会去监测每个方法所用的时间,看看有没有可优化的余地。方法监测的时间除了给每个方法单独打点外,还可以使用Traceview工具来更好的监测。

3.优化技巧

终于讲到我们的优化技巧了,具体优化我们可以分为以下几种方式:

  • 闪屏优化
  • 业务优化
  • 线程优化
  • UI优化

3.1 闪屏优化

在我们的App启动中,其实是低端机中,其实会有点击了应用,要等待一段时间,才会打开App页面的时间。这个现象对用户的体验非常不友好!那要怎样优化这个现象呢,这里可以采用theme切换的方式来达到视觉上的快速启动。

<!-- 新建layer-list的xml文件 --> 
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque"> 
	<item android:drawable="@android:color/white"/> 
	<item>
		<bitmap 
		//这里是我们想要展示的开屏图片 
		android:src="@mipmap/longkong_splash" android:gravity="fill"/> 
	</item> 
</layer-list>

然后新建Theme:

	<item name="android:windowBackground">@drawable/lanucher</item> 
	<item name="windowActionBar">false</item> 
	<item name="android:windowNoTitle">true</item> 
	<item name="windowNoTitle">true</item> 
</style>

在我们的启动页设置这个Theme:

<activity 
	android:name=".business.MainActivity" 
	android:configChanges="keyboardHidden|orientation|screenSize" 
	android:hardwareAccelerated="true" 
	//设置theme 
	android:theme="@style/ThemeActivitySplash" 
	android:windowSoftInputMode="stateUnspecified"> 
	<intent-filter> 
		<action android:name="android.intent.action.MAIN" /> 
		<category android:name="android.intent.category.LAUNCHER" /> 
	</intent-filter> 
</activity>

记得在页面onCreat中切换回我们原用的Theme:

@Override 
protected void onCreate(@Nullable Bundle savedInstanceState) { 
	setTheme(R.style.ThemeActivity); 
	super.onCreate(savedInstanceState); 
}

现在点击桌面图标,简直是秒开App。当然,这只是视觉上的秒开,实际的启动时间还是没什么变化

下面才是真正优化启动时间的方法。

3.2 业务优化

我们在一接到优化任务的时候,不要想着立马着手就做一些异步线程优化之类的。第一步应该是先梳理我们的业务。

梳理清楚我们启动的每一个模块,看看哪些是必要的,哪些是可以切掉的,哪些是可以延迟加载的。

  1. 可以切掉的:没什么可说的直接删除;
  2. 可以延迟加载处理的:比如地图SDK、扫一扫等,这些个模块很多时候不一定是在首页就需要用到的,我们可以做一些延迟加载甚至是只有在使用到时才进行初始化的处理。

延迟加载的时机:可以在首页用户数据加载完成时去进行

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { 
	@Override 
	public boolean queueIdle() { 
		//执行延迟加载的代码 return false; 
	} 
});

IdleHandler的运行机制是:只有在CPU空闲的时候才会去执行操作,这样就不会造成首页用户操作时卡顿的情况。

  1. 必要的:对于必要的代码,我们可以做以下业务相关优化操作:

使用更加优秀框架,比如我们的SharedPreferences,可以尝试夫换成腾讯的MMKV框架,在数据量大的情况下,优化效果非常明显;

使用更加优秀的算法,我们有些代码比如文件操作之类的,或许有更加优秀的算法代码,可以大大减少计算步骤;

作一些取舍,比如一些中低端的机型,或者其本身的性能没办法很好的运行我们的某些功能,在这个时间,我们可以尝试去跟产品经理沟通,做一些功能上的取舍。

3.3 线程优化

上面我们讲了一些必要代码的业务优化,在一些确实没有业务优化空间,或者优化了还不是很理想的代码,我们可以进行线程优化。

线程优化其实就是合理利用CPU的核心数,将几个耗时的任务进行并发处理,可以极大减少总的运行时间。

线程优化需要注意几点:

  • 合理控制线程的数量:每台机子的核心数都不同,如果我们线程开得太多,可能会相互竞争CPU资源,除了要用线程池进行统一管理外,设置合适的线程数也很重要。

我们可以参照AsyncTask的源码来设置线程池的线程数:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
//线程数
  • 任务的依赖关系:有些任务可能是需要前一个任务执行完后再进行操作,如果在线程优化中,没有处理好这个相关,可能会造成空转,如下图:
    在这里插入图片描述

如何处理好线程的依赖关系,可以参照或使用市面上的一些启动框架,比如阿里开源的Alpha启动框架。

我们这里使用自己自写的Task启动器,其原理都是一样的,最终都是构成一个有向无环图。

TaskDispatcher.init(this); 
TaskDispatcher dispatcher = TaskDispatcher.createInstance(); 
dispatcher.addTask(new InitBulyTask()) 
	.addTask(new InitJPushTask()) 
	.addTask(new InitRichTextTask()) 
	.addTask(new InitSkinTask()) 
	.addTask(new InitTinyTask()) 
	.addTask(new InitFragmentTask()) 
	.start(); 
dispatcher.await();

经过上面的优化技巧后,我们再来看一下现在的启动时间是多少:
在这里插入图片描述
在这里插入图片描述
从上面的图中,我们可以看出,优化的启动时间缩短了可观的50%左右。

3.4 其他优化

除了上面的优化之处,还是有很多其他的优化技巧。比如根据我们具体的业务,还可以做一些类加载的优化,I/O上的优化,GC的优化,磁盘文件的优化,还可以通过保活来达到快速重启,甚至还有一些CPU锁频的黑科技。

总结

App启动优化是门无尽的学问,还是很多可以继续深挖的点。我们在实际开发中,也可以通过监控APM上的数据来进行更加针对性的优化。只有不断的进行实操,才会发现更多可以优化的方向。

可以 点击 “此处” 即可 免费获取 下方资料 以及 更多Android学习笔记+源码解析+面试+视频

技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面
在这里插入图片描述在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值