阅读 679

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

优化背景:临近过年,项目有一个过年红包的需求,红包大家都玩过是吧,领取红包产品和UED搞了个很复杂的动画,这个动画 因为太过于复杂所以只能用帧动画来做,但是帧动画大家懂的,效率很低,而且容易OOM,需求方呢又不愿意用低质量的GIF来 展示,所以只能逼迫我们码农们另外想办法了。

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

1.首先我们至少需要2个线程,一个线程decode这些图片资源得到bitmap,另外一个线程拿到bitmap以后不管是给imageview还是 surfaceview那都随便了。反正拿到bitmap以后无非就是设置下时间间隔刷新view即可。

2.decode的过程我们知道涉及到io,而io的速度往往比cpu速度慢很多,所以这里为了不让瓶颈出现,我们可以让decode的 线程存在多个,这样decode出来的bitmap 放到一个队列里面, 我们拿bitmap的线程只要从这个队列里面按顺序取出这些bitmap即可。

3.这个队列不能太大,因为如果内存中有太多图片就和帧动画一样会oom了。这个是一定要注意的。

4.既然有多个线程同时在decode这些图片,那么一定要注意的是,每张图片我们只要decode一次,否则资源肯定浪费啊。 所以要保证每张图片仅仅被decode一次。

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

1.生产者可能有很多个。因为可以开多个线程就decode加快速度。

2.消费者只有一个。

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

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

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属性噢~

可以大大降低你内存抖动的现象。

关注下面的标签,发现更多相似文章
评论