浅谈Android性能优化方案

先给大家介绍一下UC公司的性能优化指标以及部分方案:

 

       一、性能优化六项指标:

              性能、内存、稳定性、流量、电量、安装包大小;

       二、背景 ---- Android程序卡顿产生原因:

              1、Android系统低效

              --渲染线程、同步接口、广播机制

             :没有独立的渲染线程

             :广播机制引入,可能同时又几百个广播机制在后台运行

              2、运行环境恶劣

              --后台进程、安全软件

              3、低端机占比高

               --低内存、弱cpu、IO瓶颈

             :开源平台,导致高中低端的机型普遍存在;;

             :低内存影响最大,一般可用内存在小于50M,意味着会由于小于50M就会杀死一些进程来维护内存的大小

             : GPU是其次;

             :读写速度比较慢,在有的手机上;

              4、产品考虑不足

               --功能定义简陋、功能堆积严重

             : 一般的产品只会考虑需求,我要做什么,而并没有把整个闭环考虑清楚;

             :在版本迭代的过程,在不注意间可能启动过程会越来越慢;

              5、技术考虑不足

              --很多

 

       三、用户反馈应用卡顿怎么办?

               困难:

              1、复现性

              -- 用户描述模糊、不稳定出现(复现率比较低);

              2、定位难

              -- 不同机型、固件、系统状态表现不一

              --程序细节多、可疑面广

              3、衡量难

              -- 卡顿严重程度难以量化

              -- 卡顿问题不便分类

             : 是有一点卡、非常卡、还是什么

             : 没有针对性的目标,提升百分之多少等等,不知道极限在哪里;

 

      四、解决思路

             1、卡 vs 顿,卡为主,顿为辅。卡和顿没有一个明显的界限,大部分顿的问题当环境足够恶劣时就会表现为卡。所以抓住卡,就能解决很多                      问题。 
             2、打点统计 vs 全局监控:

               短期目标:主路径性能保障,打点统计;

               长期目标:整体的卡顿优化,全局监控;
             3、 线下分析 vs 线上监控:

              线下分析:实验室调试去复现一个问题,精确定位、粒度细;

              线上监控:指标衡量、粒度粗。 

             4、打点统计分析:

              (1)、启动速度

              (2)、响应速度

              (3)、版本比对 : app版本、Android版本

             5、用户反馈分析:

               将用户性能方面的反馈,测试人员进行分析,以邮件形式发送给技术负责人,进行分析;

               反馈等级: 

                 --预警机制

                 --用户分类

                 --功能分类

                 -- 纵向对比

             6、anr日志分析:

              --精确定位 : 堆栈信息比较清晰 

              --数据量化  

              主线程超时(5s ---> 1s)

              -- 暴漏更多蕾体

              -- 精确定位问题

              -- 方便用户联调

              -- 如果一个按钮响应时间超过800ms,用户感知起来就会很难受了。

             7、全局监控 -- Looper Hook

             -- 监测系统消息循环

             --计算消息耗时

             -- 定位耗时点

             卡顿:无非就是主线程被卡住了,就是主线程的消息循环里面的某一帧执行时间非常长,导致后续的消息无法来得及执行,

             数据指标卡顿率: 卡顿用户数/日活总数

             8、 问题回顾:

             -- 下载界面展开卡顿: 分段加载

             -- 二维码界面展示慢: 延时加载、先出界面在初始化相机

             -- 启动完成后操作卡: 线程枪战,低优先级后台进程+队列

             -- 共享存储卡顿: sharedPerference( 主线程IO , commit(主) ---> apply(子))

             -- 视频播放控制卡顿(API兼容性问题,异步化,视频播放停止暂停线程)

             -- 获取网络代理卡顿(IPC异常【进程间通信】,异步DNS+缓存)

             -- 第三方反馈卡死(固件问题,shield Activity,全部采用一个新的activity去做,这样不会对原来activity产生影响)

             --  网页滑屏操作卡顿(GPU加速 开启硬件加速)

             -- So加载/jni注册卡(异步加载 + 时序控制)

             -- 安全软件事件拦截(沟通反馈)

      五、经验推广:

         禁止:

         -- 主线程文件IO(标记文件读写外)

         -- 主线程耗CPU操作

         -- 主线程同步IPC调用(时间不可预期)

         推荐:

         -- 异步化

            【1】、 产品及程序设计 : 加载肯定是需要时间的,不可能实时展现;

            【2】、预加载 (数据必备,功能执行之前将这些事先数据准备好)

            【3】、闲时加载: 利用cpu的闲时做一些事情,主线程会设置一个ido handler,主线程所有消息操作完成之后会回调一个handler

            【4】、按需加载

         -- 线程管理

             1、线程量限制 + 任务队列

             2、非主线程优先级调低

         --压力测试

         -- 防御式编程

         -- 全局性能检测

     六、延伸

         -- 精确化 & 自动化

            用户反馈、卡顿日志

         -- 新监控方案

             Api Hook

         -- 新优化方案

             卡顿率 --> 帧率

             低端机优化

 

                   -----------------------------------------------------UC浏览器方案结束-------------------------------------------------------

 

