一、问: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()过程,同样地,只绘制需要“重新绘制”的视图