Android面試題--View相關(guān)

ListView是如何進(jìn)行優(yōu)化的?

  1. Item布局層級(jí)越少越好郊供,使用hierarchyviewer工具查看
  2. 復(fù)用convertView和使用ViewHolder
  3. Item中有圖片時(shí)異步加載
  4. 快速滑動(dòng)時(shí)不加載圖片
  5. Item中有圖片時(shí),對(duì)圖片進(jìn)行適當(dāng)壓縮
  6. 列表數(shù)據(jù)分頁加載

實(shí)現(xiàn)自定義View的基本流程

  1. 自定義View的屬性碧囊,編寫attr.xml文件
  2. 在layout布局文件中引用蚓土,同時(shí)引用命名空間
  3. 在View的構(gòu)造方法中獲取自定義屬性(構(gòu)造方法中拿到attr.xml文件值)
  4. 重寫onMeasure
  5. 重寫onDraw

Android中Touch事件的傳遞機(jī)制

  1. Touch 事件傳遞的相關(guān) API 有 dispatchTouchEvent肖粮、onTouchEvent革半、onInterceptTouchEvent
  2. Touch 事件相關(guān)的類有 View砂蔽、ViewGroup洼怔、Activity
  3. Touch 事件會(huì)被封裝成 MotionEvent 對(duì)象,該對(duì)象封裝了手勢按下左驾、移動(dòng)镣隶、松開等動(dòng)作
  4. Touch 事件通常從 Activity#dispatchTouchEvent 發(fā)出,只要沒有被消費(fèi)诡右,會(huì)一直往下傳遞安岂,
    到最底層的 View
  5. 如果 Touch 事件傳遞到的每個(gè) View 都不消費(fèi)事件,那么 Touch 事件會(huì)反向向上傳遞稻爬,最
    終交由 Activity#onTouchEvent 處理.
  6. onInterceptTouchEvent 為 ViewGroup 特有嗜闻,可以攔截事件.
  7. Down 事件到來時(shí),如果一個(gè) View 沒有消費(fèi)該事件桅锄,那么后續(xù)的 MOVE/UP 事件都不會(huì)再
    給它

ViewPager

ViewPager 可視為一個(gè)可以實(shí)現(xiàn)一種卡片式左右滑動(dòng)的 View 容器琉雳,使用該類類似于 ListView样眠,需要用到自定義的適配器 PageAdapter,區(qū)別在于每次要獲取一個(gè) view 的方式翠肘,ViewPager 準(zhǔn)確的說應(yīng)該是一個(gè)ViewGroup檐束。PagerAdapter 是 ViewPager 的支持者,ViewPager 調(diào)用它來獲取所需顯示的頁面束倍,而 PagerAdapter 也會(huì)在數(shù)據(jù)變化時(shí)被丧,通知 ViewPager,這個(gè)類也是 FragmentPagerAdapter和 FragmentStatePagerAdapter 的基類绪妹。

實(shí)現(xiàn)PagerAdapter需要重寫的方法

  1. getCount():獲得 ViewPager 中有多少個(gè) view甥桂;
  2. destroyItem(viewGroup, interesting, object):移除一個(gè)給定位置的頁面;
  3. instantiateItem(viewGroup, int):將給定位置的 view 添加到 viewgroup(容器中),創(chuàng)建并
    顯示出來邮旷,返回一個(gè)代表新增頁面的 Object(key)黄选,key 和每個(gè) view 要有一一對(duì)應(yīng)的關(guān)系;
  4. isViewFromObject():判斷 instantiateItem(viewGroup, int)所返回的 key 和每一個(gè)頁面視圖是
    否是代表的同一個(gè)視圖婶肩;

FragmentPageAdapter 和 FragmentStatePagerAdapter 的區(qū)別

二者都繼承PagerAdapter办陷。FragmentPagerAdapter的每個(gè)Fragment會(huì)持久的保存在Fragment Manager中,只要用戶可以返回到頁面中律歼,它都不會(huì)被銷毀民镜;FragmentStatePagerAdapter當(dāng)頁面不可見時(shí),該Fragment就會(huì)被銷毀险毁,只保留Fragment的狀態(tài)制圈。所以FragmentPagerAdapter用在Fragment比較少的情況,F(xiàn)ragmentStatePagerAdapter用在Fragment很多的情況下辱揭。

Android 中頁面的橫屏與豎屏操作

在配置文件中為 Activity 設(shè)置屬性 android:screenOrientation="landscape(橫屏)|portrait(豎屏)"离唐;在代碼中設(shè)置:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);setR
equestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)问窃;為防止切換后重新啟動(dòng)當(dāng)前 Activity亥鬓,應(yīng)在配置文件中添加 android:configChanges=”keyboardHidden|orientation”屬性,并在 Activity 中重寫 onConfigurationChanged()方法域庇。

獲取手機(jī)中屏幕的寬和高的方法

通過 Context 的 getWindowManager()方法獲得 WindowManager 對(duì)象嵌戈,再從 WindowManager對(duì)象中通過 getDefaultDisplay()獲得 Display 對(duì)象,再從 Display 對(duì)象中獲得屏幕的寬和高听皿。

Android 屬性動(dòng)畫實(shí)現(xiàn)原理

工作原理:在一定時(shí)間間隔內(nèi)熟呛,通過不斷對(duì)值進(jìn)行改變,并不斷將該值賦給對(duì)象的屬性尉姨,從而實(shí)現(xiàn)該對(duì)象在該屬性上的動(dòng)畫效果庵朝。

  1. ValueAnimator:通過不斷控制值的變化(初始值->結(jié)束值),將值手動(dòng)賦值給對(duì)象的屬性,再不斷調(diào)用 View 的 invalidate()方法九府,去不斷 onDraw()重繪 View椎瘟,達(dá)到動(dòng)畫的效果。
    主要的三種方法:
    1. ValueAnimator.ofInt(int values):估值器是整型估值器 IntEaluator
    2. ValueAnimator.ofFloat(float values): 估值器是浮點(diǎn)型估值器 FloatEaluator
    3. ValueAnimator.ofObject(ObjectEvaluator, start, end):將初始值以對(duì)象的形式過渡到結(jié)束值侄旬,通過操作對(duì)象實(shí)現(xiàn)動(dòng)畫效果肺蔚,需要實(shí)現(xiàn) Interpolator 接口,自定義估值器儡羔。

估值器 TypeEvalutor宣羊,設(shè)置動(dòng)畫如何從初始值過渡到結(jié)束值的邏輯。插值器(Interpolator)決定值的變化模式(勻速汰蜘、加速等)仇冯;估值器(TypeEvalutor)決定值的具體變化數(shù)值。

//自定義估值器族操,需要實(shí)現(xiàn) TypeEvaluator 接口
public class ObjectEvaluator implements TypeEvaluator {
     //復(fù)寫 evaluate()赞枕,在 evaluate()里寫入對(duì)象動(dòng)畫過渡的邏輯
     @Override 
     public Object evaluate(float fraction, Object startValue, Object endValue) {
     //參數(shù)說明
     //fraction:表示動(dòng)畫完成度(根據(jù)它來計(jì)算當(dāng)前動(dòng)畫的值)
     //startValue、endValue:動(dòng)畫的初始值和結(jié)束值...
     //寫入對(duì)象動(dòng)畫過渡的邏輯
     return value;
     //返回對(duì)象動(dòng)畫過渡的邏輯計(jì)算后的值
}
  1. ObjectAnimator:直接對(duì)對(duì)象的屬性值進(jìn)行改變操作坪创,從而實(shí)現(xiàn)動(dòng)畫效果。ObjectAnimator 繼承自 ValueAnimator 類姐赡,底層的動(dòng)畫實(shí)現(xiàn)機(jī)制還是基本值的改變莱预。它是不斷控制值的變化,再不斷自動(dòng)賦給對(duì)象的屬性项滑,從而實(shí)現(xiàn)動(dòng)畫效果依沮。這里的自動(dòng)賦值,是通過調(diào)用對(duì)象屬性的 set/get 方法進(jìn)行自動(dòng)賦值枪狂,屬性動(dòng)畫初始值如果有就直接取危喉,沒有則調(diào)用屬性的 get()方法獲取,當(dāng)值更新變化時(shí)州疾,通過屬性的 set()方法進(jìn)行賦值辜限。 每次賦值都是調(diào)用 view 的 postInvalidate()/invalidate()方法不斷刷新視圖(實(shí)際調(diào)用了 onDraw()方法進(jìn)行了重繪視圖)。
    //Object 需要操作的對(duì)象严蓖;propertyName 需要操作的對(duì)象的屬性薄嫡;values 動(dòng)畫初始值&結(jié)束
    值。如果是兩個(gè)值颗胡,則從 a->b 值過渡毫深,如果是三值,則從 a->b->c
    ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String propertyName, float ...values);如果采用 ObjectAnimator 類實(shí)現(xiàn)動(dòng)畫毒姨,操作的對(duì)象的屬性必須有 get()和 set()方法哑蔫。