个人性能优化方案

 

 

Android性能优化代码规范

 

l 编码之初准备篇:

l 对于布局内容的数量要求:

       单个Activity显示的视图一般情况少于20,层数少于4

        对于Adapter控件,如ListView ,item的布局层数一般情况为2,不得超过3.

l 将Acitivity 中的Window 的背景图设置为空:

        getWindow().setBackgroundDrawable(null); 

        android的默认背景不为空。

l 将Activity的背景放到Activity的Theme中设置。同时避免fragment和activity背景重复设置:

        Theme设置属性

        <item name="android:windowBackground">src_image</item>

l 采用硬件加速:

     androidmanifest.xml中application添加 

     android:hardwareAccelerated="true"。

     需要注意的是:android 3.0以上才可以使用。

l 使用ProGuard去除不必要的代码:

   #删除无用的类 

   -assumenosideeffects class android.util.Log {

      public static *** d(...);

      public static *** v(...);

      public static *** e(...);

      public static *** i(...);

      public static *** w(...);

    }

l apk打包签名时,使用zipalign工具对齐:

        zipAlignEnabled true

 

l 后台可以处理的逻辑不要放在前台,这样可能会有预料不到的问题

l 内存泄露引入三方框架LeakCanary :使用超级方便:

     http://blog.csdn.net/walid1992/article/details/50470958

 

l Android程序冷启动优化(第一次启动应用):

1、在logoactivity设置一个theme,设置windowBackground属性,避免黑屏阶段。

2、对app进行延迟启动控制,采用延迟加载技术

 

 
  1. private Handler handler = new Handler();

  2. //延迟加载 runnable

  3. private Runnable delayLoadRunnable = new Runnable() {

  4. @Override

  5. public void run() {

  6. Logger.d("start delayLoadRunnable ");

  7. init();

  8. }

  9. };

  10. //优化的DelayLoad : 采用延迟加载策略

  11. window.getDecorView().post(new Runnable() {

  12. @Override

  13. public void run() {

  14. handler.post(delayLoadRunnable);

  15. }

  16. });


 

 

       Activity 在启动时,会在第二次执行 performTraversals 才会去真正的绘制,原因在于第一次执行 performTraversals 的时候,会走到 Egl 初始化的逻辑,然后会重新执行一次 performTraversals 。
所以有人问为何在 run 方法里面还要 post 一次,如果在 run 方法里面直接执行 updateText 方法 ,那么 updateText 就会在第一个 performTraversals 之后就执行,而不是在第一帧绘制完成后才去执行,所以我们又 Post 了一次 。所以大概的处理步骤如下:

第一步:Activity.onCreate –> Activity.onStart –> Activity.onResume

第二步:ViewRootImpl.performTraversals –>Runnable

第三步:Runnable –> ViewRootImpl.performTraversals

第四步:ViewRootImpl.performTraversals –> init();

第五步:init();

l 禁止(避免)操作篇:

  核心:少的对象创建,意味着少的GC操作。 杜绝引起内存溢出、内存抖动的操作行为;

l 禁止在单例模式中引用Activity的context:

l 禁止使用枚举:

     使用枚举访问速度要比static变量慢4倍,枚举将造成大量的内存浪费;

