Android开发者,关于性能优化几大点,你都了解了吗?

Posted 初一十五啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android开发者,关于性能优化几大点,你都了解了吗?相关的知识,希望对你有一定的参考价值。

前言

做了这么久性能相关的工作,也接触了不少模块,说实话,要做好性能这一块,真心不容易。为什么这么说?是因为需要接触的知识实在是太多了,比如UI优化丶启动优化丶崩溃优化,卡顿优化等等~android 是一个整体,牵一发而动全身,不是说只懂一个模块就可以做好。

废话不多说,先来了解一下性能优化都有哪些内容:

启动优化,UI优化,崩溃优化,内存优化,卡顿优化,APP深度优化,弱网优化,耗电优化,多线程并发优化,安装包优化,安全性优化。

一丶启动优化

一个应用App的启动速度能够影响用户的首次体验,启动速度较慢(感官上)的应用可能导致用户再次开启App的意图下降,或者卸载放弃该应用程序。

1.1.视觉优化

应用程序启动有三种状态,每种状态都会影响应用程序对用户可见所需的时间:冷启动,热启动和温启动。

在冷启动时,应用程序从头开始。在其他状态下,系统需要将正在运行的应用程序从后台运行到前台。我们建议您始终根据冷启动的假设进行优化。这样做也可以改善热启动和温启动的性能。

在冷启动开始时,系统有三个任务。这些任务是

  1. 加载并启动应用程序。
  2. 启动后立即显示应用程序空白的启动窗口。
  3. 创建应用程序进程。

一旦系统创建应用程序进程,应用程序进程就会负责下一阶段。这些阶段是:

  1. 创建app对象.
  2. 启动主线程(main thread)
  3. 创建应用入口的Activity对象.
  4. 填充加载布局Views
  5. 在屏幕上执行View的绘制过程.measure -> layout -> draw

应用程序进程完成第一次绘制后,系统进程会交换当前显示的背景窗口,将其替换为主活动。此时,用户可以开始使用该应用程序。

因为App应用进程的创建过程是由手机的软硬件决定的,所以我们只能在这个创建过程中视觉优化。

1.1.1.启动主题优化

冷启动阶段 :

  1. 加载并启动应用程序。
  2. 启动后立即显示应用程序空白的启动窗口。
  3. 创建应用程序进程。

所谓的主题优化,就是应用程序在冷启动的时候(1~2阶段),设置启动窗口的主题。
因为现在 App 应用启动都会先进入一个闪屏页(LaunchActivity) 来展示应用信息。

  • 默认情况
    如果我们对App没有做处理(设置了默认主题),并且在 Application 初始化了其它第三方的服务(假设需要加载2000ms),那么冷启动过程就会如下图 :

    系统默认会在启动应用程序的时候启动空白窗口,直到 App 应用程序的入口 Activity 创建成功,视图绘制完毕。( 大概onWindowFocusChanged方法回调的时候 )

  • 透明主题优化
    为了解决启动窗口白屏问题,许多开发者使用透明主题来解决这个问题,但是治标不治本。虽然解决了上面这个问题,但是仍然有些不足。

  <!-- Base application theme. --> 
  <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> 
     <item name="android:windowFullscreen">true</item> 
     <item name="android:windowIsTranslucent">true</item> 
  </style>

(无白屏,不过从点击到App仍然存在视觉延迟~)

  • 设置闪屏图片主题
    为了更顺滑无缝衔接我们的闪屏页,可以在启动 ActivityTheme中设置闪屏页图片,这样启动窗口的图片就会是闪屏页图片,而不是白屏。
  <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> 
      <item name="android:windowBackground">@drawable/lunch</item> //闪屏页图片 
      <item name="android:windowFullscreen">true</item> 
      <item name="android:windowDrawsSystemBarBackgrounds">false</item><!--显示 虚拟按键,并腾出空间--> 
  </style>


这样设置的话,就会在冷启动的时候,展示闪屏页的图片,等App进程初始化加载入口 Activity (也是闪屏页) 就可以无缝衔接。

其实这种方式并没有真正的加速应用进程的启动速度,而只是通过用户视觉效果带来的优化体验。

1.2.代码优化