其他用法:

  1. AnimatorSet 組合動(dòng)畫
    AnimatorSet.play(Animator anim):播放當(dāng)前動(dòng)畫
    AnimatorSet.after(long delay):將現(xiàn)有動(dòng)畫延遲 x 毫秒后執(zhí)行
    AnimatorSet.with(Animator anim):將現(xiàn)有動(dòng)畫和傳入的動(dòng)畫同時(shí)執(zhí)行
    AnimatorSet.after(Animator anim):將現(xiàn)有動(dòng)畫插入到傳入的動(dòng)畫之后執(zhí)行
    AnimatorSet.before(Animator anim):將現(xiàn)有動(dòng)畫插入到傳入的動(dòng)畫之前執(zhí)行
  2. ViewPropertyAnimator 直接對(duì)屬性操作,View.animate()返回的是一個(gè) ViewPropertyAnimat
    or 對(duì)象,之后的調(diào)用方法都是基于該對(duì)象的操作闸迷,調(diào)用每個(gè)方法返回值都是它自身的實(shí)例
    View.animate().alpha(0f).x(500).y(500).setDuration(500).setInterpolator()
  3. 設(shè)置動(dòng)畫監(jiān)聽
Animation.addListener(new AnimatorListener() {
     @Override
     public void onAnimationStart(Animation animation) {
     //動(dòng)畫開始時(shí)執(zhí)行
     }

     @Override
     public void onAnimationRepeat(Animation animation) {
     //動(dòng)畫重復(fù)時(shí)執(zhí)行
     }

     @Override
     public void onAnimationCancel(Animation animation) {
     //動(dòng)畫取消時(shí)執(zhí)行
     }

     @Override
     public void onAnimationEnd(Animation animation) {
     //動(dòng)畫結(jié)束時(shí)執(zhí)行
     }
});

Android View 動(dòng)畫(補(bǔ)間動(dòng)畫)實(shí)現(xiàn)原理

主要有四種 AlpahAnimation\ScaleAnimation\RotateAnimation\TranslateAnimation 四種嵌纲,對(duì)透明度、縮放稿黍、旋轉(zhuǎn)疹瘦、位移四種動(dòng)畫。在調(diào)用 View.startAnimation 時(shí)巡球,先調(diào)用 View.setAnimation(Animation)方法給自己設(shè)置一個(gè) Animation 對(duì)象言沐,再調(diào)用 invalidate()來重繪自己。在 View.draw(Canvas, ViewGroup, long)方法中進(jìn)行了 getAnimation()酣栈,并調(diào)用了 drawAnimation(ViewGroup, long, Animation, boolean)方法险胰,此方法調(diào)用 Animation.getTranformation()方法,再調(diào)用 applyTranformation()方法矿筝,該方法中主要是對(duì) Transformation.getMatrix().setTranslate/setRotate/setAlpha/setScale 來設(shè)置相應(yīng)的值起便,這個(gè)方法系統(tǒng)會(huì)以 60FPS 的頻率進(jìn)行調(diào)用。具體是在調(diào) Animation.start()方法中會(huì)調(diào)用 animationHandler.start()方法窖维,從而調(diào)用了 scheduleAnimation()方法榆综,這里會(huì)調(diào)用mChoreographer.postCallback(Choregrapher.CALLBACK_ANIMATION, this, null)放入事件隊(duì)列中,等待 doFrame()來消耗事件铸史。當(dāng)一個(gè) ChildView 要重畫時(shí)鼻疮,它會(huì)調(diào)用其成員函數(shù) invalidate()函數(shù)將通知其 ParentView 這個(gè)ChildView 要重畫,這個(gè)過程一直向上遍歷到 ViewRoot琳轿,當(dāng) ViewRoot 收到這個(gè)通知后就會(huì)調(diào)用 ViewRoot 中的 draw 函數(shù)從而完成繪制判沟。View.onDraw()有一個(gè)畫布參數(shù) Canvas,畫布顧名思義就是畫東西的地方崭篡,Android 會(huì)為每一個(gè) View 設(shè)置好畫布挪哄,View 就可以調(diào)用 Canvas的方法,比如:drawText()琉闪、drawBitmap()迹炼、drawPath() 等等去畫內(nèi)容。每一個(gè) ChildView 的畫布是由其 ParentView 設(shè)置的颠毙,ParentView 根據(jù) ChildView 在其內(nèi)部的布局來調(diào)整 Canvas疗涉,其中畫布的屬性之一就是定義和 ChildView 相關(guān)的坐標(biāo)系,默認(rèn)是橫軸為 X 軸吟秩,從左至右咱扣,值逐漸增大,豎軸為 Y 軸涵防,從上至下闹伪,值逐漸增大沪铭。

Android 補(bǔ)間動(dòng)畫就是通過 ParentView 來不斷調(diào)整 ChildView 的畫布坐標(biāo)系來實(shí)現(xiàn)的,在 ParentView 的 dispatchDraw()方法會(huì)被調(diào)用偏瓤。

dispatchDraw() {
       ....
       Animation a = ChildView.getAnimation();
       Transformation tm = a.getTransformation();
       Use tm to set ChildView's Canvas; Invalidate();
       ....
}

這里有兩個(gè)類:Animation 和 Transformation杀怠,這兩個(gè)類是實(shí)現(xiàn)動(dòng)畫的主要的類,Animation中主要定義了動(dòng)畫的一些屬性比如開始時(shí)間厅克、持續(xù)時(shí)間赔退、是否重復(fù)播放等,這個(gè)類主要有兩個(gè)重要的函數(shù):getTransformation()和 applyTransformation()证舟,在 getTransformation()中 Animation 會(huì)根據(jù)動(dòng)畫的屬性來產(chǎn)生一系列的差值點(diǎn)硕旗,然后將這些差值點(diǎn)傳給 applyTransformation(),這個(gè)函數(shù)將根據(jù)這些點(diǎn)來生成不同的 Transformation女责,Transformation 中包含一個(gè)矩陣和alpha 值漆枚,矩陣是用來做平移、旋轉(zhuǎn)和縮放動(dòng)畫的抵知,而 alpha 值是用來做 alpha 動(dòng)畫的(簡單理解的話墙基,alpha 動(dòng)畫相當(dāng)于不斷變換透明度或顏色來實(shí)現(xiàn)動(dòng)畫),調(diào)用 dispatchDraw()時(shí)會(huì)調(diào)用 getTransformation()來得到當(dāng)前的 Transformation刷喜。某一個(gè) View 的動(dòng)畫的繪制并不是由他自己完成的而是由它的父 View 完成残制。1)補(bǔ)間動(dòng)畫 TranslateAnimation,View 位置移動(dòng)了掖疮,
可是點(diǎn)擊區(qū)域還在原來的位置痘拆,為什么?View 在做動(dòng)畫是根據(jù)動(dòng)畫時(shí)間的插值計(jì)算出一個(gè)Matrix氮墨,不停的 invalidate(),在 onDraw()中的 Canvas 上使用這個(gè)計(jì)算出來的 Matrix 去 draw View 的內(nèi)容吐葵。某個(gè) View 的動(dòng)畫繪制并不是由它自己完成规揪,而是由它的父 View 完成,使它的父 View 畫布進(jìn)行了移動(dòng)温峭,而點(diǎn)擊時(shí)還是點(diǎn)擊原來的畫布猛铅。使得它看起來變化了。

requestLayout()凤藏、onLayout()奸忽、onDraw()、drawChild()區(qū)別與聯(lián)系

  • requestLayout():會(huì)導(dǎo)致調(diào)用 measure()過程和 layout()過程揖庄。說明:只是對(duì) View 樹重新布局layout 過程包括 measure()和 layout()過程栗菜,如果 View 的 l,t,r,b 沒有必變,那就不會(huì)觸發(fā)onDraw蹄梢;但是如果這次刷新是在動(dòng)畫里疙筹,mDirty 非空,就會(huì)導(dǎo)致 onDraw。
  • onLayout():如果該 View 是 ViewGroup 對(duì)象而咆,需要實(shí)現(xiàn)該方法霍比,對(duì)每個(gè)子視圖進(jìn)行布局
  • onDraw():繪制視圖本身(每個(gè) View 都需要重載該方法,ViewGroup 不需要實(shí)現(xiàn)該方法)
  • drawChild():去重新回調(diào)每個(gè)子視圖的 draw()方法