l 禁止使用异步回调:

    异步回调被执行的时间不确定,很有可能发生在activity已经被销毁之后,

这不仅仅很容易引起crash,还很容易发生内存泄露。

l 禁止static引用资源耗费过多的实例:

例如:context  , Activity

对于某些不得不出现static引用context的情况,在onDestroy()方法中,解除Activity与static的绑定关系,

从而去除static对Activity的引用,使Context能够被回收;

l 避免在循环(for、while、listView - getView方法、onDraw)里创建对象:

     对于onDraw中 Paint 我们可以这样优化

      private Paint paint = new Paint();

      public on Draw(){

          paint.setColor(mBorderColor);

     }

l 避免使用static成员对象:

static生命周期过长,对于需要传递的对象,使用(Intent)和(Handler)

l 避免使用浮点数:

浮点数会比整型慢两倍

l 避免Timer.schedule,对于延时操作,可用以下方式代替:

 

 
  1. ScheduledExecutorService,

  2. handler.postDelayed,

  3. handler.postAtTime ,

  4. handler.sendMessageDelayed ,

  5. View.postDelayed,

  6. AlarmManager

 

 

l 避免加载过大图片。压缩或者使用对象池后再使用

l 避免使用递归

l 避免使用轮询

l 避免长周期内部类、匿名内部类长时间持有外部类对象导致相关资源无法释放。如:Handler, Thread , AsyncTask

l 避免使用三方库,不需要的东西需要剔除

l 避免使用注解框架,毕竟是反射

l 非必要情况下,少用抽象

l 避免频繁网络请求

访问server端时,建立连接本身比传输需要跟多的时间,如非必要,不要将一交互可以做的事情分成多次交互(这需要与Server端协调好)。有效管理Service 后台服务就相当于一个持续运行的Acitivity,如果开发的程序后台都会一个service不停的去服务器上更新数据,在不更新数据的时候就让它sleep,这种方式是非常耗电的,通常情况下,可以使用AlarmManager来定时启动服务。如下所示,第30分钟执行一次。

 

 
  1. 1. AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALAR M_SERVICE);

  2. 2. Intent intent = new Intent(context, MyService.class);

  3. 3. PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);

  4. 4. long interval = DateUtils.MINUTE_IN_MILLIS * 30;

  5. 5. long firstWake = System.currentTimeMillis() + interval;

  6. 6. am.setRepeating(AlarmManager.RTC,firstWake, interval, pendingIntent);

 

 

l 优化操作建议篇:

l 当数据量在100以内时,使用ArrayMap代替HashMap

l 为了避免自动装箱,当数量在1000以下时,使用如下容器

    a)SparseBoolMap <bool , obj>

    b)SparseIntMap <int , obj>

    c)SparseLongMap <long , obj>

    d)LongSparseMap <long ,obj>

l 字符串拼接用StringBuilder或StringBuffer

    //这种string第一次初始化的情况下,下面得效率更高

    String str1 = "abc"+“def”+"hij";

    //非并发情况 , StringBuilder效率更优

    StringBuilder str2 = str3 + str1 + "builder" ;

    //并发情况使用 StringBuffer

    StringBuffer str2 = str1 + "buffer" ;

l 文件、网络IO缓存,使用有缓存机制的输入流

BufferedInputStream替代InputStream

BufferedReader替代Reader

BufferedReader替代BufferedInputStream. 

l 考虑使用Webp代替传统png图片。对于某些使用JPEG即可实现的效果,尽量采用JPEG

png虽能提供无损的图片,但相对于JPEG过大。Webp是既保持png优点,又能减少图片大小的新型格式.

l 尽量使用局部变量:

l 如果没有特殊需求,使用基本数据类型,而非对象类型

基本类似指:int , double , char等。

l 对于使用超过两次的对象成员, 将成员缓存到本地

   反复使用的变量,保存到本地成为临时变量活成员变量后进行操作。尤其是在循环中

    例:多次比较目标时间和当前时间差。  