当然上面使用设置主题的方式优化用户体验效果治标不治本,关键还在于对代码的优化。首先统计一下应用冷启动的时间。

1.2.1.冷启动耗时统计
  • adb 命令统计
    adb命令 : adb shell am start -S -W 包名/启动类的全限定名 , -S 表示重启当前应用
 C:\\Android\\Demo>adb shell am start -S -W 
 com.example.moneyqian.demo/com.example.moneyqian.demo.MainActivity 
 Stopping: com.example.moneyqian.demo 
 Starting: Intent  act=android.intent.action.MAIN cat= 
 [android.intent.category.LAUNCHER] cmp=com.example.moneyqian.demo/.MainActivity 
 
 Status: ok 
 Activity: com.example.moneyqian.demo/.MainActivity 
 ThisTime: 2247 
 TotalTime: 2247 
 WaitTime: 2278 
 Complete
  • ThisTime : 最后一个 Activity 的启动耗时(例如从 LaunchActivity - >MainActivity「adb命令输入的Activity」 , 只统计 MainActivity 的启动耗时)
  • TotalTime : 启动一连串的 Activity 总耗时.(有几个Activity 就统计几个)
  • WaitTime : 应用进程的创建过程 + TotalTime .

  • 在第①个时间段内,AMS 创建 ActivityRecord 记录块和选择合理的 Task、将当前ResumeActivity 进行 pause.
  • 在第②个时间段内,启动进程、调用无界面 ActivityonCreate() 等、 pause/finish 无界面的Activity.
  • 在第③个时间段内,调用有界面 ActivityonCreateonResume
  //ActivityRecord 

      private void reportLaunchTimeLocked(final long curTime)  
            …… 
          final long thisTime = curTime - displayStartTime; 
          final long totalTime = stack.mLaunchStartTime != 0 ? (curTime - stack.mLaunchStartTime) : thisTime; 
      

如果需要统计从点击桌面图标到 Activity 启动完毕,可以用WaitTime作为标准,但是系统的启动时间优化不了,所以优化冷启动只要在意ThisTime即可。

  • 系统日志统计
    也可以根据系统日志来统计启动耗时,在Android Studio中查找已用时间,必须在logcat视图中禁用过滤器(No Filters)。因为这个是系统的日志输出,而不是应用程序的。你也可以查看其它应用程序的启动耗时。

过滤 displayed 输出的启动日志.

根据上面启动时间的输出统计,就可以先记录优化前的冷启动耗时,然后再对比优化之后的启动时间。

1.2.2.Application 优化

Application 作为 应用程序的整个初始化配置入口,时常担负着它不应该有的负担有很多第三方组件(包括App应用本身)都在 Application 中抢占先机,完成初始化操作。

但是在 Application 中完成繁重的初始化操作和复杂的逻辑就会影响到应用的启动性能通常,有机会优化这些工作以实现性能改进,这些常见问题包括:

  1. 复杂繁琐的布局初始化
  2. 阻塞主线程 UI 绘制的操作,如 I/O 读写或者是网络访问.
  3. Bitmap 大图片或者 VectorDrawable加载
  4. 其它占用主线程的操作

我们可以根据这些组件的轻重缓急之分,对初始化做一下分类 : 1. 必要的组件一定要在主线程中立即初始化(入口 Activity 可能立即会用到)
2. 组件一定要在主线程中初始化,但是可以延迟初始化。
3. 组件可以在子线程中初始化。

放在子线程的组件初始化建议延迟初始化,这样就可以了解是否会对项目造成影响!

所以对于上面的分析,可以在项目中 Application 的加载组件进行如下优化 :

  • 将Bugly,x5内核初始化,SP的读写,友盟等组件放到子线程中初始化。(子线程初始化不能影响到组件的使用)
  new Thread(new Runnable()  
      @Override 
      public void run()  
         //设置线程的优先级,不与主线程抢资源 
         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         //子线程初始化第三方组件 
         Thread.sleep(5000);//建议延迟初始化,可以发现是否影响其它功能,或者是崩 溃! 
       
  ).start();
  • 将需要在主线程中初始化但是可以不用立即完成的动作延迟加载(原本是想在入口 Activity 中进行此项操作,不过组件的初始化放在 Application 中统一管理为妙.)
  handler.postDelayed(new Runnable()  
      @Override 
      public void run()  
         //延迟初始化组件 
       
   , 3000);
