【安卓】从源码的角度深入分析Scroller

转载须注明出处,谢谢!http://blog.csdn.net/chdjj

这篇文章我将从源码的角度深入分析Scroller类。在阅读的时候,建议大家打开源码对照着看,否则可能看的云里雾里。

一.Scroller的用途

熟悉android的同学必然对Scroller不陌生,Scroller是一个弹性滑动对象,可以制作很多酷炫的滑动效果,Lancher中的滑屏效果就有使用到Scroller。

我们知道,View类中的scrollTo和scrollBy方法提供了滑动操作,但是这种滑动操作是瞬间完成的,就是说你为scrollTo提供终点坐标,该方法只要一调用,我们就会发现已经滚动到目的地了,这种方式很显然用户体验是不好的,因而android工程师为我们封装了Scroller类,这个类可以为View带来缓慢移动的效果

具体使用方式通常是通过在你自定义的View中调用Scroller的startScroll并刷新视图,另外需重写computeScroll方法(刷新视图过程中会调用此方法),在该方法中调用Scroller的computeScrollOffset计算应该滚动到的位置,然后使用scrollTo滚动到该位置,再调用invalidate刷新,就可以实现滚动效果了(不了解的同学建议先去熟悉Scroller用法)。

Scroller使用示例(代码片段):

public class MyView extends LinearLayout
{
	private Scroller mScroller;
    ... ...
	private void smoothScrollTo(int destX,int destY)
	{
		int scrollX = getScrollX();
		int scrollY = getScrollY();

		int deltaX = destX-scrollX;
		int deltaY = destY-scrollY;
		mScroller.startScroll(scrollX,scrollY,deltaX, deltaY, 1000);
		invalidate();
	}
	@Override
	public void computeScroll()
	{
		if(mScroller != null)
		{
			if(mScroller.computeScrollOffset())
			{
				scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
				Log.d(TAG,"scrollX="+getScrollX()+",scrollY="+getScrollY());
				postInvalidate();
			}
		}
	}

}

二.ScrollTo、ScrollBy、getScrollX、getScrollY方法分析

在分析Scroller源码之前,我们必须先了解ScrollTo,ScrollBy,getScrollX,getScrollY这几个方法的作用。这四个方法都是View类提供的,这点先明确。

我们先分析getScrollX和getScrollY方法,查看源码实现:

/**
     * Return the scrolled left position of this view. This is the left edge of
     * the displayed part of your view. You do not need to draw any pixels
     * farther left, since those are outside of the frame of your view on
     * screen.
     *
     * @return The left edge of the displayed part of your view, in pixels.
     */
    public final int getScrollX() {
        return mScrollX;
    }
    /**
     * Return the scrolled top position of this view. This is the top edge of
     * the displayed part of your view. You do not need to draw any pixels above
     * it, since those are outside of the frame of your view on screen.
     *
     * @return The top edge of the displayed part of your view, in pixels.
     */
    public final int getScrollY() {
        return mScrollY;
    }

getScrollX和getScrollY方法返回的是mScrollX和mScrollY变量。这两个变量是什么呢?

这里我直接告诉大家,mScrollX和mScrollY指的是视图内容相对于视图原始起始坐标的偏移量,mScrollX和mScrollY的默认值为0,因为默认是没有偏移的。另外需注意偏移量的正负问题,因为是相对视图起始坐标的,所以如果你是向右偏移那么mScrollX应该是负数,而向左偏移mScrollX为正数。再举个例子,比如你定义了一个ImageView,其左上角的坐标为(100,80),此时mScrollX和mScrollY值都为0(没有偏移),现在你要把该ImageView移到(120,100)处,也就是右下方,那么你的mScrollX应该是100-120=-20,mScrollY应该是80-100=-20,这下你该明白了吧。

明白了这个问题,scrollBy和scrollTo也就不在话下了,我们查看源码:

 public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

先看scrollTo方法,它首先判断x,y方向的偏移量(即mScrollX,mScrollY)是否和传进来的偏移量(即x,y)相同,如果相同那么直接返回,否则我们将更新mScrollX和mScrollY,并且通知界面发生改变请求重绘。通过这种方式就可以实现滚动效果了,只是是瞬间移动的。再看这个scrollBy,直接调用的是scrollTo,根据参数很容易发现,这个方法的作用是在当前偏移基础上,再继续偏移(x,y)单位。需要注意的是这两个方法的参数是偏移量而不是实际位置哦!

