天天看點

面試體記錄第六節——(handlerThread、intentservice、view)

一、問:handerThread出現的背景是什麼?

答:我們在安卓項目開發中,經常會遇到一些耗時操作,這時候我們第一直覺,就是開啟一個子線程去執行一個耗時操作,這很友善但是卻又很耗記憶體,因為當你一個Android中的thread執行完耗時操作,線程就會被自動銷毀,如果在短時間内又要執行一個耗時操作,這個時候我們就不得不重新建立線程去執行耗時任務,這樣就存在一個性能問題,多次建立和銷毀線程是很消耗資源的。

二、問:有沒有辦法解決上述問題??

答:為了解決這個問題,我們自己可以建構一個循環線程,例如looper等。當有耗時任務需要投放到改循環線程當中時,線程就會執行耗時任務,執行完成之後,線程又會處于阻塞等待狀态。知道下一個耗時任務被投放進來。雖然這暫時解決了多次建立線程造成記憶體消耗,但是谷歌公司非常的體貼,他給我們已經封裝好了一個更好的架構,就是handlerThread。

三、問:handlerThread是什麼?

答:他就是一個thread線程,但是他跟普通的thread不通的地方就是,它内部他有一個looper,它開啟了運作器,handler+thread+looper。

四、問:子線程為什麼不能開啟handler?

答:handler、sendMeassage()、又或者post(runnable),他都需要一個消息對列來儲存他所發送的消息,而預設子線程當中他是沒有開啟looper輪循器的。而消息對列又是被looper所管理的,是以在子線程你想建立一個handler來發送消息,是沒有關聯到消息對列的來讓你存儲消息,是以會抛出異常。如果你想在子線程當中建立一個handler,你自己必須初始化一個looper,然後調用looper.loop()方法,開啟循環,才能建立handler。

五、問:handlerThread它有哪些特點?

答:

1、handlerThread它本身就是一個線程類,他繼承了thread。

2、它和傳統的thread,是它本身内部有looper對象,可以進行looper循環,是以在handlerThread中,你可以建立handler來發送和處理消息。

3、通過擷取handlerThread的looper對象傳遞給handler,可以再handleMessage方法中執行異步任務。

4、他不會阻塞UI線程,減小對性能的消耗,缺點是不能不是進行多個任務,需要等待進行處理,處理效率降低。

5、與線程池并發不同,handlerThread是串行,背後隻有一個線程。他對于線程池來說更安全。

handlerThread源碼解析:http://blog.csdn.net/lmj623565791/article/details/47079737/

六、問:intentservice是什麼是什麼?

答:intentservice是繼承service并處理異步請求的一個類,在intentservice内有一個工作線程來處理耗時操作,啟動intentservice和啟動傳統的service一樣,同時,當任務執行完,intentservice會自動停止,而不需要我們手動停止或stopSelf()。另外,可以啟動intentservice多次,而每一個耗時操作會以工作隊列的方式在intentservice的onHandleIntent回調方法中執行,并且,每次隻會執行一個工作現場,執行完第一個在執行第二個。

七、問:intentservice使用方法?

答:如下圖,建立intentService時,隻需要實作onHandlerIntent和構造方法即可,onhandlerintent為異步方法,可以執行耗時操作。

面試體記錄第六節——(handlerThread、intentservice、view)

八、intentservice源碼分析?

答:推薦部落格:鴻洋:http://blog.csdn.net/lmj623565791/article/details/47143563

我們打開IntentService源碼之後就會發現,它繼承的是service。我們來看它的onCreate()方法。不難發現,它裡面使用了HandlerThread。他就是利用handlerThread來進行異步消息傳遞的。
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();


       //傳入的是HandlerThread的對象,這裡就讓ServiceHandler變成了一個處理線程的異步類。 他就持有了HandlerThread的looper,是以可移執行異步任務。
        mServiceLooper = thread.getLooper();

       //ServiceHandler又是繼承handler,進行了内部封裝。
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
           
  • 他又是如何啟動異步的呢?