1.2.3.闪屏页业务优化

最后还剩下那些为数不多的组件在主线程初始化动作,例如埋点,点击流,数据库初始化等,不过这些消耗的时间可以在其它地方相抵。

需求背景: 应用App通常会设置一个固定的闪屏页展示时间,例如2000ms,所以我们可以根据用户手机的运行速度,对展示时间做出调整,但是总时间仍然为 2000ms。

闪屏页政展示总时间 = 组件初始化时间 + 剩余展示时间。

也就是2000ms的总时间,组件初始化了800ms,那么就再展示1200ms即可。

先了解一下 Application的启动过程,虽然这个以下图片的源码并不是最新源码(5.0源码),不过不影响整体流程。(7.0,8.0方法名会有所改变)。

冷启动的过程中系统会初始化应用程序进程,创建Application等任务,这时候会展示一个启动窗口Starting Window,如果没有优化主题的话,那么就是白屏。

分析源码后,我们可以知道 Application 初始化后会调用attachBaseContext() 方法,再调用ApplicationonCreate() ,再到入口 Activity的创建和执行 onCreate() 方法。所以我们就可以在Application 中记录启动时间。

  //Application 
      @Override 
      protected void attachBaseContext(Context base)  
          super.attachBaseContext(base); 
          SPUtil.putLong("application_attach_time", System.currentTimeMillis());// 记录Application初始化时间 
  

有了启动时间,我们得知道入口的 Acitivty 显示给用户的时间(View绘制完毕),在onWindowFocusChanged() 的回调时机中表示可以获取用户的触摸时间和View的流程绘制完毕,所以可以在这个方法里记录显示时间。

  //入口Activity 

      @Override 
      public void onWindowFocusChanged(boolean hasFocus)  
          super.onWindowFocusChanged(hasFocus); 

            long appAttachTime = SPUtil.getLong("application_attach_time"); 
            long diffTime = System.currentTimeMillis() - appAttachTime;//从 application到入口Acitity的时间 

            //所以闪屏页展示的时间为 2000ms - diffTime. 
  

所以就可以动态的设置应用闪屏的显示时间,尽量让每一部手机展示的时间一致,这样就不会让手机配置较低的用户感觉漫长难熬的闪屏页时间(例如初始化了2000ms,又要展示2000ms的闪屏页时间.),优化用户体验。

1.2.4.广告页优化

闪屏页过后就要展示金主爸爸们的广告页了。

因为项目中广告页图片有可能是大图,APng动态图片,所以需要将这些图片下载到本地文件,下载完成后再显示,这个过程往往会遇到以下两个问题 :

  • 广告页的下载,由于这个是一个异步过程,所以往往不知道加载到页面的合适时机。
  • 广告页的保存,因为保存是 I/O 流操作,很有可能被用户中断,下次拿到破损的图片。

因为不清楚用户的网络环境,有些用户下载广告页可能需要一段时间,这时候又不可能无限的等候。所以针对这个问题可以开启 IntentService 用来下载广告页图片。

  • 在入口 Acitivity 中开启IntentService来下载广告页。 或者是其它异步下载操作。
  • 在广告页图片文件流完全写入后记录图片大小,或者记录一个标识。

在下次的广告页加载中可以判断是否已经下载好了广告页图片以及图片是否完整,否则删除并且再次下载图片。

另外因为在闪屏页中仍然有剩余展示时间,所以在这个时间段里如果用户已经下载好了图片并且图片完整,就可以显示广告页。否则进入主 Activity , 因为 IntentService 仍然在后台继续默默的下载并保存图片~

1.3.优化效果


通过手上 小米6,小米 mix2s,还有小米 2s的启动测试,发现优化后App冷启动的启动速度均提升了60% !!! ,并且可以再看一下手机冷启动时候的内存情况 :

优化前 : 伴随着大量对象的创建回收,15s内系统GC 5次。内存使用波澜荡漾。

