Android优化帧动画过程当中的多线程模型思考

优化背景:临近过年,项目有一个过年红包的需求,红包你们都玩过是吧,领取红包产品和UED搞了个很复杂的动画,这个动画 由于太过于复杂因此只能用帧动画来作,可是帧动画你们懂的,效率很低,并且容易OOM,需求方呢又不肯意用低质量的GIF来 展现,因此只能逼迫咱们码农们另外想办法了。java

解决方案:将这个帧动画的源文件例如这100张帧动画须要的png图片,按照播放顺序明明成x1.png x2.png x3.png-----x100.pngapi

1.首先咱们至少须要2个线程,一个线程decode这些图片资源获得bitmap,另一个线程拿到bitmap之后不论是给imageview仍是 surfaceview那都随便了。反正拿到bitmap之后无非就是设置下时间间隔刷新view便可。bash

2.decode的过程咱们知道涉及到io,而io的速度每每比cpu速度慢不少,因此这里为了避免让瓶颈出现,咱们可让decode的 线程存在多个,这样decode出来的bitmap 放到一个队列里面, 咱们拿bitmap的线程只要从这个队列里面按顺序取出这些bitmap便可。app

3.这个队列不能太大,由于若是内存中有太多图片就和帧动画同样会oom了。这个是必定要注意的。dom

4.既然有多个线程同时在decode这些图片,那么必定要注意的是,每张图片咱们只要decode一次,不然资源确定浪费啊。 因此要保证每张图片仅仅被decode一次。ide

因此问题的核心就在于,这是一个典型的 变异的 消费者-生产者模式,只要解决了这个decode线程和播放线程的同步关系,那么剩下不论是给imageview播仍是给surfaceview 都再也不是问题,那都是api的基本使用了。最后咱们来抽象出这个变异的模式的特色:性能

1.生产者可能有不少个。由于能够开多个线程就decode加快速度。优化

2.消费者只有一个。动画

3.每一个资源仅被生产一次。ui

下面就针对这个模型来处理:

package com.wuyue.test;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by 16040657 on 2019/2/3.
 */
public class Test {

    //假设咱们有100张图片
    static final int MAX_IMAGE_NUM = 100;

    //假设最多只有3个decode线程
    static final int MAX_PRODUCER_NUM = 3;

    //从第一张图片开始取,不能够重复decode,因此要利用这个原子操做,性能比sync关键字不知道高到哪里去了
    AtomicInteger resourcesCurrentIndex = new AtomicInteger(0);

    //假设咱们队列只同时容纳5个bitmap
    LinkedBlockingQueue<Bitmap> queue = new LinkedBlockingQueue<>(5);

    public static void main(String args[]) {
        //先构造出一个100张图片的 资源池
        int[] resourcesArray = new int[MAX_IMAGE_NUM];
        for (int i = 0; i < MAX_IMAGE_NUM; i++) {
            resourcesArray[i] = i;
        }
        //开始播放吧
        Test test = new Test();
        test.play();

    }

    void play() {
        //先造3个decode线程出来 而后start他们
        for (int j = 0; j < MAX_PRODUCER_NUM; j++) {
            Thread thread = new Thread(new DecodeBitmapRunnable(), "thread:" + j);
            thread.start();
        }
        //再造一个消费者线程 模型就变成了 3个生产者---1个消费者
        Thread thread = new Thread(new PlayImageRunnable());
        thread.start();
    }

    //模拟一个bitmap对象,这里放入标号 方便咱们调试
    class Bitmap {
        public int getNumber() {
            return number;
        }

        public void setNumber(int number) {
            this.number = number;
        }

        public Bitmap(int number) {
            this.number = number;
        }

        @Override
        public String toString() {
            return "Bitmap{" +
                    "number=" + number +
                    '}';
        }

        int number;
    }