这里还有一点需要注意,那就是你调用一个View的scrollTo方法进行滚动时,滚动的并不是该View本身,而是该View的内容。比如你要对一个Button进行滚动的话,应该在Button外面包一个ViewGroup,然后调用ViewGroup的scrollTo方法。这一点也很重要,大家需要注意下!

三.Scroller源码分析

Scroller的精华在于computeScrollOffset和startScroll方法,所以我们重点分析这两个方法。

分析之前,先看这个类中定义的一些变量:

  private int mMode;//模式,有SCROLL_MODE和FLING_MODE
    private int mStartX;//起始x方向偏移
    private int mStartY;//起始y方向偏移
    private int mFinalX;//终点x方向偏移
    private int mFinalY;//终点y方向偏移
    private int mCurrX;//当前x方向偏移
    private int mCurrY;//当前y方向偏移
    private long mStartTime;//起始时间
    private int mDuration;//滚动持续时间
    private float mDurationReciprocal;//持续时间的倒数
    private float mDeltaX;//x方向应该滚动的距离,mDeltaX=mFinalX-mStartX
    private float mDeltaY;//y方向应该滚动的距离,mDeltaY=mFinalY-mStartY
    private boolean mFinished;//是否结束

对这些变量有个大致印象之后,我们就开始看startScroll源码了:

public void startScroll(int startX, int startY, int dx, int dy) {
        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
    }

    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        mMode = SCROLL_MODE;
        mFinished = false;
        mDuration = duration;
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;
        mFinalX = startX + dx;
        mFinalY = startY + dy;
        mDeltaX = dx;
        mDeltaY = dy;
        mDurationReciprocal = 1.0f / (float) mDuration;
    }

这个所谓的startScroll方法里面全部是对上面那些变量赋值的,比如将当前模式置为SCROLL_MODE,设置持续时间,起点终点偏移,起始时间等等。这个方法似乎并没有触发start scroll,恩,的确是这样的。那么到底怎么触发scroll呢?我们将这个问题留到下一个部分分析。

为了让大家理解这些变量的作用,我画了一张图。

解释下,图中我们假设从A点滚动到B点,那么,如果知道mStartX,mStartY(由startScroll的startX和startY参数得到)和mDeltaX,mDeltaY(由startScroll的dx和dy参数得到)。那么,mFinalX和mFinalY就很容易得到了。此外在加上duration持续时间,那么我们就可以根据(当前时间-mStartTime)占duration的比例来算出当前位置,也即mCurrX和mCurrY。我不会告诉你,这就是computeScrollOffset的作用!通过上面分析我们发现,startScroll方法其实相当于一个预处理,为computeScrollOffset提供数据。

下面我们查看computeScrollOffset源码:

public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }
       int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
       final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                 ... ...
                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }

看,是不是很好理解了。首先判断是否结束(mFinished==true?),如果没有结束,那么程序继续向下跑,计算timePassed,即当前时间减去开始时间,如果小于mDuration,那么就可以根据比例计算出当前位置,当然了,这里使用了Interpolator来控制动画的效果,加速或者减速等等。如果已经超过了mDuration了,那么滚动应该停止了,所以将mFinished置为ture,下次调用computeScrollOffset就会返回false了。

到这里,我们把Scroller中的重要部分都分析完了,但是我们发现这两个方法都没有涉及具体滚动。那么滚动到底是如何触发的呢?下面我们将对整个流程进行源码分析。

四.滚动流程分析

在第一部分中,我给出了一个Scroller的使用示例,在smoothScrollTo方法中我们调用了Scroller的startScroll,然后调用了invalidate方法刷新视图,这个滚动效果就是由invalidate触发的!我们知道,调用了invalidate方法将会引起整个view系统的重绘,所以我们得从View的绘制说起。有经验的同学都应该知道,View的绘制包括三个主要过程,分别是measure,layout和draw。下面我们看View类的draw方法源码:

 public void draw(Canvas canvas) {
          ... ...
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */
        // Step 1, draw the background, if needed
        int saveCount;
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
          ... ...
        // Step 2, save the canvas' layers
          ... ...
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
        // Step 4, draw the children
        dispatchDraw(canvas);
        // Step 5, draw the fade effect and restore layers
          ... ...
    }