优化后 : 趋于平稳上升状态创建对象,15s内系统GC 2次。(后期业务拓展加入新功能,所以代码量增加。)之后总内存使用平缓下降。

  • Other:应用使用的系统不确定如何分类的内存。
  • Code:应用用于处理代码和资源(如 dex 字节码、已优化或已编译的 dex 码、.so 库和字体)的内存。
  • Stack: 应用中的原生堆栈和 Java 堆栈使用的内存。 这通常与您的应用运行多少线程有关。
  • Graphics:图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。 (请注意,这是与 CPU 共享的内存,不是 GPU 专用内存。)
  • Native:从 C 或 C++ 代码分配的对象内存。即使应用中不使用 C++,也可能会看到此处使用的一些原生内存,因为 Android 框架使用原生内存代表处理各种任务,如处理图像资源和其他图形时,即使编写的代码采用 Java 或 Kotlin 语言。
  • Java:从 Java 或 Kotlin 代码分配的对象内存。
  • Allocated:应用分配的 Java/Kotlin 对象数。 它没有计入 C 或 C++ 中分配的对象。

1.4.启动窗口

优化完代码后,分析一下启动窗口的源码。基于 android-25 (7.1.1)

启动窗口是由 WindowManagerService 统一管理的 Window 窗口,一般作为冷启动页入口 Activity
预览窗口,启动窗口由 ActivityManagerService 来决定是否显示的,并不是每一个 Activity 的启动和跳转都会显示这个窗口。

WindowManagerService 通过窗口管理策略类PhoneWindowManager 来创建启动窗口。

AMS启动Activity流程

ActivityStarterstartActivityUnchecked() 方法中,调用了 ActivityStackActivity状态管理)的 startActivityLocked() 方法。此时Activity 还在启动过程中,窗口并未显示。启动窗口的显示过程

首先,由 Activity 状态管理者 ActivityStack 开始执行显示启动窗口的流程。

  //ActivityStack 
  final void startActivityLocked(ActivityRecord r, boolean newTask, boolean keepCurTransition,
             ActivityOptions options)  
             …… 
         if (!isHomeStack() || numActivities() > 0) //HOME_STACK表示Launcher桌面所 在的Stack 
             // 1.首先当前启动栈不在Launcher的桌面栈里,并且当前系统已经有激活过Activity 
             // We want to show the starting preview window if we are 
             // switching to a new task, or the next activity's process is 
             // not currently running. 

             boolean doShow = true; 
             if (newTask)  
                // 2.要将该Activity组件放在一个新的任务栈中启动 
                // Even though this activity is starting fresh, we still need 
                // to reset it to make sure we apply affinities to move any 
                // existing activities from other tasks in to it. 
                if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0)  
                   resetTaskIfNeededLocked(r, r); 
                   doShow = topRunningNonDelayedActivityLocked(null) == r; 
                 
            else if (options != null && options.getAnimationType() 
                  == ActivityOptions.ANIM_SCENE_TRANSITION)  
               doShow = false; 
           
           if (r.mLaunchTaskBehind)  
              //3. 热启动,不需要启动窗口 
              // Don't do a starting window for mLaunchTaskBehind. More importantly make sure we 
              // tell WindowManager that r is visible even though it is at the back of the stack.
              mWindowManager.setAppVisibility(r.appToken, true); 
              ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); 
           else if (SHOW_APP_STARTING_PREVIEW && doShow)  
          …… 
              //4. 显示启动窗口 
              r.showStartingWindow(prev, showStartingIcon); 
           
       else  
          // 当前启动的是桌面Launcher (开机启动) 
          // If this is the first activity, don't do any fancy animations, 
          // because there is nothing for it to animate on top of
          .…… 
       
  
  1. 首先判断当前要启动的 Activity 不在Launcher栈里
  2. 要启动的 Activity 是否处于新的 Task 里,并且没有转场动画
  3. 如果是热/温启动则不需要启动窗口,直接设置App的Visibility

接下来调用 ActivityRecordshowStartingWindow() 方法来设置启动窗口并且改变当前窗口的状态。
如果 App 的应用进程创建完成,并且入口 Activity 准备就绪,就可以根据 mStartingWindowState 来判断是否需要关闭启动窗口。

  //ActivityRecord 
 
      void showStartingWindow(ActivityRecord prev, boolean createIfNeeded)  
         final CompatibilityInfo compatInfo = service.compatibilityInfoForPackageLocked(info.applicationInfo); 
         final boolean shown = service.mWindowManager.setAppStartingWindow( appToken, packageName, theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, prev != null ? prev.appToken : null, createIfNeeded); 
         if (shown)  
             mStartingWindowState = STARTING_WINDOW_SHOWN; 
          
  

