本文从电量,内存,界面,网络,图片,线程等方面进行记录优化小结

电量优化

工具检测

电量优化可以使用 Battery Historian工具

耗电因素

屏幕:每次锁屏然后亮屏的时候发现耗电非常多

蜂窝移动数据(3G,4G),WIFI,CPU/GPU工作

优化策略

1.WIFI状态下进行操作:无线网络状态下会比WIFI状态下耗电的多

2.网络请求设置超时:在网络请求时,如果网络很差,请求需要很长时间,我们需要设置超时时间,减少网络消耗!

3.任务集中处理JobScheduler

4.使用WEAK_LOCK保持系统工作
(需谨慎:有一些意外的情况,比如小米手机是做了同步心跳包(心跳对齐)(如果超过了这个同步的频率就会被屏蔽掉或者降频),所有的app后台唤醒频率不能太高,比如每隔2S中去请求。)

内存优化

回收机制:对象不再有任何的引用的时候才会进行回收

内存总结

内存分配几种策略

1.静态的: 静态的存储区:内存在程序编译的时候就已经分配好,这块的内存在程序整个运行期间都一直存在。它主要存放静态数据、全局的static数据和一些常量。

2.栈式的 在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。栈内存包括分配的运算速度很快,因为内置在处理器的里面的。当然容量有限。

3.堆式的

又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。

核心思想

GC会从根节点(GC Roots)开始对heap(堆)进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉对堆内存中的对象进行识别,如果对象正在被引用则称其为存活对象, 如果对象不再被 引用,则称为垃圾对象,可以收回其占据的空间,用于再分配。

常见泄露汇总

  • 集合类泄漏
  • 单例造成的内存泄漏
  • 匿名内部类/非静态内部类和异步线程
  • Handler 造成的内存泄漏
  • 尽量避免使用 static 成员变量

工具使用

LeakCanary

界面UI优化

渲染机制

1.VSYNC(垂直刷新/绘制):

60HZ是屏幕刷新理想的频率。60fps—一秒内绘制的帧数。

Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染

2.渲染原理:

渲染依赖2个核心组件:CPU和GPU
CPU负责包括Measure,Layout,Record,Execute的计算操作,GPU 负责Rasterization(栅格化)操作。何为栅格化

什么是栅格化
所谓的栅格化就是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操作。它把那些组件拆分到不同的像素上进行显示,说的俗气一点,就是解决那些复杂的XML布局文件和标记语言,使之转化成用户能看懂的图像,但是这不是直接转换的,XML布局文件需要在CPU中首先转换为多边形或者纹理,然后再传递给GPU进行格栅化,对于栅格化,跟OpenGL有关,格栅化是一个特别费时的操作。

  • CPU产生的问题:不必要的布局和失效
  • GPU产生的问题:过度绘制(overdraw)

3.过度绘制处理方案

  1. 去掉window的默认背景
  2. 去掉其他不必要的背景
  3. clipRect的使用
  4. ViewStubMerge标签

总的来说,就是使用越简单的View完成复杂的布局

4.工具

使用Systrace分析UI性能,使用BlockCanary寻找UI卡顿

网络优化

工具

Android Studio内置的Monitor工具中就有一个Network Monitor

抓包工具

Wireshark, Fiddler, Charles

优化方案

  1. 减少网络数据获取
  2. 减少获取数据包的大小
  3. 使用Gzip来压缩request和response, 减少传输数据量, 从而减少流量消耗
  4. 使用Protocol Buffer代替JSON
  5. 使用网络缓存:可参考

图片优化

质量参数

  • ALPHA_8 代表8位Alpha位图
  • ARGB_4444 代表16位ARGB位图
  • ARGB_8888 代表32位ARGB位图
  • RGB_565 代表8位RGB位图

位图位数越高代表其可以存储的颜色信息越多,当然图像也就越逼真。

图片压缩

1.质量压缩

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
     设置bitmap options属性,降低图片的质量,像素不会减少
     第一个参数为需要压缩的bitmap图片对象,第二个参数为压缩后图片保存的位置
     设置options 属性0-100,来实现压缩
     * @param bmp
     * @param file
     */
    public static void compressImageToFile(Bitmap bmp,File file) {
        // 0-100 100为不压缩
        int options = 20;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 把压缩后的数据存放到baos中
        bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2.尺寸压缩

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 通过缩放图片像素来减少图片占用内存大小
     * @param bmp
     * @param file
     */

    public static void compressBitmapToFile(Bitmap bmp, File file){
        // 尺寸压缩倍数,值越大,图片尺寸越小
        int ratio = 8;
        // 压缩Bitmap到对应尺寸
        Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888);
        Canvas canvas = new Canvas(result);
        Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
        canvas.drawBitmap(bmp, null, rect, null);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 把压缩后的数据存放到baos中
        result.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }   

