Android PNG图片像素检测及剪裁优化
PNG圖片是當(dāng)前移動(dòng)終端最主流的圖片格式之一,由于android中大部分圖片顏色數(shù)比較小而且尺寸也不大,所以在各類app軟件中PNG圖片幾乎是首選的圖片格式。在手Q中PNG圖片大概有四五千張。如此眾多的PNG圖片是android安裝包資源以及內(nèi)存占用的大頭消費(fèi)者。大家都知道,在android中,有一種特殊的PNG圖片形式 .9.png,俗稱點(diǎn)九圖,也叫九宮圖。在android平臺(tái)下使用點(diǎn)九PNG技術(shù),可以將圖片橫向和縱向同時(shí)進(jìn)行拉伸,以實(shí)現(xiàn)在多分辨率下的完美顯示效果。但是對這種點(diǎn)九圖,是否還存在優(yōu)化的空間呢?是否可以進(jìn)一步剪裁呢?因?yàn)楸旧砭涂梢岳鞌U(kuò)展,我們的圖片是否可以縮小到極致,另外對顏色區(qū)域比較規(guī)范的PNG圖片是否也可以轉(zhuǎn)換為點(diǎn)九圖?一方面可以減少安裝包的大小,同時(shí)在繪制展示圖片的時(shí)候,也可以節(jié)省系統(tǒng)內(nèi)存。基于這種思路,本文嘗試使用一種工具,來實(shí)現(xiàn)png圖片的優(yōu)化處理。
要進(jìn)行剪裁處理,首先我們需要了解下PNG圖片的文件格式。
?
?
PNG格式分類
PNG格式主要有8位、24位、32位三種形式,其中8位PNG支持兩種不同的透明形式(索引透明和alpha透明,索引透明僅支持全透明,alpha透明支持半透明),24位PNG不支持透明,32位PNG在24位基礎(chǔ)上增加了8位透明通道,因此可展現(xiàn)256級透明程度。
在android中,我們經(jīng)常用到的是png8和png32兩種格式。
索引透明的png8只有256色,通過一個(gè)值表明某個(gè)像素點(diǎn)是否透明。
Alpha透明的png8也有256色,但它可以指定某個(gè)像素點(diǎn)的透明度,支持半透明。但這種alpha信息在PS中讀取不了。
PNG8(Alpha)只有256色,它通過8位索引值在調(diào)色板中索引一個(gè)顏色表示,因此在顏色數(shù)方面比PNG32少很多,所以占用的空間會(huì)比PNG32小很多,而對于大多數(shù)的圖標(biāo)、按鈕和背景等常使用PNG格式的圖片來說,256色基本都可以勝任,因此在我們制作圖片資源的時(shí)候,如果圖片質(zhì)量可以接受盡量使用PNG8格式圖片。一些有損壓縮工具大多也是將小于256色的PNG32圖片壓縮為PNG8(alpha透明)。
PNG文件解析
PNG文件主要由一個(gè)文件標(biāo)示或文件頭域和至少3個(gè)數(shù)據(jù)塊組成。其中文件頭固定為以下格式用來識別PNG:89 50 4E 47 0D 0A 1A 0A(十六進(jìn)制)
第一個(gè)字節(jié)0x89超出了ASCII字符表示的范圍,可以避免某些軟件將PNG文件當(dāng)做文本文件來處理。4E 47 0D三個(gè)字節(jié)是“PNG”三個(gè)字母對應(yīng)的ASCII碼,后面的四個(gè)字節(jié)表示了不同格式的換行符等固定的標(biāo)識。
除了特定的文件頭外,后續(xù)是由一個(gè)個(gè)按照順序排列的PNG的數(shù)據(jù)塊(Chunk)。
PNG定義了兩種類型的數(shù)據(jù)塊:一種是PNG文件必須包含、讀寫軟件也都必須要支持的關(guān)鍵數(shù)據(jù)塊(critical chunk),關(guān)鍵數(shù)據(jù)塊定義了4個(gè)標(biāo)準(zhǔn)數(shù)據(jù)塊:IHDR, PLTE, IDAT, IEND;另一種叫做輔助數(shù)據(jù)塊(ancillary chunks),PNG允許軟件忽略它不認(rèn)識的附加塊。這種基于數(shù)據(jù)塊的設(shè)計(jì),允許PNG格式在擴(kuò)展時(shí)仍能保持與舊版本兼容。
PNG中數(shù)據(jù)塊的類別如下表,其中關(guān)鍵數(shù)據(jù)塊使用橙色背景區(qū)分:
| PNG文件格式中的數(shù)據(jù)塊 | ||||
| 數(shù)據(jù)塊符號 | 數(shù)據(jù)塊名稱 | 多數(shù)據(jù)塊 | 可選否 | 位置限制 |
| IHDR | 文件頭數(shù)據(jù)塊 | 否 | 否 | 第一塊 |
| cHRM | 基色和白色點(diǎn)數(shù)據(jù)塊 | 否 | 是 | 在PLTE和IDAT之前 |
| gAMA | 圖像Y的數(shù)據(jù)塊 | 否 | 是 | 在PLTE和IDAT之前 |
| sBIT | 樣本有效位數(shù)據(jù)塊 | 否 | 是 | 在PLTE和IDAT之前 |
| PLTE | 調(diào)色板數(shù)據(jù)塊 | 否 | 是 | 在IDAT之前 |
| bKGD | 背景顏色數(shù)據(jù)塊 | 否 | 是 | 在PLTE之后IDAT之前 |
| hIST | 圖像直方圖數(shù)據(jù)塊 | 否 | 是 | 在PLTE之后IDAT之前 |
| tRNS | 圖像透明數(shù)據(jù)塊 | 否 | 是 | 在PLTE之后IDAT之前 |
| oFFs | (專用公共數(shù)據(jù)塊) | 否 | 是 | 在IDAT之前 |
| pHYs | 物理像素尺寸數(shù)據(jù)塊 | 否 | 是 | 在IDAT之前 |
| SCAL | (專用公共數(shù)據(jù)塊) | 否 | 是 | 在IDAT之前 |
| IDAT | 圖像數(shù)據(jù)塊 | 是 | 否 | 與其他IDAT連續(xù) |
| tIME | 圖像最后修改時(shí)間數(shù)據(jù)塊 | 否 | 是 | 無限制 |
| tEXt | 文本信息數(shù)據(jù)塊 | 是 | 是 | 無限制 |
| zTXt | 壓縮文本數(shù)據(jù)塊 | 是 | 是 | 無限制 |
| dRAx | (專用公共數(shù)據(jù)塊) | 是 | 是 | 無限制 |
| gIFg | (專用公共數(shù)據(jù)塊) | 是 | 是 | 無限制 |
| gIFt | (專用公共數(shù)據(jù)塊) | 是 | 是 | 無限制 |
| gIFx | (專用公共數(shù)據(jù)塊) | 是 | 是 | 無限制 |
| IEND | 圖像結(jié)束數(shù)據(jù)塊 | 否 | 否 | 最后一個(gè)數(shù)據(jù)塊 |
| ? | ? | ? | ? | ? |
?
針對于每個(gè)數(shù)據(jù)塊,又主要由4部分組成:
| 單個(gè)數(shù)據(jù)塊結(jié)構(gòu) | ||
| 名稱 | 字節(jié)數(shù) | 說明 |
| 長度(Length) | 4字節(jié) | 制定數(shù)據(jù)塊中數(shù)據(jù)域的長度,不超過(231-1)字節(jié) |
| 數(shù)據(jù)塊類型碼(Chunk Type Code) | 4字節(jié) | 數(shù)據(jù)塊類型碼有ASCII字母(A-Z和a-z)組成,即上表的數(shù)據(jù)塊符號 |
| 數(shù)據(jù)塊數(shù)據(jù)(Chunk Data) | 可變長度 | 存儲(chǔ)按照Chunk Type Code 指定的數(shù)據(jù) |
| 循環(huán)冗余檢測(CRC) | 4字節(jié) | 存儲(chǔ)用來檢測是否有錯(cuò)誤的循環(huán)冗余 |
| ? | ? | ? |
CRC(cyclic redundancy check)域中的值是對Chunk Type Code域和Chunk Data域中的數(shù)據(jù)進(jìn)行計(jì)算得到的,具體算法可以參照ISO相關(guān)文檔。
由上面的表我們發(fā)現(xiàn)很多數(shù)據(jù)塊其實(shí)是我們不需要的,所以針對PNG圖片的很多壓縮工具,無損壓縮主要是處理這些非必須的數(shù)據(jù)塊來減少PNG的體積。下面我們了解下PNG的關(guān)鍵數(shù)據(jù)塊:
IHDR(文件頭數(shù)據(jù)塊)
該數(shù)據(jù)塊是第一個(gè)數(shù)據(jù)塊,而且必須是第一個(gè),并且僅能有一個(gè),它包含了PNG圖片的主要信息,主要包括了圖片的寬度和高度(4bytes表示),以及圖片的位深度,顏色類型,壓縮以及掃描方法等(1byte),該數(shù)據(jù)塊固定為13bytes長度,因此我們可以通過00 00 00 0D 49 48 44 52來定位該數(shù)據(jù)塊(前四位表示13個(gè)字節(jié)長度,后四位表示“IHDR”的ASCII碼)。
如上圖所示,圖片寬度為?00 00 00 36即54像素,圖像高度為:00 00 00 20 即32像素。08表示色深為8,28=256色位真彩色,06代表帶alpha通道 8位alpha,即圖片格式為PNG32,壓縮類型0000表示使用Deflate壓縮編碼壓縮圖像數(shù)據(jù);接著的00表示為將來使用更好的壓縮方法預(yù)留;然后是表示非隔行掃描00后面的59 ca 8b 5b為循環(huán)冗余檢測。
PLTE(調(diào)色板數(shù)據(jù)塊)50 4C 54 45
調(diào)色板數(shù)據(jù)塊包含有與索引彩色圖像相關(guān)的彩色變換數(shù)據(jù),它僅與索引彩色圖像有關(guān)(PNG8),該數(shù)據(jù)塊定義了圖像的調(diào)色板信息,可以包含1-256個(gè)調(diào)色板信息,每個(gè)調(diào)色板信息包含RGB顏色值。因此調(diào)色板長度必須是3的倍數(shù),否則就是非法的調(diào)色板。比如,我們有一個(gè)png8格式的圖片,在圖像數(shù)據(jù)塊中,顏色值都是一個(gè)個(gè)的索引值,當(dāng)解析和渲染圖片時(shí),我們可以通過這個(gè)索引值到PLTE調(diào)色板數(shù)據(jù)塊中查找到相應(yīng)的RGB顏色值。對應(yīng)關(guān)系如下圖所示:
對于索引圖像,必須要有調(diào)色板信息,調(diào)色板的顏色索引從0開始編號,顏色數(shù)不能超過色深中規(guī)定的顏色數(shù)(如色深為4,調(diào)色板顏色數(shù)不能查過24=16),否則導(dǎo)致圖片不合法。
如上圖所示,表明PLTE的長度為15, 50 4C 54 45 為PLTE標(biāo)示位,后面就是索引數(shù)據(jù)。一直到 00 00 00,后面的08 88 39 af為循環(huán)冗余檢測。
真彩色圖像和帶α通道數(shù)據(jù)的真彩色圖像也可以有調(diào)色板數(shù)據(jù)塊,目的是便于非真彩色顯示程序用它來量化圖像數(shù)據(jù),從而顯示該圖像。
在這里順便介紹下tRNS塊,這個(gè)塊可有可無,主要是看是否使用了透明色,該區(qū)塊可以理解為透明度的索引板,循環(huán)長度為調(diào)色盤的顏色數(shù),相當(dāng)于調(diào)色盤顏色表的一個(gè)對應(yīng)表,標(biāo)識該顏色是否透明,0xFF不透明,0x00透明。
IDAT(圖像數(shù)據(jù)塊)
圖像數(shù)據(jù)塊IDAT(Image Data Chunk):它存儲(chǔ)了實(shí)際的數(shù)據(jù),在PNG文件數(shù)據(jù)中可包含多個(gè)連續(xù)順序的圖像數(shù)據(jù)塊。
如上圖所示:?表明該圖片數(shù)據(jù)長度為130,其中49 44 41 54 為IDAT數(shù)據(jù)塊的標(biāo)示。后面跟著的就是IDAT存放著的圖像真正的數(shù)據(jù)信息,因此,如果能夠了解IDAT的結(jié)構(gòu),就可以很方便的生產(chǎn)PNG圖像。IDAT 區(qū)塊是經(jīng)過壓縮的,所以數(shù)據(jù)不可讀。
IEND(圖像結(jié)束數(shù)據(jù)塊)
圖像結(jié)束數(shù)據(jù)塊(Image Trailer Chunk):用來標(biāo)識PNG文件或者數(shù)據(jù)流的結(jié)束,并且必須要放在文件的尾部。
通常情況下,文件的結(jié)尾12個(gè)字符:00 00 00 00 49 45 4E 44 AE 42 60 82
由數(shù)據(jù)塊結(jié)構(gòu)定義可知:IEND數(shù)據(jù)塊長度是0(00 00 00 00),數(shù)據(jù)標(biāo)識IEND(49 45 4E 44),CRC碼(AE 42 60 82)。
PNG圖片剪裁優(yōu)化思路
其實(shí)點(diǎn)九圖也是PNG格式圖片,它是PNG圖片的子集,和PNG圖片相比,它只是在四周各增加了一行像素點(diǎn),增加的像素點(diǎn)只有黑色和透明兩種,黑色像素點(diǎn)標(biāo)明拉伸區(qū)域:最上面的一行像素點(diǎn)通過黑色區(qū)域標(biāo)示水平可拉伸區(qū)域,左邊的一列像素點(diǎn)表明垂直拉伸區(qū)域,兩者重合部分可以全拉伸。下邊和右邊的像素點(diǎn)標(biāo)示改圖片用于資源時(shí),其上的內(nèi)容的填充區(qū)域。
因此,既然存在拉伸區(qū)域,拉伸前像素點(diǎn)完全相同的若干行拉伸后的效果和單一行拉伸后的效果是一樣的,因此就可以裁剪為一行,這樣標(biāo)注起這一行可以拉伸,就可以將我們裁剪掉的像素點(diǎn)通過拉伸的方式填充進(jìn)來,這也是我們裁剪點(diǎn)9圖圖片的主要思路。
同樣,對于PNG圖片,我們可以通過相同的思路,逐行掃描像素點(diǎn)完全相同的行和列,然后只保留一行(列),然后,對PNG圖片四周各添加一行像素值,對保留的行和列通過描點(diǎn)標(biāo)示可以拉伸,填充區(qū)域由于之前是PNG,所以我們設(shè)置為全部行和列都可以填充,最后修改圖片的后綴為.9.png,這樣就可以將PNG圖片轉(zhuǎn)換為點(diǎn)9圖。
Png剪裁實(shí)現(xiàn)
在處理PNG圖片時(shí),我們需要逐行掃描出該圖片中哪些行像素點(diǎn)是相同的,秉承不重復(fù)造輪子的原則,我們選用了一個(gè)第三方的庫。針對png處理的庫有很多,這里我們選擇了完全使用JAVA實(shí)現(xiàn)的pngj庫來實(shí)現(xiàn)PNG圖片的編解碼。該庫只是個(gè)很小的處理單元,不涉及到相關(guān)的壓縮處理,當(dāng)然也沒有庫依賴。因?yàn)楣δ軉我?#xff0c;所以處理速度也比較理想。最主要的是它能夠?qū)崿F(xiàn)逐行讀取圖片的原生數(shù)據(jù)信息。所以可以允許我們針對每個(gè)像素點(diǎn)做處理工作。下面是該庫的一些介紹:
??Very efficient in memory usage and speed.
??Pure Java (requires 5 or greater).
??Small and self contained. No dependencies on third party libraries,
??Allows to read/write progressively, row by row. This means that you can process huge images without needing to load them fully in memory.
??Reads and writes all PNG color models (true color, gray, palette, all bit depths: 1-2-4-8-16, with-without alpha). Interlaced PNG is supported (though not welcomed) for reading.
??Full support for metadata ("chunks") handling.
??The format of the pixel data (read and write) is extensible (since 2.0) and very efficient (no double copies).
??Supports (since 2.0) asyncronous reading and low level tweaking and extension in the reader.
??Note that this is a relatively low-level library. It does not provide any high-level image processing (eg, resizing, colour conversions), nor tries to abstract the concrete image format (as BufferedImage does, for example). In particular, the default format of the scanlines (as wrapped inImageLineInt or ImageLineByte) is not abstract, the meaning of the values depends on the image color model and bitdepth.
實(shí)現(xiàn)png剪裁,首先,我們需要設(shè)定一個(gè)掃描目錄,和一個(gè)輸出目錄,掃描目錄可以選擇android工程的drawable文件目錄。
1,首先需要先掃描待裁剪的png圖片。看下是否存在剪裁空間。優(yōu)化比例是否值得我們做剪裁操作:先初始化PngReader:
PngReader?pngr =?null;
try{
pngr =?new?PngReader(file);
}catch(PngjInputException e){
System.err.println(filename +?" read error");
return?null;
}
pngReader會(huì)解析圖片文件的相關(guān)結(jié)構(gòu),獲取到圖片的格式,channel,位深度,alpha,寬高等圖片基本信息存儲(chǔ)到ImgInfo中。獲取這些數(shù)據(jù)可以計(jì)算出原始圖片面積,原始圖片的基本信息等屬性。通過函數(shù)
line =?(ImageLineInt) pngr.readRow()).getScanline()
逐行掃描圖片的每一行數(shù)據(jù),然后將數(shù)據(jù)存儲(chǔ)到一個(gè)int[]數(shù)組中:
int[]?lineI?= Arrays.copyOf(line,?pngr.imgInfo.cols?* channels);
這個(gè)lineI數(shù)組,存儲(chǔ)著每個(gè)像素點(diǎn)的數(shù)據(jù),如果為png8,那么每一個(gè)int保存一個(gè)像素點(diǎn)數(shù)據(jù),如果為png32,每四個(gè)int存儲(chǔ)一個(gè)像素點(diǎn),分別代表RGBA數(shù)據(jù)。這樣可以再接著掃描第二行..第n行,獲取所有像素?cái)?shù)據(jù)。因此現(xiàn)在的問題就變成了從一個(gè)類二維數(shù)組中查詢出所有連續(xù)相同的列和連續(xù)相同的行的問題,這是個(gè)純數(shù)學(xué)的算法問題,這里不再詳述。需要注意的是:因?yàn)?9.png四個(gè)邊各有一個(gè)像素點(diǎn)的擴(kuò)展區(qū)域標(biāo)示, 表示可拉伸區(qū)域和可填充區(qū)域。所以在掃描時(shí)比對像素點(diǎn)是否相同需要去除這個(gè)區(qū)域,每行比較時(shí)去除第一和最后一個(gè)像素點(diǎn),掃描開始于第二行,結(jié)束于n-1行。
通過一輪掃描,我們可以獲取到圖片可剪裁的優(yōu)化區(qū)域:連續(xù)相同的列可以合并成一列,連續(xù)相同的行可以合并成一行。然后針對合并后保留的那一行(列),標(biāo)示為可拉伸的區(qū)域。因?yàn)樵嫉?9.png也存在拉伸區(qū)域,我們這次掃描出的拉伸區(qū)域要和原來.9.png的圖片拉伸區(qū)域進(jìn)行一次對比,如果兩者沒有重合·,就忽略處理(防止出現(xiàn)多條間斷的拉伸區(qū)間)。如果重合就使用兩者并集。
2,掃描完數(shù)據(jù)后,為了便于觀察結(jié)果,會(huì)將每一張圖片的可優(yōu)化區(qū)域通過一個(gè)excel表的形式進(jìn)行輸出,包含:圖片原始大小,圖片可裁剪行列的起止位置。圖片可優(yōu)化面積,可裁剪的比例等信息。
3,裁剪:需要將連續(xù)相同的行(列)去除,只保留一行(列)。去除之后,還需要標(biāo)示拉伸區(qū)域,如果為png還需要轉(zhuǎn)換為.9.png。
針對邊緣描邊:可拉伸區(qū)域?yàn)楹谏2豢衫靺^(qū)域?yàn)橥该鳌5轻槍ng8格式的png圖片,圖片中只保存了各個(gè)像素點(diǎn)的索引值,而不是具體的RGB數(shù)值。所以針對這種格式的png圖片我們沒法確認(rèn)黑色值和透明值具體的索引是多少,所以這種格式的圖片只是提供裁剪優(yōu)化指引,而沒有進(jìn)行裁剪處理。
針對.9.png。我們在第一輪掃描第一行的時(shí)候,可以利用這個(gè)時(shí)機(jī)記錄下兩個(gè)值:第一行第一個(gè)像素點(diǎn)肯定是透明的,后續(xù)我們填充不可拉伸區(qū)域就用這個(gè)值。第一行里和第一個(gè)像素點(diǎn)不同的像素點(diǎn)肯定是記錄了黑色的RGBA或者索引值,我們可以根據(jù)這個(gè)值來標(biāo)示我們剪裁后圖像的可拉伸區(qū)域。針對png32的png圖片,透明值為:255.255.255.0,黑色為:0.0.0.255.
4,輸出新的圖片
確定了剪裁區(qū)域,我們可以把我們新圖的一些屬性信息通過新建一個(gè)ImageInfo存儲(chǔ)下來。然后新建pngWriter:
PngWriter?pngw =?new?PngWriter(outFile, newInfo,?true);
//如果是索引圖片,需要拷貝全部的Chunk信息,主要是需要包含索引板
if?(info.channels?== 1 && info.indexed) {?
isPalettedImage =?true;
pngw.copyChunksFrom(pngr.getChunksList(), ChunkCopyBehaviour.COPY_ALL);
}?else?{
isPalettedImage =?false;
pngw.copyChunksFrom(pngr.getChunksList(),?ChunkCopyBehaviour.COPY_ALL_SAFE);
}
?
讀取原始圖片每一行的數(shù)據(jù)
ImageLineInt?line= (ImageLineInt)pngr.readRow();
int[]?oldline =?line.getScanline();
按前面的規(guī)則修改:(邊界的話,重新確立可拉伸區(qū)域,內(nèi)容,刪除重復(fù)的列所對應(yīng)的像素點(diǎn),只保留一列)
ImageLineInt?lineInt =?new?ImageLineInt(newInfo, firstPixs);
pngw.writeRow(lineInt);
這樣完成圖片所有數(shù)據(jù)的更改,生成新圖,輸出到目錄中。針對png32格式的png圖片,我們需要新增四個(gè)邊界處的像素點(diǎn),并修改為.9.png格式為圖片文件。
目前該小工具已經(jīng)接入手Q中的自動(dòng)化工具,可以針對手Q中的圖片資源進(jìn)行掃描,發(fā)現(xiàn)可以優(yōu)化的圖片時(shí),會(huì)自動(dòng)提單給相應(yīng)的開發(fā)。由于這種方式的修改圖片粒度比較低,所以,在應(yīng)用到手Q中時(shí),我們針對處理進(jìn)行了一次過濾:設(shè)定掃描圖片面積閾值為50*50像素點(diǎn),單行可優(yōu)化像素點(diǎn)最低為50,優(yōu)化比例為大于45%。剔除比較小的優(yōu)化點(diǎn)。
如下圖展示了輸出的可裁剪圖片的匯總表格:
針對單張圖片優(yōu)化前后的對比如下:
優(yōu)化前:優(yōu)化后:
?
該png剪裁工具可以高效率實(shí)現(xiàn)掃描png圖片是否存在剪裁壓縮空間的目的,并且可以根據(jù)掃描出的優(yōu)化比例自動(dòng)修正圖片,輸出更節(jié)省空間和內(nèi)存的新圖片。并支持轉(zhuǎn)換png圖片為.9.png的功能。但同時(shí)也存在一定的不足:比如一些特殊的png圖片在特定環(huán)境下不需要剪裁; 剪裁的選擇區(qū)域有時(shí)候不夠智能, png圖片同時(shí)存在行和列的優(yōu)化區(qū)域存才可轉(zhuǎn)換成點(diǎn)9; png8格式的png圖片,使用索引值表征像素點(diǎn)數(shù)據(jù)所以只能給出裁剪建議,而沒有生成裁剪后的新圖等問題。
轉(zhuǎn):http://mp.weixin.qq.com/s?__biz=MzA4MDc5OTg5MA==&mid=401181863&idx=2&sn=e3562efa052c9622d5140d52c4fc25d5&scene=23&srcid=1217QFsx12bDGwGN6dj6kZw2#rd
轉(zhuǎn)載于:https://www.cnblogs.com/tmlee/p/5054931.html
總結(jié)
以上是生活随笔為你收集整理的Android PNG图片像素检测及剪裁优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle调优总结--1(经典实践 重
- 下一篇: Android多屏幕适配之字体大小、行间