l 当new的对象并不是100%一定会被用到时,在使用时创建,有效减少不必要的对象生成 

 

 
  1. 例如: Object ob = new Object();

  2. int value;

  3. if(i>0) value = ob.getVlaue();

  4. 改写为:int value;

  5. if(i>0){

  6. Object ob = new Object(); //用到时加载

  7. value = ob.getVlaue();

  8. }

 

 

l 及时释放不用的对象

 

 
  1. a = new Object();

  2. 当a不为空时,应改写为:

  3. a = null;

  4. a = new Object();

 

 

l 不在使用的变量,手动置为null

  通常对于对象成员如此使用,局部变量不需要

  this.object = null;

l 常量用 static final修饰

l 对bitmap进行恰当的操作:

 

 
  1. 读取图片之前先查看其大小:

  2. 1. BitmapFactory.Options opts = new BitmapFactory.Options();

  3. 2. opts.inJustDecodeBounds = true;

  4. 3. Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);

  5. 使用得到的图片原始宽高计算适合自己的smaplesize:

  6. 1. BitmapFactory.Options opts = new BitmapFactory.Options();

  7. 2. opts.inSampleSize = 4 ;// 4就代表容量变为以前容量的1/4

  8. Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);

  9. 对于过时的Bitmap对象一定要及时recycle,并且把此对象赋值为null:

  10. 1. bitmap.recycle();

  11. 2. bitmap = null;

 

 

l 布局用Java完成比XML快

l 默认不会显示的布局使用 viewstub 标签

 

 
  1. <ViewStub

  2. android:id="@+id/network_error_layout"

  3. android:layout_width="match_parent"

  4. android:layout_height="match_parent"

  5. android:layout="@layout/network_error" />

  6.  
  7. //非显示的转换ViewStub 获取

  8. View viewStub = findViewById(R.id.network_error_layout);

  9. viewStub.setVisibility(View.VISIBLE); // ViewStub被展开后的布局所替换

  10. networkErrorView = findViewById(R.id.network_error_layout); // 获取 展开后的布局

 

l 对于两次以上相同的infalte操作,用成员变量代替局部变量,避免重复加载

l 正确使用fragment

 

 
  1. 界面绘制尽量使用fragment代替activity,fragment根据情况使用hide与add方式,还是replace

  2. if (!showFragment.isAdded()) { // 先判断是否被add过

  3. transaction.hide(currentFragment).add(R.id.fl_content, showFragment)

  4. .commitAllowingStateLoss(); // 隐藏当前的fragment,add下一个到Activity中

  5. } else {

  6. // 隐藏 当前的fragment,显示下一个

  7. transaction.hide(currentFragment).show(showFragment).commitAllowingStateLoss();

  8. }

  9. this.currentFragment = showFragment;

 

l 对于重复出现超过2-3次的子布局,用 include 实现复用

  <include layout="@layout/foot.xml" />

l 当复用的布局中子View对所依赖的根节点要求不高时,使用 merge 作为根节点

 

 
  1. 要求不高标准:非复杂结构布局,无Background,padding等属性,且子View数量较少

  2. <merge xmlns:android="http://schemas.android.com/apk/res/android"

  3. android:layout_width="match_parent"

  4. android:layout_height="match_parent" >

  5. <Button

  6. android:id="@+id/button"

  7. android:layout_width="match_parent"

  8. android:layout_height="@dimen/dp_40"

  9. android:layout_above="@+id/text"/>

  10. <TextView

  11. android:id="@+id/text"

  12. android:layout_width="match_parent"

  13. android:layout_height="@dimen/dp_40"

  14. android:layout_alignParentBottom="true"

  15. android:text="@string/app_name" />

  16. </merge>

 

l 数据压缩:

传输数据经过压缩 目前大部门网站都支持GZIP压缩,所以在进行大数据量下载时,尽量使用GZIP方式下载,可以减少网络流量,一般是压缩前数据大小的30%左右。

 

 
  1. 1. HttpGet request = new HttpGet("http://example.com/gzipcontent");

  2. 2. HttpResponse resp = new DefaultHttpClient().execute(request);

  3. 3. HttpEntity entity = response.getEntity();

  4. 4. InputStream compressed = entity.getContent();

  5. 5. InputStream rawData = new GZIPInputStream(compressed);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值