答:intentService啟動後,會調用一個onStartCommand()方法。如下面源碼。onStartCommand中調用了onStart方法,所有操作都是在onStart方法中來執行的。在onStart方法中,mServiceHandler是通過.sendMessage來發送消息的,而消息肯定會發送到hendlerThread中進行處理。因為henlerThread持有looper對象。最後handleMessage中回調onHandleIntent(intent)。

 @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }



注意還有一個問題,在handleMessage中,有一個stopSelf(msg.arg1);方法。

 private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }


這個方法和不帶參數的stopSelf()不一樣,如果不帶參數他會立即停止任務,如果有參數,他會等待所有全部執行完才會終止任務。
           

九、問:view樹的繪制流程?

答:通俗可以了解:我們有一個android應用視窗,裡面包含了很多的UI元素。這些元素是以樹形結構來組織的,他們存在着父與子的關系。是以,在繪制的時候,我們需要知道它裡面各個子元素在父元素裡面的大小及位置。而确定子元素在父元素中的大小和位置,可以成為測量過程和布局過程。是以,在Android應用程式視窗對UI的渲染可以分為:測量、布局、繪制三個階段。對應Measure - onlayout - ondraw。

1.measure用來測量View的寬和高。

2.layout用來确定View在父容器中放置的位置。

3.draw用來将view繪制在螢幕上。

推薦部落格:http://blog.csdn.net/guolin_blog/article/details/16330267/

十、問:measure方法詳解?

答:

  • 先看看onMeasure函數原型:
onMeasure函數原型:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
這兩個參數就是包含寬和高的資訊。它還包含了測量模式,也就是說,一個int整數,裡面放了測量模式和尺寸大小。那麼一個數怎麼放兩個資訊呢?我們知道,我們在設定寬高時有3個選擇:wrap_content、match_parent以及指定固定尺寸,而測量模式也有3種:UNSPECIFIED(unspecified),EXACTLY(exactly),AT_MOST,當然,他們并不是一一對應關系哈,這三種模式後面我會詳細介紹,但測量模式無非就是這3種情況,而如果使用二進制,我們隻需要使用2個bit就可以做到,因為2個bit取值範圍是[0,3]裡面可以存放4個數足夠我們用了。那麼Google是怎麼把一個int同時放測量模式和尺寸資訊呢?我們知道int型資料占用32個bit,而google實作的是,将int資料的前面2個bit用于區分不同的布局模式,後面30個bit存放的是尺寸的資料。
  • 問:如何擷取到模式和尺寸:
答:
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
           
  • 問測量模式有三種,他們有什麼差別?

答:

UNSPECIFIED(unspecified) 父容器沒有對目前View有任何限制,目前View可以任意取尺寸

EXACTLY (exactly) 父容器為子視圖确定一個尺寸的大小,不管你的子視圖希望多大,都必須限定在父容器給給它限定的尺寸之内(相當于match_parent)

AT_MOST 父容器為子視圖指定的一個最大的尺寸,子視圖所有的大小都必須在這個最大的尺寸内,這個模式下,父控件無法擷取子控件的尺寸,隻能用子控件自己根據需求測量自己的尺寸。(相當于wrap_content)

  • onMeasure是實作我們測量邏輯的方法,為什麼不是measure?
measure源碼,
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec <<  | (long) heightMeasureSpec & L;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray();

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? - : mMeasureCache.indexOfKey(key);
            if (cacheIndex <  || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                //注意看這裡,請注意,請注意,請注意
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> ), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) <<  |
                (long) mMeasuredHeight & L); // suppress sign extension
    }
           
measure在源碼中還是調用到了onMeasure(widthMeasureSpec, heightMeasureSpec),通過 mOldWidthMeasureSpec = widthMeasureSpec和 mOldHeightMeasureSpec = heightMeasureSpec來進行測量,是以我們在自定義view的時候,複寫onMeasure()方法即可。
  • onMeasure源碼
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),         widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
           

我們就可以看到,他又兩個參數,寬和高的測量規格,他調用了setMeasuredDimension()方法。這個方法是測量方案的終極,也是實作測量的方法,看下面setMeasuredDimension()源碼。

