小松的技术博客

六和敬

若今生迷局深陷,射影含沙。便许你来世袖手天下,一幕繁华。 你可愿转身落座,掌间朱砂,共我温酒煮茶。

MDCC参会总结之Android

本次MDCC的Android专场基本都集中在性能优化上,而性能优化其实就是指出存在的性能问题和给出解决方案。

不要让App运行缓慢

一.Broadcast receivers in AndroidManifest可能远比你想象中慢

  1. 避免使用静态声明的Broadcast Receiver,尽可能动态注册;
  2. 不再需要时禁用静态 Receiver ,例如PackageManager.setComponentEnabledSetting()

二.进程创建和初始化(在低端机型上可能需要约 1 秒)和Application.onCreate()在很多大型App中往往开销非常大。

  1. Lazy 初始化

    举个例子:优雅的单例模式

    单例模式分为饿汉式和懒汉式:饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,而且性能受影响。但我们希望是懒汉式但又没有线程安全的繁琐控制,这个时候我们就可以采用Initialization Demand Holder (IoDH)即静态内部类的技术了

    class Singleton{
        private Singleton(){}
        private static class InstanceHolder{
            private final static Singleton INSTANCE = new Singleton()
        }
        public Singleton getInstance(){
            return InstanceHolder.INSTANCE;
        }
    }
    

    由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类InstanceHolder,在该内部类中定义了一个static类型的变量INSTANCE,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。(参考链接:http://blog.csdn.net/lovelion/article/details/7420888)

  2. 分离的 UI 初始化

    利用ActivityLifecycleCallbacks(Application通过此接口提供了一套回调方法,用于让开发者对Activity的生命周期事件进行集中处理。)

    public void onCreate() {  //重写Application的onCreate()方法
        super.onCreate();  
        this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {  
            @Override  
            public void onActivityStopped(Activity activity) {}  
            @Override  
            public void onActivityStarted(Activity activity) {}  
            @Override  
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}  
            @Override  
            public void onActivityResumed(Activity activity) {}  
            @Override  
            public void onActivityPaused(Activity activity) {}  
            @Override  
            public void onActivityDestroyed(Activity activity) {}  
            @Override  
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}  
        });  
    };  
    

三.连环唤醒(非唤醒型alarm埋下的坑)、non-wakeup:同时被唤醒,叠加压力,产生瞬间的大量开销(例如解锁时卡顿)

