优化背景:临近过年,项目有一个过年红包的需求,红包大家都玩过是吧,领取红包产品和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属性噢~
可以大大降低你内存抖动的现象。