[置顶] 浅谈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 对于布局内容的数量要求:

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

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

将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进行延迟启动控制,采用延迟加载技术

  private Handler handler = new Handler();
  //延迟加载 runnable
  private Runnable delayLoadRunnable = new Runnable() {
    @Override
    public void run() {
      Logger.d("start delayLoadRunnable ");
      init();
                     }
    };
   //优化的DelayLoad : 采用延迟加载策略
    window.getDecorView().post(new Runnable() {
    @Override
    public void run() {
      handler.post(delayLoadRunnable);
                      }
   });


       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();

禁止(避免)操作篇:

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

禁止在单例模式中引用Activity的context:
禁止使用枚举:

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

禁止使用异步回调:

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

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

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

例如:context  , Activity

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

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

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

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

      private Paint paint = new Paint();

      public on Draw(){

          paint.setColor(mBorderColor);

     }

避免使用static成员对象:

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

避免使用浮点数:

浮点数会比整型慢两倍

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

  ScheduledExecutorService, 
   handler.postDelayed, 
   handler.postAtTime , 
   handler.sendMessageDelayed ,  
   View.postDelayed,      
   AlarmManager

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

避免使用递归

避免使用轮询

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

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

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

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

避免频繁网络请求

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

1. AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALAR  M_SERVICE);  
2. Intent intent = new Intent(context, MyService.class);  
3. PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);  
4. long interval = DateUtils.MINUTE_IN_MILLIS * 30;  
5. long firstWake = System.currentTimeMillis() + interval;  
6. am.setRepeating(AlarmManager.RTC,firstWake,  interval,  pendingIntent);  

优化操作建议篇:

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" ;

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

BufferedInputStream替代InputStream

BufferedReader替代Reader

BufferedReader替代BufferedInputStream. 

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

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

尽量使用局部变量:
如果没有特殊需求,使用基本数据类型,而非对象类型

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

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

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

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

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

例如:  Object ob = new Object();
        int value;
        if(i>0)  value = ob.getVlaue();
改写为:int value;
        if(i>0){
        Object ob = new Object();   //用到时加载
        value = ob.getVlaue();
}

及时释放不用的对象

  a = new Object();
  当a不为空时,应改写为:
  a = null;
  a = new Object();

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

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

  this.object = null;

常量用 static final修饰

l 对bitmap进行恰当的操作:

读取图片之前先查看其大小:
1. BitmapFactory.Options opts = new BitmapFactory.Options();  
2. opts.inJustDecodeBounds = true;  
3. Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);  
使用得到的图片原始宽高计算适合自己的smaplesize:
1. BitmapFactory.Options opts = new BitmapFactory.Options();  
2.  opts.inSampleSize = 4 ;// 4就代表容量变为以前容量的1/4  
 Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);                      
对于过时的Bitmap对象一定要及时recycle,并且把此对象赋值为null:
1. bitmap.recycle();
2. bitmap = null;  

l 布局用Java完成比XML

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

 <ViewStub
    android:id="@+id/network_error_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout="@layout/network_error" />
 
  //非显示的转换ViewStub 获取
  View viewStub = findViewById(R.id.network_error_layout);
  viewStub.setVisibility(View.VISIBLE); // ViewStub被展开后的布局所替换
  networkErrorView = findViewById(R.id.network_error_layout); // 获取 展开后的布局 

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

界面绘制尽量使用fragment代替activity,fragment根据情况使用hide与add方式,还是replace
     if (!showFragment.isAdded()) { // 先判断是否被add过
     	 transaction.hide(currentFragment).add(R.id.fl_content, showFragment)
 .commitAllowingStateLoss(); // 隐藏当前的fragment,add下一个到Activity中
    } else {
           // 隐藏 当前的fragment,显示下一个
 transaction.hide(currentFragment).show(showFragment).commitAllowingStateLoss(); 
         	}
this.currentFragment = showFragment;

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

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

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

要求不高标准:非复杂结构布局,无Background,padding等属性,且子View数量较少
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_above="@+id/text"/>
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_alignParentBottom="true"
        android:text="@string/app_name" />
</merge> 

数据压缩:

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

1. HttpGet request = new HttpGet("http://example.com/gzipcontent");  
2. HttpResponse resp = new DefaultHttpClient().execute(request);  
3. HttpEntity entity = response.getEntity();  
4. InputStream compressed = entity.getContent();  
5. InputStream rawData = new GZIPInputStream(compressed);  


---------------希望对大家能有帮助,如果有不好的地方希望大家给予建议------------------

你可能感兴趣的:([置顶] 浅谈Android性能优化方案)