Fresco知識點筆記03-ImagePipelineConfig-AnimatedImageFactory

原創-android技術研究-轉載請注明出處

Fresco初始化之前會有一些自定義的相關配置需要開發人員自己 去設置,設置配置的對象是ImagePipelineConfig,現在開始分析ImagePipelineConfig中第一個配置參數AnimatedImageFactory的相關知識點,以下代碼是使用Fresco時,設置配置屬性的需要的代碼demo。
<pre>
Set<RequestListener> listeners = new HashSet<>();
// 添加請求日志監聽包含請求及請求過程
listeners.add(new RequestLoggingListener());
// 設置配置屬性
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
.setRequestListeners(listeners)
.build();
</pre>

<pre>
//true
@Nullable private final AnimatedImageFactory mAnimatedImageFactory;
//error
@NonNull private final AnimatedImageFactory mAnimatedImageFactory = null;
</pre>

上述代碼是在ImagePipelineConfig設置的AnimatedImageFactory屬性

知識點@Nullable與@NonNull

@Nullable標記當前對象可以為null值,如果使用@NonNull則表示當前屬性不能為null值,如果給它賦值null,則會編譯器報錯。

AnimatedImageFactory是一個接口,它的主要作用是對帶動畫的圖像進行解碼,它的實現是AnimatedImageFactoryImpl,具體代碼如下(可以直接跳過,代碼后是相關的知識點):
<pre>
public interface AnimatedImageFactory {
public CloseableImage decodeGif(
final EncodedImage encodedImage,
final ImageDecodeOptions options,
final Bitmap.Config bitmapConfig);

public CloseableImage decodeWebP(
final EncodedImage encodedImage,
final ImageDecodeOptions options,
final Bitmap.Config bitmapConfig);

}
</pre>

