本篇內(nèi)容主要講解“如何優(yōu)化內(nèi)置圖網(wǎng)絡(luò)”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“如何優(yōu)化內(nèi)置圖網(wǎng)絡(luò)”吧!
公司主營業(yè)務(wù):成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、移動網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)推出嫩江免費(fèi)做網(wǎng)站回饋大家。
通過apk包結(jié)構(gòu)可以發(fā)現(xiàn),對于包大小優(yōu)化的主要手段都是集中在資源優(yōu)化方向 
經(jīng)過調(diào)研和總結(jié)可以分為以下四點(diǎn),本文也主要是針對這四點(diǎn)展開。
攔截圖片加載時(shí)機(jī)
圖片如何顯示
圖片下載和緩存
內(nèi)置圖片刪除

getDrawable
loadDrawable
Android系統(tǒng)view顯示圖片最終都是通過Resources類獲得圖片的drawable對象顯示。獲得drawable對象有兩個(gè)接口getDrawable、loadDrawable。getDrawable是一個(gè)公共接口,可以重載這個(gè)方法達(dá)到攔截,一般setBackground或者setImageDrawable會調(diào)用,loadDrawable方法系統(tǒng)View初始化獲取Drawable調(diào)用。
//Resource#getDrawable,
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
throws NotFoundException {
if(圖片是否需要網(wǎng)絡(luò)化){
return 網(wǎng)絡(luò)加載
} else {
//返回正常流程
return baseResources.getDrawable(id, theme);
}
}getDrawable比較好處理,但是loadDrawable方法是一個(gè)受保護(hù)方法,無法攔截。查看源碼loadDrawable之后流程也沒有找到可以hook的機(jī)會。一度以為攔截drawable很容易就可以實(shí)現(xiàn),最后沒想到在這個(gè)問題上花費(fèi)很多時(shí)間。查資料、看源碼最終找到一種方法!
因?yàn)閘oadDrawable這個(gè)方法只有xml配置的系統(tǒng)基礎(chǔ)view (如"ImageView、TextView、各種布局管理器等")的src和background屬性,在初始化view過程獲得drawable對象才會用到。所以影響的只是xml布局文件配置的view。那么通過實(shí)現(xiàn)LayoutInflater.Factory2,攔截xml View創(chuàng)建過程將xml 的view替換為我們自定義基礎(chǔ)view。在自定義view內(nèi)通過遍歷當(dāng)前Attr屬性判斷使用src或者background,然后調(diào)用相應(yīng)的setImageDrawable或者setBackground達(dá)到觸發(fā)Resournces#getDrawable接口完成hook。通過這種hook的方式可以達(dá)到我們對XML布局view設(shè)置drawable的攔截目的
class SkinTextView extend TextView {
public SkinTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setSkin(this,attrs);
}
private static final int[] ATTR_ARRAY = {
// 這個(gè)屬性是系統(tǒng)類View的屬性,對于APP領(lǐng)域是不可見的。
// 但是這個(gè)值是固定的,所以可以這樣寫,這里參考了RecycleView#NESTED_SCROLLING_ATTRS實(shí)現(xiàn)
16842964/* android.R.attr.background */,
android.R.attr.src
};
public static void setSkin(View view, AttributeSet attrs, DraweeHolderSupplier supplier){
Context context = view.getContext();
Resources resources = context.getResources();
TypedArray ta = context.obtainStyledAttributes(attrs, ATTR_ARRAY);
Drawable background ;
int drawableId ;
for (int i = 0; i < ATTR_ARRAY.length; i++) {
int attr = ATTR_ARRAY[i];
drawableId = ta.getResourceId(i,0);
if (drawableId == 0){
continue;
}
background = resources.getDrawable(drawableId,context.getTheme());
switch (attr) {
case 16842964:
view.setBackground(background);
break;
case android.R.attr.src:
if (view instanceof ImageView) {
((ImageView)view).setImageDrawable(background);
}
break;
}
}
ta.recycle();
}
}但是以上方案只能解決XML中系統(tǒng)基礎(chǔ)的View,如果XML中使用開發(fā)自定義View則不管用。為了解決自定義view的問題我想到了兩種解決方案。
通過字節(jié)碼修改方式將所有自定義view繼承的系統(tǒng)基礎(chǔ)view改為繼承我們自定義的基礎(chǔ)view
我通過asm字節(jié)碼修改將APP內(nèi)所有自定義view繼承的系統(tǒng)基礎(chǔ)view改為自定義基礎(chǔ)view,這個(gè)方案可行,但是缺點(diǎn)比較多需要全局修改所有庫的字節(jié)碼包括androidx庫AppCompatView,修改范圍太大,框架穩(wěn)定性不太容易保證,由于自定義基礎(chǔ)view有一些攔截代碼所以對view初始化性能也有一定影響,且ASM代碼編寫出現(xiàn)bug不易排查。如果只修改我們業(yè)務(wù)線的字節(jié)碼,可以正常運(yùn)行。但修改第三方aar字節(jié)碼后,遇到一個(gè)坑,應(yīng)用一直ANR期間沒找到具體原因。
hook LayoutInflater解析XML自定義view過程
// LayoutInflater#createViewFromTag
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
try {
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
// 以下onCreateView方法可以重載,拿到view對象強(qiáng)制觸發(fā)getDrawable即可,中間需要一些過濾。講一下大致思路,細(xì)節(jié)就不加了。
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
}這個(gè)方案可以將自定義view攔截,缺點(diǎn)就是依賴android系統(tǒng)版本,如果android系統(tǒng)這塊邏輯發(fā)生變化那么需要適配。不過對于后續(xù)需要使用Fresco框架加載圖片以及內(nèi)存管理,這個(gè)方案無法做到融合Fresco,所以該方案最終也沒有利用起來。
最終決定放棄對自定義view這種情況處理。通過遍歷xml 將自定義attr和自定義view過濾。 字節(jié)碼修改和自定義屬性、view過濾方案可以參考下圖。 
/**
* 自定義屬性、view過濾
* hook aapt打包過程,得到所有模塊res資源路徑,遍歷所有res/layout下的xml
*/
Pattern pattern = Pattern.compile("(?<=(android:(background|src)=\"@drawable/))([a-z_0-9]*)")
void eachLayoutXml(File[] resDirs){
resDirs.each {
if (it.isDirectory() && it.name == "res") {
eachLayoutXml(it.listFiles())
} else if (it.isDirectory() && it.name == "layout") {
it.listFiles().each { xml ->
// 獲得xml內(nèi)容,通過正則表達(dá)式匹配字符串
}
}
}
}當(dāng)時(shí)考慮過兩種下載圖片方案
圖片插件apk,將所有需要的圖片打包到apk,然后只下載一次插件,無需考慮圖片內(nèi)存問題
網(wǎng)絡(luò)直接下載圖片,通過Fresco管理內(nèi)存問題 
對比這兩種方案我選擇了實(shí)現(xiàn)比較容易的第二種。
這個(gè)問題比較好解決,view、drawable之間是通過Drawable.Callback進(jìn)行傳遞,所以下載圖片得到drawable對象后通過drawable callback#invalidateDrawable即可。當(dāng)然這里返回的drawable應(yīng)該是一個(gè)LayerDrawable,因?yàn)镈rawable.Callback執(zhí)行更新的Drawable必須是同一個(gè)Drawable對象,同時(shí)方便同步狀態(tài)下返回默認(rèn)圖,異步網(wǎng)絡(luò)圖返回后刷新。
需要注意一點(diǎn),這里不能直接使用Fresco RootDrawable對象返回,因?yàn)镕resco不支持view wrap_content屬性
因?yàn)樾枰玫紽resco,簡單介紹下。Fresco結(jié)構(gòu)分層可以分為三層,分別是圖層、控制器、圖片獲取,每一層結(jié)構(gòu)、功能如圖。
RootDrawable是最終返回的圖片Drawable對象
DataSources 返回圖片信息的訂閱源
Controler 圖片獲取和圖層顯示中間橋梁
第三層是圖片三級緩存,獲取圖片可以從緩存和網(wǎng)絡(luò)獲取