自定義 View 處理寬度問題

在 onMeasure()的 getDefaultSize()的默認(rèn)實(shí)現(xiàn)中暴备,當(dāng) View 的測量模式是 AT_MOST 或 EXACTLY 時(shí)悠瞬,View 的大小都會(huì)被設(shè)置成子 View MeasureSpec 的 specSize。子 View 的 MeasureSpec值是根據(jù)子 View 的布局參數(shù)和父容器的 MeasureSpec 值計(jì)算得來涯捻。當(dāng)子 View 的布局參數(shù)是wrap_content 時(shí)浅妆,對(duì)應(yīng)的測量模式是 AT_MOST,大小是 parentSize汰瘫。

invalidate()和 postInvalidate()的區(qū)別及使用

View.invalidate():層層上傳到父級(jí)狂打,直到傳遞到 ViewRootImpl 后觸發(fā)了 scheduleTraversals(),然后整個(gè) View 樹開始重新按照 View 繪制流程進(jìn)行重繪任務(wù)混弥。invalidate():在 UI 線程刷新 View趴乡;postInvalidate():在工作線程刷新 View(底層還是 handler)其實(shí)它的原理就是 invalidate+handler View.postInvalidate()最終會(huì)調(diào)用 ViewRootImpl.dispatchInvalidateDelayed()方法

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
}

這里的 mHandler 是 ViewRootHandler 實(shí)例,在該 Handler 的 handleMessage 方法中調(diào)用了 View.invalidate()方法蝗拿。

case MSG_INVALIDATE:
       ((View) msg.obj).invalidate();
       break;

requestLayout()和invalidate()的區(qū)別

  • requestLayout()會(huì)標(biāo)記PFLAG_FORCE_LAYOUT晾捏,然后一層層往上調(diào)用父布局的requestLayout方法并標(biāo)記PFLAG_FORCE_LAYOUT,最后調(diào)用ViewRootImpl中的requestLayout方法開始View的三大流程哀托,然后被標(biāo)記的View就會(huì)進(jìn)行測量惦辛、布局和繪制流程,調(diào)用的方法為onMeasure仓手、onLayout和onDraw胖齐。
  • invalidate()我們分析過,它的過程和requestLayout方法方法很像嗽冒,但是invalidate方法沒有標(biāo)記PFLAG_FORCE_LAYOUT呀伙,所以不會(huì)執(zhí)行測量和布局流程,而只是對(duì)需要重繪的View進(jìn)行重繪添坊,也就是只會(huì)調(diào)用onDraw方法剿另,不會(huì)調(diào)用onMeasure和onLayout方法。

如何優(yōu)化自定義 View

  1. 不要在 onDraw()或是 onLayout()中去創(chuàng)建對(duì)象贬蛙,因?yàn)?onDraw()方法可能會(huì)被頻繁調(diào)用雨女,可以在 View 的構(gòu)造函數(shù)中進(jìn)行創(chuàng)建對(duì)象;
  2. 降低 View 的刷新頻率阳准,盡可能減少不必要的調(diào)用 invalidate()方法氛堕。或是調(diào)用帶四種參數(shù)不同類型的 invalidate()野蝇,而不是調(diào)用無參的方法岔擂。無參變量需要刷新整個(gè) View位喂,而帶參數(shù)的方法只需刷新指定部分的 View。在 onDraw()方法中減少冗余代碼乱灵;
  3. 使用硬件加速塑崖,GPU 硬件加速可以帶來性能增加;
  4. 狀態(tài)保存與恢復(fù)痛倚,如果因內(nèi)存不足规婆,Activity 置于后臺(tái)被殺重啟時(shí),View 應(yīng)盡可能保存自己屬性蝉稳,可以重寫 onSaveInstanceState()和 onRestoreInstanceState()方法抒蚜,狀態(tài)保存

Android 中動(dòng)畫的類型

  • 幀動(dòng)畫:通過指定每一幀圖片和播放的時(shí)間,有序地進(jìn)行播放而形成動(dòng)畫效果耘戚;
  • 補(bǔ)間動(dòng)畫:通過指定的 View 的初始狀態(tài)嗡髓、變化時(shí)間、方式等收津,通過一系列的算法去進(jìn)行圖形變換從而形成動(dòng)畫效果饿这,主要有 Alpha、Scale撞秋、Translate长捧、Rotate 四種效果;
  • 屬性動(dòng)畫:在 Android3.0 開始支持吻贿,通過不斷改變 View 的屬性串结,不斷的重繪而形成動(dòng)畫效果;

RecyclerView 在很多方面能取代 ListView舅列,Google 為什么沒有把 ListView設(shè)置成過時(shí)控件

ListView 采用的是 RecyclerBin 的回收機(jī)制在一些輕量級(jí)的 List 顯示時(shí)效率更高肌割。

RecyclerView 和 ListView 的區(qū)別

RecyclerView 可以完成 ListView、GridView 的效果帐要,還可以完成瀑布流的效果把敞。同時(shí)還可以設(shè)置列表的滾動(dòng)方向(垂直或者水平);RecyclerView 中 View 的復(fù)用不需要開發(fā)者自己寫代碼宠叼,系統(tǒng)已經(jīng)幫封裝完成了。RecyclerView 可以進(jìn)行局部刷新其爵。RecyclerView 提供了 API來實(shí)現(xiàn) item 的動(dòng)畫效果冒冬。在性能上:如果需要頻繁的刷新數(shù)據(jù),需要添加動(dòng)畫摩渺,則 RecyclerView 有較大的優(yōu)勢简烤。如果只是作為列表展示,則兩者區(qū)別并不是很大摇幻。

View事件分發(fā)機(jī)制

  1. Touch 事件分發(fā)中只有兩個(gè)主角:ViewGroup 和 View横侦。ViewGroup 包含 dispatchTouchEvent挥萌、onInterceptTouchEvent、onTouchEvent 三個(gè)相關(guān)事件枉侧。View 包含 dispatchTouchEvent引瀑、onTouchEvent 兩個(gè)相關(guān)事件。其中 ViewGroup 又繼承于 View榨馁;
  2. ViewGroup 和 View 組成了一個(gè)樹狀結(jié)構(gòu)憨栽,根節(jié)點(diǎn)為 Activity 內(nèi)部包含的一個(gè) ViwGroup;
  3. 觸摸事件由 Action_Down翼虫、Action_Move屑柔、Aciton_UP 組成,其中一次完整的觸摸事件中珍剑,Down 和 Up 都只有一個(gè)掸宛,Move 有若干個(gè),可以為 0 個(gè)招拙;
  4. 當(dāng) Acitivty 接收到 Touch 事件時(shí)唧瘾,將遍歷子 View 進(jìn)行 Down 事件的分發(fā)。ViewGroup 的遍歷可以看成是遞歸的迫像。分發(fā)的目的是為了找到真正要處理本次完整觸摸事件的 View劈愚,這個(gè) View 會(huì)在 onTouchuEvent 結(jié)果返回 true;
  5. 當(dāng)某個(gè)子 View 返回 true 時(shí)闻妓,會(huì)中止 Down 事件的分發(fā)菌羽,同時(shí)在 ViewGroup 中記錄該子 View。接下去的 Move 和 Up 事件將由該子 View 直接進(jìn)行處理由缆。由于子 View 是保存在 ViewGroup 中的注祖,多層 ViewGroup 的節(jié)點(diǎn)結(jié)構(gòu)時(shí),上級(jí) ViewGroup 保存的會(huì)是真實(shí)處理事件的 View 所在的 ViewGroup 對(duì)象:如 ViewGroup0-ViewGroup1-TextView 的結(jié)構(gòu)中均唉,TextView 返回了 true是晨,它將被保存在 ViewGroup1 中,而 ViewGroup1 也會(huì)返回 true舔箭,被保存在 ViewGroup0 中施蜜。當(dāng) Move 和 UP 事件來時(shí)辈灼,會(huì)先從 ViewGroup0 傳遞至ViewGroup1,再由 ViewGroup1傳遞至 TextView;
  6. 當(dāng) ViewGroup 中所有子 View 都不捕獲 Down 事件時(shí)翘单,將觸發(fā) ViewGroup 自身的 onTouch事件浴骂。觸發(fā)的方式是調(diào)用 super.dispatchTouchEvent 函數(shù)巾陕,即父類 View 的 dispatchTouchEvent 方法琳钉。在所有子 View 都不處理的情況下,觸發(fā) Acitivity 的 onTouchEvent 方法戳表;
  7. onInterceptTouchEvent 有兩個(gè)作用:1. 攔截 Down 事件的分發(fā)桶至。2. 中止 Up 和 Move 事件向目標(biāo) View 傳遞昼伴,使得目標(biāo) View 所在的 ViewGroup 捕獲 Up 和 Move 事件;

