優化背景:臨近過年,專案有一個過年紅包的需求,紅包大家都玩過是吧,領取紅包產品和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屬性噢~
可以大大降低你記憶體抖動的現象。