    /**
     * 制造者线程,其实就是decode bitmap 根据你动画图片的多少 能够动态设置多个decode线程 加快速度
     */
    class DecodeBitmapRunnable implements Runnable {
        @Override
        public void run() {
            while (true) {
                //保证了原子操做,就能够保证每次decode线程都是取不一样的资源。并且避免了sync关键字 性能更好
                int nowIndex = resourcesCurrentIndex.getAndIncrement();
                if (nowIndex > MAX_IMAGE_NUM - 1) {
                    return;
                }

                //模拟decode bitmap的场景 每次decode的场景花费的时间都不一样 因此这里模拟下
                int random = (int) (Math.random() * (100));
                try {
                    Thread.sleep(random);
                } catch (InterruptedException e) {
                }

                //假设通过了random 的 ms时间之后 咱们就decode出来一个bitmap了
                Bitmap bitmap = new Bitmap(nowIndex);
                try {
                    //decode出来之后 就放到这个队列里面
                    queue.put(bitmap);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


        }
    }


    /**
     * 消费者线程 从队列里面取bitmap出来播放 只有一个播放线程
     */
    class PlayImageRunnable implements Runnable {

        @Override
        public void run() {

            while (true) {
                try {
                    final Bitmap bitmap = queue.take();
                    System.out.println("consumer get " + bitmap.toString());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


        }
    }


}

复制代码

看下输出结果

consumer get Bitmap{number=1}
consumer get Bitmap{number=3}
consumer get Bitmap{number=2}
consumer get Bitmap{number=0}
consumer get Bitmap{number=5}
consumer get Bitmap{number=7}
consumer get Bitmap{number=6}
consumer get Bitmap{number=4}
consumer get Bitmap{number=8}
consumer get Bitmap{number=9}
consumer get Bitmap{number=10}
consumer get Bitmap{number=12}
consumer get Bitmap{number=11}
consumer get Bitmap{number=14}
consumer get Bitmap{number=13}
consumer get Bitmap{number=15}
consumer get Bitmap{number=17}
consumer get Bitmap{number=16}
consumer get Bitmap{number=18}
consumer get Bitmap{number=21}
consumer get Bitmap{number=19}
consumer get Bitmap{number=23}
consumer get Bitmap{number=20}
consumer get Bitmap{number=24}
consumer get Bitmap{number=22}
consumer get Bitmap{number=25}
consumer get Bitmap{number=26}
consumer get Bitmap{number=27}
consumer get Bitmap{number=28}
consumer get Bitmap{number=29}
consumer get Bitmap{number=32}
consumer get Bitmap{number=30}
consumer get Bitmap{number=34}
consumer get Bitmap{number=31}
consumer get Bitmap{number=33}
consumer get Bitmap{number=35}
consumer get Bitmap{number=37}
consumer get Bitmap{number=36}
consumer get Bitmap{number=40}
consumer get Bitmap{number=38}
consumer get Bitmap{number=41}
consumer get Bitmap{number=39}
consumer get Bitmap{number=43}
consumer get Bitmap{number=42}
consumer get Bitmap{number=44}
consumer get Bitmap{number=46}
consumer get Bitmap{number=45}
consumer get Bitmap{number=48}
consumer get Bitmap{number=49}
consumer get Bitmap{number=47}
consumer get Bitmap{number=51}
consumer get Bitmap{number=50}
consumer get Bitmap{number=52}
consumer get Bitmap{number=54}
consumer get Bitmap{number=53}
consumer get Bitmap{number=55}
consumer get Bitmap{number=58}
consumer get Bitmap{number=57}
consumer get Bitmap{number=60}
consumer get Bitmap{number=56}
consumer get Bitmap{number=59}
consumer get Bitmap{number=61}
consumer get Bitmap{number=62}
consumer get Bitmap{number=63}
consumer get Bitmap{number=66}
consumer get Bitmap{number=67}
consumer get Bitmap{number=65}
consumer get Bitmap{number=64}
consumer get Bitmap{number=68}
consumer get Bitmap{number=69}
consumer get Bitmap{number=72}
consumer get Bitmap{number=71}
consumer get Bitmap{number=70}
consumer get Bitmap{number=75}
consumer get Bitmap{number=73}
consumer get Bitmap{number=77}
consumer get Bitmap{number=74}
consumer get Bitmap{number=79}
consumer get Bitmap{number=76}
consumer get Bitmap{number=78}
consumer get Bitmap{number=80}
consumer get Bitmap{number=83}
consumer get Bitmap{number=84}
consumer get Bitmap{number=82}
consumer get Bitmap{number=81}
consumer get Bitmap{number=85}
consumer get Bitmap{number=87}
consumer get Bitmap{number=86}
consumer get Bitmap{number=88}
consumer get Bitmap{number=89}
consumer get Bitmap{number=90}
consumer get Bitmap{number=93}
consumer get Bitmap{number=91}
consumer get Bitmap{number=94}
consumer get Bitmap{number=95}
consumer get Bitmap{number=92}
consumer get Bitmap{number=98}
consumer get Bitmap{number=97}
consumer get Bitmap{number=99}
consumer get Bitmap{number=96}
复制代码

这里明显能够看出来,咱们每张图片都只被decode一次的目的是达到了,可是还有一个严重的bug。

咱们的顺序是错的,也就是说,咱们的动画既然是ued按照播放标准从第一帧一直到第100帧,那么这个顺序是死的, 可是咱们的消费者线程 取出来的顺序 缺不是0-100. 缘由就是:

咱们生产者生产出来的资料并无按顺序放到队列里,这就致使了咱们取出来的时候顺序也是错的。

下面就要着手解决这个问题,咱们用一个对象锁便可,这个锁记住了每次put进去的bitmap的编号, 而后每次put操做以前 先拿出这个编号看看和本身的bitmap的序号是否是匹配的,若是匹配就直接put 不匹配就wait,等其余顺序的匹配好了再把本身put进去

package com.wuyue.test;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by 16040657 on 2019/2/3.
 */
public class Test {

    //假设咱们有100张图片
    static final int MAX_IMAGE_NUM = 100;

    //假设最多只有3个decode线程
    static final int MAX_PRODUCER_NUM = 3;

    //从第一张图片开始取,不能够重复decode,因此要利用这个原子操做,性能比sync关键字不知道高到哪里去了
    AtomicInteger resourcesCurrentIndex = new AtomicInteger(0);

    //假设咱们队列只同时容纳5个bitmap
    LinkedBlockingQueue<Bitmap> queue = new LinkedBlockingQueue<>(5);

    public static void main(String args[]) {
        //先构造出一个100张图片的 资源池
        int[] resourcesArray = new int[MAX_IMAGE_NUM];
        for (int i = 0; i < MAX_IMAGE_NUM; i++) {
            resourcesArray[i] = i;
        }
        //开始播放吧
        Test test = new Test();
        test.play();

    }

    void play() {
        PutManager putManager = new PutManager();
        //先造3个decode线程出来 而后start他们
        for (int j = 0; j < MAX_PRODUCER_NUM; j++) {
            Thread thread = new Thread(new DecodeBitmapRunnable(putManager), "thread:" + j);
            thread.start();
        }
        //再造一个消费者线程 模型就变成了 3个生产者---1个消费者
        Thread thread = new Thread(new PlayImageRunnable());
        thread.start();
    }

    //模拟一个bitmap对象,这里放入标号 方便咱们调试
    class Bitmap {
        public int getNumber() {
            return number;
        }

        public void setNumber(int number) {
            this.number = number;
        }

        public Bitmap(int number) {
            this.number = number;
        }

        @Override
        public String toString() {
            return "Bitmap{" +
                    "number=" + number +
                    '}';
        }

        int number;
    }

    //其实这个就是加了一个manager对象,每次put一个bitmap进来的时候 就把索引值+1 加1
    //之后的索引值 就表明他想要的下一个帧的序列号,若是准备put的进程的序列号和想要的序列号
    //不一致 那么就等待 ,一致等到想要的序列号所在的进程来了之后 再put
    //这样就能够保证 帧的序列是正确的了
    class PutManager {
        public int getFrameIndex() {
            return frameIndex;
        }

        int frameIndex = 0;

        public void putBitmap(Bitmap bitmap) {
            try {
                queue.put(bitmap);
                frameIndex++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 制造者线程,其实就是decode bitmap 根据你动画图片的多少 能够动态设置多个decode线程 加快速度
     */
    class DecodeBitmapRunnable implements Runnable {

        PutManager putManager;

        public DecodeBitmapRunnable(PutManager putManager) {
            this.putManager = putManager;
        }

        @Override
        public void run() {
            while (true) {
                //保证了原子操做,就能够保证每次decode线程都是取不一样的资源。并且避免了sync关键字 性能更好
                int nowIndex = resourcesCurrentIndex.getAndIncrement();
                if (nowIndex > MAX_IMAGE_NUM - 1) {
                    return;
                }

                //模拟decode bitmap的场景 每次decode的场景花费的时间都不一样 因此这里模拟下
                int random = (int) (Math.random() * (100));
                try {
                    Thread.sleep(random);
                } catch (InterruptedException e) {
                }

                //假设通过了random 的 ms时间之后 咱们就decode出来一个bitmap了
                Bitmap bitmap = new Bitmap(nowIndex);

                synchronized (putManager) {
//                    System.out.println(Thread.currentThread().getName() + " enter sync " + bitmap.toString() + " putManager.getFrameIndex()=" + putManager.getFrameIndex());
                    while (true) {
                        if (putManager.getFrameIndex() != bitmap.getNumber()) {
                            try {
                                putManager.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        } else {
                            putManager.putBitmap(bitmap);
                            putManager.notifyAll();
                            break;
                        }
                    }
                }
//                System.out.println(Thread.currentThread().getName() + " out sync");

            }


        }
    }


    /**
     * 消费者线程 从队列里面取bitmap出来播放 只有一个播放线程
     */
    class PlayImageRunnable implements Runnable {

        @Override
        public void run() {

            while (true) {
                try {
                    final Bitmap bitmap = queue.take();
                    System.out.println("consumer get " + bitmap.toString());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


        }
    }


}

复制代码

最后看下执行结果:

"C:\Program Files (x86)\Java\jdk1.8.0_131\bin\java" -Didea.launcher.port=7548 "-Didea.launcher.bin.path=D:\Program Files (x86)\JetBrains\IntelliJ IDEA 14.1.7\bin" -Dfile.encoding=GBK -classpath "C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\charsets.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\deploy.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\javaws.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\jfr.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\jsse.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\management-agent.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\plugin.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\rt.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-32.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;C:\Users\16040657\Downloads\MapTest\out\production\MapTest;D:\Program Files (x86)\JetBrains\IntelliJ IDEA 14.1.7\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain com.wuyue.test.Test
consumer get Bitmap{number=0}
consumer get Bitmap{number=1}
consumer get Bitmap{number=2}
consumer get Bitmap{number=3}
consumer get Bitmap{number=4}
consumer get Bitmap{number=5}
consumer get Bitmap{number=6}
consumer get Bitmap{number=7}
consumer get Bitmap{number=8}
consumer get Bitmap{number=9}
consumer get Bitmap{number=10}
consumer get Bitmap{number=11}
consumer get Bitmap{number=12}
consumer get Bitmap{number=13}
consumer get Bitmap{number=14}
consumer get Bitmap{number=15}
consumer get Bitmap{number=16}
consumer get Bitmap{number=17}
consumer get Bitmap{number=18}
consumer get Bitmap{number=19}
consumer get Bitmap{number=20}
consumer get Bitmap{number=21}
consumer get Bitmap{number=22}
consumer get Bitmap{number=23}
consumer get Bitmap{number=24}
consumer get Bitmap{number=25}
consumer get Bitmap{number=26}
consumer get Bitmap{number=27}
consumer get Bitmap{number=28}
consumer get Bitmap{number=29}
consumer get Bitmap{number=30}
consumer get Bitmap{number=31}
consumer get Bitmap{number=32}
consumer get Bitmap{number=33}
consumer get Bitmap{number=34}
consumer get Bitmap{number=35}
consumer get Bitmap{number=36}
consumer get Bitmap{number=37}
consumer get Bitmap{number=38}
consumer get Bitmap{number=39}
consumer get Bitmap{number=40}
consumer get Bitmap{number=41}
consumer get Bitmap{number=42}
consumer get Bitmap{number=43}
consumer get Bitmap{number=44}
consumer get Bitmap{number=45}
consumer get Bitmap{number=46}
consumer get Bitmap{number=47}
consumer get Bitmap{number=48}
consumer get Bitmap{number=49}
consumer get Bitmap{number=50}
consumer get Bitmap{number=51}
consumer get Bitmap{number=52}
consumer get Bitmap{number=53}
consumer get Bitmap{number=54}
consumer get Bitmap{number=55}
consumer get Bitmap{number=56}
consumer get Bitmap{number=57}
consumer get Bitmap{number=58}
consumer get Bitmap{number=59}
consumer get Bitmap{number=60}
consumer get Bitmap{number=61}
consumer get Bitmap{number=62}
consumer get Bitmap{number=63}
consumer get Bitmap{number=64}
consumer get Bitmap{number=65}
consumer get Bitmap{number=66}
consumer get Bitmap{number=67}
consumer get Bitmap{number=68}
consumer get Bitmap{number=69}
consumer get Bitmap{number=70}
consumer get Bitmap{number=71}
consumer get Bitmap{number=72}
consumer get Bitmap{number=73}
consumer get Bitmap{number=74}
consumer get Bitmap{number=75}
consumer get Bitmap{number=76}
consumer get Bitmap{number=77}
consumer get Bitmap{number=78}
consumer get Bitmap{number=79}
consumer get Bitmap{number=80}
consumer get Bitmap{number=81}
consumer get Bitmap{number=82}
consumer get Bitmap{number=83}
consumer get Bitmap{number=84}
consumer get Bitmap{number=85}
consumer get Bitmap{number=86}
consumer get Bitmap{number=87}
consumer get Bitmap{number=88}
consumer get Bitmap{number=89}
consumer get Bitmap{number=90}
consumer get Bitmap{number=91}
consumer get Bitmap{number=92}
consumer get Bitmap{number=93}
consumer get Bitmap{number=94}
consumer get Bitmap{number=95}
consumer get Bitmap{number=96}
consumer get Bitmap{number=97}
consumer get Bitmap{number=98}
consumer get Bitmap{number=99}

复制代码

嗯 看上去没问题了。

值得注意的是,这里咱们只是抽象出了最关键的线程模式,真正你在优化这个帧动画的时候,必定要注意使用bitmap 对象池

否则如此频繁的new 对象,容易形成内存抖动,在不少低端机上容易触发gc,致使掉帧。必定记住使用inBitmap属性噢~

能够大大下降你内存抖动的现象。

相关文章
相关标签/搜索