是否用過 SurfaceView镣屹?它的繼承方式是什么圃郊?它與 View 的區(qū)別(從源碼角度,如加載野瘦,繪制等)

SurfaceView 中采用了雙緩沖機(jī)制描沟,保證了 UI 界面的流暢性,同時(shí) SurfaceView 不在主線程中繪制鞭光,而是另開辟一個(gè)線程去繪制吏廉,所以它不妨礙 UI 線程;SurfaceView 繼承于 View惰许,它和 View 主要有以下三點(diǎn)區(qū)別:(1)View 底層沒有雙緩沖機(jī)制席覆,SurfaceView 有;(2)View 主要適用于主動(dòng)更新汹买,而 SurfaceView 適用與被動(dòng)的更新佩伤,如頻繁的刷新(3)View 會(huì)在主線程中去更新 UI,而 SurfaceView 則在子線程中刷新晦毙;SurfaceView 的內(nèi)容不在應(yīng)用窗口上生巡,所以不能使用變換(平移、縮放见妒、旋轉(zhuǎn)等)孤荣。也難以放在 ListView 或者 ScrollView 中,不能使用 UI 控件的一些特性比如 View.setAlpha()须揣。View:顯示視圖盐股,內(nèi)置畫布,提供圖形繪制函數(shù)耻卡、觸屏事件疯汁、按鍵事件函數(shù)等;必須在 UI 主線程內(nèi)更新畫面卵酪,速度較慢幌蚊。SurfaceView:
基于 view 視圖進(jìn)行拓展的視圖類,更適合 2D 游戲的開發(fā)溃卡;是 view 的子類溢豆,類似使用雙緩機(jī)制,在新的線程中更新畫面所以刷新界面速度比 view 快塑煎,Camera 預(yù)覽界面使用 SurfaceView沫换。GLSurfaceView:基于 SurfaceView 視圖再次進(jìn)行拓展的視圖類臭蚁,專用于 3D 游戲開發(fā)的視圖最铁;是 SurfaceView 的子類讯赏,OpenGL 專用。

View 的滑動(dòng)方式

  1. layout(left,top,right,bottom):通過修改 View 四個(gè)方向的屬性值來修改 View 的坐標(biāo)冷尉,從而滑動(dòng) View
  2. offsetLeftAndRight()漱挎、offsetTopAndBottom():指定偏移量滑動(dòng) view
  3. LayoutParams 改變布局參數(shù):LayoutParams 中保存了 View 的布局參數(shù),可以通過修改布局參數(shù)的方式滑動(dòng) View
  4. 通過動(dòng)畫來移動(dòng) View:注意安卓的平移動(dòng)畫不能改變 View 的位置參數(shù)雀哨,屬性動(dòng)畫可以改變 View 的位置參數(shù)
  5. scrollTo/scrollBy:注意移動(dòng)的是 View 的內(nèi)容磕谅,scrollBy(50,50)你會(huì)看到屏幕上的內(nèi)容向屏幕的左上角移動(dòng)了,這是參考對(duì)象不同導(dǎo)致的雾棺,你可以看作是它移動(dòng)的是手機(jī)屏幕膊夹,手機(jī)屏幕向右下角移動(dòng),那么屏幕上的內(nèi)容就像左上角移動(dòng)了
  6. Scroller:Scroller 需要配置 computeScroll 方法實(shí)現(xiàn) View 的滑動(dòng)捌浩,Scroller 本身并不會(huì)滑動(dòng)View放刨,它的作用可以看作一個(gè)插值器,它會(huì)計(jì)算當(dāng)前時(shí)間點(diǎn) View 應(yīng)該滑動(dòng)到的距離尸饺,然后View 不斷的重繪进统,不斷的調(diào)用 computeScroll 方法,這個(gè)方法是個(gè)空方法浪听,所以我們重寫這個(gè)方法螟碎,在這個(gè)方法中不斷的從 Scroller 中獲取當(dāng)前 View 的位置,調(diào)用 scrollTo 方法實(shí)現(xiàn)滑動(dòng)的效果

View 的加載流程

View 隨著 Activity 的創(chuàng)建而加載迹栓,startActivity 啟動(dòng)一個(gè) Activity 時(shí)掉分,在 ActivityThread 的 handleLaunchActivity 方法中會(huì)執(zhí)行 Activity 的 onCreate 方法,這個(gè)時(shí)候會(huì)調(diào)用 setContentView加載布局創(chuàng)建出 DecorView 并將我們的 layout 加載到 DecorView 中迈螟,當(dāng)執(zhí)行到 handleResumeActivity 時(shí)叉抡,Activity 的 onResume 方法被調(diào)用,然后 WindowManager 會(huì)將 DecorView 設(shè)置給 ViewRootImpl答毫,這樣 DecorView 就被加載到 Window 中了褥民,此時(shí)界面還沒有顯示出來,還需要經(jīng)過 View 的 measure洗搂、layout 和 draw 方法消返,才能完成 View 的工作流程。我們需要知道 View 的繪制是由 ViewRoot 來負(fù)責(zé)的耘拇,每一個(gè) DecorView 都有一個(gè)與之關(guān)聯(lián)的 ViewRoot撵颊,這種關(guān)聯(lián)關(guān)系是由 WindowManager 維護(hù)的,將 DecorView 和 ViewRoot 關(guān)聯(lián)之后惫叛,ViewRootImpl 的 requestLayout 會(huì)被調(diào)用以完成初步布局倡勇,通過 scheduleTraversals 方法向主線程發(fā)送消息請(qǐng)求遍歷,最終調(diào)用 ViewRootImpl 的 performTraversals 方法嘉涌,這個(gè)方法會(huì)執(zhí)行 View 的measure妻熊、layout 和 draw 流程

View 的 measure夸浅、layout 和 draw 流程