<pre>
public class AnimatedImageFactoryImpl implements AnimatedImageFactory {

private final AnimatedDrawableBackendProvider mAnimatedDrawableBackendProvider;
private final PlatformBitmapFactory mBitmapFactory;

static AnimatedImageDecoder sGifAnimatedImageDecoder = null;
static AnimatedImageDecoder sWebpAnimatedImageDecoder = null;

private static AnimatedImageDecoder loadIfPresent(final String className) {
try {
Class<?> clazz = Class.forName(className);
return (AnimatedImageDecoder) clazz.newInstance();
} catch (Throwable e) {
return null;
}
}

static {
sGifAnimatedImageDecoder = loadIfPresent("com.facebook.animated.gif.GifImage");
sWebpAnimatedImageDecoder = loadIfPresent("com.facebook.animated.webp.WebPImage");
}

public AnimatedImageFactoryImpl(
AnimatedDrawableBackendProvider animatedDrawableBackendProvider,
PlatformBitmapFactory bitmapFactory) {
mAnimatedDrawableBackendProvider = animatedDrawableBackendProvider;
mBitmapFactory = bitmapFactory;
}

/**

  • Decodes a GIF into a CloseableImage.

  • @param encodedImage encoded image (native byte array holding the encoded bytes and meta data)

  • @param options the options for the decode

  • @param bitmapConfig the Bitmap.Config used to generate the output bitmaps

  • @return a {@link CloseableImage} for the GIF image
    */
    public CloseableImage decodeGif(
    final EncodedImage encodedImage,
    final ImageDecodeOptions options,
    final Bitmap.Config bitmapConfig) {
    if (sGifAnimatedImageDecoder == null) {
    throw new UnsupportedOperationException("To encode animated gif please add the dependency " +
    "to the animated-gif module");
    }
    final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();
    Preconditions.checkNotNull(bytesRef);
    try {
    Preconditions.checkState(!options.forceOldAnimationCode);
    final PooledByteBuffer input = bytesRef.get();
    AnimatedImage gifImage = sGifAnimatedImageDecoder.decode(input.getNativePtr(), input.size());

    return getCloseableImage(options, gifImage, bitmapConfig);
    } finally {
    CloseableReference.closeSafely(bytesRef);
    }
    }

/**

  • Decode a WebP into a CloseableImage.
  • @param encodedImage encoded image (native byte array holding the encoded bytes and meta data)
  • @param options the options for the decode
  • @param bitmapConfig the Bitmap.Config used to generate the output bitmaps
  • @return a {@link CloseableImage} for the WebP image
    */
    public CloseableImage decodeWebP(
    final EncodedImage encodedImage,
    final ImageDecodeOptions options,
    final Bitmap.Config bitmapConfig) {
    if (sWebpAnimatedImageDecoder == null) {
    throw new UnsupportedOperationException("To encode animated webp please add the dependency " +
    "to the animated-webp module");
    }
    final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();
    Preconditions.checkNotNull(bytesRef);
    try {
    Preconditions.checkArgument(!options.forceOldAnimationCode);
    final PooledByteBuffer input = bytesRef.get();
    AnimatedImage webPImage = sWebpAnimatedImageDecoder.decode(
    input.getNativePtr(),
    input.size());
    return getCloseableImage(options, webPImage, bitmapConfig);
    } finally {
    CloseableReference.closeSafely(bytesRef);
    }
    }

private CloseableAnimatedImage getCloseableImage(
ImageDecodeOptions options,
AnimatedImage image,
Bitmap.Config bitmapConfig) {
List<CloseableReference<Bitmap>> decodedFrames = null;
CloseableReference<Bitmap> previewBitmap = null;
try {
int frameForPreview = options.useLastFrameForPreview ? image.getFrameCount() - 1 : 0;
if (options.decodeAllFrames) {
decodedFrames = decodeAllFrames(image, bitmapConfig);
previewBitmap = CloseableReference.cloneOrNull(decodedFrames.get(frameForPreview));
}

  if (options.decodePreviewFrame && previewBitmap == null) {
    previewBitmap = createPreviewBitmap(image, bitmapConfig, frameForPreview);
  }
  AnimatedImageResult animatedImageResult = AnimatedImageResult.newBuilder(image)
      .setPreviewBitmap(previewBitmap)
      .setFrameForPreview(frameForPreview)
      .setDecodedFrames(decodedFrames)
      .build();
  return new CloseableAnimatedImage(animatedImageResult);
} finally {
  CloseableReference.closeSafely(previewBitmap);
  CloseableReference.closeSafely(decodedFrames);
}

}

private CloseableReference<Bitmap> createPreviewBitmap(
AnimatedImage image,
Bitmap.Config bitmapConfig,
int frameForPreview) {
CloseableReference<Bitmap> bitmap = createBitmap(
image.getWidth(),
image.getHeight(),
bitmapConfig);
AnimatedImageResult tempResult = AnimatedImageResult.forAnimatedImage(image);
AnimatedDrawableBackend drawableBackend =
mAnimatedDrawableBackendProvider.get(tempResult, null);
AnimatedImageCompositor animatedImageCompositor = new AnimatedImageCompositor(
drawableBackend,
new AnimatedImageCompositor.Callback() {
@Override
public void onIntermediateResult(int frameNumber, Bitmap bitmap) {
// Don't care.
}

      @Override
      public CloseableReference<Bitmap> getCachedBitmap(int frameNumber) {
        return null;
      }
    });
animatedImageCompositor.renderFrame(frameForPreview, bitmap.get());
return bitmap;

}

private List<CloseableReference<Bitmap>> decodeAllFrames(
AnimatedImage image,
Bitmap.Config bitmapConfig) {
final List<CloseableReference<Bitmap>> bitmaps = new ArrayList<>();
AnimatedImageResult tempResult = AnimatedImageResult.forAnimatedImage(image);
AnimatedDrawableBackend drawableBackend =
mAnimatedDrawableBackendProvider.get(tempResult, null);
AnimatedImageCompositor animatedImageCompositor = new AnimatedImageCompositor(
drawableBackend,
new AnimatedImageCompositor.Callback() {
@Override
public void onIntermediateResult(int frameNumber, Bitmap bitmap) {
// Don't care.
}

      @Override
      public CloseableReference<Bitmap> getCachedBitmap(int frameNumber) {
        return CloseableReference.cloneOrNull(bitmaps.get(frameNumber));
      }
    });
for (int i = 0; i < drawableBackend.getFrameCount(); i++) {
  CloseableReference<Bitmap> bitmap = createBitmap(
      drawableBackend.getWidth(),
      drawableBackend.getHeight(),
      bitmapConfig);
  animatedImageCompositor.renderFrame(i, bitmap.get());
  bitmaps.add(bitmap);
}
return bitmaps;

}

@SuppressLint("NewApi")
private CloseableReference<Bitmap> createBitmap(
int width,
int height,
Bitmap.Config bitmapConfig) {
CloseableReference<Bitmap> bitmap = mBitmapFactory.createBitmap(width, height, bitmapConfig);
bitmap.get().eraseColor(Color.TRANSPARENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
bitmap.get().setHasAlpha(true);
}
return bitmap;
}
}
</pre>

AnimatedImageFactoryImpl代碼相關知識點

知識點final變量