3.设置图片的采样率,降低图片像素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
* @param filePath
     * @param file
     */
    public static void compressBitmap(String filePath, File file){
        // 数值越高,图片像素越低
        int inSampleSize = 8;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
//          options.inJustDecodeBounds = true;//为true的时候不会真正加载图片,而是得到图片的宽高信息。
        //采样率
        options.inSampleSize = inSampleSize;
        Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 把压缩后的数据存放到baos中
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
        try {
            if(file.exists())
            {
                file.delete();
            }
            else {
                file.createNewFile();
            }
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

多线程优化

在程序开发的实践当中,为了让程序表现得更加流畅,我们肯定会需要使用到多线程来提升程序的并发执行性能

线程池的管理

增加并发的线程数会导致内存消耗的增加,平衡好这两者的关系是非常重要的

使用线程池的好处:
1.重用已经创建的好的线程,避免频繁创建进而导致的频繁GC
2.控制线程并发数,合理使用系统资源,提高应用性能
3.可以有效的控制线程的执行,比如定时执行,取消执行等

ThreadPoolExecutor

1
2
3
4
5
6
7
public ThreadPoolExecutor(int corePoolSize,  
                          int maximumPoolSize,  
                          long keepAliveTime,  
                          TimeUnit unit,  
                          BlockingQueue<Runnable> workQueue,  
                          ThreadFactory threadFactory,  
                          RejectedExecutionHandler handler) 

参数:

  • corePoolSize 线程池中核心线程的数量
  • maximumPoolSize 线程池中最大线程数量
  • keepAliveTime 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。
  • unit 第三个参数的单位
  • workQueue 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。
  • threadFactory 为线程池提供创建新线程的功能,
  • handler 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。

workQueue:
workQueue是一个BlockingQueue类型,那么这个BlockingQueue又是什么呢?它是一个特殊的队列, 当我们从BlockingQueue中取数据时,如果BlockingQueue是空的,则取数据的操作会进入到阻塞状态,当BlockingQueue中有了新数据时,这个取数据的操作又会被重新唤醒。同理,如果BlockingQueue中的数据已经满了,往BlockingQueue中存数据的操作又会进入阻塞状态,直到BlockingQueue中又有新的空间,存数据的操作又会被冲洗唤醒。BlockingQueue有多种不同的实现类

下面我举几个例子来说一下:

1.ArrayBlockingQueue:这个表示一个规定了大小的BlockingQueue,ArrayBlockingQueue的构造函数接受一个int类型的数据,该数据表示BlockingQueue的大小,存储在ArrayBlockingQueue中的元素按照FIFO(先进先出)的方式来进行存取。

2.LinkedBlockingQueue:这个表示一个大小不确定的BlockingQueue,在LinkedBlockingQueue的构造方法中可以传一个int类型的数据,这样创建出来的LinkedBlockingQueue是有大小的,也可以不传,不传的话,LinkedBlockingQueue的大小就为Integer.MAX_VALUE

四中常见线程池

1.FixedThreadPool:

FixedThreadPool是一个核心线程数量固定的线程池

1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {  
    return new ThreadPoolExecutor(nThreads, nThreads,  
                                  0L, TimeUnit.MILLISECONDS,  
                                  new LinkedBlockingQueue<Runnable>());  
}

2.SingleThreadExecutor:

singleThreadExecutor和FixedThreadPool很像,不同的就是SingleThreadExecutor的核心线程数只有1

1
2
3
4
5
6
public static ExecutorService newSingleThreadExecutor() {  
return new FinalizableDelegatedExecutorService  
    (new ThreadPoolExecutor(1, 1,  
                            0L, TimeUnit.MILLISECONDS,  
                            new LinkedBlockingQueue<Runnable>()));  
}

3.CachedThreadPool:

CachedTreadPool一个最大的优势是它可以根据程序的运行情况自动来调整线程池中的线程数量

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {  
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  
                              60L, TimeUnit.SECONDS,  
                              new SynchronousQueue<Runnable>());  
}     

4.ScheduledThreadPool

ScheduledThreadPool是一个具有定时定期执行任务功能的线程池

1
2
3
4
5
public ScheduledThreadPoolExecutor(int corePoolSize) {  
super(corePoolSize, Integer.MAX_VALUE,  
      DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,  
      new DelayedWorkQueue());  
}