面试体记录第六节——(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为异步方法,可以执行耗时操作。

这里写图片描述


八、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 << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        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 ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || 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 >> 32), (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) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // 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) != 0) {
            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 = 0; 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()过程,同样地,只绘制需要“重新绘制”的视图

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值