Android性能优化

界面优化

过度绘制是指屏幕上某个像素在同一帧的时间内被绘制多次,这回造成CPU及GPU资源的浪费。我们可以通过以下操作来开启检测过度绘制:开发者选项->调试GPU过度绘制->显示过度绘制区域。开启此功能后,屏幕上会出现一些带色块的区域,在优化界面时,我们应该尽量避免出现粉色或红色,这些色块的意义如下图所示:
在这里插入图片描述
那么,界面优化可以优化哪些方面呢?

1、移除布局中不需要的背景

当布局中的背景不是必要的时候应当进行移除,如通常情况下我们使用的Theme都会包含一个windowBackground,此时若我们在自己的布局中再给布局添加一个背景时就会造成一次过度绘制。这里,我们就可以把Theme的windowBackground给移除掉,我们可以通过以下两种方法中的一种进行移除:
(1)在Theme中设置

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:windowBackground">@null</item>
    </style>

(2)在Activity的onCreate()方法中添加:

    getWindow().setBackgroundDrawable(null);
2、减少布局的层级

过多的层级嵌套会导致过度绘制,从而降低性能,因此我们需要将布局的层次结构尽量扁平化。

在开发过程中,我们可以通过Layout Inspector去查看layout的层次结构。在AS中点击Tools>Android>Layout Inspector。然后在出现的Choose Process对话框中选择想要检查的应用进程即可。Layout Inspector会自动捕获快照,然后显示以下内容:

  • View Tree:视图在布局中的层次结构,通过它就可以看到布局中的层次结构了。
  • Screenshot:每个视图可视边界的设备屏幕截图。
  • Properties Table:选定视图的布局属性。

此外,我们还可以使用lint来协助优化布局性能。点击Analyse>Inspect Code即可。布局性能方面的信息位于Android>Lint>Performance下。

针对布局的层级,有如下优化方案。

使用include与merge标签提高布局的复用性,减少层级

include标签可以提高布局的复用性,单独的使用include标签不能起到减少布局层级的效果,但是include与merge配合使用是可以达到减少层级的效果的。当父布局与include的父布局相同时,include布局的父布局可用merge标签达到减少层级的效果。另外,如果一个布局的根布局为FrameLayout,那么此时也可以用merge标签代替,因为DecorView本身就是一个FrameLayout。

使用ViewStub标签延迟加载

在项目中,有些比较复杂的布局在开始时不需要用到,此时我们可以通过ViewStub标签来实现在需要时再加载布局。使用ViewStub标签可以减少内存使用并加快渲染速度。如下是一个ViewStub标签的例子:

    <ViewStub
        android:id="@+id/stub_import"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:inflatedId="@+id/panel_import"
        android:layout="@layout/progress_overlay" />

我们可以通过如下代码在需要时加载ViewStub相应的布局:

findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
//或者
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

需要注意的是,ViewStub标签不能与merge标签一起使用。

3、使用性能更优的布局

当能使用LinearLayout或FrameLayout的就不要使用RelativeLayout,因为RelativeLayout会测量每个子节点两次;但是如果使用RelativeLayout能减少层级嵌套的话,那么还是推荐使用RelativeLayout;ConstraintLayout的性能比RelativeLayout更好,推荐使用ConstraintLayout。

4、避免在onDraw()方法中创建新的局部变量或进行耗时操作

在onDraw()方法可能会被频繁调用,若在此方法中创建局部变量可能会产生大量的临时对象,造成频繁GC,导致内存抖动;
Android系统每隔16ms发送VSYNC信号触发UI渲染,在onDraw()中执行耗时操作可能导致掉帧或者卡顿。

内存优化

内存泄露是指程序中不再使用的对象无法被GC识别正常回收,导致内存资源无法被释放;随着内存泄露的累积,可用的空闲内存空间会越来越少,GC会更容易被触发,GC进行时会停止其他线程的工作,可能导致界面卡顿等情况,当内存空间不够分配时就会发生内存溢出(OOM)。

在开发过程中,我们经常会碰到以下几种内存泄露的情况:

1、单例/静态变量导致的内存泄漏

