在开发Android应用中,性能一直是一个非常重要的指标,如果应用的性能差,出现界面卡顿,等待时间长等问题,用户体验就会非常不好,很有可能就会因此失去部分用户,为了避免这种情况出现,我们在开发时就应该注意性能问题,下面我们就总结一些提升Android应用性能的方法。
我们可以从以下几个方面来提升应用的性能。
下面一步一步来看看如果通过这些方面来提升应用的性能。
减少安装包体积是提升应用性能最简单的方法之一,减少安装包体积可以提升应用加载速度,减少内存使用等等,而且安装包体积太多也会流失一部分潜在的新用户。
在Android开发中我们会使用到很多系统组件,有些组件拥有自己的生命周期,当这些组件生命周期结束后,别的代码还持有这些组件的引用,那么这些组件对象以及它所引用的资源就不会被及时回收,这样就会造成内存泄漏。比如说最常见一个错误示例如下:
1public class MainActivity extends Activity {
2 //...
3 Handler handler;
4 @Override
5 protected void onCreate(Bundle savedInstanceState) {
6 super.onCreate(savedInstanceState);
7 //...
8 handler = new Handler() {
9 @Override
10 public void handleMessage(Message msg) {}
11 }
12 }
13}
我们在activity中创建了一个handler,如果在acitivity销毁的时候,handler还存在未处理的消息,比如延时消息,此时message对象持有handler的引用,而handler持有acitivity的引用,即使acitivty生命周期已经结束,但是由于还存在引用,所以acitivity对象就不会被回收,它所引用的其它对象或资源也同样不会被回收,这样就造成了内存泄漏。对于组件的引用,我们一般可以通过弱引用的方式来避免内存泄漏,也可以通过监听组件的生命周期,在组件生命周期结束的时候释放对它的引用。
但是内存泄漏一般发生的不那么直接,隐蔽性比较强,所以在实际开发中,我建议使用LeakCanary这个工具来帮助我们发现内存泄漏问题。
Android Studio的Profiler同样也可以帮助查找内存泄漏,使用方式:开启Profiler,选择Memory,选择要内存时间段,dump内存,然后就可以查看内存泄漏情况。
在Android开发中,优化内存是非常有必要的,因为移动设备的内存相对有限,如果内存使用超过限制,将会发生OutOfMemory(OOM)异常,而且问题发生点不容易定位,因为它是多方面因素导致的,崩溃点很可能并不是问题发生的根源。
在一般的Android开发中,图片加载一般是消耗内存的主要因素,所以对图片加载的处理十分重要,缓存、缩放还有回收释放内存等等,这些都比较麻烦,在实际开发中,如果不是特殊要求,我建议使用Glide,它可以完成上述的各种功能。
通过getMemoryClass来获取内存使用情况,通过onTrimMemory处理内存不足的情况,比如释放缓存的资源等。
视图加载解析默认都是在主线程执行,如果XML视图文件过于复杂,可能会引起主线程卡顿。在编写XML布局时应该注意以下几点:
如果由于业务需要,布局文件确实复杂,可以通过AsyncLayoutInflater来实现异步加载,或者使用Jetpack Compose来实现。
检测布局问题
参考:性能与视图层次结构
注释根据Google的调查,75%的用户可能会因为应用启动时间过长而放弃使用应用。应用有三种启动状态,冷启动、温启动或热启动,每种状态都会影响应用向用户显示所需的时间。在冷启动中,应用从头开始启动。在另外两种状态中,系统需要将后台运行的应用带入前台,我们主要是针对冷启动进行优化。
统计应用启动时间
在 Android 4.4(API 级别 19)及更高版本中,logcat 包含一个输出行,其中包含名为 Displayed
的值。此值代表从启动进程到在屏幕上完成对应 Activity 的绘制所用的时间。经过的时间包括以下事件序列:
可以在logcat中查找到类似于以下示例:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
或通过adb命令工具查看启动应用所用时间,示例如下:
adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN
通过手动调用来reportFullyDrawn() 来测量应用启动的时间。比如,对于一些异步、延迟加载的布局的情况。以下是 logcat 输出的示例:
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
对于线上应用启动时间,我们无法通过上面几种方式来实现,我们需要在找准开始时间点和和结束时间点来记录启动时间,根据Application和Activity的生命周期我们可以知道,从启动应用到展示界面大致会经过以下几个阶段:
-> Application 构造函数
-> Application.attachBaseContext()
-> Application.onCreate()
-> Activity 构造函数
-> Activity.setTheme()
-> Activity.onCreate()
-> Activity.onStart
-> Activity.onResume
-> Activity.onAttachedToWindow
-> Activity.onWindowFocusChanged
一般情况我们选择以Application.attachBaseContext()作为启动开始的时间点,以Activity.onWindowFocusChanged作为启动结束的时间点即可计算出启动时间。
有些情况下,我们可能并不是在进入Activity时就开始绘制界面,而是某些准备工作做完才开始绘制,我们会通过OnPreDrawListener.onPreDraw()来控制何时开始绘制,此时应该在此处记录启动结束时间。
1val content: View = findViewById(android.R.id.content)
2content.viewTreeObserver.addOnPreDrawListener(
3 object : ViewTreeObserver.OnPreDrawListener {
4 override fun onPreDraw(): Boolean {
5 return if (isReady) {
6 content.viewTreeObserver.removeOnPreDrawListener(this)
7 true
8 } else {
9 false
10 }
11 }
12 }
13)
优化应用启动时间
参考:应用启动时间
注释static final
来修饰。参考:性能提示
注释