draw方法将绘制分成了六步,其中第三步是调用onDraw去绘制内容,第四步是调用dispatchDraw去绘制子视图。我们看下dispatchDraw方法:

 /**
     * Called by draw to draw the child views. This may be overridden
     * by derived classes to gain control just before its children are drawn
     * (but after its own view has been drawn).
     * @param canvas the canvas on which to draw the view
     */
    protected void dispatchDraw(Canvas canvas) {
    }

恩,没错,是个空方法,因为只有ViewGroup才有子视图,所以我们来到ViewGroup中,找到dispatchDraw:

@Override
    protected void dispatchDraw(Canvas canvas) {
        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
         ... ...
        for (int i = 0; i < childrenCount; i++) {
            int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(childIndex);
    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
    }

代码也是极其长的,这里截取了一部分。我们看到这个方法中调用了drawChild方法,从名字上就可以看出这个方法是用来绘制子视图的,找到其实现:

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

这里调用了view的draw方法,当然,这个draw方法跟上面那个draw不一样,因为它有三个参数!那还等什么,我们回到View的代码中找到这个含有三个参数的draw方法:

 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        boolean more = false;
        final boolean childHasIdentityMatrix = hasIdentityMatrix();
        final int flags = parent.mGroupFlags;
        ... ...
        int sx = 0;
        int sy = 0;
        if (!hasDisplayList) {
            computeScroll();//终于找到了computeScroll
            sx = mScrollX;
            sy = mScrollY;
        }
         ... ...
        return more;
    }

在其中,我们找到了computeScroll方法!当然View中的computeScroll方法是空的,需要我们根据场景自己复写。

比如,我发现,TextView中就有复写这个方法:

 @Override
    public void computeScroll() {
        if (mScroller != null) {
            if (mScroller.computeScrollOffset()) {
                mScrollX = mScroller.getCurrX();
                mScrollY = mScroller.getCurrY();
                invalidateParentCaches();
                postInvalidate();  // So we draw again
            }
        }
    }

好了,折腾了这么久,下面我们再回顾下这个过程:我们在自定义view中调用了startScroll方法为滚动设置了一些基本数据,然后通过invalidate由上而下刷新view视图,首先是根视图(通常是ViewGroup)的draw方法被调用,然后调用onDraw绘制视图内容,接着dispatchDraw方法被调用去绘制子视图,dispatchDraw方法会对每个子视图调用drawChild方法,而drawChild方法会调用该子View的draw方法(三个参数),在draw方法中会调用computeScroll方法进行滚动,而computeScroll方法是被复写的,视场景而定,通常是根据computeScrollOffset来判断是否需要滑动,如果需要的话,接着调用postInvalidate再由上至下重新绘制,如此一来便实现了平滑滚动的效果!

ok,到这里总算是把Scroller讲清楚了!

五.实例分析

为了便于理解,我这里写了一个小例子,通过打印日志的形式让大家理解上面所讲的内容。

首先是几个自定义的view:

MyLinearLayout:

package com.example.scrollerdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.LinearLayout;
public class MyLinearLayout extends LinearLayout
{
	private static final String TAG = "TEST";
	public MyLinearLayout(Context context)
	{
		super(context);
	}
	public MyLinearLayout(Context context, AttributeSet attrs)
	{
		super(context, attrs);
	}
	@Override
	public void draw(Canvas canvas)
	{
		Log.i(TAG, "MyLinearLayout--->draw");
		super.draw(canvas);
	}
	@Override
	protected void onDraw(Canvas canvas)
	{
		Log.i(TAG, "MyLinearLayout--->onDraw");
		super.onDraw(canvas);
	}
	@Override
	public void computeScroll()
	{
		Log.i(TAG, "MyLinearLayout--->computeScroll");
		super.computeScroll();
	}
}

MyView(实现了平滑滚动效果):

package com.example.scrollerdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Scroller;
/**
 * @author Rowandjj
 *
 */
public class MyView extends LinearLayout
{
	private static final String TAG = "TEST";
	private Scroller mScroller;