单例与静态变量的生命周期跟整个程序的生命周期一致。只要单例或静态变量没有被销毁或者置空,其对象就一直被保存引用,也就不会被回收,从而导致内存泄漏。

针对这种情况,我们有如下几种解决方案:

  • 当静态变量不需要使用时,及时置空。
  • 若静态变量或者单例类需要持有context的引用,那么应该使用Application的Context。
  • 如果一定要持有Activity的引用,那么建议用弱引用或软引用代替强引用。
2、非静态内部类/匿名类造成的内存泄露

非静态内部类和匿名类默认持有外部类的引用,当非静态内部类或匿名类的生命周期比外部类的生命周期长时,就会发生内存泄露。以下是几种常见的此类内存泄露的情况:

Handler内存泄露

通常我们都是使用内部类的形式来实现Handler的,此时编译器会有黄色警告提示这个对象可能造成内存泄露。Handler的内存泄露的根本原因在于:

  1. 非静态的Handler类会默认持有外部类的引用,包含Activity等。
  2. 还未处理完的Message会持有Handler的引用。
  3. 还未处理完的Message会处于MessageQueue中,即MessageQueue会持有Message的引用。
  4. MessageQueue与Looper相关联,而Looper的生命周期与程序一致。

此时引用关系链为Looper->MessageQueue->Message->Handler->Activity。因此,若此时退出Activity,由于上述引用关系,Activity无法被回收,从而造成内存泄露。

针对这种情况,解决方法为

  • 将Handler定义成静态内部类,并且引用Activity的弱引用。如下代码所示:
    private static class MyHalder extends Handler {

        private WeakReference<Activity> mWeakReference;

        public MyHalder(Activity activity) {
            mWeakReference = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //...
        }
    }
  • Activity退出时,移除所有信息,移除信息后,Handler生命周期会与Activity同步,代码如下:
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
匿名内部类引起的内存泄露

如我们在使用匿名内部类启动一个线程时,匿名类会持有外部类的引用。当Activity销毁后,线程内的操作可能还在后台执行,此时就会发生内存泄漏。针对这种情况,解决方法为:

  • 将匿名内部类改为静态内部了,静态内部类不持有外部类的引用。
  • Activity退出时,结束线程,是匿名内部的生命周期与Activity保持一致。

其他非静态内部类与匿名内部类的情况,都可以参照以上解决方法进行处理:一个是改成静态内部类,另一个就是保证内部类的生命周期不超过外部类。

3、集合类内存泄露

集合添加元素后将会持有元素对象的引用,导致元素对象不能被GC回收,从而发生内存泄漏。解决方案为清空集合对象。代码如下所示:

    list.clear();
    list = null;
4、未关闭资源对象内存泄露
  • 动态注册的广播未注销。
  • 网络、文件等流未关闭。
  • 无限动画未停止。
  • 游标对象未关闭。

总之,此类情况造成的内存泄露要切记有开就有关。

常用的内存泄露检测工具
  • Android Lint:它可以帮助我们发现代码的结构/质量问题,同时提供一些解决方案,有内存泄露可能的地方会有标黄警告。
  • leakcanary:Square开源的一个库,能够自动检测发现内存泄露,使用时需要引入依赖,然后在自定义的Application中进行初始化即可,代码如下:
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'

  //可选项,如果使用了support包中的fragments
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'

  public class MyApplication extends Application {
      @Override
      public void onCreate() {
          super.onCreate();
          if (LeakCanary.isInAnalyzerProcess(this)) {
              return;
          }
          LeakCanary.install(this);
      }
  }
  • Android Profiler:AS提供的性能检测工具,可以帮助你进行内存、电量、网络等性能的监测。
启动优化
冷启动

冷启动是指App在手机启动后第一次运行或者App进程被kill后再次启动。冷启动的必要条件是App进程不存在,这就意味着系统需要创建进程,App要初始化。

在冷启动的过程中,系统会完成三个任务:1、加载并启动应用程序;2、启动后立即显示应用程序的空白启动窗口;3、创建应用程序进程。当系统为我们创建了进程后,开始创建应用程序对象:1、启动主线程;2、创建Activity;3、加载布局;4、屏幕布局;5、执行初始绘制。应用程序进程完成第一次绘制后,系统进程会交换当前显示的背景窗口,将其替换为主活动。此时,用户可以开始使用该程序。至此,启动完成。