在上邊的分析中我們知道,View 繪制流程的入口在 ViewRootImpl 的 performTraversals 方法扔役,在方法中首先調(diào)用 performMeasure 方法帆喇,傳入一個(gè) childWidthMeasureSpec 和 childHeightMeasureSpec 參數(shù),這兩個(gè)參數(shù)代表的是 DecorView 的 MeasureSpec 值亿胸,這個(gè) MeasureSpec值由窗口的尺寸和 DecorView 的 LayoutParams 決定坯钦,最終調(diào)用 View 的 measure 方法進(jìn)入測量流程
measure:View 的 measure 過程由 ViewGroup 傳遞而來,在調(diào)用 View.measure 方法之前侈玄,會(huì)首先根據(jù) View 自身的 LayoutParams 和父布局的 MeasureSpec 確定子 view 的 MeasureSpec婉刀,然后將 view 寬高對(duì)應(yīng)的 measureSpec 傳遞到 measure 方法中,那么子 view 的 MeasureSpec獲取規(guī)則是怎樣的序仙?分幾種情況進(jìn)行說明

  1. 父布局是 EXACTLY 模式:
    a. 子 View 寬或高是個(gè)確定值路星,那么子 View 的 size 就是這個(gè)確定值,mode 是 EXACTLY(是不是說子 View 寬高可以超過父 View诱桂?見下一個(gè))
    b. 子 View 寬或高設(shè)置為 match_parent洋丐,那么子 View 的 size 就是占滿父容器剩余空間,模式就是 EXACTLY
    c. 子 View 寬或高設(shè)置為 wrap_content挥等,那么子 View 的 size 就是占滿父容器剩余空間友绝,不能超過父容器大小,模式就是 AT_MOST
  2. 父布局是 AT_MOST 模式:
    a. 子 View 寬或高是個(gè)確定值那么子 View 的 size 就是這個(gè)確定值肝劲,mode 是 EXACTLY
    b. View 寬或高設(shè)置為 match_parent迁客,那么子 View 的 size 就是占滿父容器剩余空間,不能超過父容器大小辞槐,模式就是 AT_MOST
    c. 子 View 寬或高設(shè)置為 wrap_content掷漱,那么子 View 的 size 就是占滿父容器剩余空間,不能超過父容器大小榄檬,模式就是 AT_MOST
  3. 父布局是 UNSPECIFIED 模式:
    a. 子 View 寬或高是個(gè)確定值那么子 View 的 size 就是這個(gè)確定值卜范,mode 是 EXACTLY
    b. 子 View 寬或高設(shè)置為 match_parent,那么子 View 的 size 就是 0鹿榜,模式就是 UNSPECIFIED
    c. 子 View 寬或高設(shè)置為 wrap_content海雪,那么子 View 的 size 就是 0,模式就是 UNSPECIFIED 獲取到寬高的 MeasureSpec 后舱殿,傳入 View 的 measure 方法中來確定 View 的寬高奥裸,這個(gè)時(shí)候還要分情況 1. 當(dāng) MeasureSpec 的 mode 是 UNSPECIFIED,此時(shí) View 的寬或者高要看 View 有沒有設(shè)置背景沪袭,如果沒有設(shè)置背景湾宙,就返回設(shè)置的 minWidth 或 minHeight,這兩個(gè)值如果沒有設(shè)置默認(rèn)就是 0,如果 View 設(shè)置了背景侠鳄,就取 minWidth 或 minHeight 和背景這個(gè)drawable 固有寬或者高中的最大值返回 2. 當(dāng) MeasureSpec 的 mode 是 AT_MOST 和 EXACTLY嗡害,此時(shí) View 的寬高都返回從 MeasureSpec 中獲取到的 size 值,這個(gè)值的確定見上邊的分析畦攘。因此如果要通過繼承 View 實(shí)現(xiàn)自定義 View,一定要重寫 onMeasure 方法對(duì) wrap_conten 屬性做處理十电,否則知押,他的 match_parent 和 wrap_content 屬性效果就是一樣的layout:layout 方法的作用是用來確定 View 本身的位置,onLayout 方法用來確定所有子元素的位置鹃骂,當(dāng) ViewGroup 的位置確定之后台盯,它在 onLayout 中會(huì)遍歷所有的子元素并調(diào)用其 layout 方法,在子元素的 layout 方法中 onLayout 方法又會(huì)被調(diào)用畏线。layout 方法的流程是静盅,首先通過 setFrame 方法確定 View 四個(gè)頂點(diǎn)的位置,然后 View 在父容器中的位置也就確定了寝殴,接著會(huì)調(diào)用 onLayout 方法蒿叠,確定子元素的位置,onLayout 是個(gè)空方法蚣常,需要繼承者去實(shí)現(xiàn)市咽。
    getMeasuredHeight 和 getHeight 方法有什么區(qū)別?getMeasuredHeight(測量高度)形成于 View 的 measure 過程抵蚊,getHeight(最終高度)形成于 layout 過程施绎,在有些情況下,View 需要measure 多次才能確定測量寬高贞绳,在前幾次的測量過程中谷醉,得出的測量寬高有可能和最終寬高不一致,但是最終來說冈闭,還是會(huì)相同俱尼,有一種情況會(huì)導(dǎo)致兩者值不一樣,如下萎攒,此代碼會(huì)導(dǎo)致 View 的最終寬高比測量寬高大 100px
public void layout(int l, int t, int r, int b) {
        super.layout(l, t, r + 100, b + 100);
}

draw:
View 的繪制過程遵循如下幾步:
a. 繪制背景 background.draw(canvas)
b. 繪制自己(onDraw)
c. 繪制 children(dispatchDraw)
d. 繪制裝飾(onDrawScrollBars)
View 繪制過程的傳遞是通過 dispatchDraw 來實(shí)現(xiàn)的号显,它會(huì)遍歷所有的子元素的 draw 方法,如此 draw 事件就一層一層的傳遞下去了
PS:View 有一個(gè)特殊的方法 setWillNotDraw躺酒,如果一個(gè) View 不需要繪制內(nèi)容押蚤,即不需要重寫 onDraw 方法繪制,可以開啟這個(gè)標(biāo)記羹应,系統(tǒng)會(huì)進(jìn)行相應(yīng)的優(yōu)化揽碘。默認(rèn)情況下,View 沒有開啟這個(gè)標(biāo)記,默認(rèn)認(rèn)為需要實(shí)現(xiàn) onDraw 方法繪制雳刺,當(dāng)我們繼承 ViewGroup 實(shí)現(xiàn)自定義控件劫灶,并且明確知道不需要具備繪制功能時(shí),可以開啟這個(gè)標(biāo)記掖桦,如果我們重寫了 onDraw本昏,那么要顯示的關(guān)閉這個(gè)標(biāo)記子View 寬高可以超過父View?能 1. android:clipChildren="false" 這個(gè)屬性要設(shè)置在父View上涌穆。代表其中的子 View 可以超出屏幕。2. 子View 要有具體的大小雀久,一定要比父View大才能超出宿稀。比如父 view 高度 100px,子 view 設(shè)置高度 150px赖捌。子 view 比父 view 大祝沸,這樣超出的屬性才有意義。(高度可以在代碼中動(dòng)態(tài)賦值越庇,但不能用 wrap_content/match_partent)罩锐。對(duì)父布局還有要求,要求使用 LinearLayout(反正我用 RelativeLayout 是不行)卤唉。你如果必須用其他布局可以在需要超出的View 上面套一個(gè) LinearLayout 外面再套其他的布局唯欣。最外面的布局如果設(shè)置的 padding 不能超出

自定義 View 需要注意的幾點(diǎn)

  1. 讓 View 支持 wrap_content 屬性,在 onMeasure 方法中針對(duì) AT_MOST 模式做專門處理搬味,否則 wrap_content 會(huì)和 match_parent 效果一樣(繼承 ViewGroup 也同樣要在 onMeasure中做這個(gè)判斷處理)
if(widthMeasureSpec == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {
setMeasuredDimension(200,200); //wrap_content 情況下要設(shè)置一個(gè)默認(rèn)值境氢,200 只是舉個(gè)例子,最終的值需要計(jì)算得到剛好包裹內(nèi)容的寬高值
} else if(widthMeasureSpec == MeasureSpec.AT_MOST) {
setMeasuredDimension(200,heightMeasureSpec);
} else if(heightMeasureSpec == MeasureSpec.AT_MOST) {
setMeasuredDimension(heightMeasureSpec,200);
}
  1. 讓 View 支持 padding(onDraw 的時(shí)候碰纬,寬高減去 padding 值萍聊,margin 由父布局控制,不需要 View 考慮)悦析,自定義 ViewGroup 需要考慮自身的 padding 和子 View 的 margin 造成的影響
  2. 在 View 中盡量不要使用 handler寿桨,使用 View 本身的 post 方法
  3. 在 onDetachedFromWindow 中及時(shí)停止線程或動(dòng)畫
  4. View 帶有滑動(dòng)嵌套情形時(shí),處理好滑動(dòng)沖突

圖片庫對(duì)比 Picasso(Square)强戴、Glide(Google)亭螟、Fresco(Facebook)

Picasso
優(yōu)點(diǎn):框架體積小,使用方便骑歹、一行代碼完成加載圖片并顯示
缺點(diǎn):不支持 GIF 圖预烙,并且它可能是想讓服務(wù)端去處理圖片的縮放,因此它緩存的圖片是未經(jīng)過縮放的道媚,也就是緩存的原圖扁掸,默認(rèn)使用 ARGB_8888 格式緩存圖片翘县,緩存體積大;Picasso 是二級(jí)緩存谴分,它支持內(nèi)存緩存而不支持磁盤緩存锈麸,而 Glide 是三級(jí)緩存

Glide
優(yōu)點(diǎn):可以說是 Picasso 的升級(jí)版,有 Picasso 的優(yōu)點(diǎn)牺蹄,并且支持 GIF 圖片加載顯示忘伞,圖片緩存也會(huì)自動(dòng)縮放,默認(rèn)使用 RGB_565 格式緩存圖片沙兰,如果緩存同一尺寸的圖片則是 Picasso緩存體積的一半
缺點(diǎn):默認(rèn)使用 RGB_565 格式緩存圖片氓奈,沒有 ARGB_8888 格式的圖片效果好,如果對(duì)圖片質(zhì)量要求高僧凰,需要手動(dòng)設(shè)置緩存格式為 ARGB_8888

Fresco
優(yōu)點(diǎn)
1. 圖片存儲(chǔ)在安卓系統(tǒng)的匿名共享內(nèi)存,而不是虛擬機(jī)的堆內(nèi)存中熟丸;圖片的中間緩沖數(shù)據(jù)也存放在本地堆內(nèi)存训措,所以應(yīng)用程序有更多的內(nèi)存使用,不會(huì)因?yàn)閳D片加載而導(dǎo)致 OOM光羞,同時(shí)也減少 GC 頻繁調(diào)用回收 Bitmap 導(dǎo)致的界面卡頓绩鸣,性能更高
2. 漸進(jìn)式加載 JPEG 圖片,支持圖片從模糊到清晰加載
3. 圖片可以以任意的中心點(diǎn)顯示在 ImageView纱兑,而不僅僅是圖片的中心
4. JPEG 圖片改變大小也是在 native 進(jìn)行的呀闻,不是在虛擬機(jī)的堆內(nèi)存,同樣減少 OOM
缺點(diǎn):框架較大(2~3MB)潜慎,影響 Apk 包的體積捡多;API 不夠簡潔,使用比 Glide 繁瑣