WindowManagerService 会对当前 Activitytoken和主题进行判断。

  //WindowManagerService 

   @Override 
      public boolean setAppStartingWindow(IBinder token, String pkg, 
             int theme, CompatibilityInfo compatInfo, 
             CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, 
             int windowFlags, IBinder transferFrom, boolean createIfNeeded)  
          
         synchronized(mWindowMap)  

              //1. 启动窗口也是需要token的 
              AppWindowToken wtoken = findAppWindowToken(token); 

              //2. 如果已经设置过启动窗口了,不继续处理 
              if (wtoken.startingData != null)  
                  return false; 
              

              // If this is a translucent window, then don't 
              // show a starting window -- the current effect (a full-screen 
              // opaque starting window that fades away to the real contents 
              // when it is ready) does not work for this. 
              if (theme != 0)  
                  AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme, com.android.internal.R.styleable.Window, mCurrentUserId);
                  //3. 一堆代码对主题判断,不符合要求则不显示启动窗口(如透明主题) 
                  if (windowIsTranslucent)  
                      return false;
                  
                  if (windowIsFloating || windowDisableStarting)  
                      return false; 
                  
         ……
        

        //4. 创建StartingData,并且通过Handler发送消息 

        wtoken.startingData = new StartingData(pkg, theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags); 
        Message m = mH.obtainMessage(H.ADD_STARTING, wtoken); 
        // Note: we really want to do sendMessageAtFrontOfQueue() because we 
        // want to process the message ASAP, before any other queued 
        // messages. 

        mH.sendMessageAtFrontOfQueue(m); 
     
     return true; 
  
  1. 启动窗口也需要和 Activity 拥有同样令牌 token ,虽然启动窗口可能是白屏,或者一张图片,但是仍然需要走绘制流程已经通过WMS显示窗口。
  2. StartingData对象用来表示启动窗口的相关数据,描述了启动窗口的视图信息。
  3. 如果当前 Activity 是透明主题或者是浮动窗口等,那么就不需要启动窗口来过渡启动过程,所以在上面视觉优化中的设置透明主题就没有显示白色的启动窗口。
  4. 显示启动窗口也是一件心急火燎的事情,WMS的内部类H (handler) 处于主线程处理消息,所以需要将当前Message放置队列头部。

为什么需要通过 Handler 发送消息 ?
你可以在各大服务Service中见到 Handler 的身影,并且它们可能都有一个很吊的命名 H ,因为可能调用这个服务的某个执行方法处于子线程中,所以 Handler 的职责就是将它们切换到主线程中,并且也可以统一管理调度。

  //WindowManagerService --> H 
 
          public void handleMessage(Message msg)  
              switch (msg.what)  

                  case ADD_STARTING:  
                      final AppWindowToken wtoken = (AppWindowToken)msg.obj; 
                      final StartingData sd = wtoken.startingData; 

                      View view = null; 
                      try 
                           final Configuration overrideConfig = wtoken != null && wtoken.mTask != null ? wtoken.mTask.mOverrideConfig : null; 
                           view = mPolicy.addStartingWindow(wtoken.token, sd.pkg, sd.theme, sd.compatInfo, sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo, sd.windowFlags, overrideConfig);
                       catch (Exception e)  
                          Slog.w(TAG_WM, "Exception when adding starting window", e); 
                       
              …… 
                 break; 
  

在当前的 handleMessage 方法中,会处于主线程处理消息,拿到tokenStartingData启动数据后,便通过mPolicy.addStartingWindow() 方法将启动窗口添加到WIndow上。

mPolicyPhoneWindowManager ,控制着启动窗口的添加删除和修改。

PhoneWindowManager对启动窗口进行配置,获取当前Activity设置的主题和资源信息,设置到启动窗口中。

二丶UI渲染优化

2.1.CPU,GPU的职责
2.2.查找Overdraw
2.3.clipRect解决自定义ViewOverDraw
2.4.Hierarchy Viewer的使用