final相關資料
【final關鍵字的使用方法】:
【修飾變量】:
final成員變量表示常量,只能被賦值一次,賦值后值不再改變。
【修飾方法】:
final方法不能被子類方法覆蓋,但可以被繼承。
【修飾類】:
final類不能被繼承,沒有子類,final類中所有方法都是final的。
<pre>
public class MyFinal {
private static final int finalValue = 10;
public static void main(String[] args) {
//error final修飾的變量賦值后不可以被改變
finalValue = 11;
}
}
</pre>

知識點反射獲取類對象

AnimatedImageFactoryImpl靜態塊利用反射的方式初始化了兩個AnimatedImageDecoder(接口),它們實際指向的是GifImage和WebPImage對象。
<pre>
static AnimatedImageDecoder sGifAnimatedImageDecoder = null;
static AnimatedImageDecoder sWebpAnimatedImageDecoder = null;
private static AnimatedImageDecoder loadIfPresent(final String className) {
try {
//通過反射創建類實例
Class<?> clazz = Class.forName(className);
return (AnimatedImageDecoder) clazz.newInstance();
} catch (Throwable e) {
return null;
}
}
static {
sGifAnimatedImageDecoder = loadIfPresent("com.facebook.animated.gif.GifImage");
sWebpAnimatedImageDecoder = loadIfPresent("com.facebook.animated.webp.WebPImage");
}
</pre>

知識點@SuppressLint

SuppressLint作用是屏蔽android lint錯誤在Android代碼中,我們有時會使用比我們在AndroidManifest中設置的android:minSdkVersion版本更高的方法,此時編譯器會提示警告.解決方法是在方法上加上@SuppressLint("NewApi")作用僅僅是屏蔽android lint錯誤,所以在方法中還要判斷版本做不同的操作.
<pre>
@SuppressLint("NewApi")
private CloseableReference<Bitmap> createBitmap(
int width,
int height,
Bitmap.Config bitmapConfig) {
CloseableReference<Bitmap> bitmap = mBitmapFactory.createBitmap(width, height, bitmapConfig);
bitmap.get().eraseColor(Color.TRANSPARENT);
// @SuppressLint("NewApi")檢測到大于最小版本的api了
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
bitmap.get().setHasAlpha(true);
}
return bitmap;
}
</pre>

以上包含了AnimatedImageFactoryImpl的相關知識點,那么接著分析AnimatedImageFactoryImpl中相關屬性中包含的知識點,這個類中包含4個屬性,其中兩個是AnimatedImageDecoder,它們用來解碼webp和gif的,剩下兩個是AnimatedDrawableBackendProvider、PlatformBitmapFactory,具體的解釋在后續的知識點分析中再做解釋。

AnimatedImageFactoryImpl中decodeGif方法分析

<pre>
public CloseableImage decodeGif(
final EncodedImage encodedImage,//圖片編碼對象
final ImageDecodeOptions options,//圖像解碼選項
final Bitmap.Config bitmapConfig) //Bitmap配置信息{

final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();

try {
  //省略部分驗證代碼
  final PooledByteBuffer input = bytesRef.get();
  AnimatedImage gifImage = sGifAnimatedImageDecoder.decode(input.getNativePtr(), input.size());

  return getCloseableImage(options, gifImage, bitmapConfig);
} finally {
  CloseableReference.closeSafely(bytesRef);
}

}
</pre>

首先我們先分析獲取NativePooledByteBuffer過程中使用的知識點
<pre>
//這里從EncodeImage對象中獲取了一個CloseableReference
//泛型類型實際指向的是NativePooledByteBuffer,它實現了PooledByteBuffer
final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();
//獲取NativePooledByteBuffer對象
final PooledByteBuffer input = bytesRef.get();
</pre>
在上述代碼中返回了一個CloseableReference,CloseableReference實現了Cloneable和Closeable接口,然后調用了CloseableReference的get方法獲取NativePooledByteBuffer對象。
<pre>
public final class CloseableReference<T> implements Cloneable, Closeable {
//省略部分代碼

public synchronized T get() {
//從SharedReference獲取NativePooledByteBuffer對象
return mSharedReference.get();
}
}
</pre>

知識點Cloneable