他的意思就是說,我們會在onmeasure()方法中擷取到計算的尺寸,然後傳遞給setMeasuredDimension()方法。而setMeasuredDimension()是一個測量結束的方法,這個方法是必須被調用的。否則你會報異常。

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
           
  • 總結:measure方法測量

答:它開始于我們的父控件viewgroup,他會通過不斷的周遊子控件的measure方法,然後會根據viewgroup.measureSpec和子控件的layoutparent來決定我們子視圖的measureSpec(測量規格)。通過這個測量規格,進一步擷取到子view的寬高,然後一層一層的向下傳遞,不斷的儲存父控件的測量寬高,整個measure的測量過程就是一個樹型的遞歸過程,為整個view樹計算實際大小,每一個view視圖的控件的實際的寬和高,都是有父視圖和它本身的layoutparent所決定。

十一、問:layout方法詳解?

答:measure過程結束後,視圖的大小就已經測量好了,接下來就是layout的過程了。正如其名字所描述的一樣,這個方法是用于給視圖進行布局的,也就是确定視圖的位置。

  • 首先我們看下layout的源碼,别嫌棄多,就大概看下就行。
public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != ) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = ; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

如上面源碼,我們會看到,通過    
  boolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
來判斷視圖的大小是否發生過變化,以确定有沒有必要對目前的視圖進行重繪,同時還會在這裡把傳遞過來的四個參數分别指派給mLeft、mTop、mRight和mBottom這幾個變量。然後會調用  onLayout(changed, l, t, r, b);然後onLayout()方法卻是個空方法,也就是說具體的視圖都要覆寫該函數來實作自己的顯示(比如TextView在這裡實作了繪制文字的過程)。
           

十二、問:draw方法簡解?

答: draw操作利用前兩部得到的參數,将視圖顯示在螢幕上,到這裡也就完成了整個的視圖繪制工作。子類也不應該修改該方法,因為其内部定義了繪圖的基本操作:

注意:

1、繪制視圖本身,即調用onDraw()函數。在view中onDraw()是個空函數,也就是說具體的視圖都要覆寫該函數來實作自己的顯示(比如TextView在這裡實作了繪制文字的過程)。而對于ViewGroup則不需要實作該函數,因為作為容器是“沒有内容“的,其包含了多個子view,而子View已經實作了自己的繪制方法,是以隻需要告訴子view繪制自己就可以了,也就是下面的dispatchDraw()方法;

2、繪制子視圖,即dispatchDraw()函數。在view中這是個空函數,具體的視圖不需要實作該方法,它是專門為容器類準備的,也就是容器類必須實作該方法;

兩個容易混淆的方法:http://www.cnblogs.com/android-blogs/p/5778851.html

1、invalidate():調用此方法的時候,也就是請求我們的安卓系統,如果視圖大小沒有發生變法,就不會調用layout方法(放置過程)。

2、requestLayout():當布局發生變法的時候,比如說方向或者尺寸發生了改變,我們就會手動調用requestLayout()方法,而調用完此方法之後,他就會觸發measure和layout過程,但是不會觸發draw方法

十二、問:View什麼時候測量/布局/繪制?

答:

Invalidate :請求重繪view樹,假如視圖大小沒有變化就不會調用layout(),隻繪制那些需要重繪的視圖,誰請求就重繪誰(ViewGroup調用就重繪整個ViewGroup)

requestLayout :隻對view樹重新layout,會導緻調用measure和layout過程,不會調用draw()過程

requestFocus :請求view樹的draw過程,但隻繪制需要重繪的視圖

setVisibility() :當View可視狀态在INVISIBLE轉換VISIBLE時,會間接調用invalidate()方法, 當View的可視狀态在INVISIBLE/ VISIBLE 轉換為GONE狀态時,會間接調用requestLayout() 和invalidate方法。同時,由于整個個View樹大小發生了變化,會請求measure()過程以及draw()過程,同樣地,隻繪制需要“重新繪制”的視圖