2.5.内存抖动现象
在我们优化过view的树形结构和overdraw之后,可能还是感觉自己的app有卡顿和丢帧,或者滑动慢:卡顿还是存在。这时我们就要查看一下是否存在内存抖动情况了

Android有自动管理内存的机制,但是对内存的不恰当使用仍然容易引起严重的性能问题。在同一帧里面创建过多的对象是件需要特别引起注意的事情,在同一帧里创建大量对象可能引起GC的不停操作,执行GC操作的时候,所有线程的任何操作都会需要暂停,直到GC操作完成。大量不停的GC操作则会显著占用帧间隔时间。

如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了,严重时可能引起卡顿:

导致GC频繁操作有两个主要原因

  1. 内存抖动,所谓内存抖动就是短时间产生大量对象又在短时间内马上释放。
  2. 短时间产生大量对象超出阈值,内存不够,同样会触发GC操作。

观察内存抖动我们可以借助android studio中的工具,3.0以前可以使用android monitor,3.0以后被替换为android Profiler。

如果工具里面查看到短时间发生了多次内存的涨跌,这意味着很有可能发生了内存抖动,如图:

为了避免发生内存抖动,我们需要避免在for循环里面分配对象占用内存,需要尝试把对象的创建移到循环体之外,自定义View中的onDraw方法也需要引起注意,每次屏幕发生绘制以及动画执行过程中,onDraw方法都会被调用到,避免在onDraw方法里面执行复杂的操作,避免创建对象。对于那些无法避免需要创建对象的情况,我们可以考虑对象池模型,通过对象池来解决频繁创建与销毁的问题,但是这里需要注意结束使用之后,需要手动释放对象池中的对象。

三丶崩溃优化

3.1.崩溃

崩溃率是衡量一个应用质量高低的基本指标,那么,该怎样客观地衡量崩溃这个指标,以及又该如何看待和崩溃相关的稳定性。
Android 的两种崩溃:

  1. Java 崩溃
  2. Native 崩溃

简单来说,Java 崩溃就是在 Java 代码中,出现了未捕获异常,导致程序异常退出。那 Native 崩溃一般都是因为在 Native 代码中访问非法地址,也可能是地址对齐出现了问题,或者发生了程序主动 Abort,这些都会产生相应的 Signal 信号,导致程序异常退出。

3.1.1.崩溃的收集

“崩溃”就是程序出现异常,而一个产品的崩溃率,跟我们如何捕获、处理这些异常有比较大的关系。对于很多中小型公司来说,可以选择一些第三方的服务。目前各种平台也是百花齐放,包括阿里的友盟、腾讯的Bugly、网易云捕、Google 的 Firebase 等等。要懂得借力!

3.1.2.ANR

崩溃率是不是就能完全等价于应用的稳定性呢?答案是肯定不行。处理了崩溃,我们还会经常遇到 ANR(Application Not Responding,程序没有响应)这个问题。出现 ANR 的时候,系统还会弹出对话框打断用户的操作,这是用户非常不能忍受的。

ANR处理方法:

使用 FileObserver 监听 /data/anr/traces.txt 的变化。非常不幸的是,很多高版本的 ROM,已经没有读取这个文件的权限了。这个时候你可能只能思考其他路径,海外可以使用 Google Play 服
务,而国内微信利用Hardcoder框架(HC 框架是一套独立于安卓系统实现的通信框架,它让 App和厂商 ROM 能够实时“对话”了,目标就是充分调度系统资源来提升 App 的运行速度和画质,切实
提高大家的手机使用体验)向厂商获取了更大的权限。也可以将手机 ROOT 掉,然后取得traces.txt 文件。

3.1.3应用退出

除了常见的崩溃,还有一些会导致应用异常退出的情况,例如:

  1. 主动自杀。Process.killProcess()exit()
  2. 崩溃。出现了 Java 或 Native 崩溃
  3. 系统重启。系统出现异常、断电、用户主动重启等,我们可以通过比较应用开机运行时间是否比之前记录的值更小
  4. 被系统杀死。被 low memory killer 杀掉、从系统的任务管理器中划掉等
  5. ANR