Glide 源碼解析

  1. Glide.with(context)創(chuàng)建了一個(gè) RequestManager铐炫,同時(shí)實(shí)現(xiàn)加載圖片與組件生命周期綁定垒手;在 Activity 上創(chuàng)建一個(gè)透明的 RequestManagerFragment 加入到 FragmentManager 中,通過添加的 Fragment 感知 Activity/Fragment 的生命周期倒信,因?yàn)樘砑拥?Activity 中的 Fragment 會(huì)跟隨 Activity 的生命周期科贬,在 RequestManagerFragment 中的相應(yīng)生命周期方法中通過 lifecycle 傳遞給在 lifecycle 中注冊(cè)的LifecycleListener
  2. RequestManager.load(url)創(chuàng)建了一個(gè) RequestBuilder<T>對(duì)象 T 可以是 Drawable 對(duì)象
    或是 ResourceType 等
  3. RequestBuilder.into(view)-->into(glideContext.buildImageViewTarget(view,transcodeClass))返回的是一個(gè) DrawableImageViewTarget,Target 用來最終展示圖片鳖悠,buildImageViewTarget-->ImageViewTargetFactory.buildTarget()根據(jù)傳入 class 參數(shù)的不同構(gòu)建不同的 Target 對(duì)象榜掌,這個(gè) class 對(duì)象是根據(jù)構(gòu)建 Glide 時(shí)是否調(diào)用了 asBitmap()方法,如果調(diào)用了會(huì)構(gòu)建出 BitmapImageViewTarget乘综,否則構(gòu)建的是 GlideDrawableImageViewTarget 對(duì)象
  4. GenericRequestBuilder.into(Target)憎账,該方法進(jìn)行了構(gòu)建 Request,并用 RequestTracker.runRequest()
    Request request = buildRequest(target);//構(gòu)建 Request 對(duì)象卡辰,Request 是用來請(qǐng)求加載圖片的鼠哥,它調(diào)用了 buildRequestRecursive()方法熟菲,內(nèi)部調(diào)用了 GenericRequest.obtain()方法
    target.setRequest(request);
    lifecycle.addListener(target);
    requestTracker.runRequest(request);//判斷 Glide 當(dāng)前是不是處于暫停狀態(tài),若不是則調(diào)用 Request.begin() 方法來執(zhí)行 Request朴恳,否則將 Request 添加到待執(zhí)行隊(duì)列里抄罕,等暫停狀態(tài)解除了后再執(zhí)行 --> GenericRequest.begin()
  5. onSizeReady()-->Engine.load(signature,width,height,dataFetcher,loadProvider,transfor
    mation,transcoder,priority,isMemoryCacheable,diskCacheStrategy,this)
    a. 先構(gòu)建 EngineKey;
    b. loadFromCache 從緩存中獲取 EngineResource,如果緩存中獲取到 cache 就調(diào)用cb.onResourceReady(cached);
    c. 如果緩存中不存在調(diào)用 loadFromActiveResources 從 active 中獲取于颖,如果獲取到就調(diào)用cb.onResourceReady(cached);
    d. 如果是 active 中也不存在呆贿,調(diào)用 EngineJob.start(EngineRunable),從而調(diào)用 decodeFromSource()/decodeFromCache()如果是調(diào)用 decodeFromSource()-->ImageVideoFetcher.loadData()-->HttpUrlFetcher() 調(diào)用 HttpUrlConnection 進(jìn)行網(wǎng)絡(luò)請(qǐng)求資源-->得于 InputStream()后森渐,調(diào)用 decodeFromSourceData()-->loadProvider.getSourceDecoder().decode()方法解碼-->GifBitmapWrapperResourceDecoder.decode()-->decodeStream()先從流中讀取 2 個(gè)字節(jié)判斷是 GIF還是普通圖做入,若是 GIF 調(diào)用 decodeGifWrapper()來解碼,若是普通靜圖則調(diào)用 decodeBitmapWrapper()來解碼-->bitmapDecoder.decode()

Glide 的緩存機(jī)制

  • 內(nèi)存緩存:LruResourceCache(memory) + 弱引用activeResources同衣,Map<Key, WeakReference<EngineResource<?>>> activeResources 正在使用的資源竟块,當(dāng) acquired 變量大于 0,說明圖片正在使用耐齐,放到 activeResources 弱引用緩存中浪秘,經(jīng)過 release() 后,acquired = 0, 說明圖片不再使用埠况,會(huì)把它放進(jìn) LruResourceCache中
  • 磁盤緩存:DiskLruCache耸携,這里分為 Source(原始圖片)和 Result(轉(zhuǎn)換后的圖片),第一次獲取圖片肯定網(wǎng)絡(luò)取辕翰,然后存 active\disk 中夺衍,再把圖片顯示出來,第二次讀取相同的圖片喜命,并加載到相同大小的imageview 中沟沙,會(huì)先從 memory 中取,沒有再去 active 中獲取壁榕。如果 activity 執(zhí)行到 onStop 時(shí)尝胆,圖片被回收,active 中的資源會(huì)被保存到 memory 中护桦,active 中的資源被回收含衔。當(dāng)再次加載圖片時(shí),會(huì)從 memory 中取二庵,再放入 active 中贪染, 并將 memory 中對(duì)應(yīng)的資源回收。 之所以需要 activeResources催享,它是一個(gè)隨時(shí)可能被回收的資源杭隙,memory 的強(qiáng)引用頻繁讀寫可能造成內(nèi)存激增頻繁 GC,而造成內(nèi)存抖動(dòng)因妙。資源在使用過程中
    保存在 activeResources 中痰憎,而 activeResources 是弱引用票髓,隨時(shí)被系統(tǒng)回收,不會(huì)造成內(nèi)存過多使用和泄漏

Glide設(shè)置內(nèi)存緩存铣耘、設(shè)置磁盤緩存策略

    //跳過內(nèi)存緩存
    Glide.with(this).load(mUrl).skipMemoryCache(true).into(mIv);
    //DiskCacheStrategy.ALL:緩存原圖(SOURCE)和處理圖(RESULT)
    //DiskCacheStrategy.NONE:什么都不緩存
    //DiskCacheStrategy.SOURCE:只緩存原圖(SOURCE)
    //DiskCacheStrategy.RESULT:只緩存處理圖(RESULT) —默認(rèn)值
    Glide.with(this).load(mUrl).diskCacheStrategy(DiskCacheStrategy.ALL).into(mIv);

Glide 內(nèi)存緩存如何控制大小

Glide 內(nèi)存緩存最大空間(maxSize) = 每個(gè)進(jìn)程可用最大內(nèi)存 * 0.4(低配手機(jī)是每個(gè)進(jìn)程可用最大內(nèi)存 * 0.33)
磁盤緩存大小是 250MB int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;

LruCache 原理

  • LruCache 中 Lru 算法的實(shí)現(xiàn)就是通過 LinkedHashMap 來實(shí)現(xiàn)的洽沟。LinkedHashMap 繼承于HashMap,它使用了一個(gè)雙向鏈表來存儲(chǔ) Map 中的 Entry 順序關(guān)系蜗细,對(duì)于 get裆操、put、remove等操作炉媒,LinkedHashMap 除了要做 HashMap 做的事情踪区,還做些調(diào)整 Entry 順序鏈表的工作。
  • LruCache 中將 LinkedHashMap 的順序設(shè)置為 LRU 順序來實(shí)現(xiàn) LRU 緩存吊骤,每次調(diào)用 get(也就是從內(nèi)存緩存中取圖片)缎岗,則將該對(duì)象移到鏈表的尾端。調(diào)用 put 插入新的對(duì)象也是存儲(chǔ)在鏈表尾端白粉,這樣當(dāng)內(nèi)存緩存達(dá)到設(shè)定的最大值時(shí)传泊,將鏈表頭部的對(duì)象(近期最少用到的)移除。

LruCache 怎么回收 Bitmap

LruCache 每次添加 Bitmap 圖片緩存的時(shí)候(put 操作)蜗元,都會(huì)調(diào)用 sizeOf 方法或渤,返回 Bitmap的內(nèi)存大小給 LruCache系冗,然后循環(huán)增加這個(gè) size奕扣。當(dāng)這個(gè) Size 內(nèi)存大小超過初始化設(shè)定的cacheMemory 大小時(shí),則遍歷 map 集合掌敬,把最近最少使用的元素 remove 掉