温启动

App进程存在,Activity可能因为内存不足而被回收。这时候启动App不需要重新创建进程,但Activity的onCreate()方法还是需要重新执行。

热启动

App进程存在,并且Activity对象任然存在内存中没有被回收。可以避免重复对象初始化,布局的渲染和加载。场景类似于你从当前App跳到另一个App一小段时间后重新切换回当前App,此时就属于热启动。

通常,启动慢的定义为:

  • 冷启动需要5秒或更长时间。
  • 温启动需要2秒或更长时间。
  • 热启动需要1.5秒或更长时间。
启动的优化策略

无论是何种启动,我们的优化点都是:Application、Activity的创建及回调过程。

谷歌官方给出的建议是:

1. 利用提前展示出来的Window,快速展示一个界面,给用户快速反馈的体验

使用Activity的WindowBackground主题属性来为启动的Activity提供一个简单的drawable,这样在启动的时候,会先展示一个界面,等Activity加载完后,再去加载Activity的界面,从而产生一种快的感觉,但是这种方法治标不治本,具体的做法如下所示:

	//定义一个style
    <style name="AppTheme.Launcher" parent="AppTheme">
        <item name="android:windowBackground">@drawable/img_bg_1</item>
    </style>

	//将启动Activity的theme设置为style
    <activity
         android:name="com.ogawa.ec628y.activity.MainActivity"
         android:screenOrientation="landscape"
         android:theme="@style/AppTheme.Launcher">
         <intent-filter>
             <action android:name="android.intent.action.MAIN" />

             <category android:name="android.intent.category.LAUNCHER" />
         </intent-filter>
     </activity>
2. 避免在启动时做密集沉重的初始化操作

Application是程序的主入口,很多第三方SDK的初始化都需要在Application的onCreate()方法中执行,这样就增加了程序在初始化时的工作量,启动自然也就变慢了。针对这个问题,可以参考如下方法:

  • 对于像友盟、bugly这些业务非必要的SDK可以异步加载;
  • 对于地图、推送等非第一时间需要的可在主线程中做延时加载,当程序启动后再进行初始化;
  • 对于图片、网络请求框架这些必要的就必须在主线程中初始化了。
3. 避免I/O操作、反序列化、网络操作、布局嵌套等
APK大小优化

apk包的大小往往是影响用户是否下载的一个因素之一,因此优化安装包的大小也是非常有必要的。要优化apk包的大小,我们要先了解apk文件的构成,apk解压完成后包含以下文件:

  • assets文件夹:用于存放一些配置文件、资源文件,在这个文件夹下的文件不会生成对应的id,要通过AssetManager类进行获取。
  • res目录:存放资源文件,会自动生成对应的id并映射到.R文件中,可直接通过资源id进行使用。
  • META-INFO:保存应用签名信息,签名信息可以验证APK文件的完整性。
  • AndroidManifest.xml:Android应用配置清单文件,用于配置一些组件的注册信息、声明需要用到的用户权限等。
  • classes.dex:Dalvik字节码程序,让Dalvik虚拟机可执行,一般情况下,Android应用在打包时通过Android SDK中的dx工具将java字节码转换为Dalvik字节码。
  • resources.arsc:记录资源文件与资源id的映射关系,用于根据资源id寻找资源。

apk大小的优化需要从代码和资源两个方面进行,具体的优化方案有:

  1. 使用lint工具、开启资源压缩删除无用资源;
  2. 能够用xml实现的drawable效果尽量不用ui切图;
  3. 重用资源,如果界面中有样式一样只是方向不同的图片,尽量用动画进行转换,而不是切多张图用于实现。
  4. 压缩PNG和JPEG文件,可使用tinyPng等工具对图片资源进行压缩。
  5. 使用webP文件格式代替PNG或JPEG文件,可使用Android Studio将现有的JPG、PNG或静态GIF图转换为webP格式。具体操作可看Android图片优化–使用webp
  6. 使用proGuard代码混淆工具混淆代码,它具有压缩、优化、混淆等功能。

参考资料:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值