參考:http://www.itdecent.cn/p/ff8444fbc773
http://www.itdecent.cn/p/555859783f63
http://blog.csdn.net/lmj623565791/article/details/53240600
http://www.itdecent.cn/p/ed7562a34af1
http://www.itdecent.cn/p/0244e431fb3c
一、app體積優(yōu)化的幾種方式
1.使用TinyPng來優(yōu)化png格式圖片大小,就是使用方法壓縮png圖片。
2.不包含透明像素的圖片,改為JPEG格式,這樣可以減少圖片的大小。
3.大圖拆分為小圖,大小也會減少。
4.使用九宮格圖片,也就是.9.png的圖片,android和ios都適用,對于比較大的圖片和使用范圍,可是形成無損拉伸效果。
5.使用IconFont,通過字體文件來構(gòu)建純色圖,不占圖片體積,通過加載字庫文件,來顯示純色圖片。阿里的字庫IOS接入第三發(fā)庫:github上的開源庫IconFont和阿里的FontAwesomeKit(這個是個很重要的趨勢,以后將特意寫篇文章來說IconFont)
6.本章將講解的webp格式圖片。谷歌2011年發(fā)布WebP,一直對它做了很多升級和改變,2014年WebP開始支持動圖。webp格式是圖片壓縮更加極致,比常用的jpg png格式小24%-35%,webp格式已經(jīng)支持大部分瀏覽器,Android 原生。
二、webp格式的原理
2.1 來源
WebP文件格式來源于VP8視頻編解碼(你可能更知道WebM)。VP8編解碼器的其中一個強大特性是幀內(nèi)預(yù)測壓縮,或者說,視頻的每一幀都被壓縮,后續(xù)幀與幀之間的差異也會被壓縮。這就是WebP的由來:WebM文件里單個被壓縮的幀。更精確的說WebP的核心來則WebM。
2.1 原理分析 —— 有損模式
WebP的有損模式是建立在與一張靜止的JPG競爭的基礎(chǔ)上,因此,你會注意到在對文件處理上有一些驚人相識。
*宏塊(MacroBlocking)
編碼器的第一個階段是將圖片分割成不同"宏塊"。典型的宏塊包括一個16x16的亮度像素塊,和兩個8x8的色度像素塊。這個階段非常像JPEG格式里轉(zhuǎn)換顏色空間,對色度通道降低采樣,以及細(xì)分圖片。

*預(yù)測
宏塊里每個4x4的子塊都有一個預(yù)測模型。(又名過濾)。在PNG里過濾用得非常多,它對每一行都做同樣的事,而WebP過濾的是每一塊。它是這樣處理的,在一個塊周圍定義兩組像素:有一行在它上面為A,在它左邊那一列為L。

利用A和L,編碼器會將它們放在一個4x4的測試像素塊填滿,并確定哪一個生成了最接近原始塊的值。這些用不同方法填滿的塊叫做"預(yù)測塊"。
Horiz prediction(水平預(yù)測)——將塊的每列使用左列(L)數(shù)據(jù)的副本進(jìn)行填充。
Vertical Prediction(垂直預(yù)測)——將塊的每行使用上列(A)數(shù)據(jù)的副本進(jìn)行填充。
DC Prediction(DC 預(yù)測)——將塊使用 A 上列的像素與 L 左列的像素的平均值進(jìn)行填充。
True Motion (TrueMotion 預(yù)測)——一種超級先進(jìn)的模式,我暫時不講。
值得注意的是,4x4的亮度塊還有另外6種模式,但你現(xiàn)在只需知道這些就好;)

基本流程是我們找到這個快最佳的預(yù)測塊,并導(dǎo)出過濾結(jié)果(剩余誤差),然后送到下個階段。
*JPGify it
WebP編碼的最后階段看起來非常像我們的老朋友JPG:
對塊里剩余的值執(zhí)行DCT過濾
DCT矩陣后量化
轉(zhuǎn)成量化矩陣后重新排序,然后送到一個靜態(tài)壓縮器里。