圖片壓縮的詳細(xì)過程

使用 BitmapFactory.Options 設(shè)置 inSampleSize 就可以縮小圖片惯豆。屬性值 inSampleSize 表示縮略圖大小為原始圖片大小的幾分之一。即如果這個(gè)值為 2奔害,則取出的縮略圖的寬和高都是原始圖片的 1/2楷兽,圖片的大小就為原始大小的 1/4。如果知道圖片的像素過大华临,就可以對(duì)其 進(jìn) 行 縮 小 芯杀。 那 么 如 何 才 知 道 圖 片 過 大 呢 ? 使 用 BitmapFactory.Options 設(shè) 置inJustDecodeBounds 為 true 后雅潭,再使用 decodeFile()等方法揭厚,并不會(huì)真正的分配空間,即解碼出來的 Bitmap 為 null扶供,但是可計(jì)算出原始圖片的寬度和高度筛圆,即 options.outWidth 和options.outHeight。通過這兩個(gè)值椿浓,就可以知道圖片是否過大了太援。

談?wù)勀銓?duì) Bitmap 的理解闽晦,什么時(shí)候應(yīng)該手動(dòng)調(diào)用 bitmap.recycle()

Bitmap 是 android 中經(jīng)常使用的一個(gè)類,它代表了一個(gè)圖片資源提岔。Bitmap 消耗內(nèi)存很嚴(yán)重仙蛉,如果不注意優(yōu)化代碼,經(jīng)常會(huì)出現(xiàn) OOM 問題唧垦,優(yōu)化方式通常有這么幾種:1. 使用緩存捅儒;2. 壓縮圖片;3. 及時(shí)回收振亮;至于什么時(shí)候需要手動(dòng)調(diào)用 recycle()巧还,這就看具體場景了,原則是當(dāng)我們不再使用 Bitmap 時(shí)坊秸,需要回收之麸祷。另外,我們需要注意褒搔,Android3.0 之前 Bitmap對(duì)象與像素?cái)?shù)據(jù)是分開存放的阶牍,Bitmap 對(duì)象存在 Java Heap 中而像素?cái)?shù)據(jù)存放在 NativeMemory 中,這時(shí)很有必要調(diào)用 recycle 回收內(nèi)存星瘾。但是從 Android3.0 開始走孽,Bitmap 對(duì)象和像素?cái)?shù)據(jù)都是存在 Heap 中,GC 可以回收其內(nèi)存琳状。Android8.0 開始磕瓷,Bitmap 對(duì)象與像素?cái)?shù)據(jù)改為分開存放

一張 Bitmap 所占內(nèi)存以及內(nèi)存占用的計(jì)算

一張圖片(Bitmap)占用的內(nèi)存影響因素:圖片原始長和寬、手機(jī)屏幕密度念逞、圖片存放路徑下的密度困食、單位像素占用字節(jié)數(shù)
BitmapSize = 圖片長度 *(inTargetDensity(手機(jī)的 density)/ inDensity(圖片存放目錄的density)) * 圖片寬度 * (inTargetDensity(手機(jī)的 density)/ inDensity(圖片存放目錄的density)) * 單位像素占用的字節(jié)數(shù)(圖片長寬單位是像素)
a. 圖片長寬單位是像素:單位像素字節(jié)數(shù)由其參數(shù) BitmapFactory.Options.inPreferredConfig變量決定,它是Bitmap.Config 類型翎承,包括以下幾種值:ALPHA_8 圖片只有 alpha 值硕盹,占用一個(gè)字節(jié):ARGB_4444 一個(gè)像素占用 2 個(gè)字節(jié),A\R\G\B 各占 4bits叨咖;ARGB_8888 一個(gè)像素占用 4 個(gè)字節(jié)瘩例,A\R\G\B 各占 8bits(高質(zhì)量圖片格式,Bitmap 默認(rèn)格式)甸各;RGB_565一個(gè)像素占用 2 字節(jié)垛贤,不支持透明和半透明,R 占 5bit, Green 占 6bit, Blue 占用 5bit. 從Android4.0 開始該項(xiàng)無效痴晦。
b. inTargetDensity 手機(jī)的屏幕密度(跟手機(jī)分辨率有關(guān)系) inDensity 原始資源密度(mdpi:160南吮;hdpi:240;xhdpi:320誊酌;xxhdpi:480部凑;xxxhdpi:640)當(dāng) Bitmap 對(duì)象在不使用時(shí)露乏,應(yīng)該先調(diào)用 recycle(),再將它設(shè)置為 null涂邀,雖然 Bitmap 在被回收時(shí)可通過 BitmapFinalizer 來回收內(nèi)存瘟仿。但只有系統(tǒng)垃圾回收時(shí)才會(huì)回收。Android3.0 之前比勉,Bitmap 內(nèi)存分配在 Native 堆中劳较,Android3.0 開始,Bitmap 的內(nèi)存分配在 Dalvik 堆中浩聋,即 Java堆中观蜗,調(diào)用 recycle()并不能立即釋放 Native 內(nèi)存。Android8.0 開始衣洁,Bitmap 內(nèi)存分配在 Native中

Bitmap 如何處理大圖墓捻,如一張 30M 的大圖,如何預(yù)防 OOM

避免 OOM 的問題就需要對(duì)大圖片的加載進(jìn)行管理坊夫,主要通過縮放來減小圖片的內(nèi)存占用砖第。BitmapFactory 提供的加載圖片的四類方法(decodeFile、decodeResource环凿、decodeStream梧兼、decodeByteArray)都支持 BitmapFactory.Options 參數(shù),通過 inSampleSize 參數(shù)就可以很方便地對(duì)一個(gè)圖片進(jìn)行采樣縮放智听。比如一張 1024 * 1024 的高清圖片來說羽杰。那么它占有的內(nèi)存為1024 * 1024 * 4,即 4MB瞭稼,如果 inSampleSize 為 2忽洛,那么采樣后的圖片占用內(nèi)存只有 512 * 512 * 4腻惠,即 1MB(注意:根據(jù)最新的官方文檔指出环肘,inSampleSize 的取值應(yīng)該總是為 2 的指數(shù),即 1集灌、2悔雹、4、8 等等欣喧,如果外界輸入不足為 2 的指數(shù)腌零,系統(tǒng)也會(huì)默認(rèn)選擇最接近 2 的指數(shù)代替,比如 2)
綜合考慮唆阿。通過采樣率即可有效加載圖片益涧,流程如下將 BitmapFactory.Options 的inJustDecodeBounds 參數(shù)設(shè)為 true 并加載圖片從 BitmapFactory.Options 中取出圖片的原始寬高信息,它們對(duì)應(yīng) outWidth 和outHeight 參數(shù)驯鳖。根據(jù)采樣率的規(guī)則并結(jié)合目標(biāo) View 的所需大小計(jì)算出采樣率 inSampleSize闲询,將 BitmapFactory.Options 的 inJustDecodeBounds 參數(shù)設(shè)為false久免,重新加載圖片

Bitmap 的使用以及內(nèi)存優(yōu)化

位圖是相對(duì)于矢量圖而言的,也稱為點(diǎn)陣圖扭弧,位圖由像素組成阎姥。圖像的清晰度由單位長度內(nèi)的像素的多少來決定的,在 Android 系統(tǒng)中鸽捻,位圖使用 Bitmap 類來表示呼巴,該類位于android.graphics 包中,被 final 所修飾御蒲,不能被繼承衣赶,創(chuàng)建 Bitmap 對(duì)象可使用該類的靜態(tài)方法 createBitmap,也可以借助 BitmapFactory 類來實(shí)現(xiàn)厚满。Bitmap 可以獲取圖像文件信息屑埋,如寬高尺寸等,可以進(jìn)行圖像剪切痰滋、旋轉(zhuǎn)摘能、縮放等操作。BitmapFactory 是創(chuàng)建 Bitmap 的工具類敲街,能夠以文件团搞、字節(jié)數(shù)組、輸入流的形式創(chuàng)建位圖對(duì)象多艇,BitmapFactory 類提供的都是靜態(tài)方法可以直接調(diào)用逻恐,BitmapFactory.Options 類是BitmapFactory 的靜態(tài)內(nèi)部類,主要用于設(shè)定位圖的解析參數(shù)峻黍。在解析位圖時(shí)复隆,將位圖進(jìn)行相應(yīng)的縮放,當(dāng)位圖資源不再使用時(shí)姆涩,強(qiáng)制資源回收挽拂,可以有效避免內(nèi)存溢出。