知识点:alarm与wakelock

    AlarmManager有一个AlarmManagerService服务程序,一旦有Alarm触发, AlarmManagerService就会遍历Alarm列表,找到相应的注册Alarm并发出广播.AlarmManager会维持一个cpu的wake lock。这样能保证电话休眠时,也能处理alarm的广播。一旦alarm receiver的onReceive() 方法执行完,wake lock会迅速被释放。如果在receiver中开启一个service,有可能service还没启动,wake lock已经被释放了。所以此时要实现单独的wake lock策略。

    WakeLock阻止应用处理器(Application Processor)挂起,确保关键代码的运行,通过中断唤起应用处理器(Application Processor),可以阻止屏幕变暗。所有的WakeLock被释放后,系统会挂起。

    简而言之:apps通过alarm唤醒手机,通过wakelock来维持手机唤醒(from:http://bbs.gfan.com/android-7963258-1-1.html)
知识点:唤醒型alarm(wakeup)与非唤醒型alarm(non-wakeup)

wakeup版本触发时,当屏幕处于熄灭状态时依旧会唤醒设备,从而可以执行所必要的操作。

non-wakeup版本触发时,如果此时屏幕处于熄灭状态,则不会把设备唤醒,而是等到用户或者是其他操作把设备唤醒时,才会把pendingIntent传递过去从而执行任务(from:http://zlv.me/posts/2015/06/10/13_tips-when-using-repeating-alarms/)。  

使用非唤醒型alarm时,一旦手机被唤醒时,将会有大量的应用程序被唤醒,产生瞬间的大量开销,例如解锁时非常卡顿。

二、降低后台耗电

一、alarm

  1. 确保 Target SDK Version ≥ 19
    Target SDK Version ≥ 19时系统会偏移alarm来最大限度地减少唤醒和电池使用;
  2. 对齐Alarm
    如果你的Alarm是RTC_WAKEUP or RTC类型,并且你的唤起周期(interval)是15分钟的整数倍,那么你的alarm首次执行执行时间并不一定是你设置的触发时间,系统会计算一个偏差,使其跟其他Alarm对齐,目的是尽可能的减少系统唤起次数,起到优化性能的作用(对齐但不保证准确)
  3. Wake up 与否,很有讲究(non-wakeup 并不总是最佳选择)
  4. 尽可能不要使用 Exact Alarm
  5. SyncAdaptor、JobScheduler

* 二、WakeLock*

  1. 确保释放:try finally
  2. 不要用WakeLock保持大文件下载,用DownloadManager;文件上传用WifiLock;
  3. 保持屏幕常亮可以采用的方法:LayoutParam.Flag.KEEPSCREENON

三、后台持久服务

  1. 注册的各种回调,记得添加ACTIONSCREENOFF;
  2. ReferenceQueue;

四、传感器 & 定位器

三、Bitmap

inSampleSize

有时候我们展示的Bitmap的大小和它自身的大小并不一样,如果展示的大小很小,但是Bitmap的实际大小很大,那么直接加载整个Bitmap就显得非常消耗性能。这个时候就可以通过inSampleSize来载入缩放后的图片了。

inSampleSize的值越大,加载的bitmap越小(加载后的bitmap的宽高分别为原始宽高除以inSampleSize)。需要注意的是inSampleSize的传值应该为2的n次方,如果你传入的值不是2的n次方,系统也会帮你转换成2的n次方,比方说你传入3,系统实际得到的值是2。

为了计算合理的inSampleSize,我们其实是需要获取bitmap的大小的,获取手段是加载一次bitmap,但仅仅是加载它的描述信息:

/**
 * 从resources中加载Bitmap
 */
public Bitmap decodeSampledBitmapFromResource(Resources res,
        int resId, int reqWidth, int reqHeight) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    //通过更改options.inJustDecodeBounds = true只加载大小
    options.inJustDecodeBounds = true; 
    BitmapFactory.decodeResource(res, resId, options);
    //计算inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth,
            reqHeight);

   //真正加载Bitmap
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}
/**
 * 从FileDescriptor中加载Bitmap(当使用流加载bitmap时)
 */
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    //通过更改options.inJustDecodeBounds = true只加载大小
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFileDescriptor(fd, null, options);
    //计算inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth,
            reqHeight);
    //真正加载Bitmap
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFileDescriptor(fd, null, options);
}

解码格式

选择ARGB8888/RBG565/ARGB4444/ALPHA8,存在很大差异

inBitmap

如果设置了该属性,那么当使用了带有该 Options 参数的 decode 方法在加载内容时,decode 方法会尝试重用一个已经存在的位图的内存空间,避免了内存的分配与接触分配,从而性能得到了改善。

使用inBitmap的一些注意点:

  1. 在SDK 11 -> 18之间,重用的bitmap大小必须是一致的,例如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小。
  2. 新申请的bitmap与旧的bitmap必须有相同的解码格式,例如大家都是8888的,如果前面的bitmap是8888,那么就不能支持4444与565格式的bitmap了。
  3. 我们可以创建一个包含多种典型可重用bitmap的对象池,这样后续的bitmap创建都能够找到合适的“模板”去进行重用。如下图所示:

缓存LruCache

LRU是Less Recently Use,即最近最少使用算法。

内存缓存:LruCache

磁盘缓存:DiskLruCache

四、GC

Dalvik和ART不同的GC方式

  • Dalvik:Dalvik的回收器并不是移动的,所有分配的对象在堆栈的位置不会变,内存分配后的间隙空间(碎片空间)得不到有效利用。如果要分配内存给新的对象(特别是大对象),寻找自由内存空间会很难。碎片空间也会导致频繁的GC去获取内存空间给新的对象,而这些GC需要停止Dalvik,是非常昂贵的。
  • ART:ART的回收器是移动的。
    • 在app因为用户没有任何操作而长时间暂停时,ART会把堆栈中的已分配内存空间移动在一起,从而聚合碎片空间;
    • ART有一快独立的堆栈区域专门用于存储大对象如bitmaps,这使得寻找大内存空间更加快速。

五、其它性能优化

  1. 使用更加轻量的数据结构:例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构
  2. 避免在Android里面使用Enum
  3. 对象池(Object pools)
  4. 避免在onDraw中执行对象的创建
←支付宝← →微信 →
comments powered by Disqus