歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Android Picasso加載webp格式圖片節省流量

最近產品經理一直抱怨圖片加載慢,為此客戶端開發這邊也做了許多努力,比如重定向到CDN,使用webp減小圖片大小,使用降低圖片壓縮質量,更換圖片加載框架等等動作。現在講一下webp格式圖片這個方案。

WebP格式,谷歌(google)開發的一種旨在加快圖片加載速度的圖片格式。圖片壓縮體積大約只有JPEG的2/3,並能節省大量的服務器帶寬資源和數據空間。Facebook Ebay等知名網站已經開始測試並使用WebP格式。
但WebP是一種有損壓縮。相較編碼JPEG文件,編碼同樣質量的WebP文件需要占用更多的計算資源。
桌面版Chrome,Android4.0以上可打開WebP格式。 ——摘自百度百科

Picasso原生支持webp格式圖片的加載

理論上說,不需要修改任何代碼,只要服務器提供支持,Picasso就能像加載jpg一樣加載webp格式的圖片。那麼現在問題來了

1、如何識別是個下載的文件是不是webp格式。

2、如何將webp解析成bitmap的,會不會出現內存上的問題

帶著這兩個問題,我仔細閱讀了一下Picasso的源碼,摸清Picasso加載webp文件的流程。首先看一下下載代碼,以okhttp下載器為例


@Override public Response load(Uri uri, int networkPolicy) throws IOException {
    CacheControl cacheControl = null;
    //...省略一部分緩存控制代碼
    Request.Builder builder = new Request.Builder().url(uri.toString());
    if (cacheControl != null) {
      builder.cacheControl(cacheControl);
    }

    com.squareup.okhttp.Response response = client.newCall(builder.build()).execute();//發起一次新的網絡請求
    int responseCode = response.code();
    if (responseCode >= 300) {//如果遇到網絡錯誤
      response.body().close();
      throw new ResponseException(responseCode + " " + response.message(), networkPolicy,
          responseCode);
    }

    boolean fromCache = response.cacheResponse() != null;

    ResponseBody responseBody = response.body();//取出http響應的body,這個body就是圖片文件
    return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
  }
從下載器中可以看出,okhttp交給Picasso的是一個InputStream,那麼Picasso是怎樣處理這個輸入流的呢,於是繼續讀代碼


 Bitmap hunt() throws IOException {
    Bitmap bitmap = null;

    if (shouldReadFromMemoryCache(memoryPolicy)) {
      //省略從本地LRU緩存獲得bitmap的代碼
    }

    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);//通過downloader發起網絡請求
    if (result != null) {
      loadedFrom = result.getLoadedFrom();//看看是不是來自磁盤緩存
      exifRotation = result.getExifOrientation();//查看exif信息,主要是看旋轉角度

      bitmap = result.getBitmap();//嘗試能否直接拿到bitmap

      // If there was no Bitmap then we need to decode it from the stream.
      if (bitmap == null) {//如果不能直接拿到bitmap,那麼就需要從輸入流中轉換
        InputStream is = result.getStream();//獲取文件輸入流,可能是網絡流,也可能是磁盤緩存文件的流
        try {
          bitmap = decodeStream(is, data);//重要的方法,從輸入流中解析出bitmap
        } finally {
          Utils.closeQuietly(is);
        }
      }
    }

  //...省略根據exif信息調整圖片的代碼

    return bitmap;
  }
以上代碼來自BitmapHunter這個類

其中我們需要的就是decodeStream()這個方法,是這個方法將網絡獲得的輸入流轉換成bitmap的,那麼這個方法怎麼寫的呢?


/**
  * Decode a byte stream into a Bitmap. This method will take into account additional information
  * about the supplied request in order to do the decoding efficiently (such as through leveraging
  * {@code inSampleSize}).
  */
  static Bitmap decodeStream(InputStream stream, Request request) throws IOException {
    MarkableInputStream markStream = new MarkableInputStream(stream);
    stream = markStream;

    long mark = markStream.savePosition(65536); // TODO fix this crap.

    final BitmapFactory.Options options = RequestHandler.createBitmapOptions(request);
    final boolean calculateSize = RequestHandler.requiresInSampleSize(options);

    boolean isWebPFile = Utils.isWebPFile(stream);//判斷是不是webp格式的圖片文件,webp格式使用BitmapFactory與Jpg,png不同
    markStream.reset(mark);
    // When decode WebP network stream, BitmapFactory throw JNI Exception and make app crash.
    // Decode byte array instead
    if (isWebPFile) {
      byte[] bytes = Utils.toByteArray(stream);//將輸入流讀取成字節數組,此處會不會OOM呢?
      if (calculateSize) {
        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
        RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,//根據imageView的大小計算應該讀取的bitmap大小
            request);
      }
      return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);//webp格式的圖片要使用decodeByteArray方法,否則會crash
    } else {
      //...省略jpg和png的轉換代碼
      return bitmap;
    }
  }這裡有個很重要的方法是Utils.isWebPFile(),那麼這個方法是怎麼根據一個輸入流判斷是jpg還是webp格式的文件呢,肯定不能用擴展名吧,於是點進去看下


private static final int WEBP_FILE_HEADER_SIZE = 12;
private static final String WEBP_FILE_HEADER_RIFF = "RIFF";
private static final String WEBP_FILE_HEADER_WEBP = "WEBP";

 static boolean isWebPFile(InputStream stream) throws IOException {
    byte[] fileHeaderBytes = new byte[WEBP_FILE_HEADER_SIZE];
    boolean isWebPFile = false;
    if (stream.read(fileHeaderBytes, 0, WEBP_FILE_HEADER_SIZE) == WEBP_FILE_HEADER_SIZE) {
      // If a file's header starts with RIFF and end with WEBP, the file is a WebP file
      isWebPFile = WEBP_FILE_HEADER_RIFF.equals(new String(fileHeaderBytes, 0, 4, "US-ASCII"))
          && WEBP_FILE_HEADER_WEBP.equals(new String(fileHeaderBytes, 8, 4, "US-ASCII"));
    }
    return isWebPFile;
  }
實現原理也不麻煩,用的linux常用的判斷文件類型的方法,就是讀取一個文件的頭幾個字節,然後轉換成ASCII字符看看是啥文件,這樣正好也利用了inputStream讀取的順序,很簡單的完成了jpg,png和webp文件的識別。

注意:不要在這個方法執行之前對inputStream執行讀取操作,否則很有可能無法判斷webp文件

如果你的webp文件加載不出來,不要忘了測試這個方法的返回值。

更多Android相關信息見Android 專題頁面 http://www.linuxidc.com/topicnews.aspx?tid=11

Copyright © Linux教程網 All Rights Reserved