一.Cloneable 的用途
Cloneable和Serializable一樣都是標記型接口,它們內部都沒有方法和屬性,implements Cloneable表示該對象能被克隆,能使用Object.clone()方法。如果沒有implements Cloneable的類調用Object.clone()方法就會拋出CloneNotSupportedException。
二.克隆的分類
(1)淺克隆(shallow clone),淺拷貝是指拷貝對象時僅僅拷貝對象本身和對象中的基本變量,而不拷貝對象包含的引用指向的對象。
(2)深克隆(deep clone),深拷貝不僅拷貝對象本身,而且拷貝對象包含的引用指向的所有對象。
下面是一個淺克隆的demo
<pre>
public class MyClone implements Cloneable {
String name = "wyd";
public static void main(String[] args) {
MyClone myClone = new MyClone();
try {
//克隆一個當前對象
MyClone copyMyClone = (MyClone) myClone.clone();
//修改當前克隆對象中的參數值
copyMyClone.name = "wydcopy";
System.out.println("copyMyClone.name is " + copyMyClone.name + ", myClone.name is " + myClone.name);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
</pre>
輸出結果 copyMyClone.name is wydcopy, myClone.name is wyd,從上面的結果可以看出克隆的對象如果發生變化,那么不會影響到原始對象。

知識點Closeable

Closeable接口也定義了close()方法。實現了Closeable接口的類的對象可以被關閉。從JDK7開始,Closeable擴展了AutoCloseable。因此,在JDK7中,所有實現了Closeable接口的類也都實現了AutoCloseable接口。

AutoCloseable接口對JDK7新添加的帶資源的try語句提供了支持,這種try語句可以自動執行資源關閉過程。只有實現了AutoCloseable接口的類的對象才可以由帶資源的try語句進行管理。AutoCloseable接口只定義了close()方法:
void close() throws Exception
這個方法關閉調用對象,釋放可能占用的所有資源。在帶資源的try語句的末尾,會自動調用該方法,因此消除了顯式調用close()方法的需要。
<pre>
//JDK1.7以前的版本,釋放資源的寫法
//這段代碼需要自己手動調用close方法去釋放
static String readFirstLineFromFile(String path) throws IOException {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(path));
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null)
br.close();
}
return null;
}
//JDK1.7中的寫法,利用AutoCloseable接口
//這段代碼會自動調用close,不再需要手動調用了
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
</pre>
以上代碼可以看出JDK1.7后因為所有的可被關閉資源對象都實現了AutoCloseable接口,因此,只要在try的表達式中加入了資源對象,那么就不在需要close當前對象了,close的方法會被系統自動調用。

接下來分析CloseableReference上述中調用SharedReference的知識點
<pre>
public class SharedReference<T> {
@GuardedBy("itself")
private static final Map<Object, Integer> sLiveObjects = new IdentityHashMap<>();

private static void addLiveReference(Object value) {
synchronized (sLiveObjects) {
Integer count = sLiveObjects.get(value);
if (count == null) {
sLiveObjects.put(value, 1);
} else {
sLiveObjects.put(value, count + 1);
}
}
}
private static void removeLiveReference(Object value) {
synchronized (sLiveObjects) {
Integer count = sLiveObjects.get(value);
if (count == null) {
// Uh oh.
FLog.wtf(
"SharedReference",
"No entry in sLiveObjects for value of type %s",
value.getClass());
} else if (count == 1) {
sLiveObjects.remove(value);
} else {
sLiveObjects.put(value, count - 1);
}
}
}

}
</pre>

知識點IdentityHashMap

IdentityHashMap是無序的Map集合,可以添加重復的key的Map,它的key比較的是內存地址,如果兩個key的內存地址是相同的,則不能重復;如果兩個key的內存地址不一樣,如new出來的對象,這個時候兩個值完全一樣的key也是可以添加進去的。
<pre>
public class MyIdentityHashMap {
public static void main(String[] args) {
Map<Object, Integer> map = new IdentityHashMap<>();
//內存地址相等則后面的會覆蓋前面的key
map.put("aaa", 10);
map.put("aaa", 11);
//內存地址相等則后面的會覆蓋前面的key
map.put(1, 14);
map.put(1, 15);
//內存地址不想等則不會被覆蓋
map.put(new String("aaa"), 12);
map.put(new String("aaa"), 13);
Set set = map.keySet();
for (Object str :
set) {
System.out.println(map.get(str));
}
System.out.println("aaa" == "aaa");
//輸出結果
// 11
// 13
// 12
// 15
// true
}
}
</pre>

知識點@GuardedBy("itself")

@GuardedBy("itself")表示當前注釋的變量的同步由變量本身去控制
<pre>
@GuardedBy("itself")//標注變量的同步由變量本身去控制
private static final Map<Object, Integer> sLiveObjects = new IdentityHashMap<>();
private static void addLiveReference(Object value) {
// 同步自身
synchronized (sLiveObjects) {}
}
</pre>