我们可以在应用启动的时候设定一个标志,在主动自杀或崩溃后更新标志,这样下次启动时通过检测这个标志就能确认运行期间是否发生过异常退出。对应上面的五种退出场景,我们排除掉主动自杀和崩溃(崩溃会单独的统计)这两种场景,希望可以监控到剩下三种的异常退出,理论上这个异常捕获机制是可以达到 100% 覆盖的。通过这个异常退出的检测,可以反映如 ANR、low memory killer、系统强杀、死机、断电等其他无法正
常捕获到的问题。当然异常率会存在一些误报,比如用户从系统的任务管理器中划掉应用。对于线上的大数据来说,还是可以帮助我们发现代码中的一些隐藏问题。

根据应用的前后台状态,我们可以把异常退出分为前台异常退出和后台异常退出。“被系统杀死” 是后台异常退出的主要原因,当然我们会更关注前台的异常退出的情况,这会跟 ANR、OOM 等异常情况有更大的关联。

3.2.崩溃处理

  • 崩溃现场
  • 崩溃分析
  • 系统崩溃

四丶内存优化

4.1.优化工具

  • Memory Profiler
  • Memory AnalyzerMAT
  • LeakCannary

4.2.内存管理

  • 内存区域
  • 对象存活判断
  • 垃圾回收算法

4.3.内存抖动

  • 模拟内存抖动
  • 分析定位

4.5.内存泄漏

  • 模拟内存泄漏
  • 分析定位

4.6.MAT分析工具

  • Overview:当前内存整体信息

  • Histogram:列举对象所有的实例及实例所占大小,可按- package排序可以查看应用包名下Activity存在实例个数,可以查看是否存在内存泄露,这里发现内存中有8个Activity实例未释放查看未被释放的Activity的引用链

  • Dominator_tree:当前所有实例的支配树,和Histogram区别时Histogram是类维度,dominator_tree是实例维度,可以查看所有实例的所占百分比和引用链

  • SQL:通过sql语句查询相关类信息

  • Thread_overview:查看当前所有线程信息

  • Top Consumers:通过图形方式展示占用内存较高的对象,对降低内存栈优化可用内存比较有帮助

  • Leak Suspects:内存泄露分析页面直接定位到内存泄露位置

4.7.通过ARTHook检测不合理图片

  • 获取Bitmap占用内存
  • 检测大图

4.8.线上内存监控

  • 常规方案
  • LeakCannary定制改造
  • 完整方案

五丶卡顿优化

5.1.卡顿

5.2.帧率

5.3.卡顿原因

5.4.卡顿检测

  • 使用dumpsys gfxinfo
  • 使用BlockCanary
  • 使用Choreographer

六丶存储优化

6.1.交换数据格式

6.2.SharePreferences 优化

6.3.Bitmap 解码

6.4.数据库优化

  • 事物
  • SQLiteStatement
  • 索引

七丶网络优化

7.1.网络连接对用户的影响

7.2.分析网络连接的工具

  • Network Monitor
  • 网络代理工具

7.3.从哪些方面优化网络连接

  • 接口设计
  • 网络缓存
  • 弱网测试&优化

八丶耗电优化

8.1.Android Vitals

8.2.如何监控耗电

  • Java Hook
  • 插桩

九丶多线程并发优化

9.1.Thread 使用

  • Thread 中断
  • 同步

9.2.Android Threading

  • AsyncTask
  • HandlerThread
  • IntentService
  • Loader
  • ThreadPool

十丶安装包优化

常用的优化方式

  • 清理无用资源
  • 图片资源优化
  • 资源动态加载
  • lib库优化
  • 压缩资源
  • 代码混淆
  • 使用微信AndResGuard
  • Facebookredex优化字节码

以上是关于Android开发者,关于性能优化几大点,你都了解了吗?的主要内容,如果未能解决你的问题,请参考以下文章

Android启动这些事儿,你都拎得清吗?

Android面试:Android性能优化,很多开发者都表示最怕被问到这个

如何做好性能优化?字节大老历时3个月为你整理出这份Android性能优化实战全解析

金三银四,H5前端开发如何用性能优化征服前端面试官?

Android性能优化:腾讯内部性能优化全方面实战解析(面试&实战必备)

关于Elasticsearch性能优化几个点