	public MyView(Context context)
	{
		super(context);
		init();
	}
	public MyView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		init();
	}
	private void init()
	{
		mScroller = new Scroller(getContext());
	}
	public void testSmoothScroll()
	{
		//向右下方平滑滚动(向右偏移100,向下偏移100)
		smoothScrollTo(-100,-100);
	}

	private void smoothScrollTo(int destX,int destY)
	{
		int scrollX = getScrollX();
		int scrollY = getScrollY();
		Log.d(TAG,"scrollX="+scrollX+",scrollY="+scrollY);
		int deltaX = destX-scrollX;
		int deltaY = destY-scrollY;
		mScroller.startScroll(scrollX,scrollY,deltaX, deltaY, 1000);
		invalidate();
	}
	@Override
	public void draw(Canvas canvas)
	{
		Log.i(TAG,"MyView------>draw run");
		super.draw(canvas);
	}
	@Override
	protected void onDraw(Canvas canvas)
	{
		Log.i(TAG,"MyView------>onDraw run");
		super.onDraw(canvas);
	}

	@Override
	protected void dispatchDraw(Canvas canvas)
	{
		Log.i(TAG,"MyView------>dispatchDraw run");
		super.dispatchDraw(canvas);
	}

	@Override
	protected boolean drawChild(Canvas canvas, View child, long drawingTime)
	{
		Log.i(TAG,"MyView------>drawChild run");
		return super.drawChild(canvas, child, drawingTime);
	}

	@Override
	public void computeScroll()
	{
		Log.i(TAG,"MyView------>computeScroll run");
		if(mScroller != null)
		{
			if(mScroller.computeScrollOffset())
			{
				scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
				Log.d(TAG,"scrollX="+getScrollX()+",scrollY="+getScrollY());
				postInvalidate();
			}
		}
	}
}

MyImageView:

package com.example.scrollerdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
public class MyImageView extends ImageView
{
	private static final String TAG = "TEST";
	public MyImageView(Context context)
	{
		super(context);
	}
	public MyImageView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
	}
	@Override
	protected void onDraw(Canvas canvas)
	{
		Log.i(TAG,"myImageView---->onDraw run");
		super.onDraw(canvas);
	}

	@Override
	public void computeScroll()
	{
		Log.i(TAG,"myImageView---->computeScroll run");
		super.computeScroll();
	}
}

我们根据上面三个自定义view做一个布局,外层是MyLinearLayout,中间是MyView,最里面是一个MyImageView.

activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <com.example.scrollerdemo.MyLinearLayout
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ffaaff"
        tools:context="com.example.scrollerdemo.MainActivity" >
        <com.example.scrollerdemo.MyView
            android:id="@+id/mv"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_marginLeft="50dp"
            android:layout_marginTop="100dp"
            android:background="@android:color/darker_gray"
            android:orientation="vertical" >
            <com.example.scrollerdemo.MyImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_launcher" />
        </com.example.scrollerdemo.MyView>
    </com.example.scrollerdemo.MyLinearLayout>
</LinearLayout>

下面是MainActivity的代码:

package com.example.scrollerdemo;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
public class MainActivity extends Activity implements OnClickListener
{
	private MyView mv = null;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		mv = (MyView) findViewById(R.id.mv);
		mv.setOnClickListener(this);
	}
	@Override
	public void onClick(View v)
	{
		switch (v.getId())
		{
		case R.id.mv://点击时触发滚动效果
			mv.testSmoothScroll();
			break;
		default:
			break;
		}
	}
}

代码很简单,不必过多介绍,下面看显示效果:

打开logcat查看日志:

这是第一次绘制,跟上面分析过程一致,首先是MyLinearLayout的draw和onDraw方法被调用,然后通过dispatchDraw绘制孩子,所以MyView的computeScroll、draw、onDraw、dispatchDraw等方法被调用,接着MyImageView的computeScroll和onDraw方法会被调用,如此从上到下进行一次绘制。

下面我们点击安卓机器人图标,会调用testSmoothScroll方法,机器人会从左上角平滑滚动到右下角,此时我们查看logcat日志:

中间日志太多,省略了,下面这段是最后打印的

通过日志可以看出,上面分析是正确的,通过在MyView的computeScroll方法中调用postInvalidate,导致不断重绘,直到偏移量达到startScroll事先指定的(-100,-100)。

至此,本篇文章结束,希望对大家有所帮助!

时间: 2024-10-02 05:16:58