大致了解Fresco,下面描述內(nèi)置圖網(wǎng)絡(luò)化框架融合Fresco,使用Fresco進(jìn)行圖片下載和緩存。
這個(gè)實(shí)現(xiàn)可以類比Fresco的DraweeView的實(shí)現(xiàn),利用view的attach、detach、visible幾個(gè)生命周期函數(shù)通過DraweeHolder觸發(fā)drawble的加載和銷毀,做到對Fresco的圖片緩存和內(nèi)存釋放。具體過程不介紹了感興趣可以閱讀Fresco源碼。
實(shí)現(xiàn)流程如下圖。

經(jīng)過實(shí)際調(diào)研,不能直接刪除內(nèi)置圖,否則在打包過程進(jìn)行圖片鏈接的時(shí)候會拋出找不到資源錯誤,所以主要思路通過1像素圖片替換要刪除的圖片。
刪除內(nèi)置圖有以下幾種方案 
我選擇方案四,具體有以下優(yōu)點(diǎn)
方便根據(jù)圖片大小選擇批量刪除
可以直接計(jì)算得到優(yōu)化的包大小
可以直接融合到APP編譯過程,編譯一步到位
hook aapt資源打包過程moregeResources結(jié)束的時(shí)候
遍歷所有生成的圖片flat二進(jìn)制文件,將flat文件里png、webp、jpg二進(jìn)制數(shù)據(jù)替換為一像素的默認(rèn)圖
這個(gè)方案實(shí)現(xiàn)比較麻煩的是對flat文件二進(jìn)制流的讀取過程 
flat文件容器格式傳送門
到此,相信大家對“如何優(yōu)化內(nèi)置圖網(wǎng)絡(luò)”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
當(dāng)前題目:如何優(yōu)化內(nèi)置圖網(wǎng)絡(luò)
當(dāng)前URL:http://chinadenli.net/article12/ggpsdc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)建站、、網(wǎng)站設(shè)計(jì)公司、網(wǎng)站制作、關(guān)鍵詞優(yōu)化、自適應(yīng)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)