這有主要有兩點不同:
在DCT階段輸入的數(shù)據(jù)不是原始的數(shù)據(jù)塊本身,而是預(yù)測后的數(shù)據(jù)
WebP用得靜態(tài)壓縮器是算術(shù)壓縮器,它和JPG用的霍夫曼編碼器類似。
從最后的結(jié)果看WebP感覺有點像加強版的JPG。WebP的預(yù)測階段相比JPG是最大的優(yōu)勢,它減少了特殊顏色,使得在以后的處理階段能更有效的壓縮圖片數(shù)據(jù)。
WebP只是比JPG所有處理過程多了一個預(yù)測模式,在數(shù)據(jù)壓縮方面就把JPG干倒。
三、webp在Android中支持的情況
Webp在app中的使用一般包括兩個方面:
1.對與服務(wù)端交互過程中使用webp圖片
從Android 4.0 開始就對webp圖片進(jìn)行支持,直接使用就可以了。但是要注意:4.2.1+對webp格式的decode、encode是完全支持的(包含半透明的webp圖);對于4.0+ 到 4.2.1 ,只支持完全不透明的decode、encode的webp圖;4.0 以下,應(yīng)該是默認(rèn)不支持webp了。
Android 4.0之前的兼容可以通過官方的源碼庫(需要手動轉(zhuǎn)為so文件),也可以使用前人封裝好的庫:webp-android-backport和webp-android。
示例代碼:
InputStreamis= getAssets().open("weixin.webp");
Bitmapbitmap=null;if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
bitmap= WebPDecoder.getInstance().decodeWebP(streamToBytes(is));
}else{
bitmap= BitmapFactory.decodeStream(is);
}
imageView.setImageBitmap(bitmap);privatestaticbyte[]streamToBytes(InputStreamis) {
ByteArrayOutputStream os =newByteArrayOutputStream(1024);byte[] buffer =newbyte[1024];intlen;try{while((len =is.read(buffer)) >=0) {
os.write(buffer,0, len);
}
}catch(java.io.IOException e) {
}returnos.toByteArray();
}
2.應(yīng)用中的資源文件
4.0以上簡單的使用:
直接將png轉(zhuǎn)化為webp,放到res/drawable目錄
3.在4.0以下支持
public classMainActivityextendsAppCompatActivity {
private static final int[]LL=new int[]
{//
android.R.attr.src,//
};
@Override
protected voidonCreate(Bundle savedInstanceState) {
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){
LayoutInflaterCompat.setFactory(LayoutInflater.from(this),newLayoutInflaterFactory() {
@Override
publicView onCreateView(View parent, String name, Context context, AttributeSet attrs) {
AppCompatDelegate delegate = getDelegate();
View view = delegate.createView(parent, name, context, attrs);
if(viewinstanceofImageView) {
ImageView imageView = (ImageView) view;
TypedArray a = context.obtainStyledAttributes(attrs,LL);
intwebpSourceResourceID = a.getResourceId(0,0);
if(webpSourceResourceID ==0) {
returnview;
}
TypedValue value = new TypedValue();getResources().getValue(webpSourceResourceID, value, true);String resname = value.string.toString().substring(13,
value.string.toString().length());if (resname.endsWith(".webp")) {
InputStream rawImageStream = getResources().openRawResource(webpSourceResourceID);
byte[] data = streamToBytes(rawImageStream);
finalBitmap webpBitmap = WebPDecoder.getInstance().decodeWebP(data);
imageView.setImageBitmap(webpBitmap);
}
a.recycle();
}
returnview;
}
});
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
寫一個基類,所有的Activity都繼承此基類。大致邏輯為:對于4.2以下的版本,我們設(shè)置一個LayoutInflaterFactory,當(dāng)創(chuàng)建ImageView的時候,因為AppCompatActivity,ImageView的創(chuàng)建是由上述代碼中的delegate指向的對象完成的,我們通過傳入attrs,取出用戶聲明的src屬性,經(jīng)過一系列操作轉(zhuǎn)化為bitmap,最好設(shè)置到創(chuàng)建好的ImageView上。
四、webp在IOS中支持的情況
這里先說的一點xcode目前無法像Android Studio一樣支持webp作為資源,所以IOS目前無法將webp作為資源直接在IDE中支持,只能作為網(wǎng)絡(luò)圖片和H5圖片顯示在IOS程序中。
1.IOS本地中使用webp圖片
本地使用webp圖片,可以使用SDWebImage里面有個webP 框架,可以將webp-->NSData-->UIImage最后變?yōu)榭勺R別的圖片格式直接給控件調(diào)用。
但是需要額外需要‘webp’包
$pod 'SDWebImage/WebP'

#ifdef SD_WEBP
#import
// Fix for issue #416 Undefined symbols for architecture armv7 since WebP introduction when deploying to device
void WebPInitPremultiplyNEON(void);
void WebPInitUpsamplersNEON(void);
void VP8DspInitNEON(void);
@interface UIImage (WebP)
+ (UIImage *)sd_imageWithWebPData:(NSData *)data;
@end
#endif
2.IOS 網(wǎng)絡(luò)或webview使用webp格式圖片
webView 等網(wǎng)頁上如果用的是 webP 圖片,iOS 直接解析的話,會顯示?圖片。
思路 1:
A. 在網(wǎng)頁加載出后截取到HTML及內(nèi)部的JS后,調(diào)用JS預(yù)先準(zhǔn)備好的方法獲取需要轉(zhuǎn)碼的webP格式圖片下載地址(其實一個一個的遍歷也行).
B. 在App 本地開啟線程下載圖片,下載完成后,將圖片轉(zhuǎn)碼由 webP—> png—>Base64(因為實驗出直接用 png/jpg 的話 沒用)
C. 將 Base64及原圖片下載地址一一對應(yīng)調(diào)用JS準(zhǔn)備好的方法進(jìn)行替換
D. 將下載后的圖片進(jìn)行緩存,并進(jìn)行管理
注意點:
A. 圖片在最終顯示成功前會顯示成?,此處為了用戶體驗應(yīng)該采用占位圖片
B. 圖片顯示成功前應(yīng)該保持網(wǎng)頁布局不調(diào)整,需要由 JS 預(yù)先設(shè)置好布局
C. 圖片在本地的緩存需要管理
//在 `webView` 加載完 `HTML` 后,解析源碼,執(zhí)行相應(yīng)的 `JS` 方法
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
//獲取`HTML`代碼
NSString *lJs = @"document.documentElement.innerHTML";
NSString *str = [webView stringByEvaluatingJavaScriptFromString:lJs];
//執(zhí)行約定好的方法,獲取需要下載的 webp 圖片
NSString *imgs = [self.webView stringByEvaluatingJavaScriptFromString:@"YongChe.getAllWebPImg();"];
NSArray *array = [NSJSONSerialization JSONObjectWithData:[imgs dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
//此處,做示范,只轉(zhuǎn)換第一個,將圖片下載下來,并且轉(zhuǎn)為 PNG 后,再轉(zhuǎn)成 Base64,傳給 JS 腳本執(zhí)行
NSString *imgUrl = array.firstObject;
__weak typeof (self) weakSelf = self;
[SDWebImageCustomeDownLoad downloadWithURL:[NSURL URLWithString:imgUrl] progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSString *base = [NSString stringWithFormat:@"data:image/png;base64,%@",imgBase];
NSString *js = [NSString stringWithFormat:@"YongChe.replaceWebPImg('%@','%@')",imageURL,base];
[weakSelf.webView stringByEvaluatingJavaScriptFromString:js];
}];
}
+ (void)downloadWithURL:(NSURL *)url progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock
{
// [self sd_cancelCurrentImageLoad];
// objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (url) {
// __weak __typeof(self)wself = self;
id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:0 progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
// if (!wself) return;
dispatch_main_sync_safe(^{
// if (!wself) return;
if (image && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) { //沒有回調(diào),但是圖片下載完成了
} else { //image 下載失敗
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
//這一步,將這個 View 之前的下載操作全部取消,然后將這次的操作放進(jìn)去
// [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
} else {
dispatch_main_async_safe(^{
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
if (completedBlock) {
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
思路2:
采用webP的目的就是減少網(wǎng)絡(luò)傳輸數(shù)據(jù)量,加快請求整體速度,現(xiàn)在的問題就在于webView不識別webP這種格式而已,再想想 iOS內(nèi)的所有網(wǎng)絡(luò)請求/響應(yīng)我們都可以控制,那我們獲取到數(shù)據(jù)時,轉(zhuǎn)化成它識別的給它就可以了,而可以幫我們在拿到數(shù)據(jù)時進(jìn)行某些操作,操作完以后又正常進(jìn)行網(wǎng)絡(luò)流程的。這就需要用到NSURLProtocol。
NSURLProtocol是 iOS里面的URL Loading System的一部分,但是從它的名字來看,你絕對不會想到它會是一個對象,可是它偏偏是個對象。。。而且還是抽象對象(可是OC里面沒有抽象這一說)。平常我們做網(wǎng)絡(luò)相關(guān)的東西基本很少碰它,但是它的功能卻強大得要死。
*可以攔截UIWebView,基于系統(tǒng)的NSUIConnection或者NSUISession進(jìn)行封裝的網(wǎng)絡(luò)請求。
*忽略網(wǎng)絡(luò)請求,直接返回自定義的Response
*修改request(請求地址,認(rèn)證信息等等)
*返回數(shù)據(jù)攔截
*干你想干的。。。
對URL Loading System不清楚的,可以看看下面這張圖,看看里面有哪些類:

思路很簡單,就是攔截請求URL帶.png.jpg.gif的請求,首先去緩存里面取,有的話直接返回,沒有的去請求,并保存本地。
static NSString * const hasInitKey = @"JYCustomWebViewProtocolKey";
@interface JYCustomWebViewProtocol ()
@property (nonatomic, strong) NSMutableData *responseData;
@property (nonatomic, strong) NSURLConnection *connection;
@end
@implementation JYCustomWebViewProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
if ([request.URL.scheme isEqualToString:@"http"]) {
NSString *str = request.URL.path;
//只處理http請求的圖片
if (([str hasSuffix:@".png"] || [str hasSuffix:@".jpg"] || [str hasSuffix:@".gif"])
&& ![NSURLProtocol propertyForKey:hasInitKey inRequest:request]) {
return YES;
}
}
return NO;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
//這邊可用干你想干的事情。。更改地址,提取里面的請求內(nèi)容,或者設(shè)置里面的請求頭。。
return mutableReqeust;
}
- (void)startLoading
{
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//做下標(biāo)記,防止遞歸調(diào)用
[NSURLProtocol setProperty:@YES forKey:hasInitKey inRequest:mutableReqeust];
//查看本地是否已經(jīng)緩存了圖片
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
NSData *data = [[SDImageCache sharedImageCache] diskImageDataBySearchingAllPathsForKey:key];
if (data) {
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:mutableReqeust.URL
MIMEType:[NSData sd_contentTypeForImageData:data]
expectedContentLength:data.length
textEncodingName:nil];
[self.client URLProtocol:self
didReceiveResponse:response
cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocolDidFinishLoading:self];
}
else {
self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
}
- (void)stopLoading
{
[self.connection cancel];
}
#pragma mark- NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
#pragma mark - NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.responseData = [[NSMutableData alloc] init];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.responseData appendData:data];
[self.client URLProtocol:self didLoadData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
UIImage *cacheImage = [UIImage sd_imageWithData:self.responseData];
//利用SDWebImage提供的緩存進(jìn)行保存圖片
[[SDImageCache sharedImageCache] storeImage:cacheImage
recalculateFromImage:NO
imageData:self.responseData
forKey:[[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]
toDisk:YES];
[self.client URLProtocolDidFinishLoading:self];
}
注意點:
每次只能只有一個protocol進(jìn)行處理,如果有多個自定義protocol,系統(tǒng)將采取你registerClass的倒序進(jìn)行調(diào)用,一旦你需要對這個請求進(jìn)行處理,那么接下來的所有相關(guān)操作都需要這個protocol進(jìn)行管理。
一定要注意標(biāo)記請求,不然你會無限的循環(huán)下去。。。因為一旦你需要處理這個請求,那么系統(tǒng)會創(chuàng)建你這個protocol的實例,然后你自己又開啟了connection進(jìn)行請求的話,又會觸發(fā)URL Loading system的回調(diào)。系統(tǒng)給我們提供了+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;和+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;這兩個方法進(jìn)行標(biāo)記和區(qū)分。
五、總結(jié)
工具:jpeg/png可以轉(zhuǎn)換成為webp格式,官方轉(zhuǎn)換工具:webp轉(zhuǎn)換工具
在網(wǎng)頁端已經(jīng)大規(guī)模使用webp格式進(jìn)行傳輸、顯示了,但是在app端還有很多人在用png和jpg,webp是一個很好的可以減小app大小的圖片格式,在Android 端4.0之后才支持作為資源存在,在ios端無法作為資源存在,但是無論Android 和IOS端在webview 和網(wǎng)絡(luò)途徑上有方式來實現(xiàn)webp格式解析,對于網(wǎng)絡(luò)大圖需求多的app,webp格式是很好的,建議所有Android app換webp格式,IOS app看需求使用網(wǎng)絡(luò)顯示webp格式。