縮略圖:不加載位圖的原有尺寸骨饿,而是根據(jù)控件的大小呈現(xiàn)圖像的縮小尺寸亏栈,就是縮略圖。將大尺寸圖片解析為控件所指的尺寸的思路: 實(shí)例化 BitmapFactory.Options 對(duì)象來獲取解析屬性對(duì)象宏赘,設(shè)置 BitmapFactory.Options 的屬性 inJustDecodeBounds 為 true 后绒北,再解析位圖時(shí)并不分配存儲(chǔ)空間,但可以計(jì)算出原始圖片的寬度和高度察署,即 outWidth 和 outHeight闷游,將這兩個(gè)數(shù)值與控件的寬高尺寸相除,就可以得到縮放比例,即 inSampleSize 的值脐往,然后重新設(shè)置 inJustDecodeBounds 為 false俱济,inSampleSize 為計(jì)算所得的縮放比例,重新解析位圖文件钙勃,即可得到原圖的縮略圖蛛碌。

獲取控件寬高屬性的方法:可以利用控件的 getLayoutParams()方法獲得控件的 LayoutParams對(duì)象,通過 LayoutParams 的 Width 和 Height 屬性來得到控件的寬高辖源,同樣可以利用控件的setLayoutParams() 方 法 來 動(dòng) 態(tài) 的 設(shè) 置 其 寬 高 蔚携, 其 中 LayoutParams 是 繼 承 于Android.view.viewGroup.LayoutParams,LayoutParams 類是用于 child view(子視圖)向 parentview(父視圖)傳遞布局(Layout)信息包克饶,它封裝了 Layout 的位置酝蜒、寬、高等信息矾湃。

Bitmap 的內(nèi)存優(yōu)化:及時(shí)回收 Bitmap 的內(nèi)存:Bitmap 類有一個(gè)方法 recycle( )亡脑,用于回收該Bitmap 所占用的內(nèi)存,當(dāng)保證某個(gè) Bitmap 不會(huì)再被使用(因?yàn)?Bitamap 被強(qiáng)制釋放后邀跃,再次使用它會(huì)拋出異常)后霉咨,能夠在 Activity 的 onStop()方法或 onDestory()方法中將其回收,回收方法 : if (bitmap !=null && !bitmap.isRecycle()) { bitmap.recycle(); bitmap=null; }System.gc( ) ; System.gc()方法可以加快系統(tǒng)回收內(nèi)存的到來拍屑;捕獲異常:為了避免應(yīng)用在分配 Bitmap 內(nèi)存時(shí)出現(xiàn) OOM 異常以后 Crash 掉途戒,需在對(duì) Bitmap 實(shí)例化的過程中進(jìn)行OutOfMemory 異常的捕獲;

緩存通用的 Bitmap 對(duì)象:緩存分為硬盤緩存和內(nèi)存緩存僵驰,將常用的 Bitmap 對(duì)象放到內(nèi)存中緩存起來喷斋,或?qū)木W(wǎng)絡(luò)上獲取到的數(shù)據(jù)保存到 SD 卡中。

Bitmap 使用需要注意哪些問題

  • 要選擇合適的圖片規(guī)格(Bitmap 類型):通常我們優(yōu)化 Bitmap 時(shí)蒜茴,當(dāng)需要做性能優(yōu)化或者防止 OOM星爪,我們通常會(huì)使用 RGB_565,因?yàn)?ALPHA_8 只有透明度粉私,顯示一般圖片沒有意義Bitmap.Config.ARGB_4444 顯示圖片不清楚顽腾,Bitmap.Config.ARGB_8888 占用內(nèi)存最多。
    1. ALPHA_8 每個(gè)像素占用 1byte 內(nèi)存
    2. ARGB_4444 每個(gè)像素占用 2byte 內(nèi)存
    3. ARGB_8888 每個(gè)像素占用 4byte 內(nèi)存(默認(rèn))
    4. RGB_565 每個(gè)像素占用 2byte 內(nèi)存
  • 降 低 采 樣 率 : BitmapFactory.Options 參 數(shù) inSampleSize 使 用 毡鉴, 先 把Options.inJustDecodeBounds 設(shè)為 true崔泵,只是去讀取圖片的大小秒赤,在拿到圖片的大小之后和要顯示的大小做比較通過calculateInSampleSize()函數(shù)計(jì)算 inSampleSize 的具體值猪瞬,得到值之后。options.inJustDecodeBounds 設(shè)為 false 讀圖片資源入篮。
  • 復(fù)用內(nèi)存:即通過軟引用(內(nèi)存不夠的時(shí)候才會(huì)回收掉)陈瘦,復(fù)用內(nèi)存塊,不需要再重新給這個(gè) Bitmap 申請(qǐng)一塊新的內(nèi)存潮售,避免了一次內(nèi)存的分配和回收痊项,從而改善了運(yùn)行效率锅风。
  • 使用 recycle()方法及時(shí)回收內(nèi)存。
  • 壓縮圖片鞍泉。

Bitmap.recycle()會(huì)立即回收么皱埠?什么時(shí)候會(huì)回收?如果沒有地方使用Bitmap咖驮,為什么 GC 不會(huì)立即回收

通過源碼可以了解到边器,加載 Bitmap 到內(nèi)存里以后,是包含兩部分內(nèi)存區(qū)域的托修。簡單的說忘巧,一部分是 Java 部分的,一部分是 C 部分的睦刃。這個(gè) Bitmap 對(duì)象是由 Java 部分分配的砚嘴,不用的時(shí)候系統(tǒng)就會(huì)自動(dòng)回收了。但是那個(gè)對(duì)應(yīng)的 C 可用的內(nèi)存區(qū)域涩拙,虛擬機(jī)是不能直接回收的际长,這個(gè)只能調(diào)用底層的功能釋放。所以需要調(diào)用 recycle()方法來釋放 C 部分的內(nèi)存兴泥。Bitmap.recycle()方法用于回收該 Bitmap 所占用的內(nèi)存也颤,接著將 Bitmap 置空,最后使用System.gc()調(diào)用一下系統(tǒng)的垃圾回收器進(jìn)行回收郁轻,調(diào)用 System.gc()并不能保證立即開始進(jìn)行回收過程翅娶,而只是為了加快回收的到來。

Bitmap 里有兩個(gè)獲取內(nèi)存占用大小的方法

getByteCount():API12 加入好唯,代表存儲(chǔ) Bitmap 的像素需要的最少內(nèi)存竭沫。
getAllocationByteCount():API19 加入,代表在內(nèi)存中為 Bitmap 分配的內(nèi)存大小骑篙,代替了getByteCount()方法蜕提。
在不復(fù)用 Bitmap 時(shí),getByteCount()和 getAllocationByteCount 返回的結(jié)果是一樣的靶端。在通過復(fù)用 Bitmap 來解碼圖片時(shí)谎势,那么 getByteCount()表示新解碼圖片占用內(nèi)存的大小,getAllocationByteCount()表示被復(fù)用 Bitmap 真實(shí)占用的內(nèi)存大小

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杨名,一起剝皮案震驚了整個(gè)濱河市脏榆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌台谍,老刑警劉巖须喂,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件椿争,死亡現(xiàn)場離奇詭異笆包,居然都是意外死亡伊者,警方通過查閱死者的電腦和手機(jī)喜每,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來是己,“玉大人又兵,你說我怎么就攤上這事∽浞希” “怎么了寒波?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長升熊。 經(jīng)常有香客問我俄烁,道長,這世上最難降的妖魔是什么级野? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任页屠,我火速辦了婚禮,結(jié)果婚禮上蓖柔,老公的妹妹穿的比我還像新娘辰企。我一直安慰自己,他們只是感情好况鸣,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布牢贸。 她就那樣靜靜地躺著,像睡著了一般镐捧。 火紅的嫁衣襯著肌膚如雪潜索。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天懂酱,我揣著相機(jī)與錄音竹习,去河邊找鬼。 笑死列牺,一個(gè)胖子當(dāng)著我的面吹牛整陌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瞎领,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼泌辫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了九默?” 一聲冷哼從身側(cè)響起震放,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荤西,沒想到半個(gè)月后澜搅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伍俘,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡邪锌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年勉躺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片觅丰。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡饵溅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妇萄,到底是詐尸還是另有隱情蜕企,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布冠句,位于F島的核電站轻掩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏懦底。R本人自食惡果不足惜唇牧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望聚唐。 院中可真熱鬧丐重,春花似錦、人聲如沸杆查。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亲桦。三九已至崖蜜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間客峭,已是汗流浹背纳猪。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留桃笙,地道東北人氏堤。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像搏明,于是被迫代替她去往敵國和親鼠锈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345