接下來分析解碼方法中使用的知識點,相關代碼如下
<pre>
//sGifAnimatedImageDecoder代表GifImage對象,實際調用的是GifImage中的decode方法
AnimatedImage gifImage = sGifAnimatedImageDecoder.decode(input.getNativePtr(), input.size());
</pre>
GifImage對象中decode方法代碼如下
<pre>
@ThreadSafe
@DoNotStrip
public class GifImage implements AnimatedImage, AnimatedImageDecoder {

private volatile static boolean sInitialized;

// Accessed by native methods
@SuppressWarnings("unused")
@DoNotStrip
private long mNativeContext;
//判斷是否初始化了gifimage本地庫
private static synchronized void ensure() {
if (!sInitialized) {
sInitialized = true;
//下面代碼實際調用的是System.loadLibrary(libraryName)
SoLoaderShim.loadLibrary("gifimage");
}
}
//解碼方法
@Override
public AnimatedImage decode(long nativePtr, int sizeInBytes) {
return GifImage.create(nativePtr, sizeInBytes);
}
//創建GifImage對象
public static GifImage create(long nativePtr, int sizeInBytes) {
ensure();
Preconditions.checkArgument(nativePtr != 0);
return nativeCreateFromNativeMemory(nativePtr, sizeInBytes);
}
//通過本地內存創建GifImage
private static native GifImage nativeCreateFromNativeMemory(long nativePtr, int sizeInBytes);
</pre>

知識點@SuppressWarnings("unused")

如果使用 SuppressWarnings("unused") 注釋了某個變量,那么表示當前這個變量是預留變量,在當前類中沒有使用。

知識點System.loadLibrary(libraryName)

加載本地庫,只有加載了本地庫才可以調用jni中的方法,否則會報錯,這里調用的是nativeCreateFromNativeMemory本地方法

知識點volatile關鍵字

可見性:
  可見性是一種復雜的屬性,因為可見性中的錯誤總是會違背我們的直覺。通常,我們無法確保執行讀操作的線程能適時地看到其他線程寫入的值,有時甚至是根本不可能的事情。為了確保多個線程之間對內存寫入操作的可見性,必須使用同步機制。
  可見性,是指線程之間的可見性,一個線程修改的狀態對另一個線程是可見的。也就是一個線程修改的結果。另一個線程馬上就能看到。比如:用volatile修飾的變量,就會具有可見性。volatile修飾的變量不允許線程內部緩存和重排序,即直接修改內存。所以對其他線程是可見的。但是這里需要注意一個問題,volatile只能讓被他修飾內容具有可見性,但不能保證它具有原子性。比如 volatile int a = 0;之后有一個操作 a++;這個變量a具有可見性,但是a++ 依然是一個非原子操作,也就這這個操作同樣存在線程安全問題。
原子性:
  原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型) 這個操作是不可分割的,那么我們說這個操作時原子操作。再比如:a++; 這個操作實際是a = a + 1;是可分割的,所以他不是一個原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那么我們稱它具有原子性。java的concurrent包下提供了一些原子類,我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

volatile原理:
當把變量聲明為volatile類型后,編譯器與運行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值。
  在訪問volatile變量時不會執行加鎖操作,因此也就不會使執行線程阻塞,因此volatile變量是一種比sychronized關鍵字更輕量級的同步機制。


  當對非volatile變量進行讀寫的時候,每個線程先從內存拷貝變量到CPU緩存中。如果計算機有多個CPU,每個線程可能在不同的CPU上被處理,這意味著每個線程可以考慮到不同的CPU cache中。
  而聲明變量是volatile的,JVM保證了每次讀變量都從內存中讀,跳過CPU cache這一步。

從以上的一些知識點中其實可以分析出來,Fresco解碼gif是在native塊做的,webp的流程和gif的流程類似,那么ImagePipelineConfig中AnimatedImageFactory屬性的職責就是解碼webp、gif圖片。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區別 13、...
    Miley_MOJIE閱讀 3,729評論 0 11
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,360評論 11 349
  • 生活中總有一些人,他們有著自己對生活的向往與要求,他們不會將就,他們堅守著自己引以為傲的原則。而這些人,往往是固執...
    DrJoseph閱讀 534評論 0 0
  • 《桃花賦》 文:木子親斤 春風吻開了漫山遍野的桃花 桃花羞澀了姑娘緋紅的臉頰 春雨潤醒了小草嫩嫩的黃芽 那一潭春水...
    寂寞去旅行閱讀 753評論 2 2