【安卓】从源码的角度深入分析Scroller的相关文章

【转】Android 带你从源码的角度解析Scroller的滚动实现原理

今天给大家讲解的是Scroller类的滚动实现原理,可能很多朋友不太了解该类是用来干嘛的,但是研究Launcher的朋友应该对他很熟悉,Scroller类是滚动的一个封装类,可以实现View的平滑滚动效果,什么是实现View的平滑滚动效果呢,举个简单的例子,一个View从在我们指定的时间内从一个位置滚动到另外一个位置,我们利用Scroller类可以实现匀速滚动,可以先加速后减速,可以先减速后加速等等效果,而不是瞬间的移动的效果,所以Scroller可以帮我们实现很多滑动的效果. 在介绍Scrol

Android 带你从源码的角度解析Scroller的滚动实现原理

今天给大家讲解的是Scroller类的滚动实现原理,可能很多朋友不太了解该类是用来干嘛的,但是研究Launcher的朋友应该对他很熟悉,Scroller类是滚动的一个封装类,可以实现View的平滑滚动效果,什么是实现View的平滑滚动效果呢,举个简单的例子,一个View从在我们指定的时间内从一个位置滚动到另外一个位置,我们利用Scroller类可以实现匀速滚动,可以先加速后减速,可以先减速后加速等等效果,而不是瞬间的移动的效果,所以Scroller可以帮我们实现很多滑动的效果. 在介绍Scrol

android源码大放送(实战开发必备),免费安卓demo源码,例子大全文件详细列表

免费安卓demo源码,例子大全文件详细列表 本列表源码永久免费下载地址:http://www.jiandaima.com/blog/android-demo 卷 yunpan 的文件夹 PATH 列表 卷序列号为 0000-73EC E:. │ jiandaima.com文件列表生成.bat │ 例子大全说明.txt │ 本例子永久更新地址~.url │ 目录列表2016.03.10更新.txt │ ├─前台界面 │ ├─3D标签云卡片热门 │ │ Android TagCloudView云标签

Android 源码系列之&lt;十三&gt;从源码的角度深入理解LeakCanary的内存泄露检测机制(中)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52958563 在上篇文章Android 源码系列之<十二>从源码的角度深入理解LeakCanary的内存泄露检测机制(上)中主要介绍了Java内存分配相关的知识以及在Android开发中可能遇见的各种内存泄露情况并给出了相对应的解决方案,如果你还没有看过上篇文章,建议点击这里阅读一下,这篇文章我将要向大家介绍如何在我们的应用中使用square开源的LeakCanary库来检测应用中出

Android AsyncTask完全解析,带你从源码的角度彻底理解

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴趣的朋友可以参考 Android Handler.Message完全解析,带你从源码的角度彻底理解 . 不过为了更加方便我们在子线程中更新UI元素,Android从1.

Android 源码系列之&lt;十一&gt;从源码的角度深入理解AccessibilityService,打造自己的APP小外挂(下)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52843637 在上篇文章Android 源码系列之<十>从源码的角度深入理解AccessibilityService,打造自己的APP小外挂(上)中我们讲解了通过AccessibilityService实现自动安装APK小外挂的操作流程,如果你还没有看过上篇文章请点击这里.在这篇文章中我将带领小伙伴从源码的角度来深入学习一下AccessibilityServie的技术实现原理,希望这

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用Ima

从源码的角度认识 AsyncTask

从源码的角度认识 AsyncTask 时间 2016-06-18 13:13:28  公众账号 原文  http://mp.weixin.qq.com/s?__biz=MzIzMjE1Njg4Mw==&mid=2650117724&idx=1&sn=50450d999b7130683129fc0a09af99e4 主题 AsyncTask 一.为什么需要工作者线程 我们知道,Android应用的主线程(UI 线程)肩负着绘制用户界面和及时响应用户操作的重任,为了避免"用户

[学习总结]7、Android AsyncTask完全解析,带你从源码的角度彻底理解

我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴趣的朋友可以参考 Android Handler.Message完全解析,带你从源码的角度彻底理解 . 不过为了更加方便我们在子线程中更新UI元素,Android从1.5版本就引入了一个AsyncTask类,使用它就可以非常灵活方便地从子线程切换到UI线程,我们本篇文章的主角也就正是它了. Asyn